diff --git a/.editorconfig b/.editorconfig index bc137906a6d..4848954eef0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -16,3 +16,6 @@ trim_trailing_whitespace = false [COMMIT_EDITMSG] max_line_length = 0 + +[*.java] +indent_size = 4 diff --git a/.eslintignore b/.eslintignore index 38488905279..3b2c9b3a197 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,5 @@ -node_modules* +docs/ dist/ coverage +packages/hippy-vue/types +packages/hippy-vue-loader/lib diff --git a/.eslintrc.js b/.eslintrc.js index 2ab91655450..e0363d4dc4c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,3 +1,33 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const path = require('path'); + +function resolveVue(p) { + return path.resolve(__dirname, './node_modules/vue/src/', p); +} + +function resolvePackage(src, extra = 'src') { + return path.resolve(__dirname, './packages/', src, extra); +} + module.exports = { parser: 'vue-eslint-parser', parserOptions: { @@ -23,20 +53,25 @@ module.exports = { 'jsx-a11y', ], overrides: [ + { + files: ['*.test.ts'], + extends: ['plugin:jest/recommended'], + }, { files: ['**/*.ts', '**/*.tsx'], + extends: ['eslint-config-tencent/ts'], rules: { // Allow interface export 'no-undef': 'off', - - // Disable props checking - 'react/prop-types': 'off', - - // Force use 2 space for indent - '@typescript-eslint/indent': ['error', 2], - // Note you must disable the base rule as it can report incorrect errors 'no-unused-vars': 'off', + '@typescript-eslint/consistent-type-assertions': 'off', + '@typescript-eslint/naming-convention': 'off', + '@typescript-eslint/prefer-for-of': 'off', + '@typescript-eslint/no-require-imports': 'off', + }, + parserOptions: { + project: ['./**/tsconfig.json'], }, }, ], @@ -46,12 +81,15 @@ module.exports = { es6: true, }, globals: { - __PLATFORM__: 'readonly', - __GLOBAL__: 'readonly', - Hippy: 'readonly', - WebSocket: 'readonly', + __PLATFORM__: 'writable', + __GLOBAL__: 'writable', + Hippy: 'writable', + WebSocket: 'writable', + requestIdleCallback: 'writable', + cancelIdleCallback: 'writable', }, rules: { + 'no-restricted-globals': 'off', semi: ['error', 'always'], // Allow more than one component per file 'vue/one-component-per-file': 'off', @@ -65,13 +103,10 @@ module.exports = { // Allow event name not kebab-case 'vue/custom-event-name-casing': 'off', - 'import/no-unresolved': 'off', - - // Allow import name different with file name - 'import/no-named-as-default': 'off', + // Allow component names not be multi-word + 'vue/multi-word-component-names': 'off', - // Allow import cycle - 'import/no-cycle': 'off', + 'import/no-unresolved': 'off', // Disable prop-types 'react/prop-types': 'off', @@ -79,29 +114,14 @@ module.exports = { // Disable deprecated 'react/no-deprecated': 'off', - // Turn of extensions checking temporary - 'import/extensions': 'off', + 'react/no-unknown-property': 'off', - // https://github.com/benmosher/eslint-plugin-import/tree/master/docs/rules/namespace.md#allowcomputed 'import/namespace': [ 'error', { allowComputed: true, }, ], - // Allow import from devDependencies - 'import/no-extraneous-dependencies': [ - 'error', - { - devDependencies: [ - 'scripts/*.js', - // FIXME: seems not working - 'packages/**/types/*.d.ts', - 'packages/**/__tests__/*.test.js', - 'examples/**/scripts/*.js', - ], - }, - ], // Allow tsx as the jsx file 'react/jsx-filename-extension': [ 'error', @@ -116,6 +136,8 @@ module.exports = { 'warn', { allow: [ + '__PLATFORM__', + '__HIPPYCURDIR__', '__ISHIPPY__', '__GLOBAL__', '__HIPPYNATIVEGLOBAL__', @@ -130,5 +152,28 @@ module.exports = { react: { version: 'detect', // React version. "detect" automatically picks the version you have installed. }, + 'import/ignore': [resolveVue('/')], + 'import/resolver': { + node: { + extensions: ['.js', '.jsx', '.ts', '.tsx', 'd.ts'], + }, + alias: { + map: [ + ['@vue', resolvePackage('hippy-vue')], + ['@router', resolvePackage('hippy-vue-router')], + ['@css-loader', resolvePackage('hippy-vue-css-loader')], + ['@native-components', resolvePackage('hippy-vue-native-components')], + ['vue', resolveVue('core/index')], + ['web', resolveVue('platforms/web')], + ['core', resolveVue('core')], + ['shared', resolveVue('shared')], + ['sfc', resolveVue('sfc')], + ['he', path.resolve(__dirname, './packages/hippy-vue/src/util/entity-decoder')], + ['@hippy-vue-next-style-parser', resolvePackage('hippy-vue-next-style-parser')], + ['@hippy-vue-next', resolvePackage('hippy-vue-next')], + ['@hippy-vue-next-server-renderer', resolvePackage('hippy-vue-next-server-renderer')], + ], + }, + }, }, }; diff --git a/.gitattributes b/.gitattributes index 2a5c122ee6e..66e12898999 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,6 +2,5 @@ *.cr.so filter=lfs diff=lfs merge=lfs -text *.gz filter=lfs diff=lfs merge=lfs -text *.otf filter=lfs diff=lfs merge=lfs -text -*.png filter=lfs diff=lfs merge=lfs -text -*.jpg filter=lfs diff=lfs merge=lfs -text -*.a filter=lfs diff=lfs merge=lfs -text \ No newline at end of file +*.a filter=lfs diff=lfs merge=lfs -text +*.jar filter=lfs diff=lfs merge=lfs -text diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 726e4631a58..0a9c7af4e86 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,4 +8,68 @@ # https://help.github.com/en/articles/about-code-owners # -* @ilikethese @churchill-zhang @zoomchan-cxj @siguangli @ozonelmy +# universal files +* @ilikethese @etkmao + +# cmake related +*.cmake @ilikethese @etkmao +CMakeLists.txt @ilikethese @etkmao + +# driver: js +/.eslintignore @zealotchen0 +/.eslintrc.js @zealotchen0 +/.nycrc @zealotchen0 +/package.json @zealotchen0 +/package-lock.json @zealotchen0 +/ava.config.js @zealotchen0 +/core/ @ilikethese @etkmao +/core/js/ @zealotchen0 +/packages/ @zealotchen0 +/packages/hippy-web-renderer/ @pba-cra @zealotchen0 + +# framework: android +/android/ @siguangli +/android/**/src/main/jni/ @ilikethese @etkmao + +# framework: ios +/ios/ @wwwcg @ruifanyuan + +# dom: layout +/layout/ @ilikethese + +# gh: config +/.github/ @zealotchen0 + +# doc: example +/examples/ @zealotchen0 +/examples/hippy-*-demo/ @zealotchen0 +/examples/android-demo/ @siguangli +/examples/android-demo/res/ @zealotchen0 +/examples/ios-demo/ @wwwcg @ruifanyuan +/examples/ios-demo/res/ @zealotchen0 +/static/ @zealotchen0 + +# doc: pages +/*.md @zealotchen0 +/docs/ @zealotchen0 + +# build: gradle +/gradle.properties @siguangli +/gradlew @siguangli +/gradlew.bat @siguangli +/settings.gradle @siguangli +/build.gradle @siguangli +/gradle/ @siguangli + +# build: xcode +/hippy.podspec @wwwcg @ruifanyuan +/_Pods.xcodeproj/ @wwwcg @ruifanyuan + +# build: config +/buildconfig/ @ilikethese @etkmao + +# build: js +/tsdoc.json @zealotchen0 +/tsconfig.json @zealotchen0 +/lerna.json @zealotchen0 +/scripts/ @zealotchen0 diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 98d50efc95b..be028d8df69 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -5,8 +5,8 @@ Hi! I'm really excited that you are interested in contributing to Hippy. Before - [Code of Conduct](https://github.com/Tencent/Hippy/blob/master/.github/CODE_OF_CONDUCT.md) - [Issue Reporting Guidelines](#issue-reporting-guidelines) - [Pull Request Guidelines](#pull-request-guidelines) -- [Development Setup](https://github.com/Tencent/Hippy#getting-started) -- [Project Structure](https://github.com/Tencent/Hippy#project-structure) +- [Development Setup](https://github.com/Tencent/Hippy#-getting-started) +- [Project Structure](https://github.com/Tencent/Hippy#-project-structure) ## Issue Reporting Guidelines @@ -14,6 +14,8 @@ Hi! I'm really excited that you are interested in contributing to Hippy. Before ## Pull Request Guidelines +- Setting up your local environment to get started. You will need have [git](https://git-scm.com/) and [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) installed locally, then run `npm install` to download all needed dependencies. These are detailed in the [Development Setup guide](https://github.com/Tencent/Hippy#-getting-started). + - Checkout a topic branch from the relevant branch, e.g. `master`, and merge back against that branch. - Test cases have been added/updated/passed for the code you will submit. diff --git a/.github/ISSUE_TEMPLATE/crash_report.yml b/.github/ISSUE_TEMPLATE/crash_report.yml new file mode 100644 index 00000000000..ad11085d6fe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/crash_report.yml @@ -0,0 +1,74 @@ +name: 🐛 Crash Report +description: File a crash report +title: "A crash occurred" +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this crash report! + + Please fill in as much of the following form as you're able. + - type: input + attributes: + label: Version + description: Which version of Hippy SDK are you using? + placeholder: e.g. 1.0.0 + - type: dropdown + attributes: + label: Platform + description: What platform are you running on? + options: + - Android + - iOS + - Windows + - macOS + validations: + required: true + - type: dropdown + attributes: + label: Architecture + description: What is your your device architecture? + options: + - arm + - arm64 + - x86 + - x86_64 + validations: + required: true + - type: textarea + attributes: + label: Logs + description: Please copy and paste crash log output + placeholder: | + Crash log output like the following example: + ``` + pid: 4637, tid: 4637, name: crasher >>> crasher <<< + signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr -------- + Abort message: 'some_file.c:123: some_function: assertion "false" failed' + r0 00000000 r1 0000121d r2 00000006 r3 00000008 + r4 0000121d r5 0000121d r6 ffb44a1c r7 0000010c + r8 00000000 r9 00000000 r10 00000000 r11 00000000 + ip ffb44c20 sp ffb44a08 lr eace2b0b pc eace2b16 + backtrace: + #00 pc 0001cb16 /system/lib/libc.so (abort+57) + #01 pc 0001cd8f /system/lib/libc.so (__assert2+22) + #02 pc 00001531 /system/bin/crasher (do_action+764) + #03 pc 00002301 /system/bin/crasher (main+68) + #04 pc 0008a809 /system/lib/libc.so (__libc_init+48) + #05 pc 00001097 /system/bin/crasher (_start_main+38) + ``` + - type: textarea + attributes: + label: Reproduce Condition + description: How often does the crash happen? Are there some reproducing conditions? + placeholder: | + You can use the following template: + ``` + Meeting the following conditions, there is a N percent chance of crashing: + 1. condition 1... + 2. contision 2... + ``` + - type: textarea + attributes: + label: Additional Information + description: Tell us anything else you think we should know. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 3915aff589e..515cadcffbb 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,6 +1,8 @@ -Before submitting a new pull request, please make sure: +# Pre-PR Checklist -- [ ] Test cases have been added/updated/passed for the code you will submit. -- [ ] Documentation has added or updated. -- [ ] Commit message is following the [Convention Commit](https://conventionalcommits.org/) guideline with maximum 72 characters. -- [ ] Squash the repeat code commits, short patches are welcome. +- [x] I added/updated relevant documentation. +- [x] I followed the [Convention Commit](https://conventionalcommits.org/) guideline with maximum 72 characters to submit commit message. +- [x] I squashed the repeated code commits. +- [x] I signed the [CLA]. +- [x] I added/updated test cases to check the change I am making. +- [x] All existing and new tests are passing. diff --git a/.github/workflows/3rd_prebuilt_v8.yml b/.github/workflows/3rd_prebuilt_v8.yml new file mode 100644 index 00000000000..b7d37669d7e --- /dev/null +++ b/.github/workflows/3rd_prebuilt_v8.yml @@ -0,0 +1,509 @@ +name: '[3rd] prebuilt v8' + +on: + workflow_dispatch: + inputs: + v8_revision: + description: 'V8 TAG(Branch) to build' + type: string + default: '9.8-lkgr' + required: true + build_args: + description: 'Build args' + type: string + default: 'is_debug=false v8_use_external_startup_data=false is_official_build=true v8_enable_i18n_support=false treat_warnings_as_errors=false symbol_level=1 v8_enable_webassembly=false' + required: true + package_tag: + description: 'Package TAG' + type: string + default: '9.8-lkgr' + required: true + hip_v8_root: + description: 'HIP V8 root path' + type: string + default: 'global_packages' + required: true + writing_mode: + description: 'The mode of writing' + type: choice + options: + - preserve + - overwrite + default: 'preserve' + required: true + is_build_for_android: + description: 'Build for Android platform' + type: boolean + default: true + required: false + is_build_for_ios: + description: 'Build for iOS platform' + type: boolean + default: false + required: false + is_build_for_windows: + description: 'Build for Windows platform' + type: boolean + default: false + required: false + is_build_for_macos: + description: 'Build for MacOS platform' + type: boolean + default: false + required: false + is_build_for_linux: + description: 'Build for Linux platform' + type: boolean + default: false + required: false + +env: + publish_package_script: | + from qcloud_cos import CosS3Client, CosConfig + import hashlib + import os + try: + from urllib.parse import urlencode + except ImportError: + from urllib import urlencode + + metadata = {} + metadata["ci-name"] = "Github Action" + metadata["ci-id"] = "${{ github.run_id }}" + metadata["ci-url"] = "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + metadata["v8-builder"] = "${{ github.event.sender.login }}" + with open(os.environ["local_file"], "rb") as artifact_file: + metadata["v8-md5"] = hashlib.md5(artifact_file.read()).hexdigest() + metadata["v8-revision"] = "${{ github.event.inputs.v8_revision }}" + metadata["v8-head"] = os.environ["v8_head"] + + config = CosConfig(Region="${{ secrets.COS_REGION }}", SecretId="${{ secrets.TC_SECRET_ID }}", SecretKey="${{ secrets.TC_SECRET_KEY }}") + client = CosS3Client(config) + if "${{ github.event.inputs.writing_mode }}" == "preserve" and client.object_exists( + Bucket="${{ secrets.COS_BUCKET }}", + Key=os.environ["cos_key"] + ): + raise Exception("Package already exists") + + response = client.upload_file( + Bucket="${{ secrets.COS_BUCKET }}", + Key=os.environ["cos_key"], + LocalFilePath=os.environ["local_file"], + EnableMD5=True, + ContentMD5=metadata["v8-md5"], + Metadata={"x-cos-tagging": urlencode(metadata)} + ) + print("ETag: " + response["ETag"]) + + cmakelists_template: | + # + # Tencent is pleased to support the open source community by making + # Hippy available. + # + # Copyright (C) 2022 THL A29 Limited, a Tencent company. + # All rights reserved. + # + # Licensed under the Apache License, Version 2.0 (the "License"); + # you may not use this file except in compliance with the License. + # You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, + # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + # See the License for the specific language governing permissions and + # limitations under the License. + # + cmake_minimum_required(VERSION 3.0) + + project(v8) + + add_library(${{ '${{PROJECT_NAME}}' }} INTERFACE) + target_include_directories(${{ '${{PROJECT_NAME}}' }} + INTERFACE "include" + INTERFACE "include/v8") + + target_compile_definitions(${{ '${{PROJECT_NAME}}' }} + INTERFACE "-DV8_IMMINENT_DEPRECATION_WARNINGS" + INTERFACE "-DV8_DEPRECATION_WARNINGS" + {0}) + + add_library(${{ '${{PROJECT_NAME}}' }}_library STATIC IMPORTED) + set_property(TARGET ${{ '${{PROJECT_NAME}}' }}_library PROPERTY + IMPORTED_LOCATION "{1}") + target_link_libraries(${{ '${{PROJECT_NAME}}' }} + INTERFACE ${{ '${{PROJECT_NAME}}' }}_library) + +jobs: + context_in_lowercase: + if: github.event.inputs.is_build_for_android == 'true' || github.event.inputs.is_build_for_linux == 'true' + runs-on: ubuntu-latest + outputs: + repository_owner: ${{ steps.get_owner.outputs.lowercase }} + steps: + - name: Get repo owner(in lowercase) + id: get_owner + uses: ASzc/change-string-case-action@v2 + with: + string: ${{ github.repository_owner }} + + android_prebuilt: + if: github.event.inputs.is_build_for_android == 'true' + needs: context_in_lowercase + runs-on: [self-hosted, linux, shared] + container: + image: ghcr.io/${{ needs.context_in_lowercase.outputs.repository_owner }}/android-release:latest + strategy: + matrix: + cpu: [arm, arm64, x86, x64] + include: + - cpu: arm + arch: arm + - cpu: arm64 + arch: arm64 + - cpu: x86 + arch: x86 + - cpu: x64 + arch: x86_64 + steps: + - name: Fetch v8 + run: | + fetch v8 + cd v8 + git checkout ${{ github.event.inputs.v8_revision }} + - name: Fetch patch + uses: actions/checkout@v2 + with: + path: ${{ github.repository }} + - name: Apply patch + if: ${{ !startsWith(github.event.inputs.v8_revision, '7.7') }} + working-directory: ./v8 + continue-on-error: true + run: | + git apply ../${{ github.repository }}/.github/workflows/tools/v8_remove_requests_revision.patch + - name: Sync third_party + working-directory: ./v8 + run: | + echo "target_os = ['android']" >> ../.gclient + gclient sync -D + - name: Prepare android_ndk + if: ${{ startsWith(github.event.inputs.v8_revision, '7.7') }} + working-directory: ./v8 + run: | + if [ -d third_party/android_tools ]; then + rm -rf third_party/android_tools + mkdir third_party/android_tools + ln -s $ANDROID_NDK_HOME third_party/android_tools/ndk + fi + if [ -f third_party/android_ndk/BUILD.gn ]; then + cp third_party/android_ndk/BUILD.gn $ANDROID_NDK_HOME + fi + if [ -d third_party/android_tools -o -f third_party/android_ndk/BUILD.gn ]; then + rm -rf third_party/android_ndk + ln -s $ANDROID_NDK_HOME third_party/android_ndk + fi + - name: Apply 7.7 build patch + if: ${{ startsWith(github.event.inputs.v8_revision, '7.7') }} + working-directory: ./v8/build + run: | + git apply ../../${{ github.repository }}/.github/workflows/tools/v8_7_7_229_build.patch + - name: Generate ${{ matrix.arch }} + working-directory: ./v8 + run: | + gn gen out --args="target_os=\"android\" target_cpu=\"${{ matrix.cpu }}\" v8_target_cpu=\"${{ matrix.cpu }}\" android_ndk_root=\"${ANDROID_NDK_HOME}\" is_component_build=false v8_monolithic=true android32_ndk_api_level=21 android64_ndk_api_level=21 clang_use_chrome_plugins=false use_thin_lto=false use_custom_libcxx=false ${{ github.event.inputs.build_args }}" + - name: Compile ${{ matrix.arch }} + working-directory: ./v8 + run: | + ninja -C out v8_monolith + - name: Prepare package + working-directory: ./v8/out + run: | + mkdir -p artifact/include/v8 artifact/lib + cp obj/libv8_monolith.a artifact/lib + cp -r ../include/* artifact/include/v8/ + cp -r gen/include/* artifact/include/v8/ + find artifact/include/v8/. ! -name "*.h" -type f -delete + - name: Generate CMakeLists + uses: DamianReeves/write-file-action@v1.0 + with: + path: ./v8/out/artifact/CMakeLists.txt + write-mode: overwrite + contents: ${{ format(env.cmakelists_template, (!startsWith(github.event.inputs.v8_revision, '7.7') && endsWith(matrix.arch, '64')) && 'INTERFACE "-DV8_COMPRESS_POINTERS"' || '', '${CMAKE_CURRENT_SOURCE_DIR}/lib/libv8_monolith.a') }} + - name: Release package + id: release_package + working-directory: ./v8 + run: | + echo "head_full=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + cd out + tar -zcvf android-${{ matrix.arch }}.tgz -C artifact . + - name: Install Requirement + shell: bash + run: | + pip3 install -U cos-python-sdk-v5 + - name: Publish package + shell: python3 {0} + env: + local_file: ./v8/out/android-${{ matrix.arch }}.tgz + cos_key: hippy/${{ github.event.inputs.hip_v8_root }}/v8/${{ github.event.inputs.package_tag }}/android-${{ matrix.arch }}.tgz + v8_head: ${{ steps.release_package.outputs.head_full }} + run: ${{ env.publish_package_script }} + + windows_prebuilt: + if: github.event.inputs.is_build_for_windows == 'true' + runs-on: windows-2019 + strategy: + matrix: + arch: [x86, x64, arm64] + steps: + - name: Setup GN + run: | + Invoke-WebRequest -OutFile depot_tools.zip https://storage.googleapis.com/chrome-infra/depot_tools.zip + Expand-Archive depot_tools.zip -DestinationPath C:\depot_tools + rm depot_tools.zip + - name: Checkout v8 + run: | + $env:DEPOT_TOOLS_WIN_TOOLCHAIN = 0 + $env:Path += ";C:\depot_tools" + fetch v8 + cd v8 + git checkout ${{ github.event.inputs.v8_revision }} + gclient sync -D + - name: Generate ${{ matrix.arch }} + working-directory: ./v8 + run: | + $env:DEPOT_TOOLS_WIN_TOOLCHAIN = 0 + $env:Path += ";C:\depot_tools" + gn gen out --args="target_cpu=""""""${{ matrix.arch }}"""""" v8_target_cpu=""""""${{ matrix.arch }}"""""" is_component_build=false v8_monolithic=true chrome_pgo_phase=0 ${{ github.event.inputs.build_args }}" + - name: Compile ${{ matrix.arch }} + working-directory: ./v8 + run: | + $env:Path += ";C:\depot_tools" + ninja -C out v8_monolith + - name: Prepare package + working-directory: ./v8/out + run: | + New-Item -type directory -Path artifact/include/v8, artifact/lib + Copy-Item obj/v8_monolith.lib artifact/lib + Copy-Item -r ../include/* artifact/include/v8/ + Copy-Item -r gen/include/* artifact/include/v8/ + Get-ChildItem -Exclude *.h -Recurse -File -Path artifact/include/v8 | Remove-Item + - name: Generate CMakeLists + uses: DamianReeves/write-file-action@v1.0 + with: + path: ./v8/out/artifact/CMakeLists.txt + write-mode: overwrite + contents: ${{ format(env.cmakelists_template, endsWith(matrix.arch, '64') && 'INTERFACE "-DV8_COMPRESS_POINTERS"' || '', '${CMAKE_CURRENT_SOURCE_DIR}/lib/v8_monolith.lib') }} + - name: Release package + id: release_package + working-directory: ./v8 + run: | + Write-Output "head_full=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + cd out + Compress-Archive -Path artifact\* -DestinationPath windows-${{ matrix.arch }}.zip + - name: Install Requirement + shell: bash + run: | + pip install -U cos-python-sdk-v5 + - name: Publish package + shell: python + env: + local_file: ./v8/out/windows-${{ matrix.arch }}.zip + cos_key: hippy/${{ github.event.inputs.hip_v8_root }}/v8/${{ github.event.inputs.package_tag }}/windows-${{ matrix.arch }}.zip + v8_head: ${{ steps.release_package.outputs.head_full }} + run: ${{ env.publish_package_script }} + + macos_prebuilt: + if: github.event.inputs.is_build_for_macos == 'true' + runs-on: macos-latest + strategy: + matrix: + arch: [x64, arm64] + steps: + - name: Setup GN + run: | + git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git /usr/local/opt/depot_tools + export PATH=/usr/local/opt/depot_tools:$PATH + - name: Checkout v8 + run: | + export PATH=/usr/local/opt/depot_tools:$PATH + fetch v8 + cd v8 + git checkout ${{ github.event.inputs.v8_revision }} + gclient sync -D + - name: Generate ${{ matrix.arch }} + working-directory: ./v8 + run: | + export PATH=/usr/local/opt/depot_tools:$PATH + gn gen out --args="target_cpu=\"${{ matrix.arch }}\" v8_target_cpu=\"${{ matrix.arch }}\" chrome_pgo_phase=0 is_component_build=false v8_monolithic=true ${{ github.event.inputs.build_args }}" + - name: Compile ${{ matrix.arch }} + working-directory: ./v8 + run: | + export PATH=/usr/local/opt/depot_tools:$PATH + ninja -C out v8_monolith + - name: Prepare package + working-directory: ./v8/out + run: | + mkdir -p artifact/include/v8 artifact/lib + cp obj/libv8_monolith.a artifact/lib + cp -r ../include/* artifact/include/v8/ + cp -r gen/include/* artifact/include/v8/ + find artifact/include/v8/. ! -name "*.h" -type f -delete + - name: Generate CMakeLists + uses: DamianReeves/write-file-action@v1.0 + with: + path: ./v8/out/artifact/CMakeLists.txt + write-mode: overwrite + contents: ${{ format(env.cmakelists_template, endsWith(matrix.arch, '64') && 'INTERFACE "-DV8_COMPRESS_POINTERS"' || '', '${CMAKE_CURRENT_SOURCE_DIR}/lib/libv8_monolith.a') }} + - name: Release package + id: release_package + working-directory: ./v8 + run: | + echo "head_full=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + cd out + tar -zcvf macos-${{ matrix.arch }}.tgz -C artifact . + - name: Install Requirement + shell: bash + run: | + pip install -U cos-python-sdk-v5 + - name: Publish package + shell: python + env: + local_file: ./v8/out/macos-${{ matrix.arch }}.tgz + cos_key: hippy/${{ github.event.inputs.hip_v8_root }}/v8/${{ github.event.inputs.package_tag }}/macos-${{ matrix.arch }}.tgz + v8_head: ${{ steps.release_package.outputs.head_full }} + run: ${{ env.publish_package_script }} + + linux_prebuilt: + if: github.event.inputs.is_build_for_linux == 'true' + needs: context_in_lowercase + runs-on: [self-hosted, linux, shared] + container: + image: ghcr.io/${{ needs.context_in_lowercase.outputs.repository_owner }}/linux-release:latest + strategy: + matrix: + arch: [x86, x64, arm64, arm] + steps: + - name: Checkout v8 + run: | + fetch v8 + cd v8 + git checkout ${{ github.event.inputs.v8_revision }} + gclient sync -D + - name: Install sysroot(${{ matrix.arch }}) + if: ${{ contains(matrix.arch, 'arm') }} + working-directory: ./v8 + run: | + build/linux/sysroot_scripts/install-sysroot.py --arch=${{ matrix.arch }} + - name: Generate ${{ matrix.arch }} + working-directory: ./v8 + run: | + gn gen out --args="target_cpu=\"${{ matrix.arch }}\" v8_target_cpu=\"${{ matrix.arch }}\" chrome_pgo_phase=0 is_component_build=false v8_monolithic=true ${{ github.event.inputs.build_args }}" + - name: Compile ${{ matrix.arch }} + working-directory: ./v8 + run: | + ninja -C out v8_monolith + - name: Prepare package + working-directory: ./v8/out + run: | + mkdir -p artifact/include/v8 artifact/lib + cp obj/libv8_monolith.a artifact/lib + cp -r ../include/* artifact/include/v8/ + cp -r gen/include/* artifact/include/v8/ + find artifact/include/v8/. ! -name "*.h" -type f -delete + - name: Generate CMakeLists + uses: DamianReeves/write-file-action@v1.0 + with: + path: ./v8/out/artifact/CMakeLists.txt + write-mode: overwrite + contents: ${{ format(env.cmakelists_template, endsWith(matrix.arch, '64') && 'INTERFACE "-DV8_COMPRESS_POINTERS"' || '', '${CMAKE_CURRENT_SOURCE_DIR}/lib/libv8_monolith.a') }} + - name: Release package + id: release_package + working-directory: ./v8 + run: | + echo "head_full=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + cd out + tar -zcvf linux-${{ matrix.arch }}.tgz -C artifact . + - name: Install Requirement + shell: bash + run: | + pip3 install -U cos-python-sdk-v5 + - name: Publish package + shell: python3 {0} + env: + local_file: ./v8/out/linux-${{ matrix.arch }}.tgz + cos_key: hippy/${{ github.event.inputs.hip_v8_root }}/v8/${{ github.event.inputs.package_tag }}/linux-${{ matrix.arch }}.tgz + v8_head: ${{ steps.release_package.outputs.head_full }} + run: ${{ env.publish_package_script }} + + ios_prebuilt: + if: github.event.inputs.is_build_for_ios == 'true' + runs-on: macos-latest + strategy: + matrix: + arch: [x64, arm64] + steps: + - name: Setup GN + run: | + git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git /usr/local/opt/depot_tools + export PATH=/usr/local/opt/depot_tools:$PATH + - name: Checkout v8 + run: | + export PATH=/usr/local/opt/depot_tools:$PATH + fetch v8 + cd v8 + git checkout ${{ github.event.inputs.v8_revision }} + - name: Sync third_party + working-directory: ./v8 + run: | + export PATH=/usr/local/opt/depot_tools:$PATH + echo "target_os = ['ios']" >> ../.gclient + gclient sync -D + - name: Remove compiler flags + working-directory: ./v8/build/config/compiler + run: | + awk '!/"-mllvm"/' BUILD.gn > BUILD.gn.0 && mv -f BUILD.gn.0 BUILD.gn + awk '!/"-instcombine-lower-dbg-declare=0"/' BUILD.gn > BUILD.gn.0 && mv -f BUILD.gn.0 BUILD.gn + awk '!/ldflags \+= \[ "-Wl,-mllvm,-instcombine-lower-dbg-declare=0" \]/' BUILD.gn > BUILD.gn.0 && mv -f BUILD.gn.0 BUILD.gn + - name: Generate ${{ matrix.arch }} + working-directory: ./v8 + run: | + export PATH=/usr/local/opt/depot_tools:$PATH + gn gen out --args="target_os=\"ios\" target_cpu=\"${{ matrix.arch }}\" v8_target_cpu=\"${{ matrix.arch }}\" is_component_build=false v8_monolithic=true chrome_pgo_phase=0 enable_ios_bitcode=true ios_deployment_target=10 v8_enable_pointer_compression=false use_custom_libcxx=false ios_enable_code_signing=false ${{ github.event.inputs.build_args }}" + - name: Compile ${{ matrix.arch }} + working-directory: ./v8 + run: | + export PATH=/usr/local/opt/depot_tools:$PATH + ninja -C out v8_monolith + - name: Prepare package + working-directory: ./v8/out + run: | + mkdir -p artifact/include/v8 artifact/lib + cp obj/libv8_monolith.a artifact/lib + cp -r ../include/* artifact/include/v8/ + cp -r gen/include/* artifact/include/v8/ + find artifact/include/v8/. ! -name "*.h" -type f -delete + - name: Generate CMakeLists + uses: DamianReeves/write-file-action@v1.0 + with: + path: ./v8/out/artifact/CMakeLists.txt + write-mode: overwrite + contents: ${{ format(env.cmakelists_template, '', '${CMAKE_CURRENT_SOURCE_DIR}/lib/libv8_monolith.a') }} + - name: Release package + id: release_package + working-directory: ./v8 + run: | + echo "head_full=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + cd out + tar -zcvf ios-${{ matrix.arch }}.tgz -C artifact . + - name: Install Requirement + shell: bash + run: | + pip install -U cos-python-sdk-v5 + - name: Publish package + shell: python + env: + local_file: ./v8/out/ios-${{ matrix.arch }}.tgz + cos_key: hippy/${{ github.event.inputs.hip_v8_root }}/v8/${{ github.event.inputs.package_tag }}/ios-${{ matrix.arch }}.tgz + v8_head: ${{ steps.release_package.outputs.head_full }} + run: ${{ env.publish_package_script }} diff --git a/.github/workflows/android_build_tests.yml b/.github/workflows/android_build_tests.yml new file mode 100644 index 00000000000..bda5c510048 --- /dev/null +++ b/.github/workflows/android_build_tests.yml @@ -0,0 +1,44 @@ +name: '[android] build tests' + +on: + pull_request: + branches: + - master + - main + paths: + - 'android/**' + - 'core/**' + - 'gradle/**' + - 'build.gradle' + - 'gradle.properties' + - 'settings.gradle' + - 'gradlew' + - 'gradlew.bat' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + android_build_tests: + runs-on: ${{ github.repository == 'Tencent/Hippy' && fromJson('[''self-hosted'', ''linux'', ''shared'']') || 'ubuntu-latest' }} + container: + image: ghcr.io/tencent/android-release:latest # repository name must be lowercase(${{ github.repository_owner }}) + strategy: + matrix: + type: [debug, release] + v8: [v8_min, v8_target] + include: + - type: debug + task: assembleDebug + - type: release + task: assembleRelease + timeout-minutes: 10 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + with: + lfs: true + - name: Run ${{ matrix.task }} + run: | + ./gradlew ${{ matrix.task }} -PINCLUDE_ABI_X86=true -PINCLUDE_ABI_X86_64=true ${{ (matrix.v8 == 'v8_min') && '-PV8_COMPONENT=7.7.299.15' || '' }} diff --git a/.github/workflows/android_build_tests_bypass.yml b/.github/workflows/android_build_tests_bypass.yml new file mode 100644 index 00000000000..7a067d6dea6 --- /dev/null +++ b/.github/workflows/android_build_tests_bypass.yml @@ -0,0 +1,32 @@ +name: '[android] build tests' + +on: + pull_request: + branches: + - master + - main + paths-ignore: + - 'android/**' + - 'core/**' + - 'gradle/**' + - 'build.gradle' + - 'gradle.properties' + - 'settings.gradle' + - 'gradlew' + - 'gradlew.bat' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + android_build_tests: + runs-on: ubuntu-latest + strategy: + matrix: + type: [debug, release] + v8: [v8_min, v8_target] + steps: + - name: Build Test Bypass + run: | + echo "No build test required" diff --git a/.github/workflows/build_android_release_image.yml b/.github/workflows/build_android_release_image.yml deleted file mode 100644 index 99c5f137b6b..00000000000 --- a/.github/workflows/build_android_release_image.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: '[android] build release image' - -on: - workflow_dispatch: - inputs: - tag: - description: 'TAG' - default: 'latest' - required: true - push: - branches: - - master - - main - tags-ignore: - - '**' - paths: - - 'docker/android-release/Dockerfile' -jobs: - push_to_registry: - name: Push docker image - runs-on: [self-hosted, linux, trusted] - steps: - - name: Check out the repo - uses: actions/checkout@v2 - - name: Log in - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Get repo owner(in lowercase) - id: get_owner - uses: ASzc/change-string-case-action@v2 - with: - string: ${{ github.repository_owner }} - - name: Push to ghcr.io - uses: docker/build-push-action@v2 - with: - context: docker/android-release - push: true - tags: ghcr.io/${{ steps.get_owner.outputs.lowercase }}/android-release:${{ github.event.inputs.tag || 'latest' }} diff --git a/.github/workflows/commit.yml b/.github/workflows/commit.yml deleted file mode 100644 index 009b6e32c71..00000000000 --- a/.github/workflows/commit.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: '[commit-lint] check commit message format' - -on: [ pull_request ] - -jobs: - commitlint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: setup-node - uses: actions/setup-node@master - with: - node-version: 16.x - - name: Install - run: npm install commitlint-plugin-function-rules@1.4.0 @commitlint/cli@13.1.0 @commitlint/config-conventional@13.1.0 @commitlint/config-conventional@13.1.0 @commitlint/lint@13.1.0 @commitlint/prompt-cli@13.1.0 - - name: commitlint - run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose diff --git a/.github/workflows/config/license-check.json b/.github/workflows/config/license-check.json new file mode 100644 index 00000000000..1af2e76518c --- /dev/null +++ b/.github/workflows/config/license-check.json @@ -0,0 +1,42 @@ +{ + "copyright": [ + "Copyright", + "Licensed under the Apache License, Version 2.0 (the \"License\")" + ], + "include": [ + "**/*.{ts,tsx,js,jsx,vue,ejs,mjs,es6}", + "**/*.{css,less,scss}", + "**/*.{h,hpp,hh,h++,hxx}", + "**/*.{c,cc,c++,cpp,C,cxx}", + "**/*.{m,mm,swift}", + "**/*.cmake", + "**/*.java", + "**/*.txt", + "**/*.sh", + "**/*.yaml", + "**/*.bat", + "**/*.dart", + "**/*.py" + ], + "ignore": [ + "**/node_modules/**", + "**/AUTHORS/**", + "**/.git/**", + "**/.gradle/**", + "**/.DS_Store/**", + "**/.nyc_output/**", + "**/.vscode/**", + "**/.vs/**", + "**/.idea/**", + "**/.externalNativeBuild/**", + "**/.cxx/**", + "**/*.xcworkspace/**", + ".github/**", + ".husky/**", + "docs/**", + "static/**", + "examples/android-demo/res/**", + "examples/ios-demo/res/**", + "examples/hippy-**-demo/**" + ] +} diff --git a/.github/workflows/config/pr-path-labeler.yml b/.github/workflows/config/pr-path-labeler.yml new file mode 100644 index 00000000000..078283869eb --- /dev/null +++ b/.github/workflows/config/pr-path-labeler.yml @@ -0,0 +1,41 @@ +"framework: android": + - android/**/* +"framework: ios": + - ios/**/* +"driver: js": + - core/**/* + - packages/**/* + - .eslintignore + - .eslintrc.js + - .nycrc + - ava.config.js +"dom: layout": + - layout/**/* +"doc: example": + - examples/**/* +"doc: pages": + - docs/**/* +"gh: workflow": + - .github/workflows/**/* +"gh: template": + - .github/ISSUE_TEMPLATE/**/* + - .github/PULL_REQUEST_TEMPLATE.md +"gh: owner": + - .github/CODEOWNERS +"build: gradle": + - gradle/**/* + - build.gradle + - gradle.properties + - gradlew + - gradlew.bat + - settings.gradle +"build: xcode": + - Hippy.xcworkspace/**/* + - hippy.podspec +"build: cmake": + - buildconfig/cmake/**/* +"build: js": + - scripts/**/* + - tsdoc.json + - tsconfig.json + - lerna.json diff --git a/.github/workflows/config/pr-size-labeler.yml b/.github/workflows/config/pr-size-labeler.yml new file mode 100644 index 00000000000..3586b288e6e --- /dev/null +++ b/.github/workflows/config/pr-size-labeler.yml @@ -0,0 +1,5 @@ +'size: xs': 9 +'size: s': 99 +'size: m': 499 +'size: l': 999 +'size: xl': Infinity diff --git a/.github/workflows/docker_build_image.yml b/.github/workflows/docker_build_image.yml new file mode 100644 index 00000000000..36d46225d54 --- /dev/null +++ b/.github/workflows/docker_build_image.yml @@ -0,0 +1,88 @@ +name: '[docker] build docker image' + +on: + workflow_dispatch: + inputs: + images: + description: 'Images (separated by commas)' + required: true + tag: + description: 'TAG' + default: 'latest' + required: true + push: + branches: + - master + - main + tags-ignore: + - '**' + paths: + - 'buildconfig/docker/**' + +jobs: + changed_images: + runs-on: ubuntu-latest + outputs: + changed_images: ${{ steps.changed_images.outputs.changed_images }} + steps: + - name: Get changed images + id: changed_images + uses: actions/github-script@v6 + with: + script: | + const { basename, sep } = require('path'); + const { appendFileSync } = require('fs'); + const { EOL } = require('os'); + const { eventName } = context; + const { repos } = github.rest; + const push = context.payload; + + const changedImages = new Set(); + + if (eventName === 'workflow_dispatch') { + '${{ github.event.inputs.images }}'.split(',').forEach(image => changedImages.add(image.trim())); + } else if (eventName === 'push') { + const files = await github.paginate(repos.compareCommits, { + ...context.repo, + base: push.before, + head: push.after, + per_page: 100 + }); + + files.filter(({ status, filename }) => + (status === 'added' || status === 'modified' || status === 'changed' || status === 'copied' || status === 'renamed') + && basename(filename) === 'Dockerfile').forEach(({ filename }) => { + changedImages.add(filename.split(sep).slice(-2)[0]); + }); + } + + appendFileSync(process.env.GITHUB_OUTPUT, `changed_images=${JSON.stringify(Array.from(changedImages))}${EOL}`, { encoding: 'utf8' }); + + build_images: + needs: changed_images + runs-on: ${{ github.repository == 'Tencent/Hippy' && fromJson('[''self-hosted'', ''linux'', ''shared'']') || 'ubuntu-latest' }} + strategy: + matrix: + images: ${{ fromJson(needs.changed_images.outputs.changed_images) }} + steps: + - name: Checkout repo + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Log in + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Get repo owner(in lowercase) + id: get_owner + uses: ASzc/change-string-case-action@v2 + with: + string: ${{ github.repository_owner }} + - name: Make & Push to ghcr.io + uses: docker/build-push-action@v4 + with: + context: buildconfig/docker/${{ matrix.images }} + push: true + tags: ghcr.io/${{ steps.get_owner.outputs.lowercase }}/${{ matrix.images }}:${{ github.event.inputs.tag || 'latest' }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 7ff7b1a78de..00000000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: '[markdown-lint] check documents format' - -on: - push: - paths: - - '*.md' - - 'docs/**/*.md' - branches: - # Push events on main branch - - main - # Push events on master branch - - master - tags-ignore: - - '**' - pull_request: - paths: - - '*.md' - - 'docs/**/*.md' - branches: - # Pull request events to main branch - - main - # Pull request events to master branch - - master - tags-ignore: - - '**' - -jobs: - markdownlint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: markdownlint - uses: nosborn/github-action-markdown-cli@v1.1.1 - with: - config_file: .markdownlintrc.json - files: ./README*.md ./PUBLISH*.md ./docs/**/*.md diff --git a/.github/workflows/docs_markdown_lint.yml b/.github/workflows/docs_markdown_lint.yml new file mode 100644 index 00000000000..fe6d75789ec --- /dev/null +++ b/.github/workflows/docs_markdown_lint.yml @@ -0,0 +1,28 @@ +name: '[docs] markdown documents format lint' + +on: + pull_request: + branches: + - main + - master + paths: + - '*.md' + - 'docs/**/*.md' + +jobs: + markdownlint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: setup-node + uses: actions/setup-node@v3 + with: + node-version: 16.x + cache: 'npm' + cache-dependency-path: package-lock.json + - name: Install + run: | + npm install markdown-it@12.3.2 markdownlint-cli@0.31.1 + - name: markdownlint + run: | + npm run markdownlint diff --git a/.github/workflows/docs_markdown_lint_bypass.yml b/.github/workflows/docs_markdown_lint_bypass.yml new file mode 100644 index 00000000000..664bc86f8b9 --- /dev/null +++ b/.github/workflows/docs_markdown_lint_bypass.yml @@ -0,0 +1,18 @@ +name: '[docs] markdown documents format lint' + +on: + pull_request: + branches: + - main + - master + paths-ignore: + - '*.md' + - 'docs/**/*.md' + +jobs: + markdownlint: + runs-on: ubuntu-latest + steps: + - name: Lint Bypass + run: | + echo "No lint required" diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml deleted file mode 100644 index 4cc74d0d0ee..00000000000 --- a/.github/workflows/frontend.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: '[FrontEnd] check js code' - -on: - push: - paths: - - 'packages/**' - - 'examples/hippy-*-demo/**' - - 'scripts/**/*.js' - - 'core/js/**/*.js' - branches: - # Push events on main branch - - main - # Push events on master branch - - master - tags-ignore: - - '**' - pull_request: - paths: - - 'packages/**' - - 'examples/hippy-*-demo/**' - - 'scripts/**/*.js' - - 'core/js/**/*.js' - branches: - # Pull request events to main branch - - main - # Pull request events to master branch - - master - tags-ignore: - - '**' - -jobs: - check_js: - runs-on: ubuntu-latest - strategy: - matrix: - node: [ 10.x, 16.x ] - steps: - - uses: actions/checkout@v2 - - name: setup-node - uses: actions/setup-node@master - with: - node-version: ${{ matrix.node }} - registry-url: https://npm.pkg.github.com - - name: install - run: npm install - - name: lint - run: npm run lint - - name: test - run: npm run coverage - - name: coverage - if: matrix.node == '16.x' - uses: codecov/codecov-action@v2 - - name: build - run: npm run build diff --git a/.github/workflows/frontend_build_tests.yml b/.github/workflows/frontend_build_tests.yml new file mode 100644 index 00000000000..3cf64a6c141 --- /dev/null +++ b/.github/workflows/frontend_build_tests.yml @@ -0,0 +1,39 @@ +name: '[front-end] build tests' + +on: + pull_request: + branches: + - main + - master + paths: + - 'packages/**' + - 'package*.json' + - 'examples/hippy-*-demo/**' + - 'scripts/**' + - 'core/js/**' + +jobs: + frontend_build_tests: + runs-on: ubuntu-latest + strategy: + matrix: + node: [ 16.x, 17.x ] + steps: + - uses: actions/checkout@v3 + - name: setup-node + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + registry-url: https://npm.pkg.github.com + cache: 'npm' + cache-dependency-path: package-lock.json + - name: install + run: | + npm install && npx lerna bootstrap --no-ci + - name: lint & build + run: npm run lint & npm run build + - name: test + run: npm run test:jest && npm run coverage + - name: coverage + if: matrix.node == '16.x' + uses: codecov/codecov-action@v2 diff --git a/.github/workflows/frontend_build_tests_bypass.yml b/.github/workflows/frontend_build_tests_bypass.yml new file mode 100644 index 00000000000..8c16bc9a1ee --- /dev/null +++ b/.github/workflows/frontend_build_tests_bypass.yml @@ -0,0 +1,24 @@ +name: '[front-end] build tests' + +on: + pull_request: + branches: + - main + - master + paths-ignore: + - 'packages/**' + - 'package*.json' + - 'examples/hippy-*-demo/**' + - 'scripts/**' + - 'core/js/**' + +jobs: + frontend_build_tests: + runs-on: ubuntu-latest + strategy: + matrix: + node: [ 16.x, 17.x ] + steps: + - name: Build Test Bypass + run: | + echo "No build test required" diff --git a/.github/workflows/gh_action_approve_checks.yml b/.github/workflows/gh_action_approve_checks.yml new file mode 100644 index 00000000000..85bd6da45fc --- /dev/null +++ b/.github/workflows/gh_action_approve_checks.yml @@ -0,0 +1,143 @@ +name: '[gh] action approve checks' + +on: + pull_request_target: + branches: + - master + - main + - v3.0-dev + - v3.0 + types: + - labeled + +jobs: + call_approval_checks_run: + if: github.event.label.name == 'action(approve-checks)' && github.repository == 'Tencent/Hippy' + uses: ./.github/workflows/reuse_approve_checks_run.yml + with: + pull_request_number: ${{ github.event.pull_request.number }} + pull_request_head_sha: ${{ github.event.pull_request.head.sha }} + + approve_checks_action: + needs: call_approval_checks_run + runs-on: ubuntu-latest + steps: + - name: Token + uses: navikt/github-app-token-generator@v1 + id: get-token + with: + private-key: ${{ secrets.ACTION_PRIVATE_KEY }} + app-id: ${{ secrets.ACTION_APP_ID }} + - name: Action + uses: actions/github-script@v6.3.3 + env: + COMMENT_MESSAGE: | + :lock: The current `approve-checks` action execution requires privilege escalation. + + Please wait for approval of admin team member ... + + @${{ github.event.sender.login }} + WECHAT_WORK_MESSAGE: | + [${{ github.event.sender.login }}](https://github.com/${{ github.event.sender.login }}) requested privilege escalation action on [#${{ github.event.pull_request.number }}](${{ github.event.pull_request.html_url }}) pull request. + > ${{ github.event.pull_request.title }} + > [${{ github.event.pull_request.html_url }}](${{ github.event.pull_request.html_url }}) + PRIVILEGE_ESCALATION: ${{ needs.call_approval_checks_run.outputs.included_risk_files }} + with: + github-token: ${{ steps.get-token.outputs.token }} + script: | + const { pull_request } = context.payload; + const { issues } = github.rest; + + const p = []; + if (process.env.PRIVILEGE_ESCALATION == 'true') { + p.push(issues.createComment({ + issue_number: pull_request.number, + body: process.env.COMMENT_MESSAGE, + ...context.repo + })); + p.push(github.request("POST ${{ secrets.WECHAT_WORK_BOT_WEBHOOK }}", { + headers: { + "content-type": "application/json" + }, + data: { + chatid: "${{ secrets.WECHAT_WORK_ADMIN_CHAT_ID }}", + msgtype: "markdown", + markdown: { + content: process.env.WECHAT_WORK_MESSAGE, + attachments: [{ + callback_id: "approve", + actions: [{ + name: "approve_btn", + text: "Mark as Approved", + type: "button", + value: "Mark as Approved", + replace_text: "Already approved", + border_color: "2c974b", + text_color: "2c974b" + }, { + name: "reject_btn", + text: "Mark as Rejected", + type: "button", + value: "Mark as Rejected", + replace_text: "Already Rejected", + border_color: "cf222e", + text_color: "cf222e" + }, { + name: "ignored_btn", + text: "Mark as Ignored", + type: "button", + value: "Mark as Ignored", + replace_text: "Already Ignored", + border_color: "6e7781", + text_color: "6e7781" + }] + }] + } + } + })); + } else { + p.push(issues.removeLabel({ + issue_number: pull_request.number, + name: 'action(approve-checks)', + ...context.repo, + }).catch(e => { + console.error('issues.removeLabel', e); + })); + } + + await Promise.all(p); + + call_approval_checks_run_privileged: + needs: [ call_approval_checks_run, approve_checks_action ] + if: needs.call_approval_checks_run.outputs.included_risk_files == 'true' + uses: ./.github/workflows/reuse_approve_checks_run.yml + with: + pull_request_number: ${{ github.event.pull_request.number }} + pull_request_head_sha: ${{ github.event.pull_request.head.sha }} + requied_privilege_escalation: true + + approve_checks_action_privileged: + needs: call_approval_checks_run_privileged + runs-on: ubuntu-latest + steps: + - name: Token + uses: navikt/github-app-token-generator@v1 + id: get-token + with: + private-key: ${{ secrets.ACTION_PRIVATE_KEY }} + app-id: ${{ secrets.ACTION_APP_ID }} + - name: Action + uses: actions/github-script@v6.3.3 + with: + github-token: ${{ steps.get-token.outputs.token }} + script: | + const { pull_request } = context.payload; + const { issues } = github.rest; + + await issues.removeLabel({ + issue_number: pull_request.number, + name: 'action(approve-checks)', + ...context.repo, + }).catch(e => { + console.error('issues.removeLabel', e); + }); diff --git a/.github/workflows/gh_action_merge.yml b/.github/workflows/gh_action_merge.yml new file mode 100644 index 00000000000..a2a67c8f734 --- /dev/null +++ b/.github/workflows/gh_action_merge.yml @@ -0,0 +1,355 @@ +name: '[gh] action merge' + +on: + pull_request_target: + branches: + - master + - main + - v3.0-dev + - v3.0 + types: + - labeled + +env: + OUTPUT_FUNCTION: | + function output { + if [ $1 -ne 0 ]; then + if [[ -z "$2" ]]; then + echo "failed_result=(empty response)" >> $GITHUB_OUTPUT + else + echo "failed_result<> $GITHUB_OUTPUT + echo "$2" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + exit 0 + fi + } + +jobs: + call_classify_commits: + if: github.repository == 'Tencent/Hippy' && (github.event.label.name == 'action(squash-merge)' || github.event.label.name == 'action(rebase-merge)') + uses: ./.github/workflows/reuse_classify_commits.yml + with: + pull_request_number: ${{ github.event.pull_request.number }} + + squash_merge_action: + needs: call_classify_commits + if: github.event.label.name == 'action(squash-merge)' + runs-on: ubuntu-latest + steps: + - name: Token + uses: navikt/github-app-token-generator@v1 + id: get-token + with: + private-key: ${{ secrets.ACTION_PRIVATE_KEY }} + app-id: ${{ secrets.ACTION_APP_ID }} + - name: Reset + if: "contains(github.event.pull_request.labels.*.name, 'action(squash-merge): failed')" + uses: actions-ecosystem/action-remove-labels@v1.3.0 + with: + github_token: ${{ steps.get-token.outputs.token }} + labels: "action(squash-merge): failed" + - name: Checkout + if: needs.call_classify_commits.outputs.normal_commits_count > 1 + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Setup + if: needs.call_classify_commits.outputs.normal_commits_count > 1 + uses: actions/setup-node@v3 + with: + node-version: 16.x + cache: 'npm' + cache-dependency-path: package-lock.json + - name: Install + if: needs.call_classify_commits.outputs.normal_commits_count > 1 + run: | + npm install commitlint-plugin-function-rules@1.6.0 @commitlint/cli@16.2.1 @commitlint/config-conventional@16.2.1 @commitlint/lint@16.2.1 @commitlint/prompt-cli@16.2.1 + - name: Commitlint + id: commitlint + env: + PULL_REQUEST_TITLE: ${{ github.event.pull_request.title }} + shell: bash {0} + run: | + ${{ env.OUTPUT_FUNCTION }} + if [ ${{ needs.call_classify_commits.outputs.normal_commits_count }} -gt 1 ]; then + result=`echo $PULL_REQUEST_TITLE | npx commitlint -c false 2>&1` + output $? "$result" + fi + - name: Squash + id: squash + if: ${{ !steps.commitlint.outputs.failed_result && github.event.pull_request.user.login != github.event.sender.login }} + env: + GH_TOKEN: ${{ steps.get-token.outputs.token }} + shell: bash {0} + run: | + ${{ env.OUTPUT_FUNCTION }} + result=`gh pr merge ${{ github.event.pull_request.number }} --disable-auto --repo $GITHUB_REPOSITORY 2>&1` + output $? "$result" + result=`gh pr merge ${{ github.event.pull_request.number }} --squash --auto --repo $GITHUB_REPOSITORY 2>&1` + output $? "$result" + - name: Comment + uses: actions/github-script@v6.3.3 + env: + SQUASH_RESULT: ${{ steps.squash.outputs.failed_result }} + CHECK_RESULT: ${{ steps.commitlint.outputs.failed_result }} + FAILED_MESSAGE: | + :warning: `squash-merge` action execution failed, because of **%s**. + + Please check the following details: +
+ + ``` + %s + ``` + +
+ + ${{ github.event.sender.type == 'User' && format('@{0}', github.event.sender.login) || '#help' }} + with: + github-token: ${{ steps.get-token.outputs.token }} + script: | + const { pull_request, sender } = context.payload; + const { issues } = github.rest; + const util = require('util'); + const os = require('os'); + + let body; + if (process.env.CHECK_RESULT) { + body = util.format(process.env.FAILED_MESSAGE, 'pull request title does not meet the [Convention Commit](https://conventionalcommits.org/) guideline', process.env.CHECK_RESULT); + } else if (process.env.SQUASH_RESULT) { + body = util.format(process.env.FAILED_MESSAGE, 'auto squash and merge failed', process.env.SQUASH_RESULT); + } else if (pull_request.user.login === sender.login) { + body = util.format(process.env.FAILED_MESSAGE, 'cannot squash and merge your own pull request', `(empty response)`); + } + + const p = []; + p.push(issues.removeLabel({ + issue_number: pull_request.number, + name: 'action(squash-merge)', + ...context.repo, + }).catch(e => { + console.log('issues.removeLabel', e); + })); + if (body) { + p.push(issues.createComment({ + issue_number: pull_request.number, + body: body, + ...context.repo + })); + p.push(issues.addLabels({ + issue_number: pull_request.number, + labels: [ 'action(squash-merge): failed' ], + ...context.repo + })); + } + await Promise.all(p); + + rebase_merge_action_preparation: + needs: call_classify_commits + if: github.event.label.name == 'action(rebase-merge)' + runs-on: ubuntu-latest + outputs: + permission: ${{ steps.get-permission.outputs.permission }} + steps: + - name: Permission + id: get-permission + uses: actions/github-script@v6.3.3 + with: + script: | + const { repos } = github.rest; + const fs = require('fs'); + const os = require('os'); + + const { data: { permission } } = await repos.getCollaboratorPermissionLevel({ + ...context.repo, + username: "${{ github.event.sender.login }}" + }); + fs.appendFileSync(process.env.GITHUB_OUTPUT, `permission=${permission}${os.EOL}`, { encoding: 'utf8' }); + - name: Token + uses: navikt/github-app-token-generator@v1 + if: steps.get-permission.outputs.permission != 'admin' && needs.call_classify_commits.outputs.normal_commits_count > 1 + id: get-token + with: + private-key: ${{ secrets.ACTION_PRIVATE_KEY }} + app-id: ${{ secrets.ACTION_APP_ID }} + - name: Comment + if: steps.get-token.outputs.token + env: + COMMENT_MESSAGE: | + :lock: `rebase-merge` action execution needs privilege escalation. + + Please wait for admin team member approval ... + + ${{ github.event.sender.type == 'User' && format('@{0}', github.event.sender.login) || '' }} + WECHAT_WORK_MESSAGE: | + [${{ github.event.sender.login }}](https://github.com/${{ github.event.sender.login }}) requested privilege escalation action on [#${{ github.event.pull_request.number }}](${{ github.event.pull_request.html_url }}) pull request. + > ${{ github.event.pull_request.title }} + > [${{ github.event.pull_request.html_url }}](${{ github.event.pull_request.html_url }}) + uses: actions/github-script@v6.3.3 + with: + github-token: ${{ steps.get-token.outputs.token }} + script: | + const { pull_request } = context.payload; + const { issues } = github.rest; + const util = require('util'); + + const p = []; + + p.push(issues.createComment({ + issue_number: pull_request.number, + body: process.env.COMMENT_MESSAGE, + ...context.repo + })); + + p.push(github.request("POST ${{ secrets.WECHAT_WORK_BOT_WEBHOOK }}", { + headers: { + "content-type": "application/json" + }, + data: { + chatid: "${{ secrets.WECHAT_WORK_ADMIN_CHAT_ID }}", + msgtype: "markdown", + markdown: { + content: process.env.WECHAT_WORK_MESSAGE, + attachments: [{ + callback_id: "approve", + actions: [{ + name: "approve_btn", + text: "Mark as Approved", + type: "button", + value: "Mark as Approved", + replace_text: "Already approved", + border_color: "2c974b", + text_color: "2c974b" + }, { + name: "reject_btn", + text: "Mark as Rejected", + type: "button", + value: "Mark as Rejected", + replace_text: "Already Rejected", + border_color: "cf222e", + text_color: "cf222e" + }, { + name: "ignored_btn", + text: "Mark as Ignored", + type: "button", + value: "Mark as Ignored", + replace_text: "Already Ignored", + border_color: "6e7781", + text_color: "6e7781" + }] + }] + } + } + })); + + await Promise.all(p); + + rebase_merge_action: + needs: [ rebase_merge_action_preparation, call_classify_commits ] + runs-on: ubuntu-latest + environment: ${{ (needs.rebase_merge_action_preparation.outputs.permission != 'admin' && needs.call_classify_commits.outputs.normal_commits_count > 1) && 'github-actions-privileged' || '' }} + steps: + - name: Token + uses: navikt/github-app-token-generator@v1 + id: get-token + with: + private-key: ${{ secrets.ACTION_PRIVATE_KEY }} + app-id: ${{ secrets.ACTION_APP_ID }} + - name: Check + if: "contains(github.event.pull_request.labels.*.name, 'action(rebase-merge): failed')" + uses: actions-ecosystem/action-remove-labels@v1.3.0 + with: + github_token: ${{ steps.get-token.outputs.token }} + labels: "action(rebase-merge): failed" + - name: Rebase + id: rebase + env: + GH_TOKEN: ${{ steps.get-token.outputs.token }} + shell: bash {0} + run: | + ${{ env.OUTPUT_FUNCTION }} + result=`gh pr merge ${{ github.event.pull_request.number }} --disable-auto --repo $GITHUB_REPOSITORY 2>&1` + output $? "$result" + result=`gh pr merge ${{ github.event.pull_request.number }} --rebase --auto --repo $GITHUB_REPOSITORY 2>&1` + output $? "$result" + - name: Comment + uses: actions/github-script@v6.3.3 + env: + REBASE_RESULT: ${{ steps.rebase.outputs.failed_result }} + FAILED_MESSAGE: | + :warning: `rebase-merge` action execution failed, because of **%s**. + + Please check the following details: +
+ + ``` + %s + ``` + +
+ + ${{ github.event.sender.type == 'User' && format('@{0}', github.event.sender.login) || '#help' }} + with: + github-token: ${{ steps.get-token.outputs.token }} + script: | + const { pull_request } = context.payload; + const { issues } = github.rest; + const util = require('util'); + + const p = []; + p.push(issues.removeLabel({ + issue_number: pull_request.number, + name: 'action(rebase-merge)', + ...context.repo, + }).catch(e => { + console.log('issues.removeLabel', e); + })); + if (process.env.REBASE_RESULT) { + p.push(issues.createComment({ + issue_number: pull_request.number, + body: util.format(process.env.FAILED_MESSAGE, 'auto rebase and merge failed', process.env.REBASE_RESULT), + ...context.repo + })); + p.push(issues.addLabels({ + issue_number: pull_request.number, + labels: [ 'action(rebase-merge): failed' ], + ...context.repo + })); + } + await Promise.all(p); + + rebase_merge_action_rejected: + needs: [ rebase_merge_action ] + if: failure() + runs-on: ubuntu-latest + steps: + - name: Token + uses: navikt/github-app-token-generator@v1 + id: get-token + with: + private-key: ${{ secrets.ACTION_PRIVATE_KEY }} + app-id: ${{ secrets.ACTION_APP_ID }} + - name: Label + uses: actions/github-script@v6.3.3 + with: + github-token: ${{ steps.get-token.outputs.token }} + script: | + const { pull_request } = context.payload; + const { issues } = github.rest; + + const p = []; + p.push(issues.removeLabel({ + issue_number: pull_request.number, + name: 'action(rebase-merge)', + ...context.repo, + }).catch(e => { + console.log('issues.removeLabel', e); + })); + p.push(issues.addLabels({ + issue_number: pull_request.number, + labels: [ 'action(rebase-merge): failed' ], + ...context.repo + })); + + await Promise.all(p); diff --git a/.github/workflows/gh_issue_crash_report.yml b/.github/workflows/gh_issue_crash_report.yml new file mode 100644 index 00000000000..68b01c7a2d8 --- /dev/null +++ b/.github/workflows/gh_issue_crash_report.yml @@ -0,0 +1,282 @@ +name: '[gh] issue crash report' + +on: + issues: + types: + - opened + +env: + greetings_message: | + 🙇 Thank you for making us aware of the issue. + + Don\'t worry about it, our collaborators will handle it asap. + + If you have any additional information, you can add a new comment in this issue. + translates_addresses_message: | + I will try to translate addresses to human readable format, please wait a moment. + fetch_symbol_failed_message: | + I have searched the artifacts store database, but found no artifacts with version %s and %s architecture. + no_backtrace_message: | + It looks like there is no backtrace addresses in the crash log that needs to be translated. + translate_success_message: | + After my translation, the result is as follows. + ``` + %s + ``` + translate_failed_message: | + Sorry, the addresses translation encountered an error. + +jobs: + triage: + if: github.repository == 'Tencent/Hippy' && contains(github.event.issue.title, 'crash') + runs-on: [self-hosted, linux, shared] + container: + image: node:latest + outputs: + version: ${{ steps.triage-result.outputs.version }} + platform: ${{ steps.triage-result.outputs.platform }} + architecture: ${{ steps.triage-result.outputs.architecture }} + logs: ${{ steps.triage-result.outputs.logs }} + setps: ${{ steps.triage-result.outputs.setps }} + additional: ${{ steps.triage-result.outputs.additional }} + repository_owner: ${{ steps.triage-result.outputs.repository_owner }} + steps: + - name: Greetings + uses: actions/github-script@v6.3.3 + with: + script: | + const { owner, repo } = context.repo; + const { issues, reactions } = github.rest; + + const { data: { id } } = await issues.createComment({ + owner, + repo, + issue_number: ${{ github.event.issue.number }}, + body: process.env.greetings_message + }); + await reactions.createForIssue({ + owner, + repo, + issue_number: ${{ github.event.issue.number }}, + content: '+1' + }); + - run: npm install markdown-it + - name: Triage + id: triage-result + uses: actions/github-script@v6.3.3 + env: + issue_body: ${{ github.event.issue.body }} + with: + script: | + const MarkdownIt = require('markdown-it'); + const os = require('os'); + const path = require('path'); + const fs = require('fs'); + + function parse(markdown) { + const tokens = (new MarkdownIt()).parse(markdown, {}); + const result = {}; + + let inHeading = true; + let key = []; + let value = []; + tokens.forEach((token, index) => { + if (token.type === 'heading_open' && token.markup === '###') { + if (key.length > 0) { + result[key.join(os.EOL)] = value.join(os.EOL); + } + key = []; + value = []; + inHeading = true; + } else if (token.type === 'heading_close' && token.markup === '###') { + inHeading = false; + } else { + const { content } = token; + if (content) { + if (inHeading) { + key.push(content); + } else { + value.push(content); + } + } + } + + if (index === tokens.length - 1) { + result[key.join(os.EOL)] = value.join(os.EOL); + } + }); + return result; + } + + const info = parse(process.env.issue_body); + ['Version', 'Platform', 'Architecture', 'Logs', 'Reproduce Condition', 'Additional Information'].forEach(key => { + let value = info[key]; + if (value) { + if (['Version', 'Platform', 'Architecture'].includes(key)) { + value = value.replaceAll(/[^0-9a-z\-_\.]/ig, ''); + } + fs.appendFileSync(process.env.GITHUB_OUTPUT, `${key.toLowerCase()}< 0) { + appendFileSync(process.env.GITHUB_OUTPUT, `backtrace=${JSON.stringify(backtrace)}${EOL}`, { encoding: 'utf8' }); + } + + await issues.createComment({ + owner, + repo, + issue_number: ${{ github.event.issue.number }}, + body: process.env[backtrace.length > 0 ? 'translates_addresses_message' : 'no_backtrace_message'] + }); + - name: Symbols + id: symbols_fetched + if: steps.backtrace_parsed.outputs.backtrace + continue-on-error: true + shell: bash + run: | + if [[ "${{ needs.triage.outputs.architecture }}" == "arm" ]]; then + ZIP_FILE="armeabi-v7a.zip" + elif [[ "${{ needs.triage.outputs.architecture }}" == "arm64" ]]; then + ZIP_FILE="arm64-v8a.zip" + elif [[ "${{ needs.triage.outputs.architecture }}" == "x86" ]]; then + ZIP_FILE="x86.zip" + elif [[ "${{ needs.triage.outputs.architecture }}" == "x86_64" ]]; then + ZIP_FILE="x86_64.zip" + else + echo "Unknown architecture(${{ needs.triage.outputs.architecture }})" + exit 1 + fi + curl -Of https://artifacts-store.openhippy.com/hippy/android/hippy-common/${{ needs.triage.outputs.version }}/symbols/$ZIP_FILE + mkdir symbols + unzip -d symbols $ZIP_FILE + rm $ZIP_FILE + echo "success=1" >> $GITHUB_OUTPUT + - uses: actions/github-script@v6.3.3 + if: steps.symbols_fetched.result == 'success' && !steps.symbols_fetched.outputs.success + with: + github-token: ${{ steps.get-token.outputs.token }} + script: | + const { owner, repo } = context.repo; + const { issues } = github.rest; + const util = require('util'); + + await issues.createComment({ + owner, + repo, + issue_number: ${{ github.event.issue.number }}, + body: util.format(process.env.fetch_symbol_failed_message, '${{ needs.triage.outputs.version }}', '${{ needs.triage.outputs.architecture }}') + }); + - name: Translates + if: steps.symbols_fetched.outputs.success + uses: actions/github-script@v6.3.3 + with: + github-token: ${{ steps.get-token.outputs.token }} + script: | + const { owner, repo } = context.repo; + const { issues, reactions } = github.rest; + const { execSync } = require('child_process'); + const path = require('path'); + const fs = require('fs'); + const os = require('os'); + const util = require('util'); + + const bin = path.join(process.env['ANDROID_NDK_HOME'], 'toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-addr2line'); + function addr2line(lib, pc) { + const [ func, source ] = execSync(`${bin} -C -f -e ${lib} ${pc}`, { encoding: 'utf8' }).split(os.EOL); + return { func, source }; + } + + const backtrace = JSON.parse('${{ steps.backtrace_parsed.outputs.backtrace }}'); + + let body; + let error; + try { + const symbols = fs.readdirSync('symbols'); + const formatted = backtrace.map(stack => { + const lib = path.basename(stack.file); + if (symbols.includes(lib)) { + const { func, source} = addr2line(path.join("symbols", lib), stack.pc); + return { + pc: stack.pc, + file: stack.file, + func, + source + } + } + + return stack; + }).map(({ pc, func, file, source }, i) => { + return `#${i} PC ${pc} ${file} ${func ? `(${func})` : ''}${source ? ` [${source}]` : ''}`; + }); + + body = util.format(process.env.translate_success_message, [`translated backtrace:`, ...formatted.map(line => ` ${line}`)].join(os.EOL)); + } catch (e) { + body = process.env.translate_failed_message; + error = e; + } + + const { data: { id } } = await issues.createComment({ + owner, + repo, + issue_number: ${{ github.event.issue.number }}, + body + }); + + if (error) { + throw error; + } + + await reactions.createForIssueComment({ + owner, + repo, + comment_id: id, + content: 'eyes' + }); diff --git a/.github/workflows/gh_licence_checks.yml b/.github/workflows/gh_licence_checks.yml new file mode 100644 index 00000000000..0c00c84fae7 --- /dev/null +++ b/.github/workflows/gh_licence_checks.yml @@ -0,0 +1,57 @@ +name: '[gh] pull request file license header checks' + +on: + workflow_dispatch: + inputs: + commit-from: + description: 'specify the start of commit hash' + required: true + commit-to: + description: 'specify the end of commit hash' + required: true + pull_request: + branches: + - master + - main + paths: + - '**.tsx?' + - '**.jsx?' + - '**.vue' + - '**.ejs' + - '**.mjs' + - '**.es6' + - '**.css' + - '**.less' + - '**.scss' + - '**.hh?' + - '**.hpp' + - '**.h\\+\\+' + - '**.hxx' + - '**.cc?' + - '**.c\\+\\+' + - '**.cpp' + - '**.C' + - '**.cxx' + - '**.mm?' + - '**.swift' + - '**.cmake' + - '**.java' + - '**.txt' + - '**.sh' + - '**.yaml' + - '**.bat' + - '**.dart' + - "**.py" + +jobs: + pull_request_license_checks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Check License or Date from github PR files + uses: zoomchan-cxj/actions-license@v0.1.0 + with: + config-path: .github/workflows/config/license-check.json + token: ${{ secrets.GITHUB_TOKEN }} + env: + FORCE_COLOR: 3 diff --git a/.github/workflows/gh_licence_checks_bypass.yml b/.github/workflows/gh_licence_checks_bypass.yml new file mode 100644 index 00000000000..0eb2cfc0b4b --- /dev/null +++ b/.github/workflows/gh_licence_checks_bypass.yml @@ -0,0 +1,44 @@ +name: '[gh] pull request file license header checks' + +on: + pull_request: + branches: + - master + - main + paths-ignore: + - '**.tsx?' + - '**.jsx?' + - '**.vue' + - '**.ejs' + - '**.mjs' + - '**.es6' + - '**.css' + - '**.less' + - '**.scss' + - '**.hh?' + - '**.hpp' + - '**.h\\+\\+' + - '**.hxx' + - '**.cc?' + - '**.c\\+\\+' + - '**.cpp' + - '**.C' + - '**.cxx' + - '**.mm?' + - '**.swift' + - '**.cmake' + - '**.java' + - '**.txt' + - '**.sh' + - '**.yaml' + - '**.bat' + - '**.dart' + - "**.py" + +jobs: + pull_request_license_checks: + runs-on: ubuntu-latest + steps: + - name: License checks Bypass + run: | + echo "No license checks required" diff --git a/.github/workflows/gh_mark_and_close_stalled.yml b/.github/workflows/gh_mark_and_close_stalled.yml new file mode 100644 index 00000000000..71cf4753d3a --- /dev/null +++ b/.github/workflows/gh_mark_and_close_stalled.yml @@ -0,0 +1,59 @@ +name: '[gh] mark and close stale issues and PRs' + +on: + workflow_dispatch: + schedule: + # Run every day at 19:00 UTC(03:00 UTC+08:00). + - cron: '0 19 * * *' + +env: + CLOSE_PR_MESSAGE: | + Sorry, closing this PR because it has stalled for over 4 months. + Feel free to reopen if this PR is still relevant, or to ping the collaborator if you have any questions. + CLOSE_ISSUE_MESSAGE: | + Sorry, closing this issue because it has stalled for over 3 months. + Feel free to reopen if this issue is still relevant, or to ping the collaborator if you have any questions. + STALE_PR_MESSAGE: | + Pay attention 🛎️ !! + There has been no activity on this PR for 2 months, so I will label it stalled. + It will be automatically closed in 30 days if no more activity. Feel free to leave a comment if you have any questions. + STALE_ISSUE_MESSAGE: | + Pay attention 🛎️ !! + There has been no activity on this issue for 2 months, so I will label it stalled. + It will be automatically closed in 60 days if no more activity. Feel free to leave a comment if you have any questions. + +jobs: + stale: + if: github.repository == 'Tencent/Hippy' + permissions: + issues: write + pull-requests: write + runs-on: ubuntu-latest + steps: + - name: Token + uses: navikt/github-app-token-generator@v1 + id: get-token + with: + private-key: ${{ secrets.BOT_APP_KEY }} + app-id: ${{ secrets.BOT_APP_ID }} + - uses: actions/stale@v6.0.1 + with: + repo-token: ${{ steps.get-token.outputs.token }} + # pr related + days-before-pr-stale: 60 + days-before-pr-close: 60 + stale-pr-message: ${{ env.STALE_PR_MESSAGE }} + close-pr-message: ${{ env.CLOSE_PR_MESSAGE }} + stale-pr-label: 'stale: 60d' + close-pr-label: 'stale: closed' + exempt-pr-labels: 'stale: never' + # issue related + days-before-issue-stale: 60 + days-before-issue-close: 30 + stale-issue-message: ${{ env.STALE_ISSUE_MESSAGE }} + close-issue-message: ${{ env.CLOSE_ISSUE_MESSAGE }} + stale-issue-label: 'stale: 30d' + close-issue-label: 'stale: closed' + exempt-issue-labels: 'stale: never' + # max requests it will send per run to the GitHub API before it deliberately exits to avoid hitting API rate limits + operations-per-run: 500 diff --git a/.github/workflows/gh_pr_auto_merger.yml b/.github/workflows/gh_pr_auto_merger.yml new file mode 100644 index 00000000000..2134bc70f12 --- /dev/null +++ b/.github/workflows/gh_pr_auto_merger.yml @@ -0,0 +1,206 @@ +name: '[gh] pull request auto merger' + +on: + workflow_run: + workflows: + - \[gh\] pull request review + types: [ completed ] + +env: + GREETINGS_MESSAGE: ':tada: It seems that this pull request has been approved by all required reviewers.' + AUTO_MERGE_MESSAGE: As it only contains one normal commit, I will rebase and merge it automatically via add `action(rebase-merge)` label. + MANUAL_MERGE_MESSAGE: But it has more than one normal commit, I will notify admin team member to merge it manually, please wait a moment. + MERGE_SHA_MESSAGE: | +
+ + * SHA: {0} + +
+ +jobs: + call_get_workflow_output: + if: github.repository == 'Tencent/Hippy' && github.event.action == 'completed' && github.event.workflow_run.conclusion == 'success' + uses: ./.github/workflows/reuse_get_workflow_output.yml + with: + workflow_run: ${{ github.event.workflow_run.id }} + + get_decision: + needs: call_get_workflow_output + runs-on: ubuntu-latest + outputs: + review_decision: ${{ steps.decision.outputs.review_decision }} + pull_request_number: ${{ steps.parse_workflow_output.outputs.pull_request_number }} + pull_request_title: ${{ steps.parse_workflow_output.outputs.pull_request_title }} + pull_request_url: ${{ steps.parse_workflow_output.outputs.pull_request_url }} + pull_request_sha: ${{ steps.parse_workflow_output.outputs.pull_request_sha }} + steps: + - name: Parse + id: parse_workflow_output + env: + RAW: ${{ needs.call_get_workflow_output.outputs.raw }} + shell: python + run: | + import json + import os + + raw = os.getenv("RAW") + data = json.loads(raw) + with open(os.getenv("GITHUB_OUTPUT"), 'w', encoding='utf-8') as file: + file.write("review_state=%s\n" % data["review"]["state"]) + file.write("pull_request_number=%s\n" % data["pull_request"]["number"]) + file.write("pull_request_title=%s\n" % data["pull_request"]["title"]) + file.write("pull_request_url=%s\n" % data["pull_request"]["html_url"]) + file.write("pull_request_sha=%s\n" % data["pull_request"]["head"]["sha"]) + - name: Decision + id: decision + if: steps.parse_workflow_output.outputs.review_state == 'approved' + uses: actions/github-script@v6 + with: + script: | + const fs = require('fs'); + const os = require('os'); + + const query = `query($owner:String!, $repo:String!, $number:Int!) { + repository(name: $repo, owner: $owner) { + pullRequest(number: $number) { + reviewDecision + } + } + }`; + const variables = { + ...context.repo, + number: ${{ steps.parse_workflow_output.outputs.pull_request_number }} + }; + const { repository: { pullRequest: { reviewDecision } } } = await github.graphql(query, variables); + if (reviewDecision === 'APPROVED' || !reviewDecision) { + fs.appendFileSync(process.env.GITHUB_OUTPUT, `review_decision=APPROVED${os.EOL}`, { encoding: 'utf8' }); + } + + call_classify_commits: + needs: get_decision + if: needs.get_decision.outputs.review_decision == 'APPROVED' + uses: ./.github/workflows/reuse_classify_commits.yml + with: + pull_request_number: ${{ fromJSON(needs.get_decision.outputs.pull_request_number) }} + + auto_merge: + needs: [ get_decision, call_classify_commits ] + if: needs.call_classify_commits.outputs.normal_commits_count == 1 + runs-on: ubuntu-latest + steps: + - name: Token + uses: navikt/github-app-token-generator@v1 + id: get-token + with: + private-key: ${{ secrets.BOT_APP_KEY }} + app-id: ${{ secrets.BOT_APP_ID }} + - name: Auto + uses: actions/github-script@v6 + env: + COMMENT_MESSAGE: ${{ env.GREETINGS_MESSAGE }} ${{ env.AUTO_MERGE_MESSAGE }} ${{ format(env.MERGE_SHA_MESSAGE, needs.get_decision.outputs.pull_request_sha) }} + with: + github-token: ${{ steps.get-token.outputs.token }} + script: | + const { issues } = github.rest; + + const comments = await github.paginate(issues.listComments, { + ...context.repo, + per_page: 100, + issue_number: ${{ needs.get_decision.outputs.pull_request_number }} + }); + if (comments.some((comment) => comment.body.includes(process.env.COMMENT_MESSAGE))) { + return; + } + + const p = []; + + p.push(issues.addLabels({ + ...context.repo, + issue_number: ${{ needs.get_decision.outputs.pull_request_number }}, + labels: [ 'action(rebase-merge)' ] + })); + + p.push(issues.createComment({ + ...context.repo, + issue_number: ${{ needs.get_decision.outputs.pull_request_number }}, + body: process.env.COMMENT_MESSAGE + })); + + await Promise.all(p); + + manual_merge: + needs: [ get_decision, call_classify_commits ] + if: needs.call_classify_commits.outputs.normal_commits_count > 1 || !needs.call_classify_commits.outputs.normal_commits_count + runs-on: ubuntu-latest + steps: + - name: Token + uses: navikt/github-app-token-generator@v1 + id: get-token + with: + private-key: ${{ secrets.BOT_APP_KEY }} + app-id: ${{ secrets.BOT_APP_ID }} + - name: Manual + uses: actions/github-script@v6 + env: + WECHAT_WORK_MESSAGE: | + [#${{ needs.get_decision.outputs.pull_request_number }}](${{ needs.get_decision.outputs.pull_request_url }}) pull request is met merge requirements. + > ${{ needs.get_decision.outputs.pull_request_title }} + > %s normal commits ahead + COMMENT_MESSAGE: ${{ env.GREETINGS_MESSAGE }} ${{ env.MANUAL_MERGE_MESSAGE }} ${{ format(env.MERGE_SHA_MESSAGE, needs.get_decision.outputs.pull_request_sha) }} + with: + github-token: ${{ steps.get-token.outputs.token }} + script: | + const util = require('util'); + const { issues } = github.rest; + + const comments = await github.paginate(issues.listComments, { + ...context.repo, + per_page: 100, + issue_number: ${{ needs.get_decision.outputs.pull_request_number }} + }); + if (comments.some((comment) => comment.body.includes(process.env.COMMENT_MESSAGE))) { + return; + } + + const p = []; + + p.push(issues.createComment({ + ...context.repo, + issue_number: ${{ needs.get_decision.outputs.pull_request_number }}, + body: process.env.COMMENT_MESSAGE + })); + + p.push(github.request("POST ${{ secrets.WECHAT_WORK_BOT_WEBHOOK }}", { + headers: { + "content-type": "application/json" + }, + data: { + chatid: "${{ secrets.WECHAT_WORK_ADMIN_CHAT_ID }}", + msgtype: "markdown", + markdown: { + content: util.format(process.env.WECHAT_WORK_MESSAGE, (Number.parseInt("${{ needs.call_classify_commits.outputs.normal_commits_count }}") || Infinity).toLocaleString()), + attachments: [{ + callback_id: "merge", + actions: [{ + name: "merge_btn", + text: "Mark as Merged", + type: "button", + value: "Mark as Merged", + replace_text: "Already merged", + border_color: "2c974b", + text_color: "2c974b" + }, { + name: "ignore_btn", + text: "Mark as Ignored", + type: "button", + value: "Mark as Ignored", + replace_text: "Already ignored", + border_color: "6e7781", + text_color: "6e7781" + }] + }] + } + } + })); + + await Promise.all(p); diff --git a/.github/workflows/gh_pr_auto_updater.yml b/.github/workflows/gh_pr_auto_updater.yml new file mode 100644 index 00000000000..4b3da1afe61 --- /dev/null +++ b/.github/workflows/gh_pr_auto_updater.yml @@ -0,0 +1,64 @@ +name: '[gh] pull request auto updater' + +on: + pull_request_target: + types: + - auto_merge_enabled + push: + +jobs: + up_to_date: + if: github.repository == 'Tencent/Hippy' + runs-on: ubuntu-latest + steps: + - name: Updater + uses: actions/github-script@v6.3.3 + with: + # Because the Github App Token, Github Token, and Fine-grained PAT + # cannot obtain the permission to update the workflow files in the head branch, + # so use classic PAT to avoid failures caused by modification of workflow files. + # TODO: use Github App Token or Github Token if permission is granted in the future. + github-token: ${{ secrets.BOT_PAT }} + script: | + const { pulls, repos } = github.rest; + + let pull_requests; + switch (context.eventName) { + case 'push': { + pull_requests = (await github.paginate(pulls.list, { + per_page: 100, + state: 'open', + base: '${{ github.ref_name }}', + ...context.repo + })).filter(pull => pull.draft === false && pull.auto_merge); + break; + } + case 'pull_request_target': { + const { pull_request } = context.payload; + if (pull_request.draft === true) { + return; + } + pull_requests = [pull_request]; + break; + } + default: { + throw new Error(`Unsupported event name: ${context.eventName}`); + break; + } + } + + await Promise.all( + pull_requests.map(pull => + repos.compareCommitsWithBasehead({ + ...context.repo, + basehead: `${pull.base.label}...${pull.head.label}`, + }).then(({ data: comparison }) => { + if (comparison.behind_by > 0) { + return pulls.updateBranch({ + ...context.repo, + pull_number: pull.number + }).catch(error => console.error(error)); + } + }) + ) + ); diff --git a/.github/workflows/gh_pr_checks_approval.yml b/.github/workflows/gh_pr_checks_approval.yml new file mode 100644 index 00000000000..200f0186b27 --- /dev/null +++ b/.github/workflows/gh_pr_checks_approval.yml @@ -0,0 +1,123 @@ +name: '[gh] pull request checks approval' + +on: + pull_request_target: + branches: + - master + - main + - v3.0-dev + - v3.0 + types: [ opened, reopened ] + workflow_run: + workflows: + - \[gh\] pull request review + types: [ completed ] + +jobs: + pull_request_opened: + if: github.repository == 'Tencent/Hippy' && contains(github.event.action, 'opened') + outputs: + pull_request_number: ${{ github.event.pull_request.number }} + pull_request_head_sha: ${{ github.event.pull_request.head.sha }} + run: 'true' + runs-on: ubuntu-latest + steps: + - run: echo "Pull request opened" + + call_get_workflow_output: + if: github.repository == 'Tencent/Hippy' && github.event.action == 'completed' && github.event.workflow_run.conclusion == 'success' + uses: ./.github/workflows/reuse_get_workflow_output.yml + with: + workflow_run: ${{ github.event.workflow_run.id }} + + pull_request_approved: + needs: call_get_workflow_output + if: needs.call_get_workflow_output.outputs.action == 'submitted' + outputs: + pull_request_number: ${{ steps.parse_workflow_output.outputs.pull_request_number }} + pull_request_head_sha: ${{ steps.parse_workflow_output.outputs.pull_request_head_sha }} + run: ${{ steps.parse_workflow_output.outputs.run }} + runs-on: ubuntu-latest + steps: + - name: Parse + id: parse_workflow_output + env: + RAW: ${{ needs.call_get_workflow_output.outputs.raw }} + shell: python + run: | + import json + import os + + raw = os.getenv("RAW") + data = json.loads(raw) + with open(os.getenv("GITHUB_OUTPUT"), 'w', encoding='utf-8') as file: + file.write('run=%s\n' % 'true' if data["review"]["state"] == 'approved' else 'false') + file.write("pull_request_number=%s\n" % data["pull_request"]["number"]) + file.write("pull_request_head_sha=%s\n" % data["pull_request"]["head"]["sha"]) + + call_approval_checks_run: + needs: [ pull_request_opened, pull_request_approved ] + if: always() && contains(needs.*.result, 'success') && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') && contains(needs.*.outputs.run, 'true') + uses: ./.github/workflows/reuse_approve_checks_run.yml + with: + pull_request_number: ${{ fromJSON(needs.pull_request_opened.outputs.pull_request_number || needs.pull_request_approved.outputs.pull_request_number) }} + pull_request_head_sha: ${{ needs.pull_request_opened.outputs.pull_request_head_sha || needs.pull_request_approved.outputs.pull_request_head_sha }} + + checks_approval_comment: + needs: call_approval_checks_run + if: always() && needs.call_approval_checks_run.outputs.action_required == 'true' + runs-on: ubuntu-latest + env: + safety_changes_message: | + After a quick scan, I have approved workflow to run. + risky_changes_message: | + Sorry, due to risky changes, I can\'t approve workflow to run, our collaborators will handle it asap. + details_message: | +
+ + * SHA: ${{ needs.call_approval_checks_run.outputs.pull_request_head_sha }} + +
+ tips_message: | + :label: **New commits in this PR would not be tested automatically** until this pull request is reviewed by our collaborators. + :label: **No need to worry about the status of `merge_guard ` and `[gh] pull request merge guard / merge_guard (pull_request_target)` checks**, once this pull request is met merge requirements, it will be automatically converted to successful status. + steps: + - name: Message + id: generate_message + run: | + message="$${{ needs.call_approval_checks_run.outputs.included_risk_files == 'true' && 'risky_changes_message' || 'safety_changes_message' }}" + + echo "comment_message<> $GITHUB_OUTPUT + echo "$message" >> $GITHUB_OUTPUT + echo "$details_message" >> $GITHUB_OUTPUT + if [[ "${{ github.event.action }}" == *"opened"* ]]; then + echo >> $GITHUB_OUTPUT + echo "$tips_message" >> $GITHUB_OUTPUT + fi + echo "EOF" >> $GITHUB_OUTPUT + + echo "search_message<> $GITHUB_OUTPUT + echo "$message" >> $GITHUB_OUTPUT + echo "$details_message" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + - name: Find + uses: peter-evans/find-comment@v2.1.0 + id: find_comment + with: + issue-number: ${{ needs.call_approval_checks_run.outputs.pull_request_number }} + body-includes: ${{ steps.generate_message.outputs.search_message }} + direction: last + - name: Token + id: get-token + if: steps.find_comment.outputs.comment-id == '' + uses: navikt/github-app-token-generator@v1 + with: + private-key: ${{ secrets.BOT_APP_KEY }} + app-id: ${{ secrets.BOT_APP_ID }} + - name: Comment + uses: peter-evans/create-or-update-comment@v2 + if: steps.get-token.outputs.token != '' + with: + token: ${{ steps.get-token.outputs.token }} + issue-number: ${{ needs.call_approval_checks_run.outputs.pull_request_number }} + body: ${{ steps.generate_message.outputs.comment_message }} diff --git a/.github/workflows/gh_pr_guide.yml b/.github/workflows/gh_pr_guide.yml new file mode 100644 index 00000000000..ed144105f3d --- /dev/null +++ b/.github/workflows/gh_pr_guide.yml @@ -0,0 +1,105 @@ +name: '[gh] pull request guide' + +on: + pull_request_target: + branches: + - master + - main + - v3.0-dev + - v3.0 + types: + - opened + issue_comment: + types: + - created + +jobs: + pull_request_greetings: + if: github.event.action == 'opened' && github.repository == 'Tencent/Hippy' + runs-on: ubuntu-latest + env: + MESSAGE: | + Hi, @${{ github.event.sender.login }}. Thanks for your PR! :clap: + + :label: You can leave a comment in this PR with **`#help`** tag when you need help (e.g. some status checks run failed due to internal issue), admin team members will help asap. + steps: + - name: Token + uses: navikt/github-app-token-generator@v1 + id: get-token + with: + private-key: ${{ secrets.BOT_APP_KEY }} + app-id: ${{ secrets.BOT_APP_ID }} + - name: Greetings + uses: actions/github-script@v6.1.0 + with: + github-token: ${{ steps.get-token.outputs.token }} + script: | + const { owner, repo } = context.repo; + const { pull_request } = context.payload; + const { issues } = github.rest; + + await issues.createComment({ + owner, + repo, + issue_number: pull_request.number, + body: process.env.MESSAGE, + }); + + pull_request_help_needed: + if: github.event.sender.type == 'User' && github.event.issue.pull_request && contains(github.event.comment.body, '#help') && github.repository == 'Tencent/Hippy' + runs-on: ubuntu-latest + env: + MESSAGE: | + [${{ github.event.sender.login }}](https://github.com/${{ github.event.sender.login }}) needs help on [#${{ github.event.issue.number }}](${{ github.event.comment.html_url }}) pull request. + > [${{ github.event.comment.html_url }}](${{ github.event.comment.html_url }}) + > ${{ github.event.comment.body }} + steps: + - name: Token + uses: navikt/github-app-token-generator@v1 + id: get-token + with: + private-key: ${{ secrets.BOT_APP_KEY }} + app-id: ${{ secrets.BOT_APP_ID }} + - name: Action + uses: actions/github-script@v6.3.3 + with: + github-token: ${{ steps.get-token.outputs.token }} + script: | + const { owner, repo } = context.repo; + const { issue } = context.payload; + const { issues } = github.rest; + + const p = []; + + p.push(issues.addLabels({ + issue_number: issue.number, + labels: [ 'need: help' ], + ...context.repo, + })); + + p.push(github.request("POST ${{ secrets.WECHAT_WORK_BOT_WEBHOOK }}", { + headers: { + "content-type": "application/json" + }, + data: { + chatid: "${{ secrets.WECHAT_WORK_ADMIN_CHAT_ID }}", + msgtype: "markdown", + markdown: { + content: process.env.MESSAGE, + attachments: [{ + callback_id: "help_needed", + actions: [{ + name: "help_needed_btn", + text: "Mark as Resolved", + type: "button", + value: "Mark as Resolved", + replace_text: "Already resolved", + border_color: "2c974b", + text_color: "2c974b" + }] + }] + } + } + })); + + await Promise.all(p); diff --git a/.github/workflows/gh_pr_labeler.yml b/.github/workflows/gh_pr_labeler.yml new file mode 100644 index 00000000000..50a66f28a92 --- /dev/null +++ b/.github/workflows/gh_pr_labeler.yml @@ -0,0 +1,32 @@ +name: '[gh] pull request labeler' + +on: + pull_request_target: + branches: + - master + - main + types: + - opened + - reopened + - synchronize + +permissions: + contents: read + pull-requests: write + +jobs: + basic_info_labeler: + if: github.repository == 'Tencent/Hippy' && contains(fromJSON('[''opened'', ''reopened'', ''synchronize'']'), github.event.action) + runs-on: ubuntu-latest + steps: + - name: Path + uses: actions/labeler@v4 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + configuration-path: ".github/workflows/config/pr-path-labeler.yml" + sync-labels: true + - name: Size + uses: julrocas/pr-size-labeler@v1.0 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + configuration-path: ".github/workflows/config/pr-size-labeler.yml" diff --git a/.github/workflows/gh_pr_merge_guard.yml b/.github/workflows/gh_pr_merge_guard.yml new file mode 100644 index 00000000000..da11b5eaa8e --- /dev/null +++ b/.github/workflows/gh_pr_merge_guard.yml @@ -0,0 +1,34 @@ +name: '[gh] pull request merge guard' + +on: + pull_request_target: + types: + - auto_merge_enabled + - auto_merge_disabled + - synchronize + +jobs: + merge_guard: + if: github.repository == 'Tencent/Hippy' + runs-on: ubuntu-latest + steps: + - name: Goalkeeper + env: + AUTO_MERGE_ENABLED: ${{ !!github.event.pull_request.auto_merge }} + AUTO_MERGE_USER: ${{ github.event.pull_request.auto_merge.enabled_by.login }} + AUTO_MERGE_TYPE: ${{ github.event.pull_request.auto_merge.enabled_by.type }} + run: | + if [[ "$AUTO_MERGE_ENABLED" == "false" ]]; then + echo "Auto-merge is disabled." + echo "> Blocking ..." + exit -1 + fi + + echo "Auto-merge is enabled by a $AUTO_MERGE_USER." + if [[ "$AUTO_MERGE_TYPE" == "Bot" ]]; then + echo "> Passing ..." + exit 0 + else + echo "> Blocking ..." + exit -1 + fi diff --git a/.github/workflows/gh_pr_review.yml b/.github/workflows/gh_pr_review.yml new file mode 100644 index 00000000000..beeddb67c8f --- /dev/null +++ b/.github/workflows/gh_pr_review.yml @@ -0,0 +1,21 @@ +name: '[gh] pull request review' + +on: + pull_request_review: + types: [ submitted, edit ] + +jobs: + pull_request_review: + if: github.repository == 'Tencent/Hippy' + runs-on: ubuntu-latest + steps: + - name: Commit + env: + DATA: ${{ toJSON(github.event) }} + run: | + echo $DATA > ./data + - name: Push + uses: actions/upload-artifact@v3 + with: + name: data + path: ./data diff --git a/.github/workflows/gh_pr_review_notification.yml b/.github/workflows/gh_pr_review_notification.yml new file mode 100644 index 00000000000..066965c6abc --- /dev/null +++ b/.github/workflows/gh_pr_review_notification.yml @@ -0,0 +1,196 @@ +name: '[gh] pull review notification' + +on: + pull_request_target: + types: [ review_requested ] + workflow_run: + workflows: + - \[gh\] pull request review + types: [ completed ] + workflow_dispatch: + schedule: + # Run every day at 23:30 UTC(07:30 UTC+08:00). + - cron: '30 23 * * *' + +jobs: + request_notification: + if: github.repository == 'Tencent/Hippy' && github.event.action == 'review_requested' && github.event.requested_reviewer.type == 'User' + runs-on: ubuntu-latest + steps: + - name: Notice + uses: actions/github-script@v6.3.3 + env: + MESSAGE: | + [${{ github.event.sender.login }}](https://github.com/${{ github.event.sender.login }}) requested your review on [#${{ github.event.pull_request.number }}](${{ github.event.pull_request.html_url }}) pull request. + > ${{ github.event.pull_request.title }} + > [${{ github.event.pull_request.html_url }}](${{ github.event.pull_request.html_url }}) + > [%s changed files](${{ github.event.pull_request.html_url }}/files) with %s additions and %s deletions + + <@%s> + WECHAT_WORK_USERS: ${{ secrets.WECHAT_WORK_USERS }} + with: + script: | + const { format } = require("util"); + + const userid = JSON.parse(process.env.WECHAT_WORK_USERS)["${{ github.event.requested_reviewer.login }}"]; + if (!userid) { + console.log("The reviewer ${{ github.event.requested_reviewer.login }} not found in secrets.WECHAT_WORK_USERS"); + return; + } + + await github.request("POST ${{ secrets.WECHAT_WORK_BOT_WEBHOOK }}", { + headers: { + "content-type": "application/json" + }, + data: { + chatid: "${{ secrets.WECHAT_WORK_CHAT_ID }}", + visible_to_user: userid, + msgtype: "markdown", + markdown: { + content: format(process.env.MESSAGE, (${{ github.event.pull_request.changed_files }}).toLocaleString(), (${{ github.event.pull_request.additions }}).toLocaleString(), (${{ github.event.pull_request.deletions }}).toLocaleString(), userid), + attachments: [{ + callback_id: "review", + actions: [{ + name: "review_btn", + text: "Mark as Reviewed", + type: "button", + value: "Mark as Reviewed", + replace_text: "Already reviewed", + border_color: "2c974b", + text_color: "2c974b" + }, { + name: "ignore_btn", + text: "Mark as Ignored", + type: "button", + value: "Mark as Ignored", + replace_text: "Already ignored", + border_color: "6e7781", + text_color: "6e7781" + }] + }] + } + } + }); + + call_get_workflow_output: + if: github.repository == 'Tencent/Hippy' && github.event.action == 'completed' && github.event.workflow_run.conclusion == 'success' + uses: ./.github/workflows/reuse_get_workflow_output.yml + with: + workflow_run: ${{ github.event.workflow_run.id }} + + changes_requested_comment: + needs: call_get_workflow_output + if: needs.call_get_workflow_output.outputs.action == 'submitted' + runs-on: ubuntu-latest + env: + MESSAGE: | + Hi, @{0}, I noticed that our reviewers requested changes to this pull request. + When you're done, **click the `Re-request review` button in the right sidebar(shown below)** to notify the reviewer. + ![Re-request review button in the right sidebar](https://docs.github.com/assets/cb-4714/images/help/pull_requests/request-re-review.png) + steps: + - name: Parse + id: parse_workflow_output + env: + RAW: ${{ needs.call_get_workflow_output.outputs.raw }} + shell: python + run: | + import json + import os + + raw = os.getenv("RAW") + data = json.loads(raw) + with open(os.getenv("GITHUB_OUTPUT"), 'w', encoding='utf-8') as file: + file.write("review_state=%s\n" % data["review"]["state"]) + file.write("pull_request_number=%s\n" % data["pull_request"]["number"]) + file.write("pull_request_user=%s\n" % data["pull_request"]["user"]["login"]) + - name: Find + id: find_comment + if: steps.parse_workflow_output.outputs.review_state == 'changes_requested' + uses: peter-evans/find-comment@v2.1.0 + with: + issue-number: ${{ steps.parse_workflow_output.outputs.pull_request_number }} + body-includes: ${{ format(env.MESSAGE, steps.parse_workflow_output.outputs.pull_request_user) }} + - name: Token + id: get-token + if: steps.find_comment.outputs.comment-id == '' && steps.parse_workflow_output.outputs.review_state == 'changes_requested' + uses: navikt/github-app-token-generator@v1 + with: + private-key: ${{ secrets.BOT_APP_KEY }} + app-id: ${{ secrets.BOT_APP_ID }} + - name: Comment + uses: peter-evans/create-or-update-comment@v2 + if: steps.get-token.outputs.token != '' + with: + token: ${{ steps.get-token.outputs.token }} + issue-number: ${{ steps.parse_workflow_output.outputs.pull_request_number }} + body: ${{ format(env.MESSAGE, steps.parse_workflow_output.outputs.pull_request_user) }} + reactions: eyes + + requested_reviewers_notification: + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + steps: + - name: Notice + uses: actions/github-script@v6.3.3 + env: + WECHAT_WORK_USERS: ${{ secrets.WECHAT_WORK_USERS }} + with: + script: | + const { pulls } = github.rest; + const os = require('os'); + + const pull_requests = (await github.paginate(pulls.list, { + per_page: 100, + state: 'open', + ...context.repo + })).filter(pull => pull.draft === false); + + const requested_reviewers = await Promise.all( + pull_requests.map( + pull => pulls.listRequestedReviewers({ + per_page: 100, + pull_number: pull.number, + ...context.repo + }).then(({ data }) => data) + ) + ); + + if (pull_requests.length !== requested_reviewers.length) { + throw new Error("The length of pull_requests and requested_reviewers is not equal"); + } + + const wechat_work_users = JSON.parse(process.env.WECHAT_WORK_USERS); + const notification_users = {}; + + pull_requests.forEach((pull, index) => { + requested_reviewers[index].users.forEach(reviewer => { + if (reviewer.type === 'User') { + const user_id = wechat_work_users[reviewer.login]; + if (user_id) { + (notification_users[user_id] ||= []).push(pull); + } + } + }); + }); + + await Promise.all(Object.entries(notification_users).map(([user_id, pulls]) => { + const content = []; + content.push(`${pulls.length} pull requests are waiting on your review.`); + content.push(...pulls.map(pull => `> [#${pull.number}](${pull.html_url}) ${pull.title}`)); + content.push(''); + content.push(`<@${user_id}>`); + + return github.request("POST ${{ secrets.WECHAT_WORK_BOT_WEBHOOK }}", { + headers: { + "content-type": "application/json" + }, + data: { + chatid: "${{ secrets.WECHAT_WORK_CHAT_ID }}", + visible_to_user: user_id, + msgtype: "markdown", + markdown: { + content: content.join(os.EOL) + } + } + }); + })); diff --git a/.github/workflows/gh_runners_monitor.yml b/.github/workflows/gh_runners_monitor.yml new file mode 100644 index 00000000000..291acd288ac --- /dev/null +++ b/.github/workflows/gh_runners_monitor.yml @@ -0,0 +1,95 @@ +name: '[gh] self-hosted runners monitor' + +on: + workflow_dispatch: + schedule: + # Run every 30th minute. + - cron: '*/30 * * * *' + +jobs: + runners_monitor: + if: github.repository == 'Tencent/Hippy' + runs-on: ubuntu-latest + env: + cache_file: /tmp/gh_runners_monitor.data + cache_key: gh_runners_monitor + steps: + - name: Token + uses: navikt/github-app-token-generator@v1 + id: get-token + with: + private-key: ${{ secrets.BOT_APP_KEY }} + app-id: ${{ secrets.BOT_APP_ID }} + - name: Check + id: check + uses: actions/github-script@v6.3.3 + with: + github-token: ${{ steps.get-token.outputs.token }} + script: | + const { actions } = github.rest; + const os = require('os'); + const fs = require('fs'); + + const runners = await github.paginate(actions.listSelfHostedRunnersForRepo, { + per_page: 100, + ...context.repo + }); + const offline_runners = runners.filter(runner => runner.status === 'offline'); + + if (offline_runners.length > 0) { + fs.appendFileSync(process.env.GITHUB_OUTPUT, `total_runners=${runners.length}${os.EOL}`, { encoding: 'utf8' }); + fs.appendFileSync(process.env.GITHUB_OUTPUT, `offline_runners=${JSON.stringify(offline_runners)}${os.EOL}`, { encoding: 'utf8' }); + } + - name: Cache + if: steps.check.outputs.offline_runners + uses: actions/cache@v3 + env: + prefix: gh_runners_monitor + with: + path: ${{ env.cache_file }} + key: ${{ env.prefix }}-${{ github.run_id }} + restore-keys: ${{ env.prefix }} + - name: Notification + if: steps.check.outputs.offline_runners + uses: actions/github-script@v6.3.3 + env: + offline_runners: ${{ steps.check.outputs.offline_runners }} + with: + script: | + const fs = require('fs'); + const os = require('os'); + + const offline_runners = JSON.parse(process.env.offline_runners); + if (offline_runners.length === 0) { + throw new Error('offline_runners is empty'); + } + + const message = [ + `${offline_runners.length}/${{ steps.check.outputs.total_runners }} self-hosted runners are offline.`, + ...offline_runners.map(runner => `> ${runner.name}`) + ].join(os.EOL); + + try { + const stored_data = JSON.parse(fs.readFileSync(process.env.cache_file, { encoding: 'utf8'})); + if (stored_data.message === message && Date.now() - stored_data.timestamp < 6_60_60_000 /* every 6th hour */) { + return; + } + } catch(e) {} + + await github.request("POST ${{ secrets.WECHAT_WORK_BOT_WEBHOOK }}", { + headers: { + "content-type": "application/json" + }, + data: { + chatid: "${{ secrets.WECHAT_WORK_ADMIN_CHAT_ID }}", + msgtype: "markdown", + markdown: { + content: message + } + } + }); + + fs.writeFileSync(process.env.cache_file, JSON.stringify({ + message, + timestamp: Date.now() + }), { encoding: 'utf8' }); diff --git a/.github/workflows/gh_webhook_retrier.yml b/.github/workflows/gh_webhook_retrier.yml new file mode 100644 index 00000000000..d7c2205f6da --- /dev/null +++ b/.github/workflows/gh_webhook_retrier.yml @@ -0,0 +1,74 @@ +name: '[gh] webhook retrier' + +on: + workflow_dispatch: + schedule: + # Run every 10th minute. + - cron: '*/10 * * * *' + +jobs: + webhook_retrier: + if: github.repository == 'Tencent/Hippy' + runs-on: ubuntu-latest + steps: + - name: Action + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.BOT_PAT }} + script: | + const { repos } = github.rest; + const os = require('os'); + const fs = require('fs'); + + const per_page = 100; + + const hooks = (await github.paginate(repos.listWebhooks, { + per_page, + ...context.repo + })).filter((hook) => hook.last_response && hook.active); + + const retriedDeliveries = (await Promise.all(hooks.map((hook) => repos.listWebhookDeliveries({ + per_page, + ...context.repo, + hook_id: hook.id + })))).map(({ data: deliveries }) => { + const indexed = {}; + const failedList = new Set(); + deliveries.forEach((delivery) => { + (indexed[delivery.guid] ||= []).push(delivery); + if (delivery.status != 'OK') { + failedList.add(delivery.guid); + } + }); + + return Array.from(failedList).filter((guid) => { + const failedGroup = indexed[guid]; + return failedGroup.every((delivery) => delivery.status != 'OK') && failedGroup.length <= 4; // maximum attempts(origin included) + }).map((guid) => indexed[guid][0]); + }); + + (await Promise.all(retriedDeliveries.map((deliveries, hooksIndex) => + Promise.allSettled(deliveries.map((delivery) => + repos.redeliverWebhookDelivery({ + ...context.repo, + hook_id: hooks[hooksIndex].id, + delivery_id: delivery.id + }) + )) + ))).forEach((results, hooksIndex) => { + if (results.length > 0) { + const summary = []; + + const hook = hooks[hooksIndex]; + summary.push(`## ${hook.config.url} (#${hook.id})`); + summary.push('| guid | event | action | redelivery |'); + summary.push('| ---- | ----- | ------ | ---------- |'); + results.forEach((result, deliveriesIndex) => { + const delivery = retriedDeliveries[hooksIndex][deliveriesIndex]; + summary.push(`| ${delivery.guid} | ${delivery.event} | ${delivery.action || ''} | ${result.status === 'fulfilled' ? ':white_check_mark:' : ':x:'} |`); + }); + summary.push(os.EOL); + + fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, summary.join(os.EOL), { encoding: 'utf8' }); + } + }); diff --git a/.github/workflows/git_commit_lint.yml b/.github/workflows/git_commit_lint.yml new file mode 100644 index 00000000000..6bccc142297 --- /dev/null +++ b/.github/workflows/git_commit_lint.yml @@ -0,0 +1,23 @@ +name: '[git] commit message format lint' + +on: [ pull_request ] + +jobs: + commitlint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: setup-node + uses: actions/setup-node@v3 + with: + node-version: 16.x + cache: 'npm' + cache-dependency-path: package-lock.json + - name: Install + run: | + npm install commitlint-plugin-function-rules@1.6.0 @commitlint/cli@16.2.1 @commitlint/config-conventional@16.2.1 @commitlint/lint@16.2.1 @commitlint/prompt-cli@16.2.1 + - name: commitlint + run: | + npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose diff --git a/.github/workflows/hip_archive_artifact.yml b/.github/workflows/hip_archive_artifact.yml new file mode 100644 index 00000000000..4f36e1a0ed2 --- /dev/null +++ b/.github/workflows/hip_archive_artifact.yml @@ -0,0 +1,115 @@ +name: '[hip] archive artifact' + +on: + workflow_dispatch: + inputs: + source_type: + description: 'Type of source' + type: choice + options: + - Git + - URL + default: 'URL' + required: true + source_url: + description: 'URL or Git ' + type: string + required: true + git_revision: + description: 'Git ref(only Git source available)' + type: string + required: false + hip_domain: + description: 'HIP ' + type: choice + options: + - hippy + - test + default: 'hippy' + required: true + hip_artifact_path: + description: 'HIP artifact (without domain)' + type: string + required: true + hip_writing_mode: + description: 'HIP writing mode' + type: choice + options: + - preserve + - overwrite + default: 'preserve' + required: true + +jobs: + archive_artifact: + runs-on: ubuntu-latest + steps: + - name: Git clone + if: github.event.inputs.source_type == 'Git' + id: git-clone + shell: bash + run: | + name=$(basename ${{ github.event.inputs.hip_artifact_path }}) + if [[ $name != *.tgz ]]; then + echo 'In `Git` source type, must be end with |.tgz|' + exit -1 + fi + git clone ${{ github.event.inputs.source_url }} artifact + pushd artifact + git checkout ${{ github.event.inputs.git_revision }} + echo "git-head=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + rm -rf .git + popd + tar -zcvf $name -C artifact . + - name: URL fetch + if: github.event.inputs.source_type == 'URL' + shell: bash + run: | + wget ${{ github.event.inputs.source_url }} -O $(basename ${{ github.event.inputs.hip_artifact_path }}) + - name: Install Requirement + shell: bash + run: | + pip install -U cos-python-sdk-v5 + - name: Publish artifact + shell: python + run: | + from qcloud_cos import CosConfig + from qcloud_cos import CosS3Client + from urllib.parse import urlencode + import hashlib + import os + + artifact = os.path.basename("${{ github.event.inputs.hip_artifact_path }}") + + metadata = {} + metadata["ci-name"] = "Github Action" + metadata["ci-id"] = "${{ github.run_id }}" + metadata["source-type"] = "${{ github.event.inputs.source_type }}" + metadata["source-url"] = "${{ github.event.inputs.source_url }}" + metadata["artifact-publisher"] = "${{ github.event.sender.login }}" + with open(artifact, "rb") as artifact_file: + metadata["artifact-md5"] = hashlib.md5(artifact_file.read()).hexdigest() + if "${{ github.event.inputs.source_type }}" == "Git": + metadata["git-revision"] = "${{ github.event.inputs.git_revision }}" + metadata["git-head"] = "${{ steps.git-clone.outputs.git-head }}" + + config = CosConfig(Region="${{ secrets.COS_REGION }}", SecretId="${{ secrets.TC_SECRET_ID }}", SecretKey="${{ secrets.TC_SECRET_KEY }}") + client = CosS3Client(config) + + cos_key = "${{ github.event.inputs.hip_domain }}/${{ github.event.inputs.hip_artifact_path }}" + + if "${{ github.event.inputs.hip_writing_mode }}" == "preserve" and client.object_exists( + Bucket="${{ secrets.COS_BUCKET }}", + Key=cos_key + ): + raise Exception("Package already exists") + + response = client.upload_file( + Bucket="${{ secrets.COS_BUCKET }}", + Key=cos_key, + LocalFilePath=artifact, + EnableMD5=True, + ContentMD5=metadata["artifact-md5"], + Metadata={"x-cos-tagging": urlencode(metadata)} + ) + print("ETag: " + response["ETag"]) diff --git a/.github/workflows/hip_info_artifacts.yml b/.github/workflows/hip_info_artifacts.yml new file mode 100644 index 00000000000..21a7e41bd96 --- /dev/null +++ b/.github/workflows/hip_info_artifacts.yml @@ -0,0 +1,72 @@ +name: '[hip] info artifacts' + +on: + workflow_dispatch: + inputs: + hip_domain: + description: 'HIP ' + type: choice + options: + - hippy + - test + default: 'hippy' + required: true + hip_path: + description: 'HIP (without domain)' + type: string + required: false + +jobs: + info_artifacts: + runs-on: ubuntu-latest + defaults: + run: + shell: bash + steps: + - name: Install Requirement + run: | + pip install -U cos-python-sdk-v5 + - name: Info artifact + shell: python + run: | + from qcloud_cos import CosConfig, CosS3Client + from os import getenv + + def sizeof_fmt(num): + if num == 0: + return "" + for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]: + if abs(num) < 1024.0: + return f"{num:3.1f}{unit}" + num /= 1024.0 + return f"{num:.1f}Yi" + + config = CosConfig(Region="${{ secrets.COS_REGION }}", SecretId="${{ secrets.TC_SECRET_ID }}", SecretKey="${{ secrets.TC_SECRET_KEY }}") + client = CosS3Client(config) + + path = "${{ github.event.inputs.hip_domain }}/${{ github.event.inputs.hip_path }}" + + if path.endswith("/") or path.endswith("\\"): + response = client.list_objects( + Bucket="${{ secrets.COS_BUCKET }}", + Prefix=path + ) + + with open(getenv("GITHUB_STEP_SUMMARY"), 'w', encoding='utf-8') as file: + file.write("Root Path: %s\n" % path) + file.write("| File | Size | LastModified |\n") + file.write("|------|------|------|\n") + for object in response["Contents"]: + file.write("| %s | %s | %s |\n" % (object["Key"], sizeof_fmt(int(object["Size"])), object["LastModified"])) + else: + response = client.get_object_tagging( + Bucket="${{ secrets.COS_BUCKET }}", + Key=path + ) + + with open(getenv("GITHUB_STEP_SUMMARY"), 'w', encoding='utf-8') as file: + file.write("File Path: %s\n" % path) + file.write("| TAG Key | TAG Value |\n") + file.write("|------|------|\n") + for tag in response["TagSet"]["Tag"]: + file.write("| %s | %s |\n" % (tag["Key"], tag["Value"])) diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml deleted file mode 100644 index d535f73c6ff..00000000000 --- a/.github/workflows/ios.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: '[iOS] build iOS native example' - -on: - push: - paths: - - 'ios/**' - branches: - # Push events on main branch - - main - # Push events on master branch - - master - tags-ignore: - - '**' - pull_request: - paths: - - 'ios/**' - branches: - # Pull request events to main branch - - main - # Pull request events to master branch - - master - tags-ignore: - - '**' - -jobs: - buildios: - runs-on: macOS-11 - steps: - - uses: actions/checkout@v2 - - name: buildexample - run: | - # https://github.com/actions/virtual-environments/issues/4180 - # Xcode_13 will be selected on October, 18, so we need to set to xcode13 manually before that day. - sudo xcode-select --switch '/Applications/Xcode_13.0.app/' - pushd examples/ios-demo && xcodebuild build -destination 'name=iPhone 11' -scheme 'HippyDemo' && popd diff --git a/.github/workflows/ios_build_tests.yml b/.github/workflows/ios_build_tests.yml new file mode 100644 index 00000000000..6f2fd78a95b --- /dev/null +++ b/.github/workflows/ios_build_tests.yml @@ -0,0 +1,54 @@ +name: '[ios] build tests' + +on: + pull_request: + branches: + - master + - main + paths: + - 'ios/**' + - 'core/**' + - 'hippy.podspec' + - 'examples/ios-demo/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + ios_build_tests: + runs-on: macos-latest + strategy: + matrix: + type: [debug, release] + include: + - type: debug + configuration: Debug + - type: release + configuration: Release + steps: + - name: Checkout repo + uses: actions/checkout@v3 + with: + lfs: true + - name: Build + run: | + pushd examples/ios-demo + pod install + xcodebuild -workspace 'HippyDemo.xcworkspace' -list | \ + sed -n '/Schemes/,/^$/p' | \ + grep -v "Schemes:" | \ + grep -v '^\s*$' | \ + while read scheme; do + echo "Building ${scheme}…" + + xcodebuild build \ + -destination "generic/platform=iOS" \ + -workspace "HippyDemo.xcworkspace" \ + -scheme "${scheme}" \ + -configuration "${{ matrix.configuration }}" \ + CODE_SIGN_IDENTITY="" \ + CODE_SIGNING_REQUIRED=NO \ + CODE_SIGNING_ALLOWED=NO + done + popd diff --git a/.github/workflows/ios_build_tests_bypass.yml b/.github/workflows/ios_build_tests_bypass.yml new file mode 100644 index 00000000000..01669d2be9b --- /dev/null +++ b/.github/workflows/ios_build_tests_bypass.yml @@ -0,0 +1,27 @@ +name: '[ios] build tests' + +on: + pull_request: + branches: + - master + - main + paths-ignore: + - 'ios/**' + - 'core/**' + - 'hippy.podspec' + - 'examples/ios-demo/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + ios_build_tests: + runs-on: ubuntu-latest # use ubuntu to replace macos in order to save macos resources and improve efficiency + strategy: + matrix: + type: [debug, release] + steps: + - name: Build Test Bypass + run: | + echo "No build test required" diff --git a/.github/workflows/project_artifact_compare.yml b/.github/workflows/project_artifact_compare.yml new file mode 100644 index 00000000000..73676e77cf2 --- /dev/null +++ b/.github/workflows/project_artifact_compare.yml @@ -0,0 +1,154 @@ +name: "[project] artifact compare" + +on: + workflow_dispatch: + inputs: + git_ref_a: + description: 'Git Ref A' + type: string + required: true + git_ref_b: + description: 'Git Ref B(contrast)' + type: string + required: false + is_compare_for_android: + description: 'Compare for Android artifact' + type: boolean + default: true + required: true + is_compare_for_ios: + description: 'Compare for iOS artifact' + type: boolean + default: true + required: true + +jobs: + android: + if: ${{ github.event.inputs.is_compare_for_android == 'true' }} + runs-on: ${{ github.repository == 'Tencent/Hippy' && fromJson('[''self-hosted'', ''linux'']') || 'ubuntu-latest' }} + container: + image: ghcr.io/tencent/android-release:latest # repository name must be lowercase(${{ github.repository_owner }}) + strategy: + matrix: + ref: ${{ fromJSON(format('[''{0}'', ''{1}'']', github.event.inputs.git_ref_a, github.event.inputs.git_ref_b)) }} + include: + - ref: ${{ github.event.inputs.git_ref_a }} + source: ref_a + - ref: ${{ github.event.inputs.git_ref_b }} + source: ref_b + defaults: + run: + shell: bash + outputs: + ref_a: ${{ steps.get_size.outputs.ref_a }} + ref_b: ${{ steps.get_size.outputs.ref_b }} + artifact: Android(android-sdk.aar) + steps: + - name: Checkout + if: ${{ matrix.ref }} + uses: actions/checkout@v3 + with: + ref: ${{ matrix.ref }} + lfs: true + - name: Build + if: ${{ matrix.ref }} + run: | + ./gradlew assembleRelease -PINCLUDE_ABI_X86=true -PINCLUDE_ABI_X86_64=true + - name: Size + id: get_size + run: | + if [[ "${{ matrix.ref }}" ]]; then + echo "${{ matrix.source }}=$(ls -l ./android/sdk/build/outputs/aar/android-sdk.aar | awk '{print $5}')" >> $GITHUB_OUTPUT + else + echo "${{ matrix.source }}=-1" >> $GITHUB_OUTPUT + fi + + ios: + if: ${{ github.event.inputs.is_compare_for_ios == 'true' }} + runs-on: macos-latest + strategy: + matrix: + ref: ${{ fromJSON(format('[''{0}'', ''{1}'']', github.event.inputs.git_ref_a, github.event.inputs.git_ref_b)) }} + include: + - ref: ${{ github.event.inputs.git_ref_a }} + source: ref_a + - ref: ${{ github.event.inputs.git_ref_b }} + source: ref_b + outputs: + ref_a: ${{ steps.get_size.outputs.ref_a }} + ref_b: ${{ steps.get_size.outputs.ref_b }} + artifact: iOS(libhippy.a) + steps: + - name: Checkout + if: ${{ matrix.ref }} + uses: actions/checkout@v3 + with: + ref: ${{ matrix.ref }} + lfs: true + - name: Build + if: ${{ matrix.ref }} + run: | + pushd examples/ios-demo + pod install + popd + xcodebuild build \ + -destination 'generic/platform=iOS' \ + -project '_Pods.xcodeproj' \ + -scheme 'hippy' \ + -configuration 'Release' \ + CODE_SIGN_IDENTITY="" \ + CODE_SIGNING_REQUIRED=NO \ + CODE_SIGNING_ALLOWED=NO + - name: Size + id: get_size + run: | + if [[ "${{ matrix.ref }}" ]]; then + echo "${{ matrix.source }}=$(ls -l $(xcodebuild -scheme 'hippy' -showBuildSettings | grep -m 1 TARGET_BUILD_DIR | grep -oEi "\/.*")/libhippy.a | awk '{print $5}')" >> $GITHUB_OUTPUT + else + echo "${{ matrix.source }}=-1" >> $GITHUB_OUTPUT + fi + + collector: + needs: [ android, ios ] + if: ${{ always() && contains(needs.*.result, 'success') && !(contains(needs.*.result, 'failure')) }} + runs-on: ubuntu-latest + steps: + - name: Summary + shell: python + run: | + from os import getenv + from json import loads + + def sizeof_fmt(num): + if num == 0: + return "0" + for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]: + if abs(num) < 1024.0: + return f"{num:3.2f}{unit}" + num /= 1024.0 + return f"{num:.1f}Yi" + + def delta(a, b): + num = a - b + return "$$\color{%s}{%s%s (%s)}$$" % ("red" if num > 0 else "green", "+" if num > 0 else "", sizeof_fmt(num), "%.2f\\\\%%" % abs(num / a * 100)) + + json = loads("""${{ toJSON(needs.*.outputs) }}""") + with open(getenv("GITHUB_STEP_SUMMARY"), 'w', encoding='utf-8') as file: + for result in json: + if "artifact" in result: + ref_a = int(result["ref_a"]) + ref_b = int(result["ref_b"]) + if ref_a > 0 and ref_b > 0: + file.write("## %s Artifact Compare\n" % result["artifact"]) + file.write("| Ref | Size | Delta |\n") + file.write("|------|------|-------|\n") + file.write("| %s | %s | %s |\n" % ("${{ github.event.inputs.git_ref_a }}", sizeof_fmt(ref_a), delta(ref_a, ref_b))) + file.write("| %s | %s | %s |\n" % ("${{ github.event.inputs.git_ref_b }}", sizeof_fmt(ref_b), delta(ref_b, ref_a))) + file.write("\n") + elif ref_a > 0: + ref_a = int(result["ref_a"]) + file.write("## %s Artifact\n" % result["artifact"]) + file.write("| Ref | Size |\n") + file.write("|------|------|\n") + file.write("| %s | %s |\n" % ("${{ github.event.inputs.git_ref_a }}", sizeof_fmt(ref_a))) + file.write("\n") diff --git a/.github/workflows/project_artifact_release.yml b/.github/workflows/project_artifact_release.yml new file mode 100644 index 00000000000..399aa990cfc --- /dev/null +++ b/.github/workflows/project_artifact_release.yml @@ -0,0 +1,207 @@ +name: '[project] artifact release' + +on: + workflow_dispatch: + inputs: + git_ref: + description: 'Git Ref' + type: string + required: true + version_name: + description: 'Version name' + type: string + required: true + registry_choice: + description: 'Registry choice' + type: choice + required: true + default: 'Both' + options: + - Default + - Github + - Both + is_release_for_android: + description: 'Release for Android' + type: boolean + default: true + required: false + is_release_for_ios: + description: 'Release for iOS' + type: boolean + default: true + required: false + is_release_for_js: + description: 'Release for JS' + type: boolean + default: true + required: false + js_npm_dist_tag_name: + description: 'NPM dist tag name to release' + type: string + required: false + is_npm_version_to_latest_tag: + description: 'Set current version to npm latest tag' + type: boolean + default: true + required: false + + +jobs: + context_in_lowercase: + if: github.event.inputs.is_release_for_android == 'true' + runs-on: ubuntu-latest + outputs: + repository_owner: ${{ steps.get_owner.outputs.lowercase }} + steps: + - name: Get repo owner(in lowercase) + id: get_owner + uses: ASzc/change-string-case-action@v2 + with: + string: ${{ github.repository_owner }} + + android_release: + if: github.event.inputs.is_release_for_android == 'true' + needs: context_in_lowercase + runs-on: ubuntu-latest + strategy: + matrix: + build_type: [Debug, Release] + include: + - build_type: Debug + artifact_id: hippy-debug + - build_type: Release + artifact_id: hippy-common + container: + image: ghcr.io/${{ needs.context_in_lowercase.outputs.repository_owner }}/android-release:latest + steps: + - name: Checkout (${{ github.event.inputs.git_ref }}) + uses: actions/checkout@v3 + with: + ref: ${{ github.event.inputs.git_ref }} + lfs: true + - name: ${{ matrix.build_type }} build + env: + SIGNING_KEY_ID: ${{ secrets.ANDROID_SIGNING_KEY_ID }} + SIGNING_PASSWORD: ${{ secrets.ANDROID_SIGNING_PASSWORD }} + SIGNING_SECRET_KEY: ${{ secrets.ANDROID_SIGNING_SECRET_KEY }} + run: | + ./gradlew assemble${{ matrix.build_type }} -PVERSION_NAME=${{ github.event.inputs.version_name }} -PPUBLISH_ARTIFACT_ID=${{ matrix.artifact_id }} -PINCLUDE_ABI_X86=true -PINCLUDE_ABI_X86_64=true + ./gradlew signMavenAarPublication + - name: Pre Archive artifacts + shell: bash + run: | + pip3 install -U cos-python-sdk-v5 + - name: Archive artifacts + working-directory: ./android/sdk/build + shell: python3 {0} + run: | + from qcloud_cos import CosConfig + from qcloud_cos import CosS3Client + from urllib.parse import urlencode + import os + import tempfile + import zipfile + + artifacts = [("outputs/aar/android-sdk.aar", "hippy/android/${{ matrix.artifact_id }}/${{ github.event.inputs.version_name }}/android-sdk.aar")] + for path, dirs, files in os.walk("intermediates/merged_native_libs/%s/out/lib" % "${{ matrix.build_type }}".lower()): + if files: + with zipfile.ZipFile(tempfile.mkstemp()[1], "w", zipfile.ZIP_DEFLATED) as zip_file: + for file in files: + zip_file.write(os.path.join(path, file), file) + artifacts.append((zip_file.filename, "hippy/android/${{ matrix.artifact_id }}/${{ github.event.inputs.version_name }}/symbols/%s.zip" % os.path.basename(path))) + + metadata = {} + metadata["ci-name"] = "Github Action" + metadata["ci-id"] = "${{ github.run_id }}" + metadata["ci-url"] = "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + metadata["artifact-author"] = "${{ github.event.sender.login }}" + metadata["git-ref"] = "${{ github.event.inputs.git_ref }}" + + config = CosConfig(Region="${{ secrets.COS_REGION }}", SecretId="${{ secrets.TC_SECRET_ID }}", SecretKey="${{ secrets.TC_SECRET_KEY }}") + client = CosS3Client(config) + for artifact in artifacts: + print("Uploading %s" % artifact[0]) + response = client.upload_file( + Bucket="${{ secrets.COS_BUCKET_ARTIFACTS_STORE }}", + Key=artifact[1], + LocalFilePath=artifact[0], + Metadata={"x-cos-tagging": urlencode(metadata)} + ) + print("Archived %s" % artifact[1]) + - name: Publish to Github Packages + if: github.event.inputs.registry_choice == 'Both' || github.event.inputs.registry_choice == 'Github' + env: + SIGNING_KEY_ID: ${{ secrets.ANDROID_SIGNING_KEY_ID }} + SIGNING_PASSWORD: ${{ secrets.ANDROID_SIGNING_PASSWORD }} + SIGNING_SECRET_KEY: ${{ secrets.ANDROID_SIGNING_SECRET_KEY }} + MAVEN_USERNAME: ${{ secrets.GITHUB_ACTOR }} + MAVEN_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + MAVEN_URL: https://maven.pkg.github.com/${{ github.repository }} + run: | + ./gradlew publish -PVERSION_NAME=${{ github.event.inputs.version_name }} -PPUBLISH_ARTIFACT_ID=${{ matrix.artifact_id }} -PINCLUDE_ABI_X86=true -PINCLUDE_ABI_X86_64=true + - name: Publish to OSSRH + if: github.event.inputs.registry_choice == 'Both' || github.event.inputs.registry_choice == 'Default' + env: + SIGNING_KEY_ID: ${{ secrets.ANDROID_SIGNING_KEY_ID }} + SIGNING_PASSWORD: ${{ secrets.ANDROID_SIGNING_PASSWORD }} + SIGNING_SECRET_KEY: ${{ secrets.ANDROID_SIGNING_SECRET_KEY }} + MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} + run: | + ./gradlew publish -PVERSION_NAME=${{ github.event.inputs.version_name }} -PPUBLISH_ARTIFACT_ID=${{ matrix.artifact_id }} -PINCLUDE_ABI_X86=true -PINCLUDE_ABI_X86_64=true + + ios_release: + if: github.event.inputs.is_release_for_ios == 'true' + runs-on: macos-latest + steps: + - name: Checkout (${{ github.event.inputs.git_ref }}) + uses: actions/checkout@v3 + with: + ref: ${{ github.event.inputs.git_ref }} + lfs: true + - name: Publish to Cocoapods + if: github.event.inputs.registry_choice == 'Both' || github.event.inputs.registry_choice == 'Default' + env: + COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }} + run: | + pod trunk push hippy.podspec --allow-warnings --use-libraries --verbose + + js_release: + if: github.event.inputs.is_release_for_js == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout (${{ github.event.inputs.git_ref }}) + uses: actions/checkout@v3 + with: + ref: ${{ github.event.inputs.git_ref }} + lfs: true + - name: setup-node + uses: actions/setup-node@v3 + with: + node-version: 18 + registry-url: https://npm.pkg.github.com + cache: 'npm' + cache-dependency-path: package-lock.json + - name: Install dependencies + run: npm ci && lerna bootstrap --no-ci + - name: Build packages + run: npm run build + - uses: actions/setup-node@v3 + with: + node-version: 18 + registry-url: 'https://registry.npmjs.org' + - name: Publish to NPM + if: github.event.inputs.js_npm_dist_tag_name != null && (github.event.inputs.registry_choice == 'Both' || github.event.inputs.registry_choice == 'Default') + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + echo "released js_npm_dist_tag_name is '${{ github.event.inputs.js_npm_dist_tag_name }}'" + npx lerna publish from-package --ignore-scripts --yes --dist-tag ${{ github.event.inputs.js_npm_dist_tag_name }} + - name: Change tag to latest + if: github.event.inputs.is_npm_version_to_latest_tag == 'true' + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + npm run release:tag-to-latest ${{ github.event.inputs.version_name }} + + diff --git a/.github/workflows/project_code_lines.yml b/.github/workflows/project_code_lines.yml new file mode 100644 index 00000000000..e7b30ceb772 --- /dev/null +++ b/.github/workflows/project_code_lines.yml @@ -0,0 +1,32 @@ +name: "[project] code lines" + +on: + workflow_dispatch: + inputs: + git_ref: + description: 'Git Ref' + type: string + default: 'master' + required: true + exclude_files: + description: 'Exclude files(split by |)' + type: string + default: 'yarn.lock|package-lock.json|pnpm-lock.yaml' + required: false + +jobs: + cloc: + runs-on: ubuntu-latest + steps: + - name: Install + run: sudo apt-get install cloc + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ${{ github.event.inputs.git_ref }} + lfs: true + - name: Cloc + env: + EXCLUDE_FILES: ${{ github.event.inputs.exclude_files }} + run: | + cloc . --md --not-match-f="$EXCLUDE_FILES" | sed -E '1,4d' >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/project_dependency_review.yml b/.github/workflows/project_dependency_review.yml new file mode 100644 index 00000000000..b57e4d3fe9a --- /dev/null +++ b/.github/workflows/project_dependency_review.yml @@ -0,0 +1,22 @@ +name: "[project] dependency review" + +on: + pull_request: + branches: + - main + - master + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + - name: Dependency Review + uses: actions/dependency-review-action@v3 + with: + deny-licenses: | + GPL-1.0-only, GPL-1.0-or-later, GPL-2.0-only, GPL-2.0-or-later, GPL-3.0-only, GPL-3.0-or-later, AGPL-1.0, AGPL-3.0, AGPL-1.0-only, AGPL-1.0-or-later, AGPL-3.0-only, AGPL-3.0-or-later, MPL-1.0, MPL-1.1, MPL-2.0, MPL-2.0-no-copyleft-exception, LGPL-2.0, LGPL-2.0+, LGPL-2.1, LGPL-2.1+, LGPL-3.0, LGPL-3.0+, LGPL-2.0-only, LGPL-2.0-or-later, LGPL-2.1-only, LGPL-2.1-or-later, LGPL-3.0-only, LGPL-3.0-or-later diff --git a/.github/workflows/reuse_approve_checks_run.yml b/.github/workflows/reuse_approve_checks_run.yml new file mode 100644 index 00000000000..72f359eec3e --- /dev/null +++ b/.github/workflows/reuse_approve_checks_run.yml @@ -0,0 +1,104 @@ +name: '[reuse] approve checks run' + +on: + workflow_call: + inputs: + pull_request_number: + description: 'Pull request number' + required: true + type: number + pull_request_head_sha: + description: 'Pull request head sha' + required: true + type: string + requied_privilege_escalation: + description: 'Privilege escalation required' + required: false + default: false + type: boolean + outputs: + pull_request_number: + description: 'Pull request number' + value: ${{ inputs.pull_request_number }} + pull_request_head_sha: + description: 'Pull request head sha' + value: ${{ inputs.pull_request_head_sha }} + action_required: + description: "Checks require approve" + value: ${{ jobs.approve_checks_run.outputs.action_required }} + included_risk_files: + description: "Contains risk files" + value: ${{ jobs.approve_checks_run.outputs.included_risk_files }} + +jobs: + approve_checks_run: + runs-on: ubuntu-latest + outputs: + included_risk_files: ${{ steps.action.outputs.included_risk_files }} + action_required: ${{ steps.action.outputs.action_required }} + workflow_runs: ${{ steps.action.outputs.workflow_runs }} + steps: + - name: Action + id: action + uses: actions/github-script@v6.3.3 + with: + script: | + const { actions, pulls } = github.rest; + const fs = require('fs'); + const os = require('os'); + + const per_page = 100; + + let workflow_runs = (await github.paginate(actions.listWorkflowRunsForRepo, { + per_page, + event: 'pull_request', + status: 'action_required', + ...context.repo + })).filter(workflow_run => workflow_run.head_sha === '${{ inputs.pull_request_head_sha }}').map(workflow_run => workflow_run.id); + if (workflow_runs.length === 0) { + fs.appendFileSync(process.env.GITHUB_OUTPUT, `action_required=false${os.EOL}`, { encoding: 'utf8' }); + return; + } + fs.appendFileSync(process.env.GITHUB_OUTPUT, `action_required=true${os.EOL}`, { encoding: 'utf8' }); + fs.appendFileSync(process.env.GITHUB_OUTPUT, `workflow_runs=${JSON.stringify(workflow_runs)}${os.EOL}`, { encoding: 'utf8' }); + + const [includedRiskFiles] = await github.paginate(pulls.listFiles, { + per_page, + pull_number: ${{ inputs.pull_request_number }}, + ...context.repo + }, ({ data: files }, done) => { + if (files.some(file => file.filename.startsWith('.github/workflows'))) { + done(); + return [true]; + } + return []; + }); + fs.appendFileSync(process.env.GITHUB_OUTPUT, `included_risk_files=${includedRiskFiles}${os.EOL}`, { encoding: 'utf8' }); + + if (!includedRiskFiles) { + await Promise.all(workflow_runs.map(workflow_run => actions.approveWorkflowRun({ + run_id: workflow_run, + ...context.repo + }))); + } + + approve_checks_run_privileged: + needs: approve_checks_run + if: needs.approve_checks_run.outputs.included_risk_files == 'true' && inputs.requied_privilege_escalation == true + environment: github-actions-privileged + runs-on: ubuntu-latest + steps: + - name: Action + id: action + env: + workflow_runs: ${{ needs.approve_checks_run.outputs.workflow_runs }} + uses: actions/github-script@v6.1.0 + with: + script: | + const { actions } = github.rest; + + const workflow_runs = JSON.parse(process.env.workflow_runs); + await Promise.all(workflow_runs.map(workflow_run => actions.approveWorkflowRun({ + run_id: workflow_run, + ...context.repo + }))); diff --git a/.github/workflows/reuse_classify_commits.yml b/.github/workflows/reuse_classify_commits.yml new file mode 100644 index 00000000000..d5f17538106 --- /dev/null +++ b/.github/workflows/reuse_classify_commits.yml @@ -0,0 +1,59 @@ +name: '[reuse] classify commits' + +on: + workflow_call: + inputs: + pull_request_number: + description: 'Pull request number' + required: true + type: number + outputs: + merge_commits_sha: + description: 'Merge commits sha' + value: ${{ jobs.classify_commits.outputs.merge_commits_sha }} + merge_commits_count: + description: 'Merge commits count' + value: ${{ jobs.classify_commits.outputs.merge_commits_count }} + normal_commits_sha: + description: 'Normal commits sha' + value: ${{ jobs.classify_commits.outputs.normal_commits_sha }} + normal_commits_count: + description: 'Normal commits count' + value: ${{ jobs.classify_commits.outputs.normal_commits_count }} + +jobs: + classify_commits: + runs-on: ubuntu-latest + outputs: + merge_commits_sha: ${{ steps.action.outputs.merge_commits_sha }} + merge_commits_count: ${{ steps.action.outputs.merge_commits_count }} + normal_commits_sha: ${{ steps.action.outputs.normal_commits_sha }} + normal_commits_count: ${{ steps.action.outputs.normal_commits_count }} + steps: + - name: Action + id: action + uses: actions/github-script@v6.3.3 + with: + script: | + const { pulls } = github.rest; + const fs = require('fs'); + const os = require('os'); + + const commits = (await github.paginate(pulls.listCommits, { + per_page: 100, + pull_number: ${{ inputs.pull_request_number }}, + ...context.repo + })); + + if (commits.length >= 250) { // exceeded maximum of 250 commits per pull request + return; + } + + const merge_commits = []; + const normal_commits = []; + commits.forEach(commit => commit.parents.length > 1 ? merge_commits.push(commit.sha) : normal_commits.push(commit.sha)); + + fs.appendFileSync(process.env.GITHUB_OUTPUT, `merge_commits_sha=${JSON.stringify(merge_commits)}${os.EOL}`, { encoding: 'utf8' }); + fs.appendFileSync(process.env.GITHUB_OUTPUT, `merge_commits_count=${merge_commits.length}${os.EOL}`, { encoding: 'utf8' }); + fs.appendFileSync(process.env.GITHUB_OUTPUT, `normal_commits_sha=${JSON.stringify(normal_commits)}${os.EOL}`, { encoding: 'utf8' }); + fs.appendFileSync(process.env.GITHUB_OUTPUT, `normal_commits_count=${normal_commits.length}${os.EOL}`, { encoding: 'utf8' }); diff --git a/.github/workflows/reuse_get_workflow_output.yml b/.github/workflows/reuse_get_workflow_output.yml new file mode 100644 index 00000000000..e130b0a2f71 --- /dev/null +++ b/.github/workflows/reuse_get_workflow_output.yml @@ -0,0 +1,67 @@ +name: '[reuse] get workflow output' + +on: + workflow_call: + inputs: + workflow_run: + description: 'A workflow run id' + required: true + type: number + outputs: + action: + description: "A workflow run action field" + value: ${{ jobs.get_workflow_output.outputs.action }} + sender: + description: "A workflow run sender.login field" + value: ${{ jobs.get_workflow_output.outputs.sender }} + raw: + description: "A workflow run raw output" + value: ${{ jobs.get_workflow_output.outputs.raw }} + +jobs: + get_workflow_output: + runs-on: ubuntu-latest + outputs: + action: ${{ steps.parse.outputs.action }} + sender: ${{ steps.parse.outputs.sender }} + raw: ${{ steps.parse.outputs.raw }} + steps: + - name: Pull + uses: actions/github-script@v6.3.3 + with: + script: | + const { actions } = github.rest; + const fs = require('fs'); + + const { data: { artifacts } } = await actions.listWorkflowRunArtifacts({ + run_id: ${{ inputs.workflow_run }}, + ...context.repo + }); + const [ artifact ] = artifacts.filter((artifact) => { + return artifact.name == "data" + }); + if (!artifact) { + throw new Error("Missing data artifact generated in parent workflow(${{ inputs.workflow_run }})"); + } + + const download = await actions.downloadArtifact({ + artifact_id: artifact.id, + archive_format: 'zip', + ...context.repo + }); + fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/data.zip`, Buffer.from(download.data)); + - name: Parse + id: parse + shell: python + run: | + import zipfile + import json + import os + + with zipfile.ZipFile("data.zip", "r") as zip_ref: + buf = zip_ref.read("data") + data = json.loads(buf) + with open(os.getenv("GITHUB_OUTPUT"), 'w', encoding='utf-8') as file: + file.write("action=%s\n" % data["action"]) + file.write("sender=%s\n" % data["sender"]["login"]) + file.write("raw=%s\n" % json.dumps(data)) diff --git a/.github/workflows/tools/v8_7_7_229_build.patch b/.github/workflows/tools/v8_7_7_229_build.patch new file mode 100644 index 00000000000..265207157e5 --- /dev/null +++ b/.github/workflows/tools/v8_7_7_229_build.patch @@ -0,0 +1,68 @@ +diff --git a/config/android/BUILD.gn b/config/android/BUILD.gn +index 233e8b0e7..d2286cd77 100644 +--- a/config/android/BUILD.gn ++++ b/config/android/BUILD.gn +@@ -105,9 +105,7 @@ config("runtime_library") { + # arm-linux-androideabi-4.4.3 toolchain (circa Gingerbread) will exhibit + # strange errors. The include ordering here is important; change with + # caution. +- cflags_cc = [ "-isystem" + +- rebase_path("$android_ndk_root/sources/android/support/include", +- root_build_dir) ] ++ cflags_cc = [] + + defines = [ + "__GNU_SOURCE=1", # Necessary for clone(). +@@ -117,20 +115,31 @@ config("runtime_library") { + lib_dirs = [ android_libcpp_lib_dir ] + + libs = [] +- libs += [ "android_support" ] ++ if (!use_custom_libcxx) { ++ # The libc++ runtime library (must come first). ++ # ASan needs to dynamically link to libc++ even in static builds so ++ # that it can interpose operator new. ++ if (is_component_build || is_asan) { ++ libs += [ "c++_shared" ] ++ } else { ++ libs += [ "c++_static" ] ++ } ++ libs += [ "c++abi" ] ++ } ++ # On 64-bit platforms, the only symbols provided by libandroid_support.a are ++ # strto{d,f,l,ul}_l. These symbols are not used by our libc++, and newer NDKs ++ # don't provide a libandroid_support.a on 64-bit platforms, so we only depend ++ # on this library on 32-bit platforms. ++ if (current_cpu == "arm" || current_cpu == "x86") { ++ libs += [ "android_support" ] ++ } ++ + + # arm builds of libc++ starting in NDK r12 depend on unwind. + if (current_cpu == "arm") { + libs += [ "unwind" ] + } + +- # Manually link the libgcc.a that the cross compiler uses. This is +- # absolute because the linker will look inside the sysroot if it's not. +- libs += [ +- rebase_path(android_libgcc_file), +- "c", +- ] +- + if (current_cpu == "arm" && arm_version == 6) { + libs += [ "atomic" ] + } +diff --git a/config/sysroot.gni b/config/sysroot.gni +index 701c66082..bc33b7cd1 100644 +--- a/config/sysroot.gni ++++ b/config/sysroot.gni +@@ -27,7 +27,7 @@ if (current_os == target_os && current_cpu == target_cpu && + import("//build/config/android/config.gni") + + # Android uses unified headers, and thus a single compile time sysroot +- sysroot = "$android_ndk_root/sysroot" ++ sysroot = "$android_ndk_root/toolchains/llvm/prebuilt/linux-x86_64/sysroot" + } else if (is_linux && use_sysroot) { + # By default build against a sysroot image downloaded from Cloud Storage + # during gclient runhooks. diff --git a/.github/workflows/tools/v8_build.patch b/.github/workflows/tools/v8_build.patch deleted file mode 100644 index ae94974f0a0..00000000000 --- a/.github/workflows/tools/v8_build.patch +++ /dev/null @@ -1,55 +0,0 @@ -diff --git a/config/android/BUILD.gn b/config/android/BUILD.gn -index 233e8b0e7..d78a09e0b 100644 ---- a/config/android/BUILD.gn -+++ b/config/android/BUILD.gn -@@ -105,9 +105,7 @@ config("runtime_library") { - # arm-linux-androideabi-4.4.3 toolchain (circa Gingerbread) will exhibit - # strange errors. The include ordering here is important; change with - # caution. -- cflags_cc = [ "-isystem" + -- rebase_path("$android_ndk_root/sources/android/support/include", -- root_build_dir) ] -+ cflags_cc = [] - - defines = [ - "__GNU_SOURCE=1", # Necessary for clone(). -@@ -117,7 +115,25 @@ config("runtime_library") { - lib_dirs = [ android_libcpp_lib_dir ] - - libs = [] -- libs += [ "android_support" ] -+ if (!use_custom_libcxx) { -+ # The libc++ runtime library (must come first). -+ # ASan needs to dynamically link to libc++ even in static builds so -+ # that it can interpose operator new. -+ if (is_component_build || is_asan) { -+ libs += [ "c++_shared" ] -+ } else { -+ libs += [ "c++_static" ] -+ } -+ libs += [ "c++abi" ] -+ } -+ # On 64-bit platforms, the only symbols provided by libandroid_support.a are -+ # strto{d,f,l,ul}_l. These symbols are not used by our libc++, and newer NDKs -+ # don't provide a libandroid_support.a on 64-bit platforms, so we only depend -+ # on this library on 32-bit platforms. -+ if (current_cpu == "arm" || current_cpu == "x86") { -+ libs += [ "android_support" ] -+ } -+ - - # arm builds of libc++ starting in NDK r12 depend on unwind. - if (current_cpu == "arm") { -diff --git a/config/sysroot.gni b/config/sysroot.gni -index 701c66082..bc33b7cd1 100644 ---- a/config/sysroot.gni -+++ b/config/sysroot.gni -@@ -27,7 +27,7 @@ if (current_os == target_os && current_cpu == target_cpu && - import("//build/config/android/config.gni") - - # Android uses unified headers, and thus a single compile time sysroot -- sysroot = "$android_ndk_root/sysroot" -+ sysroot = "$android_ndk_root/toolchains/llvm/prebuilt/linux-x86_64/sysroot" - } else if (is_linux && use_sysroot) { - # By default build against a sysroot image downloaded from Cloud Storage - # during gclient runhooks. diff --git a/.github/workflows/tools/v8_remove_requests_revision.patch b/.github/workflows/tools/v8_remove_requests_revision.patch new file mode 100644 index 00000000000..e8b1307c1d2 --- /dev/null +++ b/.github/workflows/tools/v8_remove_requests_revision.patch @@ -0,0 +1,13 @@ +diff --git a/DEPS b/DEPS +index 102f46264b..9b9cb5ae9b 100644 +--- a/DEPS ++++ b/DEPS +@@ -270,7 +270,7 @@ deps = { + 'dep_type': 'cipd', + }, + 'third_party/requests': { +- 'url': Var('chromium_url') + '/external/github.com/kennethreitz/requests.git' + '@' + '2c2138e811487b13020eb331482fb991fd399d4e', ++ 'url': Var('chromium_url') + '/external/github.com/kennethreitz/requests.git', + 'condition': 'checkout_android', + }, + 'third_party/zlib': diff --git a/.github/workflows/update_v8.yml b/.github/workflows/update_v8.yml deleted file mode 100644 index 00630318e33..00000000000 --- a/.github/workflows/update_v8.yml +++ /dev/null @@ -1,214 +0,0 @@ -name: '[android] [third_party] update v8' - -on: - workflow_dispatch: - inputs: - v8_revision: - description: 'V8 TAG(Branch) to build' - default: '9.5-lkgr' - required: true - release_tag: - description: 'Release tag' - default: 'latest' - required: true - build_args: - description: 'Build args' - default: 'is_component_build=false is_debug=false v8_use_external_startup_data=false is_official_build=true v8_enable_i18n_support=false treat_warnings_as_errors=false symbol_level=0 use_custom_libcxx=false clang_use_chrome_plugins=false use_thin_lto=false v8_enable_webassembly=false v8_monolithic=true' - required: true - build_target: - description: 'Build target' - default: 'v8_monolith' - required: true - -jobs: - build: - runs-on: [self-hosted, linux, trusted] - container: - image: ghcr.io/tencent/android-release:latest # repository name must be lowercase(${{ github.repository_owner }}) - credentials: - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - strategy: - matrix: - cpu: [arm, arm64, x86, x64] - include: - - cpu: arm - arch: armeabi-v7a - host-tag: arm-linux-androideabi - - cpu: arm64 - arch: arm64-v8a - host-tag: aarch64-linux-android - - cpu: x86 - arch: x86 - host-tag: i686-linux-android - - cpu: x64 - arch: x86_64 - host-tag: x86_64-linux-android - steps: - - name: Clean up before - run: | - shopt -s dotglob - rm -rf * - shopt -u dotglob - - name: Fetch v8 - run: | - fetch v8 - cd v8 - git checkout ${{ github.event.inputs.v8_revision }} - - name: Sync third_party - working-directory: ./v8 - run: | - echo "target_os = ['android']" >> ../.gclient - gclient sync -D - - name: Prepare android_ndk - working-directory: ./v8 - run: | - if [ -d third_party/android_tools ]; then - rm -rf third_party/android_tools - mkdir third_party/android_tools - ln -s $ANDROID_NDK_HOME third_party/android_tools/ndk - fi - if [ -f third_party/android_ndk/BUILD.gn ]; then - cp third_party/android_ndk/BUILD.gn $ANDROID_NDK_HOME - fi - if [ -d third_party/android_tools -o -f third_party/android_ndk/BUILD.gn ]; then - rm -rf third_party/android_ndk - ln -s $ANDROID_NDK_HOME third_party/android_ndk - fi - - name: Fetch patch - uses: actions/checkout@v2 - with: - path: ${{ github.repository }} - lfs: true - - name: Apply patch - working-directory: ./v8/build - continue-on-error: true - run: | - git apply ../../${{ github.repository }}/.github/workflows/tools/v8_build.patch - - name: Generate ${{ matrix.arch }} - working-directory: ./v8 - run: | - gn gen out/${{ matrix.arch }} --args="target_os=\"android\" target_cpu=\"${{ matrix.cpu }}\" v8_target_cpu=\"${{ matrix.cpu }}\" android_ndk_root=\"${ANDROID_NDK_HOME}\" ${{ github.event.inputs.build_args }}" - - name: Compile ${{ matrix.arch }} - working-directory: ./v8 - run: | - \cp -f build/linux/debian_sid_amd64-sysroot/usr/lib/x86_64-linux-gnu/libstdc++.so.6 /lib64 - \cp -f build/linux/debian_sid_i386-sysroot/usr/lib/i386-linux-gnu/libstdc++.so.6 /lib - ninja -C out/${{ matrix.arch }} ${{ github.event.inputs.build_target }} - - name: Release artifact - working-directory: ./v8/out/${{ matrix.arch }} - run: | - mkdir -p artifact/include/v8 artifact/libs/${{ matrix.arch }} - cp obj/libv8_monolith.a artifact/libs/${{ matrix.arch }} - cp -r ../../include/* artifact/include/v8/ - cp -r gen/include/* artifact/include/v8/ - find artifact/include/v8/. ! -name "*.h" -type f -delete - - name: Upload ${{ matrix.arch }} - uses: actions/upload-artifact@v2 - with: - name: ${{ matrix.arch }} - path: ./v8/out/${{ matrix.arch }}/artifact - if-no-files-found: error - - name: Clean up after - if: ${{ always() }} - run: | - shopt -s dotglob - rm -rf * - shopt -u dotglob - pull-request: - needs: build - runs-on: [self-hosted, linux, trusted] - container: - image: ghcr.io/tencent/android-release:latest # repository name must be lowercase(${{ github.repository_owner }}) - credentials: - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - env: - v8-dist: ./android/sdk/src/main/jni/third_party/v8/${{ github.event.inputs.release_tag }} - steps: - - name: Clean up before - run: | - shopt -s dotglob - rm -rf * - shopt -u dotglob - - name: Checkout repo - uses: actions/checkout@v2 - with: - lfs: true - - name: Construct directory structure - working-directory: ${{ env.v8-dist }}/official-release - run: | - shopt -s dotglob - shopt -s extglob - rm -rf -- !(CMakeLists.txt) - shopt -u extglob - shopt -u dotglob - - name: Fetch artifact - uses: actions/download-artifact@v2 - with: - path: ${{ env.v8-dist }}/official-release - - name: Merge all artifact - working-directory: ${{ env.v8-dist }}/official-release - run: | - for d in */ ; do - cp -r $d/* ./ - rm -rf $d - done - - name: Generate Readme - uses: DamianReeves/write-file-action@v1.0 - with: - path: ${{ env.v8-dist }}/official-release/README.md - contents: | - This v8 release is auto generated by Github Action([${{ github.run_id }}][2]). - - _Do NOT modify this release manually._ - - Refs: [${{ github.event.inputs.v8_revision }}][1] - - [1]: https://github.com/v8/v8/tree/${{ github.event.inputs.v8_revision }} - [2]: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - write-mode: overwrite - - name: Create Pull Request - uses: peter-evans/create-pull-request@v3 - with: - commit-message: | - chore(v8): update V8 to ${{ github.event.inputs.v8_revision }} - - Ref-URL: https://github.com/v8/v8/tree/${{ github.event.inputs.v8_revision }} - Action-Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - branch: chore/update-v8-to-${{ github.event.inputs.v8_revision }} - delete-branch: true - title: 'chore(v8): update V8 to ${{ github.event.inputs.v8_revision }}' - body: | - Refs: [${{ github.event.inputs.v8_revision }}][1] - Release TAG: ${{ github.event.inputs.release_tag }} - Action Run: [${{ github.run_id }}][2] - - [1]: https://github.com/v8/v8/tree/${{ github.event.inputs.v8_revision }} - [2]: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - labels: | - dependencies - android - - name: Clean up after - if: ${{ always() }} - run: | - shopt -s dotglob - rm -rf * - shopt -u dotglob - notification: - if: ${{ always() }} - needs: [build, pull-request] - runs-on: [self-hosted, linux, trusted] - continue-on-error: true - env: - WECHAT_WORK_BOT_WEBHOOK: ${{ secrets.WECHAT_WORK_BOT_WEBHOOK }} - steps: - - name: Wechat Work notification - uses: chf007/action-wechat-work@1.0.5 - with: - msgtype: markdown - content: "Github [Action] Notification\n - > repository: ${{ github.repository }}\n - > workflow: ${{ github.workflow }}\n - > result: ${{ needs.pull-request.result == 'success' && 'success' || 'failure'}}\n - > run: [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" diff --git a/.gitignore b/.gitignore index d993f9505ec..90f58e85938 100644 --- a/.gitignore +++ b/.gitignore @@ -1,41 +1,42 @@ -*.log .DS_Store .nyc_output .vscode +.vs +.idea +.gradle +.externalNativeBuild +.cxx + +build coverage dist node_modules + +*.log +*.iml +local.properties +maven-auth.properties + layout/out -layout/build layout/cproject layout/.project layout/.settings + +docs/assets/*.png +docs/assets/*.jpg +docs/assets/*.gif +docs/assets/*.ico +docs/assets/*.js +docs/assets/*.css + examples/*/res/vendor-manifest.json -android/sdk/src/main/jni/.vs -android/sdk/maven-auth.properties -android/sdk/local.properties -android/sdk/.cxx/* -android/sdk/libs/armeabi-v7a/libhippybridge.so -android/sdk/libs/arm64-v8a/libhippybridge.so -android/sdk/libs/x86/libhippybridge.so -android/sdk/libs/x86_64/libhippybridge.so -examples/android-demo/example/src/main/assets/ -examples/android-demo/example/libs/* -examples/ios-demo/HippyDemo.xcodeproj/project.xcworkspace/xcuserdata/* +examples/android-demo/libs/* +examples/android-demo/src/main/assets/* examples/ios-demo/HippyDemo.xcodeproj/xcuserdata/* -examples/hippy-react-demo/package-lock.json -examples/hippy-vue-demo/package-lock.json -examples/android-demo/maven-auth.properties -examples/android-demo/example/.cxx/ +examples/ios-demo/HippyDemo.xcodeproj/project.xcworkspace/xcuserdata/* +examples/ios-demo/Pods +examples/ios-demo/Podfile.lock +examples/ios-demo/HippyDemo.xcworkspace -# android -*.iml -local.properties -.idea/ -.gradle/ -build/ -.externalNativeBuild -# core -core/ARM -.vs/ +packages/hippy-vue-loader/remote-debug-profile/* diff --git a/.husky/post-checkout b/.husky/post-checkout index cab40f26495..ca7fcb40088 100755 --- a/.husky/post-checkout +++ b/.husky/post-checkout @@ -1,3 +1,3 @@ #!/bin/sh -command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting .git/hooks/post-checkout.\n"; exit 2; } +command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'post-checkout' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks').\n"; exit 2; } git lfs post-checkout "$@" diff --git a/.husky/post-commit b/.husky/post-commit index 9443f4161ac..52b339cb3f4 100755 --- a/.husky/post-commit +++ b/.husky/post-commit @@ -1,3 +1,3 @@ #!/bin/sh -command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting .git/hooks/post-commit.\n"; exit 2; } +command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'post-commit' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks').\n"; exit 2; } git lfs post-commit "$@" diff --git a/.husky/post-merge b/.husky/post-merge index 828b70891ed..a912e667aa3 100755 --- a/.husky/post-merge +++ b/.husky/post-merge @@ -1,3 +1,3 @@ #!/bin/sh -command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting .git/hooks/post-merge.\n"; exit 2; } +command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'post-merge' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks').\n"; exit 2; } git lfs post-merge "$@" diff --git a/.husky/pre-push b/.husky/pre-push index 81a9cc6398a..0f0089bc25d 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,3 +1,3 @@ #!/bin/sh -command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting .git/hooks/pre-push.\n"; exit 2; } +command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'pre-push' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks').\n"; exit 2; } git lfs pre-push "$@" diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 06721a60591..00000000000 --- a/.npmignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules -src -.rpt2_cache diff --git a/CHANGELOG.md b/CHANGELOG.md index 384d35d5a22..4a3b6f0fdbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,5 +3,5 @@ All notable changes to this project will be documented in different release branch. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. -# [v2.x](//github.com/Tencent/Hippy/blob/v2.x/CHANGELOG.md) +# [Release Notes](//github.com/Tencent/Hippy/releases) diff --git a/PUBLISH.md b/PUBLISH.md new file mode 100644 index 00000000000..a1cd18cf5c0 --- /dev/null +++ b/PUBLISH.md @@ -0,0 +1,108 @@ +# Hippy publish document + +Hippy version management follows principle that all modules use same version. + +## 1. Update Version Number + +The front-end Uses [lerna](https://lerna.js.org/) for versioning and CHANGELOG generation, but cannot use its publishing functionality because it needs to update the terminal package. + +Update version and CHANGELOG usage: + +```bash +npx lerna version [VERSION] --conventional-commits --tag-version-prefix='' --no-push +``` + +* `[version]` - The version number to be released, such as 2.1.0. +* `--conventional-commits` - Generates a CHANGELOG based on the conventional commit specification. +* `--tag-version-prefix` - it is changed to a null character, so that the generated version number tag is not preceded by the default `v` +* `--no-push` - it does not push tags to remote. + +## 2. Rollback commit and delete auto-generated tag + +After Lerna generates the version number and CHANGELOG, it needs to roll back the version, and all the published changes need to be merged into a commit. + +```bash +git reset --soft HEAD^ +``` + +Delete the tag at the same time, after a while update need to regenerate the tag + +```bash +git tag -d [VERSION] +``` + +## 3. Update native version number + +The native version number is mainly located in the following files, which need to be updated to the version number to be released + +iOS + +* [hippy.podspec](https://github.com/Tencent/Hippy/blob/master/hippy.podspec) [s.version] +* [HippyBridge.mm](https://github.com/Tencent/Hippy/blob/master/ios/sdk/base/HippyBridge.mm) [_HippySDKVersion] + +Android + +* [gradle.properties](https://github.com/Tencent/Hippy/blob/master/android/sdk/gradle.properties) [VERSION_NAME] + +## 4. Update built-in packages and verify functionality + +The new front-end SDK is then compiled with + +```bash +npm run build +``` + +If some code updated under `core/js`, you need to compile the core code with + +```bash +npm run buildcore +``` + +Then update the dependencies under the target `examples` and update the built-in packages of the native. Generally speaking, the built-in hippy-react-demo is the default, but be sure to check that hippy-vue-demo functions properly. + +```bash +npm run buildexample hippy-react-demo +``` + +## 5. Resubmit when everything is ready + +Check again that all files have been modified correctly + +```bash +git status +``` + +Submit document modification + +```bash +git add [FILES] +``` + +Enter a commit message that conforms to [Convention Commit](https://conventionalcommits.org/) specifications + +```bash +git commit -m 'chore(release): released [VERSION]' +``` + +tag + +```bash +git tag [VERSION] +``` + +Commit the code and tag. + +```bash +git push origin vxxx - branch +git push origin [VERSION] - tag +``` + +## 6. Publish + +Run [Release Workflow](https://github.com/Tencent/Hippy/actions/workflows/project_artifact_release.yml) to publish all packages. + +### Android + +* sign up for [sonatype](https://oss.sonatype.org). +* After successful verification, Close the repository of `Staging Repositories` through clicking `Close` and then click `Release` to release hippy maven packages. +* After the success of the Release can be searched in the Repository to the corresponding version of aar, Maven home page need to wait for more than 2 hours to synchronize diff --git a/PUBLISH.zh_CN.md b/PUBLISH.zh_CN.md deleted file mode 100644 index ad6922ec949..00000000000 --- a/PUBLISH.zh_CN.md +++ /dev/null @@ -1,161 +0,0 @@ -# Hippy 发布文档 - -Hippy版本管理遵循所有模块使用同一版本原则 - -## 1. 更新版本号 - -前端使用 [lerna](https://lerna.js.org/) 进行版本管理和 CHANGELOG 生成,但因为需要更新终端包所以不能使用它的发布功能。 - -更新版本和 CHANGELOG 使用: - -```bash -lerna version [VERSION] --conventional-commits --tag-version-prefix='' --no-push -``` - -* `[VERSION]` - 要发布的版本号,如2.1.0。 -* `--conventional-commits` - 生成基于 conventional 提交规范的 CHANGELOG。 -* `--tag-version-prefix` - 改为空字符,这样生成的版本号 tag 前面就不会带上默认的 `v`。 -* `--no-push` - 不会将tag推动到远程。 - -## 2. 回退 commit 并删除自动生成的 tag - -lerna 生成版本号和 CHANGELOG 后,需要回退一下版本,所有发布改动需要合并到一个 commit 中。 - -```bash -git reset --soft HEAD^ -``` - -同时删除 tag,一会儿更新后需要重新生成 tag - -```bash -git tag -d [VERSION] -``` - -## 3. 更新终端版本号 - -终端版本号主要位于以下几个文件,都需要更新到即将发布的版本号 - -iOS - -* [hippy.podspec](https://github.com/Tencent/Hippy/blob/master/hippy.podspec#L11) -* [HippyBridge.mm](https://github.com/Tencent/Hippy/blob/master/ios/sdk/base/HippyBridge.mm#L45) - -Android - -* [gradle.properties](https://github.com/Tencent/Hippy/blob/master/android/sdk/gradle.properties#L25) - -## 4. 更新内置包并校验功能正常 - -随后编译新的前端 SDK - -```bash -npm run build -``` - -如果有 `core/js` 下的代码更新,则需要编译一下 core 代码 - -```bash -npm run buildcore -``` - -随后更新目标 `examples` 下的依赖并更新终端内置包,一般而言默认内置 hippy-react-demo,但务必检查一下 hippy-vue-demo 的功能正常。 - -```bash -npm run buildexample -- hippy-react-demo -``` - -> 注意:buildexample 可能会使用旧版本的前端 SDK,需要手工更新 node_modules 下的文件后再更新内置包,buildexample 也需要将 npm install 那一步暂时注释掉。 - -## 5. 一切准备完毕后重新提交 - -再一次检查所有文件都正确修改 - -```bash -git status -``` - -提交文件修改 - -```bash -git add [FILES] -``` - -输入符合 [Convention Commit](https://conventionalcommits.org/) 规范的 commit message - -```bash -git commit -m 'chore(release): released [VERSION]' -``` - -打上 tag - -```bash -git tag [VERSION] -``` - -提交代码,并准备发布 PR 合并到 master 分支。 - -```bash -git push # 提交代码 -git push --tags # 提交 tag -``` - -## 6. 发布 - -* 前端发布到 npmjs.com - - ```bash - lerna exec "npm publish" - ``` - - > 如果开启了 npm 二次验证会一直问你一次性密码,正常输入即可。 - -* iOS 发布到 cocoapods.org - - * 如果没有cocoapod账户,先进行注册 - - ```bash - pod trunk register [EMAIL] [NAME] - ``` - - * 然后发布 - - ```bash - pod trunk push hippy.podspec - ``` - - > 如果发布时参数检查失败,可以在`pod`命令前面加上 `COCOAPODS_VALIDATOR_SKIP_XCODEBUILD=1` 参数 - -* Android 发布到 Maven Central,原有jCenter仓库已经废弃 [版本查询](https://search.maven.org/search?q=com.tencent.hippy)。 - - * 参照 [发布 Maven Central](https://zhuanlan.zhihu.com/p/362205023) 的步骤,并注册 [sonatype](https://oss.sonatype.org) 的账号。 - * 增加系统环境变量配置 - - ```bash - SIGNING_KEY_ID=gpg公钥key后8位 - SIGNING_PASSWORD=gpg密钥对密码 - SIGNING_SECRET_KEY_RING_FILE=gpg文件存放路径, 如/Users/user/.gnupg/secring.gpg - OSSRH_USERNAME=sonatype账号 - OSSRH_PASSWORD=sonatype密码 - ``` - - * Gradle Task 先执行 `other` => `assembleRelease`, 再执行 `publishing` => `publish`, 发布成功后SDK会在 sonatype 的 `staging`状态 - * 在 sonatype 左边 `Staging Repositories` 里找到刚发布的 repository,如果想Release前进行测试,可以在 `Content` 下将 `aar` 下载,替换`examples` => `android-demo` => `example` => `libs` 下的 aar(名字改成 `android-sdk-release.aar`) - - ```bash - // 注释 `setting.gradle` 本地 SDK 的引用 - // include 'android-sdk' - // project(':android-sdk').projectDir = new File('../../android/sdk') - - -------------- - - // `android-demo` => `example` => `build.gradle` 的 dependencies 修改如下,这样就会默认采用本地 aar - if (1) { - api (name: 'android-sdk-release', ext: 'aar') - } else { - api project(path: ':android-sdk') - } - ``` - - * 验证成功后, 将 `Staging Repositories` 的 repository `Close`,再点击 `Release`。 - * Release 成功后就可以在 Repository 里 搜索到对应版本的aar,Maven主页需要等待2个小时以上才会同步 - \ No newline at end of file diff --git a/PrivacyInfo.xcprivacy b/PrivacyInfo.xcprivacy new file mode 100644 index 00000000000..8fe86d36970 --- /dev/null +++ b/PrivacyInfo.xcprivacy @@ -0,0 +1,31 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyCollectedDataTypes + + NSPrivacyTrackingDomains + + NSPrivacyTracking + + + diff --git a/README.md b/README.md index dde677e6d26..872a93ae4ba 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,11 @@ ![Hippy Group](https://img.shields.io/badge/group-Hippy-blue.svg) [![license](https://img.shields.io/badge/license-Apache%202-blue)](https://github.com/Tencent/Hippy/blob/master/LICENSE) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/Tencent/Hippy/pulls) ![node](https://img.shields.io/badge/node-%3E%3D10.0.0-green.svg) [![Actions Status](https://github.com/Tencent/Hippy/workflows/build/badge.svg?branch=master)](https://github.com/Tencent/Hippy/actions) [![Codecov](https://img.shields.io/codecov/c/github/Tencent/Hippy)](https://codecov.io/gh/Tencent/Hippy) [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/Tencent/Hippy)](https://github.com/Tencent/Hippy/releases) -English | [简体中文](./README.zh_CN.md) | [Homepage](//tencent.github.io/Hippy/) - ## 💡 Introduction -Hippy is a cross-platform development framework, aiming to help developers write once, run on three platforms(iOS, Android and Web). Hippy is quite friendly to Web developers, especially who are familiar with React or Vue. With Hippy, developers are able to create the cross platform app easily. +Hippy is a cross-platform development framework, that aims to help developers write once, and run on multiple platforms(iOS, Android, Web, and so on). Hippy is quite friendly to Web developers, especially those who are familiar with React or Vue. With Hippy, developers can create the cross-platform app easily. -Hippy is now applied in 27+ [Tencent](http://www.tencent.com/) apps such as Mobile QQ, Mobile QQ Browser, Tencent Video App, QQ Music App, Tencent News, reaching hundreds of millions of ordinary users. +Hippy is now applied in [Tencent](http://www.tencent.com/) major apps such as Mobile QQ, Mobile QQ Browser, Tencent Video App, QQ Music App, and Tencent News, reaching hundreds of millions of ordinary users. ## 💯 Advantages @@ -19,114 +17,16 @@ Hippy is now applied in 27+ [Tencent](http://www.tencent.com/) apps such as Mobi * Smoothly and gracefully migrate to Web browser. * Fully supported Flex [Layout engine](./layout). -## 🔨 Getting started - -### Preparing environment - -Run `git clone https://github.com/Tencent/Hippy.git` - -> Hippy repository applies [git-lfs](https://git-lfs.github.com/) to manage so,gz,otf,png,jpg files, make sure you have installed [git-lfs](https://git-lfs.github.com/) first. - - -For macOS developers: - -* [Xcode](https://developer.apple.com/xcode/) with iOS sdk: build the iOS app. -* [Android Studio](https://developer.android.com/studio) with NDK: build the android app. -* [Node.JS](https://nodejs.org/en/): run the build scripts. - -[homebrew](https://brew.sh/) is recommended to install the dependencies. - -For Windows developers: - -* [Android Studio](https://developer.android.com/studio) with NDK: build the android app. -* [Node.JS](https://nodejs.org/en/): run the build scripts. - -> Windows can't run the iOS development environment so far. - -### Build the iOS simulator with js demo - -For iOS, we recommend to use iOS simulator when first try. However, you can change the Xcode configuration to install the app to iPhone if you are an iOS expert. - -1. Install the project build scripts dependencies at root directory with `npm install`. -2. Install dependencies of each npm package at root directory with `lerna bootstrap`. - (Hippy uses [Lerna](https://lerna.js.org/) to manage multi js packages, if `lerna` command is not found, execute `npm install lerna -g` first.) -3. Build each front-end sdk package at root directory with `npm run build`. -4. Choose a demo to build with `npm run buildexample -- [hippy-react-demo|hippy-vue-demo]` at root directory. -5. Start the Xcode and build the iOS app with `open examples/ios-demo/HippyDemo.xcodeproj`. - -> If `Step 4` throw error, you can `cd` to `examples` hippy-react-demo or hippy-vue-demo, and run `npm install --legacy-peer-deps` to install demo dependencies first. -> -> More details for [iOS SDK integration](https://hippyjs.org/#/ios/integration?id=ios-%e9%9b%86%e6%88%90). - -### Build the Android app with js demo - -For Android, we recommend using the real cellphone for better develop experience, because Hippy is using [X5](https://x5.tencent.com/) JS engine which can't support x86 simulator, as well as ARM simulator has a low performance. - -Before build the android app, please make sure the SDK and NDK is installed, And *DO NOT* update the build toolchain. - -1. Install the whole project dependencies at root directory with `npm install`. -2. Install dependencies of each npm package at root directory with `lerna bootstrap`. - (Hippy uses [Lerna](https://lerna.js.org/) to manage multi js packages, if `lerna` command is not found, execute `npm install lerna -g` first.) -3. Build each front-end sdk package at root directory with `npm run build`. -4. Choose a demo to build with `npm run buildexample -- [hippy-react-demo|hippy-vue-demo]` at root directory. -5. Open the `examples/android-demo` with Android Studio. -6. Connect Android phone with USB cable and make sure USB debugging mode is enabled(Run `adb devices` on the computer terminal to check cellphone connection status). -7. Open the project with Android Studio, run and install the apk. - -> If `Step 4` throw error, you can `cd` to `examples` hippy-react-demo or hippy-vue-demo, and run `npm install --legacy-peer-deps` to install demo dependencies first. -> -> If you encounter the issue of `No toolchains found in the NDK toolchains folder for ABI with prefix: mips64el-linux-android`, here is the [solution](https://github.com/google/filament/issues/15#issuecomment-415423557). -> -> More details for [Android SDK integration](https://hippyjs.org/#/android/integration?id=android-%e9%9b%86%e6%88%90). - -### Debug the js demo - -1. Follow [Build the iOS simulator with js demo](https://github.com/Tencent/Hippy#build-the-ios-simulator-with-js-demo) or [Build the Android app with js demo](https://github.com/Tencent/Hippy#build-the-android-app-with-js-demo) first to build the App. -2. `cd` to `examples` hippy-react-demo or hippy-vue-demo. -3. Run `npm install` to install demo js dependencies. -4. Run `npm run hippy:dev` and `npm run hippy:debug` (`npm run hippy:local-debug` will link to source code in packages) respectively to start the live debug mode. - -> On example debug mode, npm packages such as @hippy/react, @hippy/vue are linked to `packages` > `[different package]` > `dist`(not node_modules), so if you have changed js package source code and want to make it take effect in target example, please call `npm run build` at root directory again. -> -> More details for debugging can be read in [Hippy Debug Document](https://hippyjs.org/#/guide/debug). - ## 📁 Documentation -To check out [hippy examples](https://github.com/Tencent/Hippy/tree/master/examples) and visit [hippyjs.org](https://hippyjs.org). +* [Getting Started](https://hippyjs.org/#/guide/integration?id=getting-started) + +For experiencing more features, check out [hippy examples](https://github.com/Tencent/Hippy/tree/master/examples) and visit [Documents page](https://hippyjs.org). ## 📅 Changelog Detailed changes for each release version are documented in the [project release notes](https://github.com/Tencent/Hippy/releases). -## 🧱 Project structure - -```text -Hippy -├── examples # Demo code for frontend or native developer. -│   ├── hippy-react-demo # hippy-react js demo code. -│   ├── hippy-vue-demo # hippy-vue js demo code. -│   ├── ios-demo # iOS native demo code. -│   └── android-demo # Android native demo code. -├── packages # npm packages. -│   ├── hippy-debug-server # Debug the Hippy with native. -│   ├── hippy-react # React binding for Hippy. -│   ├── hippy-react-web # Web adapter for hippy-react. -│   ├── hippy-vue # Vue binding for Hippy. -│   ├── hippy-vue-css-loader # Webpack loader for convert CSS text to JS AST. -│   ├── hippy-vue-native-components # Native components extensions for hippy-vue. -│   ├── hippy-vue-router # Vue router for hippy-vue. -│   └── types # Global type definition. -├── ios -│   └── sdk # iOS SDK -├── android -│   ├── support_ui # Android native components. -│   └── sdk # Android SDK. -├── core # JS modules implemented by C++, binding to JS engine. -├── docker # Native release docker image and build scripts. -├── layout # Hippy layout engine. -└── scripts # Project build script. -``` - ## 🤝 Contribution Developers are welcome to contribute to Tencent's open source, and we will also give them incentives to acknowledge and thank them. Here we provide an official description of Tencent's open source contribution. Specific contribution rules for each project are formulated by the project team. Developers can choose the appropriate project and participate according to the corresponding rules. The Tencent Project Management Committee will report regularly to qualified contributors and awards will be issued by the official contact. Before making a pull request or issue to Hippy, please make sure to read [Contributing Guide](https://github.com/Tencent/Hippy/blob/master/.github/CONTRIBUTING.md). @@ -146,4 +46,3 @@ Hippy is [Apache-2.0 licensed](./LICENSE). [Hippy Eco-System](https://github.com/hippy-contrib) [Taitank Layout Engine](https://github.com/Tencent/Taitank) - diff --git a/README.zh_CN.md b/README.zh_CN.md deleted file mode 100644 index cb1f1590f6e..00000000000 --- a/README.zh_CN.md +++ /dev/null @@ -1,147 +0,0 @@ -# Hippy 跨端开发框架 - -![Hippy Group](https://img.shields.io/badge/group-Hippy-blue.svg) [![license](https://img.shields.io/badge/license-Apache%202-blue)](https://github.com/Tencent/Hippy/blob/master/LICENSE) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/Tencent/Hippy/pulls) ![node](https://img.shields.io/badge/node-%3E%3D10.0.0-green.svg) [![Actions Status](https://github.com/Tencent/Hippy/workflows/build/badge.svg?branch=master)](https://github.com/Tencent/Hippy/actions) [![Codecov](https://img.shields.io/codecov/c/github/Tencent/Hippy)](https://codecov.io/gh/Tencent/Hippy) [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/Tencent/Hippy)](https://github.com/Tencent/Hippy/releases) - -[English](./README.md) | 简体中文 | [官网](//tencent.github.io/Hippy/) | [文章专栏](https://cloud.tencent.com/developer/column/84006) | [QQ群:784894901](//shang.qq.com/wpa/qunwpa?idkey=7bff52aca3aac75a4f1ba96c1844a5e3b62000351890182eb60311542d75fa1a) - -## 💡 介绍 - -Hippy 是一个新生的跨端开发框架,目标是使开发者可以只写一套代码就直接运行于三个平台(iOS、Android 和 Web)。Hippy 的设计是面向传统 Web 开发者的,特别是之前有过 React 和 Vue 开发经验的开发者用起来会更为顺手,Hippy 致力于让前端开发跨端 App 更加容易。 - -到目前为止,[腾讯](http://www.tencent.com/)公司内已经有 27+ 款主流 App 在使用 Hippy 框架,包括手机QQ、QQ浏览器、腾讯视频、QQ音乐、腾讯新闻等,每日触达数亿用户。 - - - -## 💯 特征 - -* 为传统 Web 前端开发者设计,官方支持 `React` 和 `Vue` 两种主流前端框架。 -* 不同的平台保持了相同的接口。 -* 通过 JS 引擎 binding 模式实现的前端-终端通讯,具备超强性能。 -* 提供了高性能的可复用列表。 -* 皆可平滑迁移到 Web 浏览器。 -* 完整支持 Flex 的[布局引擎](./layout)。 - -## 🔨 开始 - -### 准备环境 - -运行 `git clone https://github.com/Tencent/Hippy.git` - -> Hippy 仓库使用 [git-lfs](https://git-lfs.github.com/) 来管理 so,gz,otf,png,jpg 文件, 请确保你已经安装 [git-lfs](https://git-lfs.github.com/)。 - -macOS 用户需要以下软件: - -* [Xcode](https://developer.apple.com/xcode/) 和 iOS SDK: 用以编译 iOS 终端 app。 -* [Android Studio](https://developer.android.com/studio) 和 NDK: 用以编译 Android app。 -* [Node.JS](http://nodejs.cn/): 用以运行前端编译脚本。 - -我们推荐使用 [homebrew](https://brew.sh/) 来安装依赖。 - -Windows 用户者需要以下软件: - -* [Android Studio](https://developer.android.com/studio) 和 NDK: 用以编译 Android app。 -* [Node.JS](http://nodejs.cn/): 用以运行前端编译脚本。 - -> Windows 用户受条件所限,暂时无法进行 iOS app 开发。 - -### 使用 JS 范例来构建 iOS App - -我们推荐 iOS 开发者使用模拟器来进行开发和调试工作。当然如果你是一个 iOS 开发高手,也可以通过修改配置将 Hippy app 安装到 iPhone 手机上。 - -1. 在根目录运行命令 `npm install` 安装项目构建脚本的依赖。 -2. 在根目录运行命令 `lerna bootstrap` 安装前端每一个 package 依赖。(Hippy 采用 [Lerna](https://lerna.js.org/) 管理多 JS SDK 包仓库,如果出现 `lerna command is not found`, 先执行 `npm install lerna -g` 全局安装 `Lerna`。) -3. 在根目录运行命令 `npm run build` 编译每一个 JS SDK 包。 -4. 选择一个前端范例项目来进行编译,在项目根目录运行 `npm run buildexample -- [hippy-react-demo|hippy-vue-demo]`。 -5. 启动 Xcode 并且开始编译终端 App:`open examples/ios-demo/HippyDemo.xcodeproj`。 - -> 如果步骤4出现错误,可以先 `cd` 到 `examples` hippy-react-demo 或者 hippy-vue-demo 目录下,执行 `npm install --legacy-peer-deps`,提前将 demo 的 NPM 包依赖先安装好。 -> -> 更多信息请参考 [iOS SDK 集成](https://hippyjs.org/#/ios/integration?id=ios-%e9%9b%86%e6%88%90)。 - -### 使用 JS 范例来构建 Android App - -我们推荐 Android 开发者使用真机,因为 Hippy 使用的 [X5](https://x5.tencent.com/) JS 引擎没有提供 x86 的库以至于无法支持 x86 模拟器,同时使用 ARM 模拟器也比较慢。 - -在开始前请确认好 SDK 和 NDK 都安装了范例的指定版本,并且**请勿**更新编译工具链。 - -1. 在根目录运行命令 `npm install` 安装项目构建脚本的依赖。 -2. 在根目录运行命令 `lerna bootstrap` 安装前端每一个 package 依赖。(Hippy 采用 [Lerna](https://lerna.js.org/) 管理多 JS SDK 包仓库,如果出现 `lerna command is not found`, 先执行 `npm install lerna -g` 全局安装 `Lerna`。) -3. 在根目录运行命令 `npm run build` 编译每一个 JS SDK 包。 -4. 选择一个前端范例项目来进行编译,在项目根目录运行 `npm run buildexample -- [hippy-react-demo|hippy-vue-demo]`。 -5. 用 Android Studio 来打开终端范例工程 `examples/android-demo`。 -6. 用 USB 数据线插上你的 Android 手机,需要确认手机已经打开 USB 调试模式(可通过在电脑 Terminal 执行 `adb devices` 判断手机是否已经连上了电脑)。 -7. 运行工程,并安装 APK。 - -> 如果步骤4出现错误,可以先 `cd` 到 `examples` hippy-react-demo 或者 hippy-vue-demo 目录下,执行 `npm install --legacy-peer-deps`,提前将 demo 的 NPM 包依赖先安装好。 -> -> 如果 Android Studio 报了这个错误 `No toolchains found in the NDK toolchains folder for ABI with prefix: mips64el-linux-android`,这里有[解决办法](https://github.com/google/filament/issues/15#issuecomment-415423557)。 -> -> 更多信息请参考 [Android SDK 集成](https://hippyjs.org/#/android/integration?id=android-%e9%9b%86%e6%88%90)。 - -### 调试前端 Demo - -1. 先按照 **[使用 JS 范例来构建 iOS App]** 和 **[使用 JS 范例来构建 Android App]** 步骤执行。 -2. `cd` 到 `examples` hippy-react-demo 或者 hippy-vue-demo 目录。 -3. 执行 `npm install` 安装相应 js demo 的依赖包。 -4. 分别执行 `npm run hippy:dev` 和 `npm run hippy:debug`(`npm run hippy:local-debug` 会调用 packages 下的源码) 来开启实时 Debug 模式。 - -> 在 example 调试模式下,@hippy/react、@hippy/vue 等 npm 模块会直接链接到 `packages` > `[different package]` > `dist` 目录下面的 js 文件(非 node_modules),所以如果你修改了 packages 下的 JS 源代码并且想让其在 example 中生效,请重新在根目录执行 `npm run build`。 -> -> 更多关于调试的说明请浏览 [Hippy Debug Document](https://hippyjs.org/#/guide/debug)。 - -## 📁 文档 - -参考 [hippy examples](https://github.com/Tencent/Hippy/tree/master/examples) 下的代码和浏览官网 [hippyjs.org](https://hippyjs.org)。 - -## 📅 更新日志 - -每个发布版本的详细更新日志会记录在 [project release notes](https://github.com/Tencent/Hippy/releases)。 - -## 🧱 项目结构 - -```text -Hippy -├── examples # 前终端范例代码。 -│   ├── hippy-react-demo # hippy-react 前端范例代码。 -│   ├── hippy-vue-demo # hippy-vue 前端范例代码。 -│   ├── ios-demo # iOS 终端范例代码。 -│   └── android-demo # Android 终端范例代码。 -├── packages # 前端 npm 包。 -│   ├── hippy-debug-server # Hippy 的前终端调试服务。 -│   ├── hippy-react # Hippy 的 React 语法绑定。 -│   ├── hippy-react-web # hippy-react 转 Web 的库。 -│   ├── hippy-vue # Hippy 的 Vue 语法绑定。 -│   ├── hippy-vue-css-loader # 用来将 CSS 文本转换为 JS 语法树以供解析的 Webpack loader。 -│   ├── hippy-vue-native-components # hippy-vue 中浏览器中所没有的,额外的,终端定制组件。 -│   ├── hippy-vue-router # 在 hippy-vue 中运行的 vue-router。 -│   └── types # 全局 Typescript 类型 -├── ios -│   └── sdk # iOS SDK。 -├── android -│   ├── support_ui # Android 终端实现的组件。 -│   └── sdk # Android SDK。 -├── core # C++ 实现的 JS 模块,通过 Binding 方式运行在 JS 引擎中。 -├── docker # 发布 Native 的 Docker 镜像和构建脚本 -├── layout # Hippy 布局引擎。 -└── scripts # 项目编译脚本。 -``` - -## 🤝 贡献 - -欢迎开发人员为腾讯的开源做出贡献,我们将持续激励他们并感谢他们。我们提供了腾讯对开源贡献的说明,每个项目的具体贡献规则由项目团队制定。开发人员可以选择适当的项目并根据相应的规则参与。腾讯项目管理委员会将定期汇报合格的贡献者,奖项将由官方联络人颁发。在发起 Pull Request 或者 issue 前, 请确保你已经阅读 [Contributing Guide](https://github.com/Tencent/Hippy/blob/master/.github/CONTRIBUTING.md)。 - -所有曾经参与 Hippy 项目贡献的开发者都会记录在 [Contributors](https://github.com/Tencent/Hippy/graphs/contributors) 和 [Authors File](https://github.com/Tencent/Hippy/blob/master/AUTHORS) 。 - -## ❤️ 追星数 - -[![Stargazers over time](https://starchart.cc/Tencent/Hippy.svg)](https://starchart.cc/Tencent/Hippy) - -## 📄 许可协议 - -Hippy 遵守 [Apache-2.0 licensed](./LICENSE) 协议。 - -## 🔗 链接 - -[Hippy 生态](https://github.com/hippy-contrib) - -[Taitank 布局引擎](https://github.com/Tencent/Taitank) diff --git a/_Pods.xcodeproj b/_Pods.xcodeproj new file mode 120000 index 00000000000..c4e4beeb180 --- /dev/null +++ b/_Pods.xcodeproj @@ -0,0 +1 @@ +examples/ios-demo/Pods/Pods.xcodeproj \ No newline at end of file diff --git a/android/sdk/build.gradle b/android/sdk/build.gradle index e30336d8d5f..71738fa5ddd 100644 --- a/android/sdk/build.gradle +++ b/android/sdk/build.gradle @@ -1,12 +1,27 @@ -apply plugin: 'com.android.library' -apply from: './mavencentral.gradle' +/* + * + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2022 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ -ext { - CMAKE_PATH = "$projectDir.absolutePath/src/main/jni/CMakeLists.txt" - OUTPUT_PATH = "$buildDir/outputs/aar" - THIRD_PARTY_LIBRARY_PATH = "$projectDir.absolutePath/src/main/jni/third_party" - skipCmakeAndNinja = System.getenv('skipCmakeAndNinja') != null -} +apply plugin: 'com.android.library' +apply from: './publish.gradle' buildscript { repositories { @@ -14,31 +29,14 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.2.2' + classpath "com.android.tools.build:gradle:$AGP_VERSION" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { - ext { - MAVEN_USER = System.getenv('maven_username') - MAVEN_PWD = System.getenv('maven_password') - MAVEN_URL = System.getenv('maven_url') - - println("MAVEN_USER=" + MAVEN_USER + ", MAVEN_PWD=" + MAVEN_PWD) - } - repositories { - if (MAVEN_URL != null) { - maven { - url MAVEN_URL - credentials { - username MAVEN_USER - password MAVEN_PWD - } - } - } google() mavenCentral() } @@ -46,25 +44,25 @@ allprojects { android { archivesBaseName = ARCHIVES_BASE_NAME - compileSdkVersion COMPILE_SDK_VERSION as int + compileSdkVersion COMPILE_VERSION as int ndkVersion = NDK_VERSION defaultConfig { - minSdkVersion MIN_SDK_VERSION as int - targetSdkVersion TARGET_SDK_VERSION as int - versionCode VERSION_CODE as int + minSdkVersion MIN_VERSION as int + targetSdkVersion TARGET_VERSION as int versionName VERSION_NAME // 保证app使用aar时,会自动将该proguard文件添加到本身的proguard规则中 consumerProguardFiles 'proguard-rules.pro' buildConfigField("boolean", "ENABLE_SO_DOWNLOAD", ENABLE_SO_DOWNLOAD) - buildConfigField("boolean", "INCLUDE_SUPPORT_UI", INCLUDE_SUPPORT_UI) + buildConfigField("boolean", "ENABLE_BUGLY_REPORT", ENABLE_BUGLY_REPORT) + buildConfigField("String", "LIBRARY_VERSION", "String.valueOf(\"$VERSION_NAME\")") packagingOptions { if (!INCLUDE_ABI_ARMEABI.toBoolean()) { exclude 'lib/armeabi/*' } - if (!INCLUDE_ABI_ARMEABI_V7A.toBoolean()) { + if (!INCLUDE_ABI_ARMEABI_V7A.toBoolean() && !INCLUDE_ABI_ARMEABI.toBoolean()) { exclude 'lib/armeabi-v7a/*' } if (!INCLUDE_ABI_ARM64_V8A.toBoolean()) { @@ -79,16 +77,6 @@ android { } } - def V8_COMPONENT_DEBUG - def V8_COMPONENT_RELEASE - if (V8_COMPONENT == 'auto') { - V8_COMPONENT_DEBUG = 'third_party/v8/stable/official-release' - V8_COMPONENT_RELEASE = 'third_party/v8/stable/x5-lite' - } else { - V8_COMPONENT_DEBUG = V8_COMPONENT - V8_COMPONENT_RELEASE = V8_COMPONENT - } - buildTypes { release { minifyEnabled false @@ -96,11 +84,13 @@ android { externalNativeBuild { cmake { arguments "-DJS_ENGINE=V8", - "-DANDROID_CPP_FEATURES=rtti exceptions", + "-DANDROID_CPP_FEATURES=no-rtti no-exceptions", "-DANDROID_PLATFORM=android-21", + "-DANDROID_ARM_NEON=${INCLUDE_ABI_ARMEABI.toBoolean() ? 'FALSE' : 'TRUE'}", "-DCMAKE_BUILD_TYPE=Release", "-DANDROID_STL=$ANDROID_STL", - "-DV8_COMPONENT=$V8_COMPONENT_RELEASE", + "-DVERSION_NAME=$VERSION_NAME", + "-DV8_COMPONENT=$V8_COMPONENT", "-DHIDDEN_LIBRARY_SYMBOL=$HIDDEN_LIBRARY_SYMBOL" if (INCLUDE_ABI_ARMEABI_V7A.toBoolean() || INCLUDE_ABI_ARMEABI.toBoolean()) { @@ -123,11 +113,13 @@ android { externalNativeBuild { cmake { arguments "-DJS_ENGINE=V8", - "-DANDROID_CPP_FEATURES=rtti exceptions", + "-DANDROID_CPP_FEATURES=no-rtti no-exceptions", "-DANDROID_PLATFORM=android-21", + "-DANDROID_ARM_NEON=${INCLUDE_ABI_ARMEABI.toBoolean() ? 'FALSE' : 'TRUE'}", "-DCMAKE_BUILD_TYPE=Debug", "-DANDROID_STL=$ANDROID_STL", - "-DV8_COMPONENT=$V8_COMPONENT_DEBUG", + "-DVERSION_NAME=$VERSION_NAME", + "-DV8_COMPONENT=$V8_COMPONENT", "-DHIDDEN_LIBRARY_SYMBOL=$HIDDEN_LIBRARY_SYMBOL" if (INCLUDE_ABI_ARMEABI_V7A.toBoolean() || INCLUDE_ABI_ARMEABI.toBoolean()) { @@ -147,10 +139,11 @@ android { } } - if (!SKIP_CMAKE_AND_NINJA.toBoolean() || !skipCmakeAndNinja) { + if (!SKIP_CMAKE_AND_NINJA.toBoolean() && System.getenv('skipCmakeAndNinja') == null) { externalNativeBuild { cmake { - path CMAKE_PATH + path "src/main/jni/CMakeLists.txt" + version CMAKE_VERSION } } } @@ -158,27 +151,14 @@ android { tasks.withType(JavaCompile) { options.encoding = "UTF-8" } - - sourceSets{ - main { - if (INCLUDE_SUPPORT_UI.toBoolean() == false) { - java.exclude("com/tencent/mtt/supportui/**") - } - - jniLibs.srcDirs = ["${THIRD_PARTY_LIBRARY_PATH}/layout"] - } - } } dependencies { api fileTree(dir: 'libs', include: ['*.jar']) //noinspection GradleDependency implementation 'androidx.annotation:annotation:1.0.0' - //noinspection GradleDependency - implementation 'androidx.recyclerview:recyclerview:1.1.0' - if (!INCLUDE_SUPPORT_UI.toBoolean()) { - compileOnly('com.tencent.mtt:support-ui:99.2.3') - } + api 'androidx.recyclerview:recyclerview:1.1.0' + api 'androidx.viewpager:viewpager:1.0.0' testImplementation "junit:junit:4.13.2" testImplementation "org.mockito:mockito-core:2.8.9" @@ -207,6 +187,14 @@ project.tasks.whenTaskAdded { task -> if (task.name == 'mergeDebugNativeLibs') { task.finalizedBy dealAfterMergeDebugNativeLibs } + + if (task.name == 'stripReleaseDebugSymbols') { + task.finalizedBy dealAfterStripReleaseDebugSymbols + } + + if (task.name == 'stripDebugDebugSymbols') { + task.finalizedBy dealAfterStripDebugDebugSymbols + } } def dealAfterMergeNativeLibs(mergedNativeLibsPath) { @@ -224,16 +212,6 @@ def dealAfterMergeNativeLibs(mergedNativeLibsPath) { delete "$mergedNativeLibsPath/x86_64/$filename" } } - delete "$mergedNativeLibsPath/armeabi" - if (INCLUDE_ABI_ARMEABI.toBoolean()) { - copy { - from "$mergedNativeLibsPath/armeabi-v7a" - into "$mergedNativeLibsPath/armeabi" - } - if (!INCLUDE_ABI_ARMEABI_V7A.toBoolean()) { - delete "$mergedNativeLibsPath/armeabi-v7a" - } - } } def MERGED_NATIVE_LIBS_PATH = "$buildDir/intermediates/merged_native_libs" @@ -249,34 +227,63 @@ task dealAfterMergeDebugNativeLibs() { } } -def dealAfterAssemble(buildType) { - file("$OUTPUT_PATH/$buildType").deleteDir() +def dealAfterStripSymbols(strippedNativeLibsPath) { + delete "$strippedNativeLibsPath/armeabi" + if (INCLUDE_ABI_ARMEABI.toBoolean()) { + copy { + from "$strippedNativeLibsPath/armeabi-v7a" + into "$strippedNativeLibsPath/armeabi" + } + if (!INCLUDE_ABI_ARMEABI_V7A.toBoolean()) { + delete "$strippedNativeLibsPath/armeabi-v7a" + } + } +} + +def STRIPPED_NATIVE_LIBS_PATH = "$buildDir/intermediates/stripped_native_libs" +task dealAfterStripReleaseDebugSymbols() { + doLast { + dealAfterStripSymbols("$STRIPPED_NATIVE_LIBS_PATH/release/out/lib") + } +} + +task dealAfterStripDebugDebugSymbols() { + doLast { + dealAfterStripSymbols("$STRIPPED_NATIVE_LIBS_PATH/debug/out/lib") + } +} - file(OUTPUT_PATH).list().each{fileName -> +def dealAfterAssemble(outputPath, buildType) { + file("$outputPath/$buildType").deleteDir() + File target = file("$outputPath/$ARCHIVES_BASE_NAME" + ".aar") + delete target + file(outputPath).list().each{fileName -> if (fileName.contains(buildType + ".aar")) { copy { - from zipTree("$OUTPUT_PATH/$fileName") - into "$OUTPUT_PATH/$buildType" + from zipTree("$outputPath/$fileName") + into "$outputPath/$buildType" } copy { - from "$OUTPUT_PATH/$buildType" - into OUTPUT_PATH + from "$outputPath/$buildType" + into outputPath include 'classes.jar' rename 'classes.jar', 'hippy.jar' } + file("$outputPath/$fileName").renameTo(target) } } } +def OUTPUT_PATH = "$buildDir/outputs/aar" task dealAfterAssembleDebug() { doLast { - dealAfterAssemble("debug") + dealAfterAssemble(OUTPUT_PATH, "debug") } } task dealAfterAssembleRelease() { doLast { - dealAfterAssemble("release") + dealAfterAssemble(OUTPUT_PATH, "release") copy { from "$OUTPUT_PATH/$ARCHIVES_BASE_NAME-release.aar" into "$projectDir.absolutePath/../../examples/android-demo/example/libs" diff --git a/android/sdk/gradle.properties b/android/sdk/gradle.properties index aac8735ef62..c20448de192 100644 --- a/android/sdk/gradle.properties +++ b/android/sdk/gradle.properties @@ -16,10 +16,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -android.enableD8=true -android.enableR8=false android.enableJetifier=true android.useAndroidX=true +android.disableAutomaticComponentCreation=true +org.gradle.jvmargs = -Xms1024m -Xmx4096m +org.gradle.warning.mode=all # # Specifies Android archives base name @@ -30,28 +31,25 @@ android.useAndroidX=true ARCHIVES_BASE_NAME=android-sdk # -# Specifies Android SDK version +# Specifies CMake version # -COMPILE_SDK_VERSION=29 -MIN_SDK_VERSION=19 -TARGET_SDK_VERSION=29 +# Equals to setting android.externalNativeBuild.cmake.version property in the build.gradle file. +# Default is 3.22.1 +# +CMAKE_VERSION=3.22.1 # # Specifies Android NDK version # # Equals to setting android.ndkVersion property in the build.gradle file. -# Default is 21.4.7075529 +# Default is 25.0.8775105 # -NDK_VERSION=21.4.7075529 +NDK_VERSION=25.0.8775105 # -# Specifies Maven Central SDK version -# -# Equals to setting android.defaultConfig.versionCode and versionName property -# in the build.gradle file. +# Specifies SDK version # -VERSION_NAME=1.0.0 -VERSION_CODE=1 +VERSION_NAME=unspecified # # Whether to skip build C/C++ code @@ -76,21 +74,21 @@ INCLUDE_ABI_X86=false INCLUDE_ABI_X86_64=false # -# V8 Component Directory +# V8 Component # -# The following built-in V8 component directories are available: -# * third_party/v8/stable/x5-lite -# * third_party/v8/stable/official-release -# * third_party/v8/latest/official-release +# The following prebuilt V8 versions are available: +# * 10.6.194 (recommend) +# * 9.8.177.13 +# * 7.7.299.17 (minimum size without inspector features, by [TBS X5 Team](https://x5.tencent.com/)) +# * 7.7.299.15 (minimum version) # -# You can also specify the external V8 component directory to be used, -# by the way, if set to 'auto'(default), it will be automatic selection of -# v8 component directory based on the current build type. +# You can also specify the absolute path to the V8 component to use, +# e.g. /opt/v8-component # -V8_COMPONENT=auto +V8_COMPONENT=10.6.194 # -# Whether to hide Hippy library symbols +# Whether to hide library symbols # # * true(recommend): hide non-export symbols from library: # it can very substantially improve load times of libraries, @@ -121,10 +119,19 @@ EXCLUDE_LIBRARY_FILES= #features ENABLE_SO_DOWNLOAD=false -INCLUDE_SUPPORT_UI=true -#maven central publish params +ENABLE_BUGLY_REPORT=false + +# +# Specifies maven publish artifact ID +# +# The following names are available: +# * hippy-common: for release build +# * hippy-debug: for debug build +# PUBLISH_ARTIFACT_ID=hippy-common + +# +# Specifies maven publish group ID +# PUBLISH_GROUP_ID=com.tencent.hippy -PUBLISH_GIT_URL=https://github.com/Tencent/Hippy -PUBLISH_VERSION_DESC=Hippy library for Android diff --git a/android/sdk/gradle/wrapper/gradle-wrapper.properties b/android/sdk/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 2539a6e96aa..00000000000 --- a/android/sdk/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Thu Dec 19 10:43:01 CST 2019 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip diff --git a/android/sdk/mavencentral.gradle b/android/sdk/mavencentral.gradle deleted file mode 100644 index 99b8994c353..00000000000 --- a/android/sdk/mavencentral.gradle +++ /dev/null @@ -1,66 +0,0 @@ -apply plugin: 'maven-publish' -apply plugin: 'signing' - -ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID') -ext["signing.password"] = System.getenv('SIGNING_PASSWORD') -ext["signing.secretKeyRingFile"] = System.getenv('SIGNING_SECRET_KEY_RING_FILE') -ext["ossrhUsername"] = System.getenv('OSSRH_USERNAME') -ext["ossrhPassword"] = System.getenv('OSSRH_PASSWORD') - -println("signing.keyId=" + ext["signing.keyId"]) -println("signing.password=" + ext["signing.password"]) -println("signing.secretKeyRingFile=" + ext["signing.secretKeyRingFile"]) -println("ossrhUsername=" + ossrhUsername) -println("ossrhPassword=" + ossrhPassword) - -publishing { - publications { - mavenAar(MavenPublication) { - artifact("$buildDir/outputs/aar/android-sdk-release.aar") - groupId PUBLISH_GROUP_ID - artifactId PUBLISH_ARTIFACT_ID - version VERSION_NAME - pom { - name = PUBLISH_ARTIFACT_ID - description = PUBLISH_VERSION_DESC - url = PUBLISH_GIT_URL - licenses { - license { - name = 'The Apache Software License, Version 2.0' - url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' - } - } - developers { - developer { - id = PUBLISH_ARTIFACT_ID - name = 'Tencent Hippy' - email = 'maxli@tencent.com' - } - } - scm { - connection = "scm:git@github.com:Tencent/Hippy.git" - developerConnection = "scm:git@github.com:Tencent/Hippy.git" - url = PUBLISH_GIT_URL - } - } - } - } - repositories { - maven { - def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2" - def snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots" - url = VERSION_NAME.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl - - credentials { - username ossrhUsername - password ossrhPassword - } - } - } -} - -signing { - sign publishing.publications.mavenAar -} - - diff --git a/android/sdk/proguard-rules.pro b/android/sdk/proguard-rules.pro index 02c889acaef..23359de472a 100644 --- a/android/sdk/proguard-rules.pro +++ b/android/sdk/proguard-rules.pro @@ -169,6 +169,7 @@ public void removeActionInterceptor(com.tencent.mtt.hippy.dom.node.DomActionInte -keep class com.tencent.mtt.hippy.dom.node.TypeFaceUtil {*;} -keep class com.tencent.mtt.hippy.adapter.image.HippyImageLoader {*;} -keep class com.tencent.mtt.hippy.dom.node.DomActionInterceptor {*;} +-keep class com.tencent.mtt.hippy.dom.node.DomNodeRecord {*;} -keepclasseswithmembers class com.tencent.mtt.supportui.** { public ; @@ -217,3 +218,4 @@ protected ; public ; public ; } +-keep class com.tencent.mtt.hippy.dom.flex.FlexMeasureMode {*;} diff --git a/android/sdk/publish.gradle b/android/sdk/publish.gradle new file mode 100644 index 00000000000..7d1da826b81 --- /dev/null +++ b/android/sdk/publish.gradle @@ -0,0 +1,66 @@ +apply plugin: 'maven-publish' +apply plugin: 'signing' + +ext["signing.keyId"] = project.findProperty("signing.keyId") ?: System.getenv("SIGNING_KEY_ID") +ext["signing.password"] = project.findProperty("signing.password") ?: System.getenv("SIGNING_PASSWORD") +ext["signing.secretKeyRingFile"] = project.findProperty("signing.secretKeyRingFile") ?: System.getenv("SIGNING_SECRET_KEY_RING_FILE") + +task androidSourcesJar(type: Jar) { + from android.sourceSets.main.java.srcDirs + classifier = 'sources' +} + +publishing { + publications { + mavenAar(MavenPublication) { + artifact(androidSourcesJar) + artifact("$buildDir/outputs/aar/android-sdk.aar") + groupId PUBLISH_GROUP_ID + artifactId PUBLISH_ARTIFACT_ID + version VERSION_NAME + pom { + name = PUBLISH_ARTIFACT_ID + description = "Hippy Cross Platform Framework" + url = "https://hippyjs.org" + licenses { + license { + name = "The Apache Software License, Version 2.0" + url = "http://www.apache.org/licenses/LICENSE-2.0.txt" + } + } + developers { + developer { + id = PUBLISH_ARTIFACT_ID + name = "OpenHippy Team" + } + } + scm { + connection = "scm:git@github.com:Tencent/Hippy.git" + developerConnection = "scm:git@github.com:Tencent/Hippy.git" + url = "https://github.com/Tencent/Hippy.git" + } + } + } + } + repositories { + maven { + url project.findProperty("maven.url") + ?: System.getenv("MAVEN_URL") + ?: (VERSION_NAME.endsWith('SNAPSHOT') + ? "https://oss.sonatype.org/content/repositories/snapshots" + : "https://oss.sonatype.org/service/local/staging/deploy/maven2") + credentials { + username project.findProperty("maven.username") ?: System.getenv("MAVEN_USERNAME") + password project.findProperty("maven.password") ?: System.getenv("MAVEN_PASSWORD") + } + } + } +} + +signing { + def secretKey = project.findProperty("secretKey") ?: System.getenv("SIGNING_SECRET_KEY") + if (secretKey) { + useInMemoryPgpKeys(project.findProperty("signing.keyId"), secretKey, project.findProperty("signing.password")) + } + sign publishing.publications.mavenAar +} diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/EasyLinearLayoutManager.java b/android/sdk/src/main/java/androidx/recyclerview/widget/EasyLinearLayoutManager.java deleted file mode 100644 index fdfdc00ba1d..00000000000 --- a/android/sdk/src/main/java/androidx/recyclerview/widget/EasyLinearLayoutManager.java +++ /dev/null @@ -1,161 +0,0 @@ -/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. - * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView.Adapter; -import androidx.recyclerview.widget.RecyclerView.LayoutParams; -import androidx.recyclerview.widget.RecyclerView.State; -import android.util.AttributeSet; -import android.view.View; -import java.util.HashMap; - -public class EasyLinearLayoutManager extends LinearLayoutManager { - - /** - * 无效的高度 - */ - public static final int INVALID_HEIGHT = -1; - private static final LayoutParams ITEM_LAYOUT_PARAMS = new LayoutParams(0, 0); - /** - * 由于排版后才知道对应item的高度,topMargin和bottomMargin,所以这里缓存一下这些值 - */ - protected HashMap itemHeightMaps = new HashMap<>(); - protected HashMap itemTopMarginMaps = new HashMap<>(); - protected HashMap itemBottomMarginMaps = new HashMap<>(); - - public EasyLinearLayoutManager(Context context) { - super(context); - } - - public EasyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) { - super(context, orientation, reverseLayout); - } - - public EasyLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - /** - * 排版完成后,缓存view的排版信息 - */ - @Override - public void layoutDecoratedWithMargins(@NonNull View child, int left, int top, int right, - int bottom) { - super.layoutDecoratedWithMargins(child, left, top, right, bottom); - cacheItemLayoutParams(bottom - top, (LayoutParams) child.getLayoutParams(), getPosition(child)); - } - - /** - * 缓存Item排版信息,缓存信息有两种来源 一种是排版过程中的得到实际上屏的排版信息 {@link #layoutDecoratedWithMargins(View, int, int, - * int, int)} 一种是从adapter获取的item的预排版信息 {@link #getItemHeightFromAdapter(int)} - * - * @param height item的高度 - * @param layoutParams 排版参数 - * @param position 位置 - */ - private void cacheItemLayoutParams(int height, LayoutParams layoutParams, int position) { - itemBottomMarginMaps.put(position, layoutParams.bottomMargin); - itemTopMarginMaps.put(position, layoutParams.topMargin); - itemHeightMaps.put(position, height); - } - - /** - * 从adapter 获取position对应的排版信息,这里不需要排版,是item预先指定的高度 - */ - int getItemHeightFromAdapter(int position) { - Adapter adapter = mRecyclerView.getAdapter(); - if (adapter instanceof IItemLayoutParams) { - IItemLayoutParams layoutInfo = (IItemLayoutParams) adapter; - resetLayoutParams(); - layoutInfo.getItemLayoutParams(position, ITEM_LAYOUT_PARAMS); - if (ITEM_LAYOUT_PARAMS.height >= 0) { - cacheItemLayoutParams(ITEM_LAYOUT_PARAMS.height, ITEM_LAYOUT_PARAMS, position); - return ITEM_LAYOUT_PARAMS.height + ITEM_LAYOUT_PARAMS.bottomMargin - + ITEM_LAYOUT_PARAMS.topMargin; - } - } - return INVALID_HEIGHT; - } - - /** - * 清除之前的缓存数据,ITEM_LAYOUT_PARAMS 不是最终用来排版的,只是一个参数的载体 - */ - private static void resetLayoutParams() { - ITEM_LAYOUT_PARAMS.height = 0; - ITEM_LAYOUT_PARAMS.topMargin = 0; - ITEM_LAYOUT_PARAMS.rightMargin = 0; - ITEM_LAYOUT_PARAMS.leftMargin = 0; - ITEM_LAYOUT_PARAMS.bottomMargin = 0; - } - - /** - * 计算position以前(包含position)的高度,如果发现其中有一个没有缓存,那么得出来的值是无效的 - */ - int getHeightUntilPosition(int position) { - int totalHeight = 0; - for (int i = 0; i <= position; i++) { - Integer height = itemHeightMaps.get(i); - if (height == null) { - height = getItemHeightFromAdapter(i); - } - if (height != null && height != INVALID_HEIGHT) { - totalHeight += height; - } else { - return INVALID_HEIGHT; - } - } - return totalHeight; - } - - /** - * 由于父类的computeVerticalScrollOffset是基于平均值计算高度。对于Item类型和高度不一样的情况,计算是有误差的。 - * 用第一个可见view的前面总的内容高度减去第一个可见view的底部位置 - * - * @return 当前的内容偏移,也就是被推出顶部以外的内容高度。 - */ - @Override - public int computeVerticalScrollOffset(State state) { - if (getChildCount() <= 0 || getItemCount() <= 0) { - return 0; - } - int firstVisiblePosition = findFirstVisibleItemPosition(); - View firstVisibleView = findViewByPosition(firstVisiblePosition); - int heightUntilPosition = getHeightUntilPosition(firstVisiblePosition); - if (firstVisibleView != null && heightUntilPosition != INVALID_HEIGHT) { - return heightUntilPosition - mOrientationHelper.getDecoratedEnd(firstVisibleView); - } - return super.computeVerticalScrollOffset(state); - } - - - /** - * 由于父类的computeVerticalScrollRange是基于平均值计算高度。对于Item类型和高度不一样的情况,计算是有误差的。 - * - * @return 内容的总高度 - **/ - @Override - public int computeVerticalScrollRange(State state) { - int heightUntilPosition = getHeightUntilPosition(getItemCount() - 1); - if (heightUntilPosition != INVALID_HEIGHT) { - return heightUntilPosition; - } - return super.computeVerticalScrollRange(state); - } -} diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/EasyRecyclerView.java b/android/sdk/src/main/java/androidx/recyclerview/widget/EasyRecyclerView.java deleted file mode 100644 index 776c2a11444..00000000000 --- a/android/sdk/src/main/java/androidx/recyclerview/widget/EasyRecyclerView.java +++ /dev/null @@ -1,233 +0,0 @@ -/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. - * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.RecycledViewPool.ScrapData; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import java.lang.reflect.Field; -import java.util.ArrayList; - -public class EasyRecyclerView extends RecyclerView { - - protected OverPullHelper overPullHelper; - protected OverPullListener overPullListener; - protected VelocityTracker velocityTracker; - - public EasyRecyclerView(@NonNull Context context) { - super(context); - init(); - } - - public EasyRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - init(); - } - - public EasyRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(); - } - - protected void init() { - } - - public int getOverPullState() { - if (overPullHelper != null) { - return overPullHelper.getOverPullState(); - } - return OverPullHelper.OVER_PULL_NONE; - } - - public boolean isOverPulling() { - int pullState = getOverPullState(); - return pullState == OverPullHelper.OVER_PULL_DOWN_ING - || pullState == OverPullHelper.OVER_PULL_UP_ING - || pullState == OverPullHelper.OVER_PULL_SETTLING; - } - - /** - * 下拉的时候,返回值<0,表示顶部被下拉了一部分距离,顶部有空白 - */ - public int getOverPullUpOffset() { - if (overPullHelper != null) { - return overPullHelper.getOverPullUpOffset(); - } - return 0; - } - - /** - * 上拉的时候,返回值>0,表示底部被上拉了一部分距离,底部有空白 - */ - public int getOverPullDownOffset() { - if (overPullHelper != null) { - return overPullHelper.getOverPullDownOffset(); - } - return 0; - } - - public void setOverPullListener(OverPullListener listener) { - overPullListener = listener; - if (overPullHelper != null) { - overPullHelper.setOverPullListener(listener); - } - } - - public void setEnableOverPull(boolean enableOverDrag) { - if (enableOverDrag) { - if (overPullHelper == null) { - overPullHelper = new OverPullHelper(this); - } - overPullHelper.setOverPullListener(overPullListener); - } else { - overPullHelper = null; - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (overPullHelper != null && overPullHelper.onTouchEvent(event)) { - return true; - } - boolean handled = super.onTouchEvent(event); - if (overPullHelper != null) { - overPullHelper.handleEventUp(event); - } - return handled; - } - - @Override - public void requestLayout() { - super.requestLayout(); - } - - public void recycleAndClearCachedViews() { - mRecycler.recycleAndClearCachedViews(); - } - - public int getChildCountWithCaches() { - return getCachedViewHolderCount() + getChildCount(); - } - - public View getChildAtWithCaches(int index) { - ArrayList viewHolders = getCachedViewHolders(); - if (index < viewHolders.size()) { - return viewHolders.get(index).itemView; - } else { - return getChildAt(index - viewHolders.size()); - } - } - - private int getCachedViewHolderCount() { - int count = mRecycler.mAttachedScrap.size() + mRecycler.mCachedViews.size(); - for (int i = 0; i < mRecycler.getRecycledViewPool().mScrap.size(); i++) { - ScrapData scrapData = mRecycler.getRecycledViewPool().mScrap.valueAt(i); - count += scrapData.mScrapHeap.size(); - } - return count; - } - - public ArrayList getCachedViewHolders() { - ArrayList listViewHolder = new ArrayList<>(); - listViewHolder.addAll(mRecycler.mAttachedScrap); - listViewHolder.addAll(mRecycler.mCachedViews); - for (int i = 0; i < mRecycler.getRecycledViewPool().mScrap.size(); i++) { - ScrapData scrapData = mRecycler.getRecycledViewPool().mScrap.valueAt(i); - listViewHolder.addAll(scrapData.mScrapHeap); - } - return listViewHolder; - } - - public boolean didStructureChange() { - return mState.didStructureChange(); - } - - - public int getFirstChildPosition() { - return getChildLayoutPosition(getChildCount() > 0 ? getChildAt(0) : null); - } - - public int getLashChildPosition() { - return getChildLayoutPosition(getChildCount() > 0 ? getChildAt(getChildCount() - 1) : null); - } - - /** - * 通过位置获取一个ViewHolder,目前暂时提供给header使用 - */ - public ViewHolder getViewHolderForPosition(int position) { - View view = mRecycler.getViewForPosition(position); - if (view.getLayoutParams() instanceof LayoutParams) { - return ((LayoutParams) view.getLayoutParams()).mViewHolder; - } - return null; - } - - public ViewHolder getFistChildViewHolder() { - View view = getChildAt(0); - if (view != null && view.getLayoutParams() instanceof LayoutParams) { - return ((LayoutParams) view.getLayoutParams()).mViewHolder; - } - return null; - } - - /** - * 改成public接口,主要用于hippy业务的特殊需求 - */ - @Override - public void dispatchLayout() { - super.dispatchLayout(); - } - - @Override - public void invalidateGlows() { - super.invalidateGlows(); - } - - /** - * 反射获取滚动的VelocityTracker - */ - public VelocityTracker getVelocityTracker() { - if (velocityTracker == null) { - try { - Field velocityTrackerField = RecyclerView.class.getDeclaredField("mVelocityTracker"); - velocityTrackerField.setAccessible(true); - velocityTracker = (VelocityTracker) velocityTrackerField.get(this); - } catch (NoSuchFieldException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - } - return velocityTracker; - } - - /** - * recyclerView的adapter状态已经变化,但是没有进行notify,导致state和adapter 的itemCount对不齐,比如hippy场景,直接把recyclerView的renderNode删除了,adapter的itemCount直接变为0, - * 由于没有notifyDatSetChange,state的itemCount不为0,这样就会出现validateViewHolderForOffsetPosition报 - * IndexOutOfBoundsException - */ - public boolean isDataChangedWithoutNotify() { - return getAdapter().getItemCount() != mState.getItemCount(); - } -} diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/HippyItemTypeHelper.java b/android/sdk/src/main/java/androidx/recyclerview/widget/HippyItemTypeHelper.java index be032212389..ae0fa98b93b 100644 --- a/android/sdk/src/main/java/androidx/recyclerview/widget/HippyItemTypeHelper.java +++ b/android/sdk/src/main/java/androidx/recyclerview/widget/HippyItemTypeHelper.java @@ -16,6 +16,7 @@ package androidx.recyclerview.widget; +import androidx.recyclerview.widget.RecyclerView.RecycledViewPool; import androidx.recyclerview.widget.RecyclerView.RecycledViewPool.ScrapData; import androidx.recyclerview.widget.RecyclerView.Recycler; import androidx.recyclerview.widget.RecyclerView.ViewHolder; @@ -25,95 +26,100 @@ import com.tencent.mtt.hippy.views.hippylist.HippyRecyclerViewHolder; import java.util.ArrayList; +/** + * Created on 2021/1/4. + * Description + * Hippy 前端如果发生Item类型的变化,终端的RecyclerView需要将所有的ViewHolder进行同步修改 + */ public class HippyItemTypeHelper { - HippyRecyclerViewBase recyclerView; - private Recycler recycler; - - public HippyItemTypeHelper(HippyRecyclerViewBase recyclerView) { - this.recyclerView = recyclerView; - this.recycler = recyclerView.mRecycler; - } + HippyRecyclerViewBase recyclerView; + private Recycler recycler; - /** - * 更新3层缓存的ViewHolder - * - * @param oldType 老的type - * @param newType 新的type - * @param listItemRenderNode 前端变化type的RenderNode - */ - public void updateItemType(int oldType, int newType, ListItemRenderNode listItemRenderNode) { - int count = recyclerView.getChildCount(); - for (int i = 0; i < count; i++) { - final ViewHolder holder = recyclerView - .getChildViewHolder(recyclerView.getChildAt(i)); - if (changeTypeIfNeed(oldType, newType, listItemRenderNode, holder)) { - return; - } + public HippyItemTypeHelper(HippyRecyclerViewBase recyclerView) { + this.recyclerView = recyclerView; + this.recycler = recyclerView.mRecycler; } - if (updateItemType(oldType, newType, listItemRenderNode, recycler.mAttachedScrap)) { - return; - } - - if (updateItemType(oldType, newType, listItemRenderNode, recyclerView.mRecycler.mCachedViews)) { - return; - } + /** + * 更新3层缓存的ViewHolder + * + * @param oldType 老的type + * @param newType 新的type + * @param listItemRenderNode 前端变化type的RenderNode + */ + public void updateItemType(int oldType, int newType, ListItemRenderNode listItemRenderNode) { + int count = recyclerView.getChildCount(); + for (int i = 0; i < count; i++) { + final RecyclerView.ViewHolder holder = recyclerView + .getChildViewHolder(recyclerView.getChildAt(i)); + if (changeTypeIfNeed(oldType, newType, listItemRenderNode, holder)) { + return; + } + } - updateTypeForRecyclerPool(oldType, newType, listItemRenderNode); - } + if (updateItemType(oldType, newType, listItemRenderNode, recycler.mAttachedScrap)) { + return; + } - private void updateTypeForRecyclerPool(int oldType, int newType, ListItemRenderNode renderNode) { - if (recycler.getRecycledViewPool() != null) { - SparseArray scrap = recycler.getRecycledViewPool().mScrap; - ScrapData scrapData = scrap.get(oldType); - if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) { - for (ViewHolder holder : scrapData.mScrapHeap) { - if (changeTypeIfNeed(oldType, newType, renderNode, holder)) { - scrapData.mScrapHeap.remove(holder); - addNewType(newType, holder); + if (updateItemType(oldType, newType, listItemRenderNode, recyclerView.mRecycler.mCachedViews)) { return; - } } - } - } - } - /** - * 重新将viewHolder加入缓存池 - */ - private void addNewType(int newType, ViewHolder holder) { - holder.mItemViewType = newType; - SparseArray scrap = recycler.getRecycledViewPool().mScrap; - ScrapData newScrapData = scrap.get(newType); - if (newScrapData == null) { - newScrapData = new ScrapData(); - scrap.append(newType, newScrapData); + updateTypeForRecyclerPool(oldType, newType, listItemRenderNode); } - newScrapData.mScrapHeap.add(holder); - } - private boolean updateItemType(int oldType, int newType, ListItemRenderNode listItemRenderNode, - ArrayList viewHolders) { - final int cacheSize = viewHolders.size(); - for (int i = 0; i < cacheSize; i++) { - final ViewHolder holder = viewHolders.get(i); - if (changeTypeIfNeed(oldType, newType, listItemRenderNode, holder)) { - return true; - } + private void updateTypeForRecyclerPool(int oldType, int newType, ListItemRenderNode renderNode) { + if (recycler.getRecycledViewPool() != null) { + SparseArray scrap = recycler.getRecycledViewPool().mScrap; + RecycledViewPool.ScrapData scrapData = scrap.get(oldType); + if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) { + for (RecyclerView.ViewHolder holder : scrapData.mScrapHeap) { + if (changeTypeIfNeed(oldType, newType, renderNode, holder)) { + scrapData.mScrapHeap.remove(holder); + addNewType(newType, holder); + return; + } + } + } + } } - return false; - } - private boolean changeTypeIfNeed(int oldType, int newType, ListItemRenderNode listItemRenderNode, - ViewHolder holder) { - if (holder.getItemViewType() == oldType && holder instanceof HippyRecyclerViewHolder) { - RenderNode holderNode = ((HippyRecyclerViewHolder) holder).bindNode; - if (holderNode == listItemRenderNode) { + /** + * 重新将viewHolder加入缓存池 + */ + private void addNewType(int newType, ViewHolder holder) { holder.mItemViewType = newType; - return true; - } + SparseArray scrap = recycler.getRecycledViewPool().mScrap; + ScrapData newScrapData = scrap.get(newType); + if (newScrapData == null) { + newScrapData = new ScrapData(); + scrap.append(newType, newScrapData); + } + newScrapData.mScrapHeap.add(holder); + } + + private boolean updateItemType(int oldType, int newType, ListItemRenderNode listItemRenderNode, + ArrayList viewHolders) { + final int cacheSize = viewHolders.size(); + for (int i = 0; i < cacheSize; i++) { + final RecyclerView.ViewHolder holder = viewHolders.get(i); + if (changeTypeIfNeed(oldType, newType, listItemRenderNode, holder)) { + return true; + } + } + return false; + } + + private boolean changeTypeIfNeed(int oldType, int newType, ListItemRenderNode listItemRenderNode, + RecyclerView.ViewHolder holder) { + if (holder.getItemViewType() == oldType && holder instanceof HippyRecyclerViewHolder) { + RenderNode holderNode = ((HippyRecyclerViewHolder) holder).bindNode; + if (holderNode == listItemRenderNode) { + holder.mItemViewType = newType; + return true; + } + } + return false; } - return false; - } } diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/HippyLinearLayoutManager.java b/android/sdk/src/main/java/androidx/recyclerview/widget/HippyLinearLayoutManager.java new file mode 100644 index 00000000000..085bf5dd61a --- /dev/null +++ b/android/sdk/src/main/java/androidx/recyclerview/widget/HippyLinearLayoutManager.java @@ -0,0 +1,200 @@ +/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. + * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.recyclerview.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView.Adapter; +import androidx.recyclerview.widget.RecyclerView.LayoutParams; +import androidx.recyclerview.widget.RecyclerView.State; +import java.util.HashMap; + +/** + * Created on 2021/3/16. + * Description + * 精确计算内容的offset + */ +public class HippyLinearLayoutManager extends LinearLayoutManager { + + /** + * 无效的高度 + */ + public static final int INVALID_SIZE = -1; + private static final LayoutParams ITEM_LAYOUT_PARAMS = new LayoutParams(0, 0); + /** + * 由于排版后才知道对应item的宽高,所以这里缓存一下这些值 + */ + protected HashMap itemSizeMaps = new HashMap<>(); + + public HippyLinearLayoutManager(Context context) { + super(context); + } + + public HippyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) { + super(context, orientation, reverseLayout); + } + + public HippyLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + /** + * 排版完成后,缓存view的排版信息 + */ + @Override + public void layoutDecoratedWithMargins(@NonNull View child, int left, int top, int right, int bottom) { + super.layoutDecoratedWithMargins(child, left, top, right, bottom); + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + int size = mOrientation == RecyclerView.VERTICAL + ? bottom - top + lp.topMargin + lp.bottomMargin + : right - left + lp.leftMargin + lp.rightMargin; + cacheItemLayoutParams(size, getPosition(child)); + } + + /** + * 缓存Item排版信息,缓存信息有两种来源 + * 一种是排版过程中的得到实际上屏的排版信息 {@link #layoutDecoratedWithMargins(View, int, int, int, int)} + * 一种是从adapter获取的item的预排版信息 {@link #getItemSizeFromAdapter(int)} + * + * @param size item height (for vertical list) or width (for horizontal list) + * @param position 位置 + */ + private void cacheItemLayoutParams(int size, int position) { + itemSizeMaps.put(position, size); + } + + /** + * 从adapter 获取position对应的排版信息,这里不需要排版,是item预先指定的size + */ + int getItemSizeFromAdapter(int position) { + Adapter adapter = mRecyclerView.getAdapter(); + if (adapter instanceof ItemLayoutParams) { + ItemLayoutParams layoutInfo = (ItemLayoutParams) adapter; + resetLayoutParams(); + layoutInfo.getItemLayoutParams(position, ITEM_LAYOUT_PARAMS); + if (mOrientation == RecyclerView.VERTICAL) { + if (ITEM_LAYOUT_PARAMS.height >= 0) { + int size = ITEM_LAYOUT_PARAMS.height + ITEM_LAYOUT_PARAMS.bottomMargin + ITEM_LAYOUT_PARAMS.topMargin; + cacheItemLayoutParams(size, position); + return size; + } + } else { + if (ITEM_LAYOUT_PARAMS.width >= 0) { + int size = ITEM_LAYOUT_PARAMS.width + ITEM_LAYOUT_PARAMS.leftMargin + ITEM_LAYOUT_PARAMS.rightMargin; + cacheItemLayoutParams(size, position); + return size; + } + } + } + return INVALID_SIZE; + } + + /** + * 清除之前的缓存数据,ITEM_LAYOUT_PARAMS 不是最终用来排版的,只是一个参数的载体 + */ + private static void resetLayoutParams() { + ITEM_LAYOUT_PARAMS.width = 0; + ITEM_LAYOUT_PARAMS.height = 0; + ITEM_LAYOUT_PARAMS.topMargin = 0; + ITEM_LAYOUT_PARAMS.rightMargin = 0; + ITEM_LAYOUT_PARAMS.leftMargin = 0; + ITEM_LAYOUT_PARAMS.bottomMargin = 0; + } + + /** + * 计算position以前(包含position)的size,如果发现其中有一个没有缓存,那么得出来的值是无效的 + */ + int getSizeUntilPosition(int position) { + int totalSize = 0; + for (int i = 0; i <= position; i++) { + Integer size = itemSizeMaps.get(i); + if (size == null) { + size = getItemSizeFromAdapter(i); + } + if (size != null && size != INVALID_SIZE) { + totalSize += size; + } else { + return INVALID_SIZE; + } + } + return totalSize; + } + + /** + * 由于父类的computeVerticalScrollOffset是基于平均值计算高度。对于Item类型和高度不一样的情况,计算是有误差的。 + * 用第一个可见view的前面总的内容高度减去第一个可见view的底部位置 + * + * @return 当前的内容偏移,也就是被推出顶部以外的内容高度。 + */ + @Override + public int computeVerticalScrollOffset(State state) { + if (getChildCount() <= 0 || getItemCount() <= 0) { + return 0; + } + int firstVisiblePosition = findFirstVisibleItemPosition(); + View firstVisibleView = findViewByPosition(firstVisiblePosition); + int heightUntilPosition = getSizeUntilPosition(firstVisiblePosition); + if (firstVisibleView != null && heightUntilPosition != INVALID_SIZE) { + return heightUntilPosition - mOrientationHelper.getDecoratedEnd(firstVisibleView); + } + return super.computeVerticalScrollOffset(state); + } + + + /** + * 由于父类的computeVerticalScrollRange是基于平均值计算高度。对于Item类型和高度不一样的情况,计算是有误差的。 + * + * @return 内容的总高度 + **/ + @Override + public int computeVerticalScrollRange(State state) { + int heightUntilPosition = getSizeUntilPosition(getItemCount() - 1); + if (heightUntilPosition != INVALID_SIZE) { + return heightUntilPosition; + } + return super.computeVerticalScrollRange(state); + } + + @Override + public int computeHorizontalScrollOffset(State state) { + if (getChildCount() <= 0 || getItemCount() <= 0) { + return 0; + } + int firstVisiblePosition = findFirstVisibleItemPosition(); + View firstVisibleView = findViewByPosition(firstVisiblePosition); + int widthUntilPosition = getSizeUntilPosition(firstVisiblePosition); + if (firstVisibleView != null && widthUntilPosition != INVALID_SIZE) { + return widthUntilPosition - mOrientationHelper.getDecoratedEnd(firstVisibleView); + } + return super.computeHorizontalScrollOffset(state); + } + + @Override + public int computeHorizontalScrollRange(State state) { + int widthUntilPosition = getSizeUntilPosition(getItemCount() - 1); + if (widthUntilPosition != INVALID_SIZE) { + return widthUntilPosition; + } + return super.computeHorizontalScrollRange(state); + } + + public void resetCache() { + itemSizeMaps.clear(); + } +} diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/HippyOverPullHelper.java b/android/sdk/src/main/java/androidx/recyclerview/widget/HippyOverPullHelper.java new file mode 100644 index 00000000000..55f1c06bc10 --- /dev/null +++ b/android/sdk/src/main/java/androidx/recyclerview/widget/HippyOverPullHelper.java @@ -0,0 +1,356 @@ +/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. + * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.recyclerview.widget; + +import static android.view.View.OVER_SCROLL_NEVER; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.view.MotionEvent; +import android.view.ViewConfiguration; +import android.view.animation.DecelerateInterpolator; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView.OnScrollListener; +import com.tencent.mtt.hippy.views.hippylist.recyclerview.helper.AnimatorListenerBase; + +/** + * Created on 2021/3/15. + * Description + * 原生recyclerView是不支持拉到最顶部,还可以继续拉动,要实现继续拉动,并且松手回弹的效果 + * recyclerView上拉回弹和下拉回弹的效果实现 + */ +public class HippyOverPullHelper { + + private static final int DURATION = 150; + private final OnScrollListener listener; + protected float lastRawY = -1; + protected float downRawY = -1; + + private int overPullState = OVER_PULL_NONE; + public static final int OVER_PULL_NONE = 0; + public static final int OVER_PULL_DOWN_ING = 1; + public static final int OVER_PULL_UP_ING = 2; + public static final int OVER_PULL_NORMAL = 3; + public static final int OVER_PULL_SETTLING = 4; + + private ValueAnimator animator; + private boolean enableOverDrag = true; + private int lastOverScrollMode = -1; + private boolean isRollBacking = false; + private HippyOverPullListener overPullListener = null; + private RecyclerViewBase recyclerView; + private int scrollState; + + public HippyOverPullHelper(RecyclerViewBase recyclerView) { + this.recyclerView = recyclerView; + lastOverScrollMode = recyclerView.getOverScrollMode(); + listener = new OnScrollListener() { + @Override + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { + if (scrollState != newState && newState == RecyclerView.SCROLL_STATE_IDLE) { + rollbackToBottomOrTop(); + } + scrollState = newState; + } + }; + recyclerView.addOnScrollListener(listener); + } + + public void destroy() { + recyclerView.removeOnScrollListener(listener); + } + + private int getTouchSlop() { + final ViewConfiguration vc = ViewConfiguration.get(recyclerView.getContext()); + return vc.getScaledTouchSlop(); + } + + public void setOverPullListener(HippyOverPullListener overPullListener) { + this.overPullListener = overPullListener; + } + + private boolean isMoving(MotionEvent event) { + return lastRawY > 0 && Math.abs(event.getRawY() - downRawY) > getTouchSlop(); + } + + public boolean onTouchEvent(MotionEvent event) { + if (isRollBacking) { + return true; + } + if (checkOverDrag(event)) { + return true; + } + return false; + } + + /** + * 检测是否处于顶部过界拉取,或者顶部过界拉取 + */ + private boolean checkOverDrag(MotionEvent event) { + if (!enableOverDrag) { + return false; + } + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + lastRawY = event.getRawY(); + downRawY = event.getRawY(); + break; + case MotionEvent.ACTION_MOVE: + boolean overPullDown = isOverPullDown(event); + boolean overScrollUp = isOverPullUp(event); + if ((overPullDown || overScrollUp)) { + recyclerView.setOverScrollMode(OVER_SCROLL_NEVER); + recyclerView.invalidateGlows(); + if (overPullDown) { + setOverPullState(OVER_PULL_DOWN_ING); + } else { + setOverPullState(OVER_PULL_UP_ING); + } + int deltaY = (int) (event.getRawY() - lastRawY) / 2; +// if (deltaY > 0) { +// //下拉的时候除以2,放慢拉动的速度,调节拉动的手感 +// deltaY = deltaY / 2; +// } + recyclerView.offsetChildrenVertical(deltaY); + if (overPullListener != null) { + overPullListener.onOverPullStateChanged(overPullState, overPullState, getOverPullOffset()); + } + } else { + setOverPullState(OVER_PULL_NORMAL); + } + lastRawY = event.getRawY(); + break; + default: + reset(); + } + if (overPullState == OVER_PULL_DOWN_ING || overPullState == OVER_PULL_UP_ING) { + return true; + } + return false; + } + + /** + * 在松开手后, + * 1、如果当前处于fling状态,scrollState的值是SCROLL_STATE_SETTLING,先不做rollbackToBottomOrTop + * 等到onScrollStateChanged 变成 IDLE的时候,再做rollbackToBottomOrTop + * 2、如果当前处于非fling状态,scrollState的值不是SCROLL_STATE_SETTLING,就立即做rollbackToBottomOrTop + */ + public void handleEventUp(MotionEvent event) { + if (isActionUpOrCancel(event)) { + revertOverScrollMode(); + if (recyclerView.getScrollState() != RecyclerView.SCROLL_STATE_SETTLING) { + rollbackToBottomOrTop(); + } + } + } + + private void revertOverScrollMode() { + if (lastOverScrollMode != -1) { + recyclerView.setOverScrollMode(lastOverScrollMode); + } + } + + private int getOverPullOffset() { + if (overPullState == OVER_PULL_DOWN_ING) { + return getOverPullDownOffset(); + } else if (overPullState == OVER_PULL_UP_ING) { + return getOverPullUpOffset(); + } + return 0; + } + + void setOverPullState(int newOverPullState) { + if (overPullListener != null) { + overPullListener.onOverPullStateChanged(overPullState, newOverPullState, getOverPullOffset()); + } + overPullState = newOverPullState; + } + + /** + * 因为可能出现越界拉取,松手后需要回退到原来的位置,要么回到顶部,要么回到底部 + */ + void rollbackToBottomOrTop() { + int distanceToTop = recyclerView.computeVerticalScrollOffset(); + if (distanceToTop < 0) { + //顶部空出了一部分,需要回滚上去 + rollbackTo(distanceToTop, 0); + } else { + //底部空出一部分,需要混滚下去 + int overPullUpOffset = getOverPullUpOffset(); + if (overPullUpOffset != 0) { + rollbackTo(overPullUpOffset, 0); + } + } + } + + /** + * 计算底部被overPull的偏移,需要向下回滚的距离 + * 要么出现底部内容顶满distanceToBottom,要么出现顶部内容顶满distanceToTop,取最小的那一个 + * + * @return + */ + public int getOverPullUpOffset() { + int contentOffset = recyclerView.computeVerticalScrollOffset(); + int verticalScrollRange = recyclerView.computeVerticalScrollRange(); + int blankHeightToBottom = contentOffset + recyclerView.getHeight() - verticalScrollRange; + if (blankHeightToBottom > 0 && contentOffset > 0) { + return Math.min(blankHeightToBottom, contentOffset); + } + return 0; + } + + private boolean isActionUpOrCancel(MotionEvent event) { + return event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL; + } + + private void endAnimation() { + if (animator != null) { + animator.removeAllListeners(); + animator.removeAllUpdateListeners(); + animator.end(); + animator = null; + } + isRollBacking = false; + } + + /** + * 回弹动画的接口 + */ + private void rollbackTo(int from, int to) { + endAnimation(); + animator = ValueAnimator.ofInt(from, to); + animator.setInterpolator(new DecelerateInterpolator()); + animator.addUpdateListener(new RollbackUpdateListener(from)); + animator.addListener(new AnimatorListenerBase() { + @Override + public void onAnimationEnd(Animator animation) { + setOverPullState(OVER_PULL_NONE); + isRollBacking = false; + } + }); + isRollBacking = true; + animator.setDuration(DURATION).start(); + } + + private void reset() { + revertOverScrollMode(); + lastRawY = -1; + downRawY = -1; + setOverPullState(OVER_PULL_NONE); + } + + /** + * 顶部是否可以越界下拉,拉出一段空白区域,越界的部分最多不能超过RecyclerView高度+1 + */ + private boolean isOverPullDown(MotionEvent event) { + //常规情况,内容在顶部offset为0,异常情况,内容被完全拉到最底部,看不见内容的时候,offset也为0 + int offset = recyclerView.computeVerticalScrollOffset(); + int dy = Math.abs((int) (event.getRawY() - lastRawY)) + 1; + //不能把内容完全拉得看不见 + if (Math.abs(offset) + dy < recyclerView.getHeight()) { + return isMoving(event) && isPullDownAction(event) && !canOverPullDown(); + } + return false; + } + + /** + * 底部是否可以越界上拉,拉出一段空白区域,越界的部分最多不能超过RecyclerView高度的一般 + */ + private boolean isOverPullUp(MotionEvent event) { + int dy = Math.abs((int) (event.getRawY() - lastRawY)) + 1; + //不能让内容完全被滚出屏幕,否则computeVerticalScrollOffset为0是一个无效的值 + int distanceToBottom = recyclerView.computeVerticalScrollOffset() + recyclerView.getHeight() - recyclerView + .computeVerticalScrollRange(); + if (distanceToBottom + dy < recyclerView.getHeight()) { + return isMoving(event) && isPullUpAction(event) && !canOverPullUp(); + } + return false; + } + + boolean isPullDownAction(MotionEvent event) { + return event.getRawY() - lastRawY > 0; + } + + boolean isPullUpAction(MotionEvent event) { + return event.getRawY() - lastRawY <= 0; + } + + /** + * 顶部还有内容,还可以向下拉到 + */ + boolean canOverPullDown() { + return recyclerView.canScrollVertically(-1); + } + + /** + * 底部还有内容,还可以向上拉动 + */ + boolean canOverPullUp() { + return recyclerView.canScrollVertically(1); + } + + public int getOverPullState() { + return overPullState; + } + + /** + * 下拉的时候,返回值<0,表示顶部被下拉了一部分距离 + */ + public int getOverPullDownOffset() { + if (overPullState == OVER_PULL_DOWN_ING) { + return recyclerView.computeVerticalScrollOffset(); + } + return 0; + } + + private class RollbackUpdateListener implements ValueAnimator.AnimatorUpdateListener { + + int currentValue; + int totalConsumedY; + + RollbackUpdateListener(int fromValue) { + currentValue = fromValue; + } + + @Override + public void onAnimationUpdate(ValueAnimator animation) { + if (recyclerView.isDataChangedWithoutNotify()) { + //由于动画是一个异步操作,做动画的时候,recyclerView的adapter状态已经变化,但是没有进行notify,导致state和adapter + //的itemCount对不齐,比如hippy场景,直接把recyclerView的renderNode删除了,adapter的itemCount直接变为0, + //由于没有notifyDatSetChange,state的itemCount不为0,这样就会出现validateViewHolderForOffsetPosition报 + //IndexOutOfBoundsException + return; + } + int value = (int) animation.getAnimatedValue(); + int[] consumed = new int[2]; + int dy = value - currentValue; + //dy>0 上回弹,列表内容向上滚动,慢慢显示底部的内容;dy<0 下回弹,列表内容向下滚动,慢慢显示顶部的内容 + recyclerView.scrollStep(0, dy, consumed); + int consumedY = consumed[1]; + totalConsumedY += consumedY; + + //consumedY是排版view消耗的Y的距离,没有内容填充,即consumedY为0,需要强行offsetChildrenVertical + int leftOffset = consumedY - dy; + if (leftOffset != 0) { + //leftOffset<0 向上回弹,leftOffset>0 向下回弹 + recyclerView.offsetChildrenVertical(leftOffset); + } + setOverPullState(OVER_PULL_SETTLING); + currentValue = value; + } + } +} diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/HippyOverPullListener.java b/android/sdk/src/main/java/androidx/recyclerview/widget/HippyOverPullListener.java new file mode 100644 index 00000000000..86002c34d5f --- /dev/null +++ b/android/sdk/src/main/java/androidx/recyclerview/widget/HippyOverPullListener.java @@ -0,0 +1,31 @@ +/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. + * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.recyclerview.widget; + +/** + * Created on 2021/3/15. + * Description + */ +public interface HippyOverPullListener { + + /** + * @param newState {@link HippyOverPullHelper#OVER_PULL_DOWN_ING} + * @param offset + */ + + void onOverPullStateChanged(int oldState, int newState, int offset); +} diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerExtension.java b/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerExtension.java index 24d738cf215..4a7affdf6f4 100644 --- a/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerExtension.java +++ b/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerExtension.java @@ -26,90 +26,96 @@ import com.tencent.mtt.hippy.views.hippylist.NodePositionHelper; import java.util.ArrayList; +/** + * Created on 2021/1/4. + * Description + * + * 目的是精确通过renderNode命中缓存 + * RecyclerView的扩展的缓存,如果mAttachedScrap 和 mCachedViews 都没有命中,会在访问RecyclerPool之前 + * 先访问ViewCacheExtension。参看{@link Recycler#tryGetViewHolderForPositionByDeadline}的执行流程 + */ public class HippyRecyclerExtension extends RecyclerView.ViewCacheExtension { - private final HippyEngineContext hpContext; - private final NodePositionHelper nodePositionHelper; - private HippyRecyclerViewBase recyclerView; - private int currentPosition; - - public HippyRecyclerExtension(HippyRecyclerViewBase recyclerView, HippyEngineContext hpContext, - NodePositionHelper nodePositionHelper) { - this.nodePositionHelper = nodePositionHelper; - this.recyclerView = recyclerView; - this.hpContext = hpContext; - } + private final HippyEngineContext hpContext; + private final NodePositionHelper nodePositionHelper; + private HippyRecyclerViewBase recyclerView; + private int currentPosition; - public int getCurrentPosition() { - return currentPosition; - } + public HippyRecyclerExtension(HippyRecyclerViewBase recyclerView, HippyEngineContext hpContext, + NodePositionHelper nodePositionHelper) { + this.nodePositionHelper = nodePositionHelper; + this.recyclerView = recyclerView; + this.hpContext = hpContext; + } - @Override - public View getViewForPositionAndType(Recycler recycler, int position, int type) { - currentPosition = position; - View bestView = findInAttachedScrap(recycler, position, type); - if (bestView == null) { - bestView = findInCachedScrap(recycler, position, type); + public int getCurrentPosition() { + return currentPosition; } - return bestView; - } - private View findInCachedScrap(Recycler recycler, int position, int type) { - ViewHolder bestHolder = findBestHolder(recycler.mCachedViews, position, type); - if (bestHolder != null) { - recycler.mCachedViews.remove(bestHolder); - return bestHolder.itemView; + @Override + public View getViewForPositionAndType(Recycler recycler, int position, int type) { + currentPosition = position; + View bestView = findInAttachedScrap(recycler, position, type); + if (bestView == null) { + bestView = findInCachedScrap(recycler, position, type); + } + return bestView; } - return null; - } - protected View findInAttachedScrap(Recycler recycler, int position, int type) { - ViewHolder bestHolder = findBestHolder(recycler.mAttachedScrap, position, type); - if (bestHolder != null) { - bestHolder.unScrap(); - return bestHolder.itemView; + private View findInCachedScrap(Recycler recycler, int position, int type) { + ViewHolder bestHolder = findBestHolder(recycler.mCachedViews, position, type); + if (bestHolder != null) { + recycler.mCachedViews.remove(bestHolder); + return bestHolder.itemView; + } + return null; } - return null; - } - private ViewHolder findBestHolder(ArrayList viewHolders, int position, - int type) { - int scrapCount = viewHolders.size(); - for (int i = 0; i < scrapCount; i++) { - final ViewHolder holder = viewHolders.get(i); - if (isTheBestHolder(position, type, holder)) { - return holder; - } + protected View findInAttachedScrap(Recycler recycler, int position, int type) { + ViewHolder bestHolder = findBestHolder(recycler.mAttachedScrap, position, type); + if (bestHolder != null) { + bestHolder.unScrap(); + return bestHolder.itemView; + } + return null; } - return null; - } - /** - * 找到对应的bindNode,比对缓存池的holder是否正好是当前position位置对应的Holder - * - * @param position 要获取Holder的position - * @param type 节点类型 - * @param scrapHolder 缓存池的Holder - * @return - */ - protected boolean isTheBestHolder(int position, int type, ViewHolder scrapHolder) { - if (scrapHolder.getAdapterPosition() != position || scrapHolder.isInvalid() || scrapHolder - .isRemoved()) { - return false; + private RecyclerView.ViewHolder findBestHolder(ArrayList viewHolders, int position, + int type) { + int scrapCount = viewHolders.size(); + for (int i = 0; i < scrapCount; i++) { + final RecyclerView.ViewHolder holder = viewHolders.get(i); + if (isTheBestHolder(position, type, holder)) { + return holder; + } + } + return null; } - if (scrapHolder.getItemViewType() == type && scrapHolder instanceof HippyRecyclerViewHolder) { - RenderNode nodeOfPosition = hpContext.getRenderManager().getRenderNode(recyclerView.getId()) - .getChildAt(nodePositionHelper.getRenderNodePosition(position)); - return isNodeEquals(((HippyRecyclerViewHolder) scrapHolder).bindNode, - (ListItemRenderNode) nodeOfPosition); + + /** + * 找到对应的bindNode,比对缓存池的holder是否正好是当前position位置对应的Holder + * + * @param position 要获取Holder的position + * @param type 节点类型 + * @param scrapHolder 缓存池的Holder + * @return + */ + protected boolean isTheBestHolder(int position, int type, ViewHolder scrapHolder) { + if (scrapHolder.getAdapterPosition() != position || scrapHolder.isInvalid() || scrapHolder.isRemoved()) { + return false; + } + if (scrapHolder.getItemViewType() == type && scrapHolder instanceof HippyRecyclerViewHolder) { + RenderNode nodeOfPosition = hpContext.getRenderManager().getRenderNode(recyclerView.getId()) + .getChildAt(nodePositionHelper.getRenderNodePosition(position)); + return isNodeEquals(((HippyRecyclerViewHolder) scrapHolder).bindNode, (ListItemRenderNode) nodeOfPosition); + } + return false; } - return false; - } - public static boolean isNodeEquals(ListItemRenderNode node1, ListItemRenderNode node2) { - if (node1 == null || node2 == null) { - return false; + public static boolean isNodeEquals(ListItemRenderNode node1, ListItemRenderNode node2) { + if (node1 == null || node2 == null) { + return false; + } + return node1.equals(node2); } - return node1.equals(node2); - } } diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerPool.java b/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerPool.java index db0d53dbf3c..e8c2fced40b 100644 --- a/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerPool.java +++ b/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerPool.java @@ -24,90 +24,97 @@ import com.tencent.mtt.hippy.views.hippylist.HippyRecyclerViewHolder; import com.tencent.mtt.hippy.views.hippylist.NodePositionHelper; +/** + * Created on 2021/1/4. Description + * + * 继承RecycledViewPool,主要用于renderNode节点的精确命中,不能从RecycledViewPool里面随意取一个node + * 所有重写了getRecycledView方法;putRecycledView也需要检测缓存是否会抛弃viewHolder,如果抛弃需要把 + * 事件同步给RenderManager进行相应的节点删除。 + */ public class HippyRecyclerPool extends RecyclerView.RecycledViewPool { - private final View recyclerView; - private final HippyRecyclerExtension viewCacheExtension; - private final HippyEngineContext hpContext; - private final NodePositionHelper nodePositionHelper; - private IHippyViewAboundListener viewAboundListener; - - public HippyRecyclerPool(HippyEngineContext hpContext, View recyclerView, - HippyRecyclerExtension viewCacheExtension, NodePositionHelper nodePositionHelper) { - this.nodePositionHelper = nodePositionHelper; - this.hpContext = hpContext; - this.recyclerView = recyclerView; - this.viewCacheExtension = viewCacheExtension; - } - - public void setViewAboundListener(IHippyViewAboundListener viewAboundListener) { - this.viewAboundListener = viewAboundListener; - } + private final View recyclerView; + private final HippyRecyclerExtension viewCacheExtension; + private final HippyEngineContext hpContext; + private final NodePositionHelper nodePositionHelper; + private IHippyViewAboundListener viewAboundListener; - /** - * 从缓存池里面获取ViewHolder进行复用 1、精确命中相同的renderNode 2、命中相同Type的ViewHolder,并且对应的RenderNode是没有被前端删除的 - * 如果renderNode.isDelete为true,说明前端删除了RenderNode, 此时会调用 RenderManager框架的deleteChild, 所以view也不会存在了。 - * 即使找到了相同type的Holder,也不能复用了。 - */ - @Override - public ViewHolder getRecycledView(int viewType) { - ScrapData scrapData = mScrap.get(viewType); - if (scrapData == null) { - return null; + public HippyRecyclerPool(HippyEngineContext hpContext, View recyclerView, + HippyRecyclerExtension viewCacheExtension, NodePositionHelper nodePositionHelper) { + this.nodePositionHelper = nodePositionHelper; + this.hpContext = hpContext; + this.recyclerView = recyclerView; + this.viewCacheExtension = viewCacheExtension; } - ViewHolder delegateHolder = null; - for (ViewHolder holder : scrapData.mScrapHeap) { - if (isTheSameRenderNode((HippyRecyclerViewHolder) holder)) { - scrapData.mScrapHeap.remove(holder); - delegateHolder = holder; - break; - } - } - //没有精确命中,再看看缓存池里面有没有相同类型的viewType - if (delegateHolder == null) { - delegateHolder = super.getRecycledView(viewType); + + public void setViewAboundListener(IHippyViewAboundListener viewAboundListener) { + this.viewAboundListener = viewAboundListener; } - //检测对应的节点是否被删除 - if (delegateHolder instanceof HippyRecyclerViewHolder - && ((HippyRecyclerViewHolder) delegateHolder).isRenderDeleted()) { - return null; + + /** + * 从缓存池里面获取ViewHolder进行复用 1、精确命中相同的renderNode 2、命中相同Type的ViewHolder,并且对应的RenderNode是没有被前端删除的 + * 如果renderNode.isDelete为true,说明前端删除了RenderNode, 此时会调用 RenderManager框架的deleteChild, + * 所以view也不会存在了。 即使找到了相同type的Holder,也不能复用了。 + */ + @Override + public ViewHolder getRecycledView(int viewType) { + ScrapData scrapData = mScrap.get(viewType); + if (scrapData == null) { + return null; + } + ViewHolder delegateHolder = null; + for (ViewHolder holder : scrapData.mScrapHeap) { + if (isTheSameRenderNode((HippyRecyclerViewHolder) holder)) { + scrapData.mScrapHeap.remove(holder); + delegateHolder = holder; + break; + } + } + //没有精确命中,再看看缓存池里面有没有相同类型的viewType + if (delegateHolder == null) { + delegateHolder = super.getRecycledView(viewType); + } + //检测对应的节点是否被删除 + if (delegateHolder instanceof HippyRecyclerViewHolder + && ((HippyRecyclerViewHolder) delegateHolder).isRenderDeleted()) { + return null; + } + return delegateHolder; } - return delegateHolder; - } - /** - * putRecycledView 可能出现缓存已经超过最大值,会发生ViewHolder被抛弃, 抛弃需要后,需要同步修改 renderManager内部创建对应的view,这样 {@link - * com.tencent.mtt.hippy.views.hippylist.HippyRecyclerListAdapter#onCreateViewHolder(ViewGroup, - * int)},才能通过 {@link RenderNode#createViewRecursive()} 创建新的view, 否则createViewRecursive会返回null。 - * - * @param scrap - */ - @Override - public void putRecycledView(ViewHolder scrap) { - notifyAboundIfNeed(scrap); - super.putRecycledView(scrap); - } + /** + * putRecycledView 可能出现缓存已经超过最大值,会发生ViewHolder被抛弃, 抛弃需要后,需要同步修改 renderManager内部创建对应的view,这样 + * {@link com.tencent.mtt.hippy.views.hippylist.HippyRecyclerListAdapter#onCreateViewHolder( + *ViewGroup, int)},才能通过 {@link RenderNode#createViewRecursive()} 创建新的view, + * 否则createViewRecursive会返回null。 + * + * @param scrap + */ + @Override + public void putRecycledView(ViewHolder scrap) { + notifyAboundIfNeed(scrap); + super.putRecycledView(scrap); + } - private void notifyAboundIfNeed(ViewHolder scrap) { - int viewType = scrap.getItemViewType(); - ScrapData scrapData = this.mScrap.get(viewType); - if (scrapData != null && scrapData.mScrapHeap.size() >= scrapData.mMaxScrap) { - viewAboundListener.onViewAbound((HippyRecyclerViewHolder) scrap); + private void notifyAboundIfNeed(ViewHolder scrap) { + int viewType = scrap.getItemViewType(); + ScrapData scrapData = this.mScrap.get(viewType); + if (scrapData != null && scrapData.mScrapHeap.size() >= scrapData.mMaxScrap) { + viewAboundListener.onViewAbound((HippyRecyclerViewHolder) scrap); + } } - } - /** - * 是否是节点完全相等 - * - * @param scrapHolder 缓存池里面的Holder - */ - private boolean isTheSameRenderNode(HippyRecyclerViewHolder scrapHolder) { - if (scrapHolder.bindNode == null) { - return false; + /** + * 是否是节点完全相等 + * + * @param scrapHolder 缓存池里面的Holder + */ + private boolean isTheSameRenderNode(HippyRecyclerViewHolder scrapHolder) { + if (scrapHolder.bindNode == null) { + return false; + } + RenderNode nodeForCurrent = hpContext.getRenderManager().getRenderNode(recyclerView.getId()) + .getChildAt(nodePositionHelper.getRenderNodePosition(viewCacheExtension.getCurrentPosition())); + return scrapHolder.bindNode.equals(nodeForCurrent); } - RenderNode nodeForCurrent = hpContext.getRenderManager().getRenderNode(recyclerView.getId()) - .getChildAt( - nodePositionHelper.getRenderNodePosition(viewCacheExtension.getCurrentPosition())); - return scrapHolder.bindNode.equals(nodeForCurrent); - } } diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerViewBase.java b/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerViewBase.java index f68b9477781..b287114122f 100644 --- a/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerViewBase.java +++ b/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerViewBase.java @@ -18,58 +18,110 @@ import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import android.util.AttributeSet; -import androidx.recyclerview.widget.LinearLayoutManager; - -public class HippyRecyclerViewBase extends EasyRecyclerView { - - public HippyRecyclerViewBase(@NonNull Context context) { - super(context); - } - - public HippyRecyclerViewBase(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - } - - public HippyRecyclerViewBase(@NonNull Context context, @Nullable AttributeSet attrs, - int defStyle) { - super(context, attrs, defStyle); - } - - /** - * @param position 从哪一个数据位置开始排版,将position的item置顶 - * @param offset 相对于RecyclerView底部的offset,offset>0:内容下移,offset<0:内容上移 - */ - public void scrollToPositionWithOffset(int position, int offset) { - if (mLayoutSuppressed) { - return; + +/** + * Created on 2020/10/14. + * + * 由于Hippy的特殊需求,需要看到更多的RecyclerVew的方法和成员,这里创建和系统RecyclerView同包名。 + */ + +public class HippyRecyclerViewBase extends RecyclerViewBase { + + private boolean isBatching; + + public HippyRecyclerViewBase(@NonNull Context context) { + super(context); + } + + public HippyRecyclerViewBase(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public HippyRecyclerViewBase(@NonNull Context context, @Nullable AttributeSet attrs, + int defStyle) { + super(context, attrs, defStyle); } - stopScroll(); - if (this.mLayout == null) { - android.util.Log.e("RecyclerView", - "Cannot scroll to position a LayoutManager set. Call setLayoutManager with a non-null argument."); - } else { - LayoutManager layoutManager = getLayoutManager(); - if (layoutManager instanceof LinearLayoutManager) { - ((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(position, offset); - } else { - this.mLayout.scrollToPosition(position); - } - this.awakenScrollBars(); + + /** + * @param position 从哪一个数据位置开始排版,将position的item置顶 + * @param offset 相对于RecyclerView底部的offset,offset>0:内容下移,offset<0:内容上移 + */ + public void scrollToPositionWithOffset(int position, int offset) { + if (mLayoutSuppressed) { + return; + } + stopScroll(); + if (this.mLayout == null) { + android.util.Log.e("RecyclerView", + "Cannot scroll to position a LayoutManager set. Call setLayoutManager with a non-null argument."); + } else { + LayoutManager layoutManager = getLayoutManager(); + if (layoutManager instanceof LinearLayoutManager) { + ((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(position, offset); + } else { + this.mLayout.scrollToPosition(position); + } + this.awakenScrollBars(); + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + //这里不调用super.onLayout,因为HippyListView的RenderNode的update会走onLayout,导致多余的排版 + //HippyListView的dispatchLayout统一走setListData函数 + } + + @Override + public void dispatchLayout() { + if (!isBatching) { + LayoutManager layoutManager = getLayoutManager(); + if (layoutManager instanceof HippyLinearLayoutManager) { + ((HippyLinearLayoutManager) layoutManager).resetCache(); + } + super.dispatchLayout(); + } + //由于上面屏蔽了super.onLayout,这里需要对齐框架的代码,把mFirstLayoutComplete该为true + this.mFirstLayoutComplete = true; + } + + @Override + String exceptionLabel() { + return super.exceptionLabel() + ",state:" + getStateInfo(); + } + + public String getStateInfo() { + if (mState != null) { + return mState.toString(); + } + return null; } - } - @Override - String exceptionLabel() { - return super.exceptionLabel() + ",state:" + getStateInfo(); - } + public void onBatchStart() { + isBatching = true; + } + + public void onBatchComplete() { + isBatching = false; + } - public String getStateInfo() { - if (mState != null) { - return mState.toString(); + /** + * view 被Hippy的RenderNode 删除了,这样会导致View的child完全是空的,这个view是不能再被recyclerView复用了 + * 否则如果被复用,在adapter的onBindViewHolder的时候,view的实际子view和renderNode的数据不匹配,diff会出现异常 + * 导致item白条,显示不出来,所以被删除的view,需要把viewHolder.setIsRecyclable(false),刷新list后,这个view就 + * 不会进入缓存。 + */ + public void disableRecycle(View childView) { + ViewGroup.LayoutParams layoutParams = childView.getLayoutParams(); + if (layoutParams instanceof LayoutParams) { + ViewHolder viewHolder = ((LayoutParams) layoutParams).mViewHolder; + if (viewHolder != null) { + viewHolder.setIsRecyclable(false); + } + } } - return null; - } } diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/IHippyViewAboundListener.java b/android/sdk/src/main/java/androidx/recyclerview/widget/IHippyViewAboundListener.java index 0516c1488ff..401028b957b 100644 --- a/android/sdk/src/main/java/androidx/recyclerview/widget/IHippyViewAboundListener.java +++ b/android/sdk/src/main/java/androidx/recyclerview/widget/IHippyViewAboundListener.java @@ -1,8 +1,27 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package androidx.recyclerview.widget; import com.tencent.mtt.hippy.views.hippylist.HippyRecyclerViewHolder; +/** + * Created on 2021/1/12. + * Description + */ public interface IHippyViewAboundListener { - void onViewAbound(HippyRecyclerViewHolder viewHolder); + void onViewAbound(HippyRecyclerViewHolder viewHolder); } diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/IItemLayoutParams.java b/android/sdk/src/main/java/androidx/recyclerview/widget/IItemLayoutParams.java deleted file mode 100644 index 7193f98210a..00000000000 --- a/android/sdk/src/main/java/androidx/recyclerview/widget/IItemLayoutParams.java +++ /dev/null @@ -1,8 +0,0 @@ -package androidx.recyclerview.widget; - -import androidx.recyclerview.widget.RecyclerView.LayoutParams; - -public interface IItemLayoutParams { - - void getItemLayoutParams(int position, LayoutParams lp); -} diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/ItemLayoutParams.java b/android/sdk/src/main/java/androidx/recyclerview/widget/ItemLayoutParams.java new file mode 100644 index 00000000000..7702422c62c --- /dev/null +++ b/android/sdk/src/main/java/androidx/recyclerview/widget/ItemLayoutParams.java @@ -0,0 +1,12 @@ +package androidx.recyclerview.widget; + +import androidx.recyclerview.widget.RecyclerView.LayoutParams; + +/** + * Created on 2021/3/17. + * Description + */ +public interface ItemLayoutParams { + + void getItemLayoutParams(int position, LayoutParams lp); +} diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/OverPullHelper.java b/android/sdk/src/main/java/androidx/recyclerview/widget/OverPullHelper.java deleted file mode 100644 index 654f8ce3ec7..00000000000 --- a/android/sdk/src/main/java/androidx/recyclerview/widget/OverPullHelper.java +++ /dev/null @@ -1,342 +0,0 @@ -/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. - * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import static android.view.View.OVER_SCROLL_NEVER; - -import android.animation.Animator; -import android.animation.ValueAnimator; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView.OnScrollListener; -import android.view.MotionEvent; -import android.view.ViewConfiguration; -import android.view.animation.DecelerateInterpolator; -import com.tencent.mtt.nxeasy.recyclerview.helper.AnimatorListenerBase; - -public class OverPullHelper { - - private static final int DURATION = 150; - protected float lastRawY = -1; - protected float downRawY = -1; - - private int overPullState = OVER_PULL_NONE; - public static final int OVER_PULL_NONE = 0; - public static final int OVER_PULL_DOWN_ING = 1; - public static final int OVER_PULL_UP_ING = 2; - public static final int OVER_PULL_NORMAL = 3; - public static final int OVER_PULL_SETTLING = 4; - - private ValueAnimator animator; - private boolean enableOverDrag = true; - private int lastOverScrollMode = -1; - private boolean isRollBacking = false; - private OverPullListener overPullListener = null; - private EasyRecyclerView recyclerView; - - public OverPullHelper(EasyRecyclerView recyclerView) { - this.recyclerView = recyclerView; - lastOverScrollMode = recyclerView.getOverScrollMode(); - recyclerView.addOnScrollListener(new OnScrollListener() { - @Override - public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { - if (newState == RecyclerView.SCROLL_STATE_IDLE) { - rollbackToBottomOrTop(); - } - } - }); - } - - private int getTouchSlop() { - final ViewConfiguration vc = ViewConfiguration.get(recyclerView.getContext()); - return vc.getScaledTouchSlop(); - } - - public void setOverPullListener(OverPullListener overPullListener) { - this.overPullListener = overPullListener; - } - - private boolean isMoving(MotionEvent event) { - return lastRawY > 0 && Math.abs(event.getRawY() - downRawY) > getTouchSlop(); - } - - public boolean onTouchEvent(MotionEvent event) { - if (isRollBacking) { - return true; - } - if (checkOverDrag(event)) { - return true; - } - return false; - } - - /** - * 检测是否处于顶部过界拉取,或者顶部过界拉取 - */ - private boolean checkOverDrag(MotionEvent event) { - if (!enableOverDrag) { - return false; - } - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - lastRawY = event.getRawY(); - downRawY = event.getRawY(); - break; - case MotionEvent.ACTION_MOVE: - boolean overPullDown = isOverPullDown(event); - boolean overScrollUp = isOverPullUp(event); - if ((overPullDown || overScrollUp)) { - recyclerView.setOverScrollMode(OVER_SCROLL_NEVER); - recyclerView.invalidateGlows(); - if (overPullDown) { - setOverPullState(OVER_PULL_DOWN_ING); - } else { - setOverPullState(OVER_PULL_UP_ING); - } - int deltaY = (int) (event.getRawY() - lastRawY) / 2; -// if (deltaY > 0) { -// //下拉的时候除以2,放慢拉动的速度,调节拉动的手感 -// deltaY = deltaY / 2; -// } - recyclerView.offsetChildrenVertical(deltaY); - if (overPullListener != null) { - overPullListener - .onOverPullStateChanged(overPullState, overPullState, getOverPullOffset()); - } - } else { - setOverPullState(OVER_PULL_NORMAL); - } - lastRawY = event.getRawY(); - break; - default: - reset(); - } - if (overPullState == OVER_PULL_DOWN_ING || overPullState == OVER_PULL_UP_ING) { - return true; - } - return false; - } - - /** - * 在松开手后, 1、如果当前处于fling状态,scrollState的值是SCROLL_STATE_SETTLING,先不做rollbackToBottomOrTop - * 等到onScrollStateChanged 变成 IDLE的时候,再做rollbackToBottomOrTop 2、如果当前处于非fling状态,scrollState的值不是SCROLL_STATE_SETTLING,就立即做rollbackToBottomOrTop - */ - public void handleEventUp(MotionEvent event) { - if (isActionUpOrCancel(event)) { - revertOverScrollMode(); - if (recyclerView.getScrollState() != RecyclerView.SCROLL_STATE_SETTLING) { - rollbackToBottomOrTop(); - } - } - } - - private void revertOverScrollMode() { - if (lastOverScrollMode != -1) { - recyclerView.setOverScrollMode(lastOverScrollMode); - } - } - - private int getOverPullOffset() { - if (overPullState == OVER_PULL_DOWN_ING) { - return getOverPullDownOffset(); - } else if (overPullState == OVER_PULL_UP_ING) { - return getOverPullUpOffset(); - } - return 0; - } - - void setOverPullState(int newOverPullState) { - if (overPullListener != null) { - overPullListener.onOverPullStateChanged(overPullState, newOverPullState, getOverPullOffset()); - } - overPullState = newOverPullState; - } - - /** - * 因为可能出现越界拉取,松手后需要回退到原来的位置,要么回到顶部,要么回到底部 - */ - void rollbackToBottomOrTop() { - int distanceToTop = recyclerView.computeVerticalScrollOffset(); - if (distanceToTop < 0) { - //顶部空出了一部分,需要回滚上去 - rollbackTo(distanceToTop, 0); - } else { - //底部空出一部分,需要混滚下去 - int overPullUpOffset = getOverPullUpOffset(); - if (overPullUpOffset != 0) { - rollbackTo(overPullUpOffset, 0); - } - } - } - - /** - * 计算底部被overPull的偏移,需要向下回滚的距离 要么出现底部内容顶满distanceToBottom,要么出现顶部内容顶满distanceToTop,取最小的那一个 - * - * @return - */ - public int getOverPullUpOffset() { - int contentOffset = recyclerView.computeVerticalScrollOffset(); - int verticalScrollRange = recyclerView.computeVerticalScrollRange(); - int blankHeightToBottom = contentOffset + recyclerView.getHeight() - verticalScrollRange; - if (blankHeightToBottom > 0 && contentOffset > 0) { - return Math.min(blankHeightToBottom, contentOffset); - } - return 0; - } - - private boolean isActionUpOrCancel(MotionEvent event) { - return event.getAction() == MotionEvent.ACTION_UP - || event.getAction() == MotionEvent.ACTION_CANCEL; - } - - private void endAnimation() { - if (animator != null) { - animator.removeAllListeners(); - animator.removeAllUpdateListeners(); - animator.end(); - animator = null; - } - isRollBacking = false; - } - - /** - * 回弹动画的接口 - */ - private void rollbackTo(int from, int to) { - endAnimation(); - animator = ValueAnimator.ofInt(from, to); - animator.setInterpolator(new DecelerateInterpolator()); - animator.addUpdateListener(new RollbackUpdateListener(from)); - animator.addListener(new AnimatorListenerBase() { - @Override - public void onAnimationEnd(Animator animation) { - setOverPullState(OVER_PULL_NONE); - isRollBacking = false; - } - }); - isRollBacking = true; - animator.setDuration(DURATION).start(); - } - - private void reset() { - revertOverScrollMode(); - lastRawY = -1; - downRawY = -1; - setOverPullState(OVER_PULL_NONE); - } - - /** - * 顶部是否可以越界下拉,拉出一段空白区域,越界的部分最多不能超过RecyclerView高度+1 - */ - private boolean isOverPullDown(MotionEvent event) { - //常规情况,内容在顶部offset为0,异常情况,内容被完全拉到最底部,看不见内容的时候,offset也为0 - int offset = recyclerView.computeVerticalScrollOffset(); - int dy = Math.abs((int) (event.getRawY() - lastRawY)) + 1; - //不能把内容完全拉得看不见 - if (Math.abs(offset) + dy < recyclerView.getHeight()) { - return isMoving(event) && isPullDownAction(event) && !canOverPullDown(); - } - return false; - } - - /** - * 底部是否可以越界上拉,拉出一段空白区域,越界的部分最多不能超过RecyclerView高度的一般 - */ - private boolean isOverPullUp(MotionEvent event) { - int dy = Math.abs((int) (event.getRawY() - lastRawY)) + 1; - //不能让内容完全被滚出屏幕,否则computeVerticalScrollOffset为0是一个无效的值 - int distanceToBottom = - recyclerView.computeVerticalScrollOffset() + recyclerView.getHeight() - recyclerView - .computeVerticalScrollRange(); - if (distanceToBottom + dy < recyclerView.getHeight()) { - return isMoving(event) && isPullUpAction(event) && !canOverPullUp(); - } - return false; - } - - boolean isPullDownAction(MotionEvent event) { - return event.getRawY() - lastRawY > 0; - } - - boolean isPullUpAction(MotionEvent event) { - return event.getRawY() - lastRawY <= 0; - } - - /** - * 顶部还有内容,还可以向下拉到 - */ - boolean canOverPullDown() { - return recyclerView.canScrollVertically(-1); - } - - /** - * 底部还有内容,还可以向上拉动 - */ - boolean canOverPullUp() { - return recyclerView.canScrollVertically(1); - } - - public int getOverPullState() { - return overPullState; - } - - /** - * 下拉的时候,返回值<0,表示顶部被下拉了一部分距离 - */ - public int getOverPullDownOffset() { - if (overPullState == OVER_PULL_DOWN_ING) { - return recyclerView.computeVerticalScrollOffset(); - } - return 0; - } - - private class RollbackUpdateListener implements ValueAnimator.AnimatorUpdateListener { - - int currentValue; - int totalConsumedY; - - RollbackUpdateListener(int fromValue) { - currentValue = fromValue; - } - - @Override - public void onAnimationUpdate(ValueAnimator animation) { - if (recyclerView.isDataChangedWithoutNotify()) { - //由于动画是一个异步操作,做动画的时候,recyclerView的adapter状态已经变化,但是没有进行notify,导致state和adapter - //的itemCount对不齐,比如hippy场景,直接把recyclerView的renderNode删除了,adapter的itemCount直接变为0, - //由于没有notifyDatSetChange,state的itemCount不为0,这样就会出现validateViewHolderForOffsetPosition报 - //IndexOutOfBoundsException - return; - } - int value = (int) animation.getAnimatedValue(); - int[] consumed = new int[2]; - int dy = value - currentValue; - //dy>0 上回弹,列表内容向上滚动,慢慢显示底部的内容;dy<0 下回弹,列表内容向下滚动,慢慢显示顶部的内容 - recyclerView.scrollStep(0, dy, consumed); - int consumedY = consumed[1]; - totalConsumedY += consumedY; - - //consumedY是排版view消耗的Y的距离,没有内容填充,即consumedY为0,需要强行offsetChildrenVertical - int leftOffset = consumedY - dy; - if (leftOffset != 0) { - //leftOffset<0 向上回弹,leftOffset>0 向下回弹 - recyclerView.offsetChildrenVertical(leftOffset); - } - setOverPullState(OVER_PULL_SETTLING); - currentValue = value; - } - } -} diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/OverPullListener.java b/android/sdk/src/main/java/androidx/recyclerview/widget/OverPullListener.java deleted file mode 100644 index ad7eb67f3e2..00000000000 --- a/android/sdk/src/main/java/androidx/recyclerview/widget/OverPullListener.java +++ /dev/null @@ -1,27 +0,0 @@ -/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. - * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -public interface OverPullListener { - - /** - * @param newState {@link OverPullHelper#OVER_PULL_DOWN_ING} - * @param offset - */ - - void onOverPullStateChanged(int oldState, int newState, int offset); -} diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/RecyclerViewBase.java b/android/sdk/src/main/java/androidx/recyclerview/widget/RecyclerViewBase.java new file mode 100644 index 00000000000..73cd84fbfd7 --- /dev/null +++ b/android/sdk/src/main/java/androidx/recyclerview/widget/RecyclerViewBase.java @@ -0,0 +1,254 @@ +/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. + * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.recyclerview.widget; + + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView.RecycledViewPool.ScrapData; +import java.lang.reflect.Field; +import java.util.ArrayList; + +/** + * Created on 2020/10/14. + */ + +public class RecyclerViewBase extends RecyclerView { + + protected HippyOverPullHelper overPullHelper; + protected HippyOverPullListener overPullListener; + protected VelocityTracker velocityTracker; + private boolean enableOverDrag; + + public RecyclerViewBase(@NonNull Context context) { + super(context); + init(); + } + + public RecyclerViewBase(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public RecyclerViewBase(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + protected void init() { + } + + public int getOverPullState() { + if (overPullHelper != null) { + return overPullHelper.getOverPullState(); + } + return HippyOverPullHelper.OVER_PULL_NONE; + } + + public boolean isOverPulling() { + int pullState = getOverPullState(); + return pullState == HippyOverPullHelper.OVER_PULL_DOWN_ING || pullState == HippyOverPullHelper.OVER_PULL_UP_ING + || pullState == HippyOverPullHelper.OVER_PULL_SETTLING; + } + + /** + * 下拉的时候,返回值<0,表示顶部被下拉了一部分距离,顶部有空白 + */ + public int getOverPullUpOffset() { + if (overPullHelper != null) { + return overPullHelper.getOverPullUpOffset(); + } + return 0; + } + + /** + * 上拉的时候,返回值>0,表示底部被上拉了一部分距离,底部有空白 + */ + public int getOverPullDownOffset() { + if (overPullHelper != null) { + return overPullHelper.getOverPullDownOffset(); + } + return 0; + } + + public void setOverPullListener(HippyOverPullListener listener) { + overPullListener = listener; + if (overPullHelper != null) { + overPullHelper.setOverPullListener(listener); + } + } + + public boolean isEnableOverDrag() { + return enableOverDrag; + } + + public void enableOverPullIfNeeded() { + LayoutManager layoutManager = getLayoutManager(); + boolean isVertical = (layoutManager != null) ? layoutManager.canScrollVertically() : false; + if (enableOverDrag && isVertical) { + if (overPullHelper == null) { + overPullHelper = new HippyOverPullHelper(this); + } + overPullHelper.setOverPullListener(overPullListener); + } else { + if (overPullHelper != null) { + overPullHelper.destroy(); + } + overPullHelper = null; + } + } + + public void setEnableOverPull(boolean enableOverDrag) { + this.enableOverDrag = enableOverDrag; + enableOverPullIfNeeded(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (overPullHelper != null && overPullHelper.onTouchEvent(event)) { + return true; + } + boolean handled = super.onTouchEvent(event); + if (overPullHelper != null) { + overPullHelper.handleEventUp(event); + } + return handled; + } + + @Override + public void requestLayout() { + super.requestLayout(); + } + + public void recycleAndClearCachedViews() { + mRecycler.recycleAndClearCachedViews(); + } + + public int getChildCountWithCaches() { + return getCachedViewHolderCount() + getChildCount(); + } + + public View getChildAtWithCaches(int index) { + ArrayList viewHolders = getCachedViewHolders(); + if (index < viewHolders.size()) { + return viewHolders.get(index).itemView; + } else { + return getChildAt(index - viewHolders.size()); + } + } + + private int getCachedViewHolderCount() { + int count = mRecycler.mAttachedScrap.size() + mRecycler.mCachedViews.size(); + for (int i = 0; i < mRecycler.getRecycledViewPool().mScrap.size(); i++) { + ScrapData scrapData = mRecycler.getRecycledViewPool().mScrap.valueAt(i); + count += scrapData.mScrapHeap.size(); + } + return count; + } + + public ArrayList getCachedViewHolders() { + ArrayList listViewHolder = new ArrayList<>(); + listViewHolder.addAll(mRecycler.mAttachedScrap); + listViewHolder.addAll(mRecycler.mCachedViews); + for (int i = 0; i < mRecycler.getRecycledViewPool().mScrap.size(); i++) { + ScrapData scrapData = mRecycler.getRecycledViewPool().mScrap.valueAt(i); + listViewHolder.addAll(scrapData.mScrapHeap); + } + return listViewHolder; + } + + public boolean didStructureChange() { + return mState.didStructureChange(); + } + + public void smoothScrollBy(int dx, int dy, int duration) { + smoothScrollBy(dx, dy, null, duration); + } + + public int getFirstChildPosition() { + return getChildLayoutPosition(getChildCount() > 0 ? getChildAt(0) : null); + } + + public int getLashChildPosition() { + return getChildLayoutPosition(getChildCount() > 0 ? getChildAt(getChildCount() - 1) : null); + } + + /** + * 通过位置获取一个ViewHolder,目前暂时提供给header使用 + */ + public ViewHolder getViewHolderForPosition(int position) { + View view = mRecycler.getViewForPosition(position); + if (view.getLayoutParams() instanceof LayoutParams) { + return ((LayoutParams) view.getLayoutParams()).mViewHolder; + } + return null; + } + + public ViewHolder getFistChildViewHolder() { + View view = getChildAt(0); + if (view != null && view.getLayoutParams() instanceof LayoutParams) { + return ((LayoutParams) view.getLayoutParams()).mViewHolder; + } + return null; + } + + /** + * 改成public接口,主要用于hippy业务的特殊需求 + */ + @Override + public void dispatchLayout() { + super.dispatchLayout(); + } + + @Override + public void invalidateGlows() { + super.invalidateGlows(); + } + + /** + * 反射获取滚动的VelocityTracker + */ + public VelocityTracker getVelocityTracker() { + if (velocityTracker == null) { + try { + Field velocityTrackerField = RecyclerView.class.getDeclaredField("mVelocityTracker"); + velocityTrackerField.setAccessible(true); + velocityTracker = (VelocityTracker) velocityTrackerField.get(this); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + return velocityTracker; + } + + /** + * recyclerView的adapter状态已经变化,但是没有进行notify,导致state和adapter + * 的itemCount对不齐,比如hippy场景,直接把recyclerView的renderNode删除了,adapter的itemCount直接变为0, + * 由于没有notifyDatSetChange,state的itemCount不为0,这样就会出现validateViewHolderForOffsetPosition报 + * IndexOutOfBoundsException + */ + public boolean isDataChangedWithoutNotify() { + return getAdapter().getItemCount() != mState.getItemCount(); + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyEngine.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyEngine.java index c9efbeabfde..19428289d23 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyEngine.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyEngine.java @@ -1,5 +1,5 @@ /* Tencent is pleased to support the open source community by making Hippy available. - * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2018-2022 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import android.content.Context; import android.text.TextUtils; +import androidx.annotation.NonNull; import com.tencent.mtt.hippy.adapter.DefaultLogAdapter; import com.tencent.mtt.hippy.adapter.HippyLogAdapter; import com.tencent.mtt.hippy.adapter.device.DefaultDeviceAdapter; @@ -42,13 +43,17 @@ import com.tencent.mtt.hippy.bridge.HippyCoreAPI; import com.tencent.mtt.hippy.bridge.bundleloader.HippyBundleLoader; import com.tencent.mtt.hippy.bridge.libraryloader.LibraryLoader; +import com.tencent.mtt.hippy.utils.BuglyUtils; +import com.tencent.mtt.hippy.v8.V8; import com.tencent.mtt.hippy.common.HippyJsException; import com.tencent.mtt.hippy.common.HippyMap; +import com.tencent.mtt.hippy.dom.node.DomNodeRecord; import com.tencent.mtt.hippy.utils.ContextHolder; import com.tencent.mtt.hippy.utils.LogUtils; import com.tencent.mtt.hippy.utils.UIThreadUtils; import com.tencent.mtt.hippy.adapter.thirdparty.HippyThirdPartyAdapter; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -67,19 +72,11 @@ public abstract class HippyEngine { // Engine所属的分组ID,同一个组共享线程和isolate,不同context protected int mGroupId; ModuleListener mModuleListener; + DebugMode mDebugMode; - static { - LibraryLoader.loadLibraryIfNeed(); - } - - public static void setNativeLogHandler(IHippyNativeLogHandler handler) { - if (handler != null) { - initNativeLogHandler(handler); - } - } - + private static HippyLogAdapter sLogAdapter = null; @SuppressWarnings("JavaJniMissingFunction") - private static native void initNativeLogHandler(IHippyNativeLogHandler handler); + private static native void setNativeLogHandler(HippyLogAdapter handler); /** * @param params 创建实例需要的参数 创建一个HippyEngine实例 @@ -88,17 +85,20 @@ public static HippyEngine create(EngineInitParams params) { if (params == null) { throw new RuntimeException("Hippy: initParams must no be null"); } + LibraryLoader.loadLibraryIfNeeded(params.soLoader); + if (sLogAdapter == null && params.logAdapter != null) { + setNativeLogHandler(params.logAdapter); + } + ContextHolder.initAppContext(params.context); + BuglyUtils.registerSdkAppIdIfNeeded(params.context); params.check(); LogUtils.enableDebugLog(params.enableLog); - ContextHolder.initAppContext(params.context); - HippyEngine hippyEngine; if (params.groupId == -1) { hippyEngine = new HippyNormalEngineManager(params, null); } else { hippyEngine = new HippySingleThreadEngineManager(params, null); } - return hippyEngine; } @@ -157,9 +157,13 @@ public int getId() { public abstract void initEngine(EngineListener listener); - // 是否调试模式 - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - public abstract boolean isDebugMode(); + // debug mode is including DebugMode.Dev mode and DebugMode.UserLocal mode + public boolean isDebugMode() { return mDebugMode != DebugMode.None; } + + // Dev mode is one of the debug mode + public boolean isDevMode() { + return mDebugMode == DebugMode.Dev; + } /** * destroy the hippy engine All hippy instance will be destroyed @@ -176,6 +180,9 @@ public int getId() { public abstract HippyRootView loadModule(ModuleLoadParams loadParams, ModuleListener listener); + public abstract HippyRootView loadInstance(ModuleLoadParams loadParams, ModuleListener listener, + HippyRootView.OnLoadCompleteListener onLoadCompleteListener); + @SuppressWarnings("unused") public abstract HippyRootView loadModule(ModuleLoadParams loadParams, ModuleListener listener, HippyRootView.OnLoadCompleteListener onLoadCompleteListener); @@ -192,6 +199,8 @@ public abstract HippyRootView loadModule(ModuleLoadParams loadParams, ModuleList */ public abstract void onEnginePause(); + public abstract void onFontChanged(int rootId); + public abstract void sendEvent(String event, Object params); public abstract void sendEvent(String event, Object params, BridgeTransferType transferType); @@ -202,6 +211,19 @@ public abstract HippyRootView loadModule(ModuleLoadParams loadParams, ModuleList public abstract HippyEngineContext getEngineContext(); + public abstract V8 getV8(); + + public abstract void saveInstanceState(); + + public abstract void saveInstanceState(Object params); + + public abstract HippyRootView restoreInstanceState(ArrayList domNodeRecordList, + HippyEngine.ModuleLoadParams loadParams, boolean isSync); + + public abstract void destroyInstanceState(HippyRootView rootView); + + public abstract void runScript(@NonNull String script); + public interface BackPressHandler { void handleBackPress(); @@ -216,6 +238,24 @@ public enum EngineState { DESTROYED } + public enum DebugMode { + None, // production + Dev, + UserLocal, // open Hippy remote debug in production. just like debugMode=None but use local bundle, not from remote server + } + + public enum V8SnapshotType { + NoSnapshot, CreateSnapshot, UseSnapshot + } + + public static class V8InitParams { + public long initialHeapSize; + public long maximumHeapSize; + public int type; + public String uri; // blob_uri, Currently only supports the file protocol + public ByteBuffer blob; + } + // Hippy 引擎初始化时的参数设置 @SuppressWarnings("deprecation") public static class EngineInitParams { @@ -233,12 +273,16 @@ public static class EngineInitParams { public HippyBundleLoader jsPreloadAssetsPath; // 可选参数 指定需要预加载的业务模块bundle 文件路径 public HippyBundleLoader jsPreloadFilePath; - public boolean debugMode = false; + public DebugMode debugMode = DebugMode.None; // 可选参数 是否开启调试模式,默认为false,不开启 // 可选参数 Hippy Server的jsbundle名字,默认为"index.bundle"。debugMode = true时有效 - public final String debugBundleName = "index.bundle"; + public String debugBundleName = "index.bundle"; // 可选参数 Hippy Server的Host。默认为"localhost:38989"。debugMode = true时有效 public String debugServerHost = "localhost:38989"; + // optional args, Hippy Debug ws name as the componentName when you need differ engine to debug + public String debugComponentName = ""; + // optional args, Hippy Server url using remote debug in no usb (if not empty will replace debugServerHost and debugBundleName). debugMode = true take effect + public String remoteServerUrl = ""; // 可选参数 自定义的,用来提供Native modules、JavaScript modules、View controllers的管理器。1个或多个 public List providers; //Optional is use V8 serialization or json @@ -272,10 +316,10 @@ public static class EngineInitParams { // 设置Hippy引擎的组,同一组的HippyEngine,会共享C层的v8 引擎实例。 默认值为-1(无效组,即不属于任何group组) public int groupId = -1; // 可选参数 日志输出 - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated public HippyLogAdapter logAdapter; + public V8InitParams v8InitParams; public boolean enableTurbo; + public boolean runningOnTVPlatform; protected void check() { if (context == null) { @@ -287,7 +331,8 @@ protected void check() { EngineInitParams.class.getName() + " imageLoader must not be null!"); } if (sharedPreferencesAdapter == null) { - sharedPreferencesAdapter = new DefaultSharedPreferencesAdapter(context); + sharedPreferencesAdapter = new DefaultSharedPreferencesAdapter( + context.getApplicationContext()); } if (exceptionHandler == null) { exceptionHandler = new DefaultExceptionHandler(); @@ -299,7 +344,8 @@ protected void check() { executorSupplier = new DefaultExecutorSupplierAdapter(); } if (storageAdapter == null) { - storageAdapter = new DefaultStorageAdapter(context, executorSupplier.getDBExecutor()); + storageAdapter = new DefaultStorageAdapter(context.getApplicationContext(), + executorSupplier.getDBExecutor()); } if (engineMonitor == null) { engineMonitor = new DefaultEngineMonitorAdapter(); @@ -320,7 +366,7 @@ protected void check() { providers = new ArrayList<>(); } providers.add(0, new HippyCoreAPI()); - if (!debugMode) { + if (debugMode != DebugMode.Dev && v8InitParams.type == V8SnapshotType.NoSnapshot.ordinal()) { if (TextUtils.isEmpty(coreJSAssetsPath) && TextUtils.isEmpty(coreJSFilePath)) { throw new RuntimeException( "Hippy: debugMode=true, initParams.coreJSAssetsPath and coreJSFilePath both null!"); @@ -348,7 +394,7 @@ public static class ModuleLoadParams { // 可选参数 传递给前端的rootview:比如:Hippy.entryPage: class App extends Component public HippyMap jsParams; // 可选参数 目前只有一个用处:映射:"CustomViewCreator" <==> 宿主自定义的一个HippyCustomViewCreator(这个creator还得通过ModuleParams.Builder.setCustomViewCreator来指定才行) - public Map nativeParams; + public Map nativeParams; // 可选参数 方便对将本View和hippyContext进行绑定。对于这种场景时有用:某些View组件的创建先于业务模块初始化的时机(也就是View组件的预先创建、预加载)。 public HippyInstanceContext hippyContext; // 可选参数 Bundle加载器,老式用法,不建议使用(若一定要使用,则会覆盖jsAssetsPath,jsFilePath的值)。参见jsAssetsPath,jsFilePath @@ -448,6 +494,8 @@ public interface EngineListener { @SuppressWarnings("unused") public interface ModuleListener { + default void onLoadCompletedInCurrentThread(ModuleLoadStatus statusCode, String msg, HippyRootView hippyRootView) {} + void onLoadCompleted(ModuleLoadStatus statusCode, String msg, HippyRootView hippyRootView); @SuppressWarnings("SameReturnValue") diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyEngineContext.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyEngineContext.java index 9931eae0ae3..cab64bf112c 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyEngineContext.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyEngineContext.java @@ -15,6 +15,7 @@ */ package com.tencent.mtt.hippy; +import androidx.annotation.Nullable; import com.tencent.mtt.hippy.bridge.HippyBridgeManager; import com.tencent.mtt.hippy.common.ThreadExecutor; import com.tencent.mtt.hippy.devsupport.DevSupportManager; @@ -23,9 +24,20 @@ import com.tencent.mtt.hippy.uimanager.RenderManager; import com.tencent.mtt.hippy.utils.TimeMonitor; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + @SuppressWarnings("unused") public interface HippyEngineContext { + String getComponentName(); + + boolean isRunningOnTVPlatform(); + + @Nullable + Map getNativeParams(); + HippyGlobalConfigs getGlobalConfigs(); HippyModuleManager getModuleManager(); @@ -40,8 +52,11 @@ public interface HippyEngineContext { RenderManager getRenderManager(); + @Deprecated HippyRootView getInstance(int id); + HippyRootView getInstance(); + void addInstanceLifecycleEventListener(HippyInstanceLifecycleEventListener listener); void removeInstanceLifecycleEventListener(HippyInstanceLifecycleEventListener listener); @@ -55,4 +70,15 @@ public interface HippyEngineContext { TimeMonitor getStartTimeMonitor(); int getEngineId(); + + /** + * Add API providers after initializing hippy engine. + * + * @apiNote This method should be used before + * {@link HippyEngine#loadModule(HippyEngine.ModuleLoadParams)}, otherwise APIs or views + * defined in this providers maybe registered after use. + * + * @param apiProviders The Api providers need to be registered. + */ + void addApiProviders(List apiProviders); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyEngineHost.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyEngineHost.java index 2e2c0daaaae..2cb59b5f4a8 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyEngineHost.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyEngineHost.java @@ -36,11 +36,11 @@ public HippyEngineHost(Application application) { ContextHolder.initAppContext(mApplication); } - public HippyEngineManager createDebugHippyEngineManager(String debugJs) { + public HippyEngineManager createDebugHippyEngineManager(String debugJs, String remoteServerUrl) { HippyEngineManager.Builder builder = new HippyEngineManager.Builder(); builder.setHippyGlobalConfigs(getHippyGlobalConfigs()).setCoreBundleLoader(null) .setPackages(getPackages()).setSupportDev(true) - .setDebugJs(debugJs).setGroupId(getGroupId()); + .setDebugJs(debugJs).setGroupId(getGroupId()).setRemoteDebugUrl(remoteServerUrl); return builder.build(); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyEngineManager.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyEngineManager.java index b70dc21defb..66c7aff9598 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyEngineManager.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyEngineManager.java @@ -91,6 +91,7 @@ static class Builder { private String mDebugJs; private boolean enableV8Serialization = true; private int mGroupId = -1; + private String mRemoteServerUrl; Builder() { } @@ -138,6 +139,11 @@ Builder setGroupId(int groupId) { return this; } + Builder setRemoteDebugUrl(String remoteServerUrl) { + mRemoteServerUrl = remoteServerUrl; + return this; + } + HippyEngineManager build() { if (mCoreBundleLoader == null && !mSupportDev) { throw new RuntimeException("In non-debug mode, it must be set core bundle loader!"); @@ -151,7 +157,7 @@ HippyEngineManager build() { EngineInitParams params = new EngineInitParams(); mGlobalConfigs.toDebug(params); - params.debugMode = mSupportDev; + params.debugMode = mSupportDev ? DebugMode.Dev : DebugMode.None; if (mCoreBundleLoader instanceof HippyAssetBundleLoader) { params.coreJSAssetsPath = mCoreBundleLoader.getRawPath(); } else if (mCoreBundleLoader instanceof HippyFileBundleLoader) { @@ -166,6 +172,7 @@ HippyEngineManager build() { params.codeCacheTag = mCoreBundleLoader.getCodeCacheTag(); } params.groupId = mGroupId; + params.remoteServerUrl = mRemoteServerUrl; params.check(); HippyEngineManager hippyEngineManager; diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyEngineManagerImpl.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyEngineManagerImpl.java index 8c88313dcf5..c2812dad55b 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyEngineManagerImpl.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyEngineManagerImpl.java @@ -1,7 +1,7 @@ /* * Tencent is pleased to support the open source community by making Hippy * available. - * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2018-2022 THL A29 Limited, a Tencent company. All rights reserved. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -14,14 +14,18 @@ */ package com.tencent.mtt.hippy; +import android.annotation.SuppressLint; import android.content.Context; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.text.TextUtils; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.tencent.mtt.hippy.adapter.monitor.HippyEngineMonitorAdapter; import com.tencent.mtt.hippy.adapter.monitor.HippyEngineMonitorEvent; +import com.tencent.mtt.hippy.adapter.monitor.HippyEngineMonitorPoint; import com.tencent.mtt.hippy.adapter.thirdparty.HippyThirdPartyAdapter; import com.tencent.mtt.hippy.bridge.HippyBridgeManager; import com.tencent.mtt.hippy.bridge.HippyBridgeManagerImpl; @@ -36,6 +40,9 @@ import com.tencent.mtt.hippy.devsupport.DevServerCallBack; import com.tencent.mtt.hippy.devsupport.DevSupportManager; import com.tencent.mtt.hippy.dom.DomManager; +import com.tencent.mtt.hippy.dom.node.DomNode; +import com.tencent.mtt.hippy.dom.node.DomNodeRecord; +import com.tencent.mtt.hippy.dom.node.NodeProps; import com.tencent.mtt.hippy.modules.HippyModuleManager; import com.tencent.mtt.hippy.modules.HippyModuleManagerImpl; import com.tencent.mtt.hippy.modules.javascriptmodules.EventDispatcher; @@ -44,9 +51,13 @@ import com.tencent.mtt.hippy.utils.LogUtils; import com.tencent.mtt.hippy.utils.TimeMonitor; import com.tencent.mtt.hippy.utils.UIThreadUtils; +import com.tencent.mtt.hippy.v8.V8; + import java.io.InputStream; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; @SuppressWarnings({"deprecation", "unused"}) @@ -57,6 +68,7 @@ public abstract class HippyEngineManagerImpl extends HippyEngineManager implemen static final String TAG = "HippyEngineManagerImpl"; static final int MSG_ENGINE_INIT_TIMEOUT = 100; + /** * All HippyCompoundView Instance collections,multi-thread read and write */ @@ -82,19 +94,26 @@ public abstract class HippyEngineManagerImpl extends HippyEngineManager implemen */ DevSupportManager mDevSupportManager; HippyEngineContextImpl mEngineContext; - // 从网络上加载jsbundle - final boolean mDebugMode; + V8 mHippyV8; // Hippy Server的jsbundle名字,调试模式下有效 final String mServerBundleName; // Hippy Server的host,调试模式下有效 private final String mServerHost; + // Hippy Debug ws name as the componentName when you need differ engine to debug + private final String mDebugComponentName; + // Hippy Server url using remote debug in no usb,only take effect in debugMode = true + private final String mRemoteServerUrl; final boolean enableV8Serialization; + final boolean mRunningOnTVPlatform; boolean mDevManagerInited = false; final TimeMonitor mStartTimeMonitor; boolean mHasReportEngineLoadResult = false; private final HippyThirdPartyAdapter mThirdPartyAdapter; + private final V8InitParams v8InitParams; + private Object mRestoreSyncObject = new Object(); + private boolean mRestoreSucceed = false; final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override @@ -127,12 +146,16 @@ public void handleMessage(Message msg) { this.mPreloadBundleLoader = preloadBundleLoader; this.mAPIProviders = params.providers; this.mDebugMode = params.debugMode; - this.mServerBundleName = params.debugMode ? params.debugBundleName : ""; - this.mStartTimeMonitor = new TimeMonitor(!params.debugMode); + this.mServerBundleName = isDevMode() ? params.debugBundleName : ""; + this.mStartTimeMonitor = new TimeMonitor(!isDevMode()); this.enableV8Serialization = params.enableV8Serialization; this.mServerHost = params.debugServerHost; + this.mDebugComponentName = params.debugComponentName; + this.mRemoteServerUrl = params.remoteServerUrl; this.mGroupId = params.groupId; this.mThirdPartyAdapter = params.thirdPartyAdapter; + this.v8InitParams = params.v8InitParams; + this.mRunningOnTVPlatform = params.runningOnTVPlatform; } /** @@ -156,11 +179,12 @@ public void initEngine(EngineListener listener) { mHandler.removeMessages(MSG_ENGINE_INIT_TIMEOUT); try { - mDevSupportManager = new DevSupportManager(mGlobalConfigs, mDebugMode, mServerHost, - mServerBundleName); + mDevSupportManager = new DevSupportManager(mGlobalConfigs, isDebugMode(), mServerHost, + mServerBundleName, mRemoteServerUrl); mDevSupportManager.setDevCallback(this); - if (mDebugMode) { + if (isDevMode()) { + mDevSupportManager.setDebugComponentName(mDebugComponentName); String url = mDevSupportManager.createResourceUrl(mServerBundleName); mCoreBundleLoader = new HippyRemoteBundleLoader(url); ((HippyRemoteBundleLoader) mCoreBundleLoader).setIsDebugMode(true); @@ -190,7 +214,7 @@ public void run() { } }); } - }); + }, false); } protected void onDestroy() { @@ -223,6 +247,83 @@ public HippyRootView loadModule(ModuleLoadParams loadParams, ModuleListener list return loadModule(loadParams, listener, null); } + @Override + public HippyRootView loadInstance(ModuleLoadParams loadParams, ModuleListener listener, + HippyRootView.OnLoadCompleteListener onLoadCompleteListener) { + if (loadParams == null) { + throw new RuntimeException("Hippy: loadModule loadParams must no be null"); + } + if (loadParams.context == null) { + throw new RuntimeException("Hippy: loadModule loadParams.context must no be null"); + } + if (!isDebugMode() && TextUtils.isEmpty(loadParams.jsAssetsPath) && TextUtils + .isEmpty(loadParams.jsFilePath) && v8InitParams.type == V8SnapshotType.NoSnapshot.ordinal()) { + throw new RuntimeException( + "Hippy: loadModule debugMode=true, loadParams.jsAssetsPath and jsFilePath both null!"); + } + if (mEngineContext != null) { + mEngineContext.setComponentName(loadParams.componentName); + mEngineContext.setNativeParams(loadParams.nativeParams); + } + if (loadParams.jsParams == null) { + loadParams.jsParams = new HippyMap(); + } + if (loadParams.hippyContext != null) { + loadParams.hippyContext.setModuleParams(loadParams); + } + if (!TextUtils.isEmpty(loadParams.jsAssetsPath)) { + loadParams.jsParams.pushString("sourcePath", loadParams.jsAssetsPath); + } else { + loadParams.jsParams.pushString("sourcePath", loadParams.jsFilePath); + } + mModuleListener = listener; + + HippyRootView view = new HippyRootView(loadParams); + + if (mCurrentState == EngineState.DESTROYED) { + notifyModuleLoaded(ModuleLoadStatus.STATUS_ENGINE_UNINIT, + "load module error wrong state, Engine destroyed", view); + return view; + } + if (onLoadCompleteListener != null) { + view.setOnLoadCompleteListener(onLoadCompleteListener); + } + view.setTimeMonitor(new TimeMonitor(!isDebugMode())); + view.getTimeMonitor().begine(); + view.getTimeMonitor().startEvent(HippyEngineMonitorEvent.MODULE_LOAD_EVENT_WAIT_ENGINE); + view.setOnResumeAndPauseListener(this); + view.setOnSizeChangedListener(this); + view.attachEngineManager(this); + mInstances.add(view); + mDevSupportManager.attachToHost(view); + if (!mDevManagerInited && isDebugMode()) { + mDevManagerInited = true; + } + + LogUtils.d(TAG, "internalLoadInstance start..."); + if (mCurrentState == EngineState.INITED) { + LogUtils.d(TAG, "in internalLoadInstance"); + if (mEngineContext.mInstanceLifecycleEventListeners != null) { + for (HippyInstanceLifecycleEventListener lifecycleEventListener : mEngineContext.mInstanceLifecycleEventListeners) { + lifecycleEventListener.onInstanceLoad(view.getId()); + } + } + view.attachToEngine(mEngineContext); + HippyMap launchParams = view.getLaunchParams(); + HippyBundleLoader loader = ((HippyInstanceContext) view.getContext()).getBundleLoader(); + LogUtils.d(TAG, "in internalLoadInstance before loadInstance"); + mEngineContext.getBridgeManager().loadInstance(view.getName(), view.getId(), launchParams); + if (isDebugMode()) { + notifyModuleLoaded(ModuleLoadStatus.STATUS_OK, null, view); + } + } else { + notifyModuleLoaded(ModuleLoadStatus.STATUS_ENGINE_UNINIT, + "error wrong state, Engine state not INITED, state:" + mCurrentState, view); + } + + return view; + } + @Override public HippyRootView loadModule(ModuleLoadParams loadParams, ModuleListener listener, HippyRootView.OnLoadCompleteListener onLoadCompleteListener) { @@ -232,12 +333,13 @@ public HippyRootView loadModule(ModuleLoadParams loadParams, ModuleListener list if (loadParams.context == null) { throw new RuntimeException("Hippy: loadModule loadParams.context must no be null"); } - if (!mDebugMode && TextUtils.isEmpty(loadParams.jsAssetsPath) && TextUtils + if (!isDevMode() && TextUtils.isEmpty(loadParams.jsAssetsPath) && TextUtils .isEmpty(loadParams.jsFilePath)) { - throw new RuntimeException( - "Hippy: loadModule debugMode=true, loadParams.jsAssetsPath and jsFilePath both null!"); } - + if (mEngineContext != null) { + mEngineContext.setComponentName(loadParams.componentName); + mEngineContext.setNativeParams(loadParams.nativeParams); + } if (loadParams.jsParams == null) { loadParams.jsParams = new HippyMap(); } @@ -261,15 +363,20 @@ public HippyRootView loadModule(ModuleLoadParams loadParams, ModuleListener list if (onLoadCompleteListener != null) { view.setOnLoadCompleteListener(onLoadCompleteListener); } - view.setTimeMonitor(new TimeMonitor(!mDebugMode)); - view.getTimeMonitor().begine(); - view.getTimeMonitor().startEvent(HippyEngineMonitorEvent.MODULE_LOAD_EVENT_WAIT_ENGINE); + TimeMonitor timeMonitor = new TimeMonitor(!isDevMode()); + timeMonitor.setParent(mStartTimeMonitor); + timeMonitor.begine(); + timeMonitor.startEvent(HippyEngineMonitorEvent.MODULE_LOAD_EVENT_WAIT_ENGINE); + timeMonitor.addPoint(HippyEngineMonitorPoint.FIRST_PAINT_START); + view.setTimeMonitor(timeMonitor); view.setOnResumeAndPauseListener(this); view.setOnSizeChangedListener(this); view.attachEngineManager(this); mInstances.add(view); - mDevSupportManager.attachToHost(view); - if (!mDevManagerInited && mDebugMode) { + if (isDevMode()) { + mDevSupportManager.attachToHost(view); + } + if (!mDevManagerInited && isDevMode()) { mDevManagerInited = true; } @@ -349,6 +456,19 @@ public HippyEngineContextImpl getEngineContext() { return mEngineContext; } + public V8 getV8() { + if (mEngineContext == null) { + return null; + } + if (mHippyV8 == null) { + long runTimeId = mEngineContext.getBridgeManager().getV8RuntimeId(); + if (runTimeId != HippyBridgeManagerImpl.V8_RUNTIME_ID_EMPTY) { + mHippyV8 = new V8(runTimeId); + } + } + return mHippyV8; + } + @Override public void onEngineResume() { if (mEngineContext != null && mEngineContext.mEngineLifecycleEventListeners != null) { @@ -401,6 +521,17 @@ public void onEnginePause() { } } + @Override + public void onFontChanged(final int rootId) { + final DomManager domManager = mEngineContext.getDomManager(); + getThreadExecutor().postOnDomThread(new Runnable() { + @Override + public void run() { + domManager.onFontChanged(rootId); + } + }); + } + @Override public void sendEvent(String event, Object params, BridgeTransferType transferType) { if (mEngineContext != null && mEngineContext.getModuleManager() != null) { @@ -445,13 +576,196 @@ public void handleBackPress() { return onBackPressed(handler); } - @Override - public boolean isDebugMode() { - return mDebugMode; + private void addNodeRecordOfChild(ArrayList recordList, DomNode parent, int index, int rootId) { + int count = parent.getChildCount(); + for (int i = 0; i < count; i++) { + DomNode child = parent.getChildAt(i); + if (child == null) { + continue; + } + DomNodeRecord record = new DomNodeRecord(); + record.rootId = rootId; + record.id = child.getId(); + record.index = i; + record.pid = parent.getId(); + record.className = child.getViewClass(); + record.props = child.getTotalProps(); + recordList.add(record); + addNodeRecordOfChild(recordList, child, i, rootId); + } + } + + public void saveInstanceState() { + saveInstanceState(null); + } + + public void saveInstanceState(final Object params) { + final DomManager domManager = mEngineContext.getDomManager(); + if (mEngineContext == null || domManager == null || mThirdPartyAdapter == null) { + return; + } + + getThreadExecutor().postOnDomThread(new Runnable() { + @Override + public void run() { + int rootId = domManager.getRootNodeId(); + DomNode rootNode = domManager.getNode(rootId); + if (rootNode == null) { + LogUtils.e(TAG, "saveInstanceState root node is null!"); + return; + } + ArrayList recordList = new ArrayList<>(); + DomNodeRecord rootRecord = new DomNodeRecord(); + rootRecord.rootId = rootNode.getId(); + rootRecord.id = rootNode.getId(); + rootRecord.className = rootNode.getViewClass(); + rootRecord.props = new HippyMap(); + rootRecord.props.pushInt(NodeProps.WIDTH, Math.round(rootNode.getStyleWidth())); + rootRecord.props.pushInt(NodeProps.HEIGHT, Math.round(rootNode.getStyleHeight())); + recordList.add(rootRecord); + int count = rootNode.getChildCount(); + for (int i = 0; i < count; i++) { + DomNode child = rootNode.getChildAt(i); + if (child == null) { + continue; + } + DomNodeRecord record = new DomNodeRecord(); + record.rootId = rootId; + record.id = child.getId(); + record.index = i; + record.pid = rootId; + record.className = child.getViewClass(); + record.props = child.getTotalProps(); + recordList.add(record); + addNodeRecordOfChild(recordList, child, i, rootId); + } + mThirdPartyAdapter.saveInstanceState(recordList, params); + } + }); + } + + @Nullable + public HippyRootView restoreInstanceState(final ArrayList domNodeRecordList, + HippyEngine.ModuleLoadParams loadParams, final boolean isSync) { + if (domNodeRecordList == null || domNodeRecordList.isEmpty() || mEngineContext == null) { + return null; + } + mRestoreSucceed = false; + final long start = System.currentTimeMillis(); + final DomManager domManager = mEngineContext.getDomManager(); + final RenderManager renderManager = mEngineContext.getRenderManager(); + final HippyInstanceContext context = new HippyInstanceContext(loadParams.context, loadParams); + context.setEngineContext(mEngineContext); + final HippyRootView tempRootView = new HippyRootView(context, loadParams); + TimeMonitor timeMonitor = new TimeMonitor(true); + timeMonitor.setParent(mStartTimeMonitor); + timeMonitor.begine(); + timeMonitor.startEvent(HippyEngineMonitorEvent.MODULE_LOAD_EVENT_RESTORE_INSTANCE_STATE); + tempRootView.setTimeMonitor(timeMonitor); + tempRootView.setOnSizeChangedListener(this); + final int tempRootId = tempRootView.getId(); + renderManager.getControllerManager().addFakeRootView(tempRootView); + + getThreadExecutor().postOnDomThread(new Runnable() { + @Override + public void run() { + try { + domManager.renderBatchStart(); + for (int i = 0; i < domNodeRecordList.size(); i++) { + DomNodeRecord domNodeRecord = domNodeRecordList.get(i); + if (domNodeRecord == null || domNodeRecord.id < 0) { + continue; + } + if (i == 0) { + int width = 0; + int height = 0; + if (domNodeRecord.className.equals(NodeProps.ROOT_NODE) && domNodeRecord.props != null) { + width = domNodeRecord.props.getInt(NodeProps.WIDTH); + height = domNodeRecord.props.getInt(NodeProps.HEIGHT); + } + domManager.createFakeRootNode(tempRootId, width, height); + if (domNodeRecord.className.equals(NodeProps.ROOT_NODE)) { + continue; + } + } + int pid = domNodeRecord.pid; + if (pid % 10 == 0) { + pid = tempRootId; + } else { + pid = 0 - pid; + } + int id = 0 - domNodeRecord.id; + domManager.createNode(tempRootView, tempRootId, id, pid, domNodeRecord.index, + domNodeRecord.className, domNodeRecord.tagName, domNodeRecord.props); + } + domManager.screenshotBatchEnd(isSync); + if (isSync) { + synchronized (mRestoreSyncObject) { + mRestoreSucceed = true; + mRestoreSyncObject.notify(); + } + } + } catch (Exception e) { + LogUtils.w("restoreInstanceState", "dom restore exception: " + e.getMessage()); + domManager.screenshotBatchStop(isSync); + if (isSync) { + synchronized (mRestoreSyncObject) { + mRestoreSyncObject.notify(); + } + } + } + } + }); + if (isSync) { + try { + synchronized (mRestoreSyncObject) { + mRestoreSyncObject.wait(); + LogUtils.d("restoreInstanceState", "dom batch end: " + (System.currentTimeMillis() - start)); + if (!mRestoreSucceed) { + LogUtils.w("restoreInstanceState", "restore dom node failed!!"); + destroyInstanceState(tempRootView); + return null; + } + } + domManager.flushPendingBatches(); + LogUtils.d("restoreInstanceState", "render batch end: " + (System.currentTimeMillis() - start)); + } catch (Exception e) { + LogUtils.w("restoreInstanceState", "render restore exception: " + e.getMessage()); + destroyInstanceState(tempRootView); + return null; + } + } + return tempRootView; + } + + public void destroyInstanceState(HippyRootView rootView) { + if (rootView == null || mEngineContext == null) { + return; + } + + rootView.setOnSizeChangedListener(null); + final int rootId = rootView.getId(); + final DomManager domManager = mEngineContext.getDomManager(); + getThreadExecutor().postOnDomThread(new Runnable() { + @Override + public void run() { + domManager.deleteNode(rootId); + } + }); + + if (mInstances.contains(rootView)) { + mInstances.remove(rootView); + } + } + + public void runScript(@NonNull String script) { + if (mEngineContext != null) { + mEngineContext.runScript(script); + } } private void notifyModuleLoaded(final ModuleLoadStatus statusCode, final String msg, - final HippyRootView hippyRootView) { + final HippyRootView hippyRootView) { if (mModuleListener != null) { if (UIThreadUtils.isOnUiThread()) { if (mModuleListener != null) { @@ -479,40 +793,33 @@ void notifyEngineInitialized(EngineInitStatus statusCode, Throwable e) { preloadModule(mPreloadBundleLoader); } - if (UIThreadUtils.isOnUiThread()) { - mStartTimeMonitor.end(); - reportEngineLoadResult( - mCurrentState == EngineState.INITED ? HippyEngineMonitorAdapter.ENGINE_LOAD_RESULT_SUCCESS + Runnable action = new Runnable() { + @Override + public void run() { + if (mCurrentState != EngineState.DESTROYED) { + mStartTimeMonitor.end(); + mStartTimeMonitor.addPoint(HippyEngineMonitorPoint.BRIDGE_STARTUP_END); + reportEngineLoadResult(mCurrentState == EngineState.INITED + ? HippyEngineMonitorAdapter.ENGINE_LOAD_RESULT_SUCCESS : HippyEngineMonitorAdapter.ENGINE_LOAD_RESULT_ERROR, e); - for (EngineListener listener : mEventListeners) { - listener.onInitialized(statusCode, e == null ? null : e.toString()); - } - mEventListeners.clear(); - } else { - final EngineInitStatus code = statusCode; - final Throwable error = e; - UIThreadUtils.runOnUiThread(new Runnable() { - @Override - public void run() { - if (mCurrentState != EngineState.DESTROYED) { - mStartTimeMonitor.end(); - reportEngineLoadResult(mCurrentState == EngineState.INITED - ? HippyEngineMonitorAdapter.ENGINE_LOAD_RESULT_SUCCESS - : HippyEngineMonitorAdapter.ENGINE_LOAD_RESULT_ERROR, error); - } + } - for (EngineListener listener : mEventListeners) { - listener.onInitialized(code, error == null ? null : error.toString()); - } - mEventListeners.clear(); + for (EngineListener listener : mEventListeners) { + listener.onInitialized(statusCode, e == null ? null : e.toString()); } - }); + mEventListeners.clear(); + } + }; + if (UIThreadUtils.isOnUiThread()) { + action.run(); + } else { + UIThreadUtils.runOnUiThread(action); } } private void reportEngineLoadResult(int code, Throwable e) { mHandler.removeMessages(MSG_ENGINE_INIT_TIMEOUT); - if (!mDebugMode && !mHasReportEngineLoadResult) { + if (!isDevMode() && !mHasReportEngineLoadResult) { mHasReportEngineLoadResult = true; mGlobalConfigs.getEngineMonitorAdapter() .reportEngineLoadResult(code, mStartTimeMonitor.getTotalTime(), @@ -530,6 +837,9 @@ private synchronized void restartEngineInBackground() { } mStartTimeMonitor.begine(); mStartTimeMonitor.startEvent(HippyEngineMonitorEvent.ENGINE_LOAD_EVENT_INIT_INSTANCE); + mStartTimeMonitor.clearAllPoints(); + mStartTimeMonitor.addPoint(HippyEngineMonitorPoint.BRIDGE_STARTUP_START); + mStartTimeMonitor.addPoint(HippyEngineMonitorPoint.INIT_JS_FRAMEWORK_START); if (mCurrentState != EngineState.INITING) { mCurrentState = EngineState.ONRESTART; } @@ -539,6 +849,7 @@ private synchronized void restartEngineInBackground() { mEngineContext.getBridgeManager().initBridge(new Callback() { @Override public void callback(Boolean param, Throwable e) { + mStartTimeMonitor.addPoint(HippyEngineMonitorPoint.COMMON_EXECUTE_SOURCE_END); if (mCurrentState != EngineState.INITING && mCurrentState != EngineState.ONRESTART) { LogUtils.e(TAG, "initBridge callback error STATUS_WRONG_STATE, state=" + mCurrentState); notifyEngineInitialized(EngineInitStatus.STATUS_WRONG_STATE, e); @@ -586,10 +897,13 @@ private void internalLoadInstance(HippyRootView instance) { instance.attachToEngine(mEngineContext); HippyMap launchParams = instance.getLaunchParams(); HippyBundleLoader loader = ((HippyInstanceContext) instance.getContext()).getBundleLoader(); - if (!mDebugMode) { + + if (!isDevMode()) { if (loader != null) { - instance.getTimeMonitor() - .startEvent(HippyEngineMonitorEvent.MODULE_LOAD_EVENT_WAIT_LOAD_BUNDLE); + if (instance.getTimeMonitor() != null) { + instance.getTimeMonitor() + .startEvent(HippyEngineMonitorEvent.MODULE_LOAD_EVENT_WAIT_LOAD_BUNDLE); + } mEngineContext.getBridgeManager() .runBundle(instance.getId(), loader, mModuleListener, instance); } else { @@ -601,7 +915,7 @@ private void internalLoadInstance(HippyRootView instance) { LogUtils.d(TAG, "in internalLoadInstance before loadInstance"); mEngineContext.getBridgeManager() .loadInstance(instance.getName(), instance.getId(), launchParams); - if (mDebugMode) { + if (isDevMode()) { notifyModuleLoaded(ModuleLoadStatus.STATUS_OK, null, instance); } } @@ -655,7 +969,7 @@ public void run() { } }); } - }); + }, true); } @Override @@ -685,7 +999,7 @@ public void run() { @Override public void handleThreadUncaughtException(Thread t, Throwable e, Integer groupId) { - if (mDebugMode && mDevSupportManager != null) { + if (isDevMode() && mDevSupportManager != null) { mDevSupportManager.handleException(e); } else { mGlobalConfigs.getExceptionHandler().handleNativeException(new RuntimeException(e), false); @@ -719,15 +1033,42 @@ public class HippyEngineContextImpl implements HippyEngineContext { */ private final DomManager mDomManager; - public HippyEngineContextImpl(boolean isDevModule, String debugServerHost) { + private String mComponentName; + private Map mNativeParams; + + public HippyEngineContextImpl(DebugMode debugMode, String debugServerHost) { mModuleManager = new HippyModuleManagerImpl(this, mAPIProviders); mBridgeManager = new HippyBridgeManagerImpl(this, mCoreBundleLoader, - HippyEngineManagerImpl.this.getBridgeType(), - enableV8Serialization, isDevModule, debugServerHost, mGroupId, mThirdPartyAdapter); + HippyEngineManagerImpl.this.getBridgeType(), enableV8Serialization, + debugMode, debugServerHost, mGroupId, mThirdPartyAdapter, v8InitParams); mRenderManager = new RenderManager(this, mAPIProviders); mDomManager = new DomManager(this); } + public void setComponentName(String componentName) { + mComponentName = componentName; + } + + public void setNativeParams(Map nativeParams) { + mNativeParams = nativeParams; + } + + @Override + public boolean isRunningOnTVPlatform() { + return mRunningOnTVPlatform; + } + + @Override + @Nullable + public Map getNativeParams() { + return mNativeParams; + } + + @Override + public String getComponentName() { + return mComponentName; + } + @Override public HippyGlobalConfigs getGlobalConfigs() { return mGlobalConfigs; @@ -773,6 +1114,17 @@ public HippyRootView getInstance(int id) { return null; } + @Override + public HippyRootView getInstance() { + for (HippyRootView rootView : mInstances) { + //noinspection ResourceType + if (rootView.getId() >= 0) { + return rootView; + } + } + return null; + } + @Override public void addInstanceLifecycleEventListener(HippyInstanceLifecycleEventListener listener) { if (mInstanceLifecycleEventListeners == null) { @@ -813,7 +1165,7 @@ public void removeEngineLifecycleEventListener(HippyEngineLifecycleEventListener @Override public void handleException(Throwable throwable) { - if (mDebugMode && mDevSupportManager != null) { + if (isDevMode() && mDevSupportManager != null) { mDevSupportManager.handleException(throwable); } else { if (throwable instanceof HippyJsException) { @@ -837,8 +1189,18 @@ public int getEngineId() { return HippyEngineManagerImpl.this.getId(); } - public void destroyBridge(Callback callback) { - mBridgeManager.destroyBridge(callback); + @Override + public void addApiProviders(List apiProviders) { + mModuleManager.addModules(apiProviders); + mRenderManager.getControllerManager().addControllers(apiProviders); + } + + public void destroyBridge(Callback callback, boolean isReload) { + mBridgeManager.destroyBridge(callback, isReload); + } + + void runScript(@NonNull String script) { + mBridgeManager.runScript(script); } public void destroy() { @@ -860,7 +1222,9 @@ public void destroy() { if (mEngineLifecycleEventListeners != null) { mEngineLifecycleEventListeners.clear(); } + if (mNativeParams != null) { + mNativeParams.clear(); + } } } - } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyGlobalConfigs.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyGlobalConfigs.java index 507a43da575..4cb4b578820 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyGlobalConfigs.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyGlobalConfigs.java @@ -48,7 +48,7 @@ public class HippyGlobalConfigs { */ private final HippySharedPreferencesAdapter mSharedPreferencesAdapter; - private final Context mContext; + private Context mContext; /** * Crash Handler @@ -143,17 +143,16 @@ public void destroyIfNeed() { if (mHttpAdapter != null) { mHttpAdapter.destroyIfNeed(); } - if (mStorageAdapter != null) { mStorageAdapter.destroyIfNeed(); } - if (mExecutorSupplierAdapter != null) { mExecutorSupplierAdapter.destroyIfNeed(); } if (mImageLoaderAdapter != null) { mImageLoaderAdapter.destroyIfNeed(); } + mContext = null; } catch (Throwable e) { LogUtils.d("HippyGlobalConfigs", "destroyIfNeed: " + e.getMessage()); } @@ -252,7 +251,6 @@ public static class Builder { private HippyLogAdapter mLogAdapter; - public HippyLogAdapter getLogAdapter() { return mLogAdapter; } @@ -324,7 +322,8 @@ public HippyGlobalConfigs build() { throw new IllegalArgumentException("HippyGlobalConfigs Context must is not null!"); } if (mSharedPreferencesAdapter == null) { - mSharedPreferencesAdapter = new DefaultSharedPreferencesAdapter(mContext); + mSharedPreferencesAdapter = new DefaultSharedPreferencesAdapter( + mContext.getApplicationContext()); } if (mExceptionHandler == null) { mExceptionHandler = new DefaultExceptionHandler(); @@ -336,7 +335,7 @@ public HippyGlobalConfigs build() { mExecutorSupplierAdapter = new DefaultExecutorSupplierAdapter(); } if (mStorageAdapter == null) { - mStorageAdapter = new DefaultStorageAdapter(mContext, + mStorageAdapter = new DefaultStorageAdapter(mContext.getApplicationContext(), mExecutorSupplierAdapter.getDBExecutor()); } if (mEngineMonitorAdapter == null) { diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyInstanceContext.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyInstanceContext.java index 1c1741287fd..237207c3959 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyInstanceContext.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyInstanceContext.java @@ -98,12 +98,6 @@ public void unregisterInstanceDestroyListener(InstanceDestroyListener listener) } void notifyInstanceDestroy() { - if (mModuleParams != null) { - @SuppressWarnings("rawtypes") Map map = mModuleParams.nativeParams; - if (map != null) { - map.clear(); - } - } if (mDestroyListeners != null && mDestroyListeners.size() > 0) { Iterable listeners = mDestroyListeners.getNotifyListeners(); for (InstanceDestroyListener l : listeners) { diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyNormalEngineManager.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyNormalEngineManager.java index 0d7dfefbd8d..2714e72ecac 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyNormalEngineManager.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyNormalEngineManager.java @@ -62,7 +62,7 @@ public int getBridgeType() { @Override public void handleThreadUncaughtException(Thread t, Throwable e, Integer groupId) { super.handleThreadUncaughtException(t, e, groupId); - if (mDebugMode && mDevSupportManager != null) { + if (isDevMode() && mDevSupportManager != null) { synchronized (mLock) { if (mThreadExecutor != null) { mThreadExecutor.destroy(); diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyRootView.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyRootView.java index cfc3eef37cc..b3fd6c6fdcf 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyRootView.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/HippyRootView.java @@ -28,6 +28,7 @@ import com.tencent.mtt.hippy.adapter.device.HippyDeviceAdapter; import com.tencent.mtt.hippy.adapter.monitor.HippyEngineMonitorEvent; +import com.tencent.mtt.hippy.adapter.monitor.HippyEngineMonitorPoint; import com.tencent.mtt.hippy.common.HippyMap; import com.tencent.mtt.hippy.common.HippyTag; import com.tencent.mtt.hippy.devsupport.DevFloatButton; @@ -71,6 +72,15 @@ public class HippyRootView extends FrameLayout { protected boolean mLoadCompleted = false; + public HippyRootView(Context context, HippyEngine.ModuleLoadParams loadParams) { + super(context); + mInstanceId = -10; + setId(mInstanceId); + HippyMap tagMap = HippyTag.createTagMap(NodeProps.ROOT_NODE, null); + setTag(tagMap); + mLoadParams = loadParams; + } + public HippyRootView(HippyEngine.ModuleLoadParams loadParams) { super(loadParams.hippyContext != null ? loadParams.hippyContext : new HippyInstanceContext(loadParams.context, loadParams)); @@ -100,12 +110,17 @@ public void onViewAdded(View child) { mLoadCompleted = true; if (mTimeMonitor != null) { mTimeMonitor.end(); + mTimeMonitor.addPoint(HippyEngineMonitorPoint.FIRST_PAINT_END); if (mOnLoadCompleteListener != null) { mOnLoadCompleteListener .onLoadComplete(mTimeMonitor.getTotalTime(), mTimeMonitor.getEvents()); } - mEngineContext.getGlobalConfigs().getEngineMonitorAdapter() - .reportModuleLoadComplete(this, mTimeMonitor.getTotalTime(), mTimeMonitor.getEvents()); + final int id = getId(); + if (id > 0) { + mEngineContext.getGlobalConfigs().getEngineMonitorAdapter() + .reportModuleLoadComplete(this, mTimeMonitor.getTotalTime(), + mTimeMonitor.getEvents()); + } } } } @@ -237,12 +252,6 @@ private GlobalLayoutListener getGlobalLayoutListener() { return mGlobalLayoutListener; } - public void startMonitorEvent(String eventName) { - if (mTimeMonitor != null) { - mTimeMonitor.startEvent(eventName); - } - } - public TimeMonitor getTimeMonitor() { return mTimeMonitor; } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/HippySingleThreadEngineManager.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/HippySingleThreadEngineManager.java index dcb4a056541..1d6cc960bb0 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/HippySingleThreadEngineManager.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/HippySingleThreadEngineManager.java @@ -73,7 +73,7 @@ public int getBridgeType() { @Override public void handleThreadUncaughtException(Thread t, Throwable e, Integer groupId) { super.handleThreadUncaughtException(t, e, groupId); - if (mDebugMode && mDevSupportManager != null) { + if (isDevMode() && mDevSupportManager != null) { synchronized (mLock) { if (mThreadExecutor != null) { mThreadExecutor.destroy(); diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/IHippyNativeLogHandler.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/IHippyNativeLogHandler.java deleted file mode 100644 index cc36ba890f8..00000000000 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/IHippyNativeLogHandler.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.tencent.mtt.hippy; - -@SuppressWarnings({"unused"}) -public interface IHippyNativeLogHandler { - - void onReceiveNativeLogMessage(String msg); -} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/DefaultLogAdapter.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/DefaultLogAdapter.java index 7caaa4d811c..363d1b225cb 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/DefaultLogAdapter.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/DefaultLogAdapter.java @@ -15,23 +15,29 @@ */ package com.tencent.mtt.hippy.adapter; -@SuppressWarnings({"unused"}) -public class DefaultLogAdapter implements HippyLogAdapter { - - @Override - public void log(String tag, String msg) { - - } - - @Override - public void init(int rootId, String module) { - - } - - @Override - public void upload(callBack callBack) { - callBack.onSuccess(); - } +import androidx.annotation.NonNull; +import com.tencent.mtt.hippy.utils.LogUtils; +public class DefaultLogAdapter implements HippyLogAdapter { + @Override + public void onReceiveLogMessage(int level, @NonNull String tag, @NonNull String msg) { + switch (level) { + case LOG_SEVERITY_INFO: + LogUtils.i(tag, msg); + break; + case LOG_SEVERITY_WARNING: + LogUtils.w(tag, msg); + break; + case LOG_SEVERITY_ERROR: + // fall through + case LOG_SEVERITY_FATAL: + LogUtils.e(tag, msg); + break; + case LOG_SEVERITY_DEBUG: + // fall through + default: + LogUtils.d(tag, msg); + } + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/HippyLogAdapter.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/HippyLogAdapter.java index 70a8ad07422..4c28aa09336 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/HippyLogAdapter.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/HippyLogAdapter.java @@ -15,17 +15,15 @@ */ package com.tencent.mtt.hippy.adapter; -@SuppressWarnings({"EmptyMethod", "unused"}) -public interface HippyLogAdapter { - - void log(String tag, String msg); +import androidx.annotation.NonNull; - void init(int rootId, String module); - - void upload(callBack callBack); +public interface HippyLogAdapter { - interface callBack { + int LOG_SEVERITY_DEBUG = -1; + int LOG_SEVERITY_INFO = 0; + int LOG_SEVERITY_WARNING = 1; + int LOG_SEVERITY_ERROR = 2; + int LOG_SEVERITY_FATAL = 3; - void onSuccess(); - } + void onReceiveLogMessage(int level, @NonNull String tag, @NonNull String msg); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/font/DefaultFontScaleAdapter.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/font/DefaultFontScaleAdapter.java index fcbcff1d81f..f20e21700f8 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/font/DefaultFontScaleAdapter.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/font/DefaultFontScaleAdapter.java @@ -15,6 +15,7 @@ */ package com.tencent.mtt.hippy.adapter.font; +import androidx.annotation.Nullable; import com.tencent.mtt.hippy.utils.LogUtils; @SuppressWarnings({"unused"}) @@ -31,9 +32,16 @@ public CharSequence getEmoticonText(CharSequence text, int fontSize) { } @Override + @Nullable public String getCustomFontFilePath(String fontFamilyName, int style) { LogUtils.d("DefaultFontScaleAdapter", "getCustomFontFilePath fontFamilyName=" + fontFamilyName + ", style=" + style); return null; } + + @Override + @Nullable + public String getCustomDefaultFontFamily() { + return null; + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/font/HippyFontScaleAdapter.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/font/HippyFontScaleAdapter.java index d063ecf5f53..a8bf5f1bedb 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/font/HippyFontScaleAdapter.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/font/HippyFontScaleAdapter.java @@ -15,6 +15,8 @@ */ package com.tencent.mtt.hippy.adapter.font; +import androidx.annotation.Nullable; + @SuppressWarnings("SameReturnValue") public interface HippyFontScaleAdapter { @@ -23,5 +25,9 @@ public interface HippyFontScaleAdapter { @SuppressWarnings("unused") CharSequence getEmoticonText(CharSequence text, int fontSize); + @Nullable String getCustomFontFilePath(String fontFamilyName, int style); + + @Nullable + String getCustomDefaultFontFamily(); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/http/DefaultHttpAdapter.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/http/DefaultHttpAdapter.java index 4c29501be34..f6dd4b1df4d 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/http/DefaultHttpAdapter.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/http/DefaultHttpAdapter.java @@ -17,207 +17,508 @@ import android.text.TextUtils; +import android.webkit.CookieManager; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.tencent.mtt.hippy.common.HippyArray; -import com.tencent.mtt.hippy.modules.nativemodules.network.NetworkModule; +import com.tencent.mtt.hippy.common.HippyMap; +import com.tencent.mtt.hippy.modules.Promise; +import com.tencent.mtt.hippy.utils.LogUtils; +import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.zip.GZIPInputStream; public class DefaultHttpAdapter implements HippyHttpAdapter { - private ExecutorService mExecutorService; + private static final String TAG = "DefaultHttpAdapter"; + private ExecutorService mExecutorService; - private static URL toURL(String url) throws MalformedURLException { - URL _URL = new URL(url); + protected void execute(Runnable runnable) { + if (mExecutorService == null) { + mExecutorService = Executors.newFixedThreadPool(3); + } + mExecutorService.execute(runnable); + } - // 有个别 URL 在 path 和 querystring 之间缺少 / 符号,需补上 - if (_URL.getPath() == null || "".equals(_URL.getPath())) { - if (_URL.getFile() != null && _URL.getFile().startsWith("?")) { - // 补斜杠符号 - int idx = url.indexOf('?'); - if (idx != -1) { - String sb = url.substring(0, idx) - + '/' - + url.substring(idx); - _URL = new URL(sb); + @Override + public void fetch(final HippyMap initParams, final Promise promise, Map nativeParams) { + final HippyHttpRequest httpRequest = generateHttpRequest(initParams, promise, nativeParams); + if (httpRequest != null) { + handleRequestCookie(httpRequest); + sendRequest(httpRequest, new HttpTaskCallbackImpl(promise)); + } + } - // System.out.println("toURL : " + _URL.toString()); + @Override + public void getCookie(String url, Promise promise) { + CookieManager cookieManager = getCookieManager(); + if (cookieManager == null) { + promise.reject("get cookie manager failed!"); + return; } - } + String cookie = cookieManager.getCookie(url); + promise.resolve(cookie); + } - // 分支走到这里,没有path也没有file,证明为一个没有/的host,例如: - // http://m.cnbeta.com(注意:后面没有/) - if (_URL.getFile() == null || "".equals(_URL.getFile())) { - String sb = url - + "/"; - _URL = new URL(sb); - } + @Override + public void setCookie(String url, String keyValue, String expires) { + if (!TextUtils.isEmpty(url) && keyValue != null) { + if (keyValue.trim().length() == 0) { + clearCookie(url); + } else { + saveCookie2Manager(url, keyValue, expires); + } + } + } + + @Override + public void sendRequest(final HippyHttpRequest request, final HttpTaskCallback callback) { + execute(new Runnable() { + @Override + public void run() { + if (callback == null) { + return; + } + HippyHttpResponse response = null; + HttpURLConnection connection = null; + try { + connection = createConnection(request); + fillHeader(connection, request); + fillPostBody(connection, request); + response = createResponse(connection); + callback.onTaskSuccess(request, response); + } catch (Throwable e) { + callback.onTaskFailed(request, e); + } finally { + if (response != null) { + response.close(); + } + if (connection != null) { + connection.disconnect(); + } + } + } + }); } - return _URL; - } - private void execute(Runnable runnable) { - if (mExecutorService == null) { - mExecutorService = Executors.newFixedThreadPool(3); + protected HippyHttpResponse createResponse(HttpURLConnection urlConnection) throws Exception { + HippyHttpResponse response = new HippyHttpResponse(); + parseResponseHeaders(urlConnection, response); + boolean isException = false; + InputStream inputStream = null; + try { + inputStream = urlConnection.getInputStream(); + } catch (IOException ie) { + ie.printStackTrace(); + isException = true; + } + + InputStream errorStream = null; + if (isException || urlConnection.getResponseCode() >= 400) { + try { + errorStream = urlConnection.getErrorStream(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (isException) { + inputStream = errorStream; + } + response.setInputStream(inputStream); + response.setErrorStream(errorStream); + response.setResponseMessage(urlConnection.getResponseMessage()); + + return response; } - mExecutorService.execute(runnable); - } - @Override - public void sendRequest(final HippyHttpRequest request, final HttpTaskCallback callback) { - execute(new Runnable() { - @Override - public void run() { - if (callback == null) { - return; + protected HttpURLConnection createConnection(HippyHttpRequest request) throws Exception { + if (TextUtils.isEmpty(request.getUrl())) { + throw new RuntimeException("url is null"); } - HippyHttpResponse response = null; - HttpURLConnection connection = null; - try { - connection = createConnection(request); - fillHeader(connection, request); - fillPostBody(connection, request); - response = createResponse(connection); - - callback.onTaskSuccess(request, response); - } catch (Throwable e) { - callback.onTaskFailed(request, e); - } finally { - if (response != null) { - response.close(); - } - if (connection != null) { - connection.disconnect(); - } - } - } - }); - } - - HippyHttpResponse createResponse(HttpURLConnection urlConnection) throws Exception { - HippyHttpResponse response = new HippyHttpResponse(); - parseResponseHeaders(urlConnection, response); - boolean isException = false; - InputStream inputStream = null; - try { - inputStream = urlConnection.getInputStream(); - } catch (IOException ie) { - ie.printStackTrace(); - isException = true; - } - - InputStream errorStream = null; - if (isException || urlConnection.getResponseCode() >= 400) { - try { - errorStream = urlConnection.getErrorStream(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - if (isException) { - inputStream = errorStream; - } - response.setInputStream(inputStream); - response.setErrorStream(errorStream); - response.setResponseMessage(urlConnection.getResponseMessage()); - - return response; - } - - HttpURLConnection createConnection(HippyHttpRequest request) throws Exception { - if (TextUtils.isEmpty(request.getUrl())) { - throw new RuntimeException("url is null"); - } - URL url = toURL(request.getUrl()); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - - if (TextUtils.isEmpty(request.getMethod())) { - request.setMethod("GET"); - } - connection.setRequestMethod(request.getMethod()); - connection.setUseCaches(request.isUseCaches()); - connection.setInstanceFollowRedirects(request.isInstanceFollowRedirects()); - - connection.setConnectTimeout(request.getConnectTimeout()); - connection.setReadTimeout(request.getReadTimeout()); - - if (request.getMethod().equalsIgnoreCase("POST") || request.getMethod().equalsIgnoreCase("PUT") - || request.getMethod().equalsIgnoreCase("PATCH")) { - - connection.setDoOutput(true); - } - - return connection; - } - - void fillHeader(URLConnection urlConnection, HippyHttpRequest request) { - Map headerMap = request.getHeaders(); - if (headerMap != null && !headerMap.isEmpty()) { - Set keySets = headerMap.keySet(); - for (String key : keySets) { - Object obj = headerMap.get(key); - if (obj instanceof String) { - urlConnection.setRequestProperty(key, (String) obj); - } else if (obj instanceof List) { - @SuppressWarnings("unchecked") List requestProperties = (List) obj; - if (!requestProperties.isEmpty()) { - for (String oneReqProp : requestProperties) { - if (!TextUtils.isEmpty(oneReqProp)) { - urlConnection.addRequestProperty(key, oneReqProp); - } + URL url = toURL(request.getUrl()); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + + if (TextUtils.isEmpty(request.getMethod())) { + request.setMethod("GET"); + } + connection.setRequestMethod(request.getMethod()); + connection.setUseCaches(request.isUseCaches()); + connection.setInstanceFollowRedirects(request.isInstanceFollowRedirects()); + + connection.setConnectTimeout(request.getConnectTimeout()); + connection.setReadTimeout(request.getReadTimeout()); + + if (request.getMethod().equalsIgnoreCase("POST") || request.getMethod() + .equalsIgnoreCase("PUT") + || request.getMethod().equalsIgnoreCase("PATCH")) { + + connection.setDoOutput(true); + } + + return connection; + } + + protected void fillHeader(URLConnection urlConnection, HippyHttpRequest request) { + Map headerMap = request.getHeaders(); + if (headerMap == null || headerMap.isEmpty()) { + return; + } + Set keySets = headerMap.keySet(); + for (String key : keySets) { + Object obj = headerMap.get(key); + if (obj instanceof String) { + urlConnection.setRequestProperty(key, (String) obj); + } else if (obj instanceof List) { + @SuppressWarnings("unchecked") List requestProperties = (List) obj; + if (requestProperties.isEmpty()) { + continue; + } + for (String oneReqProp : requestProperties) { + if (!TextUtils.isEmpty(oneReqProp)) { + urlConnection.addRequestProperty(key, oneReqProp); + } + } + } + } + } + + protected void fillPostBody(HttpURLConnection connection, HippyHttpRequest request) + throws IOException { + if (TextUtils.isEmpty(request.getBody())) { + return; + } + connection.setRequestProperty("Content-Length", request.getBody().getBytes().length + ""); + DataOutputStream out = new DataOutputStream(connection.getOutputStream()); + //TODO big stream will cause OOM; Progress callback is meaningless + out.write(request.getBody().getBytes()); + out.flush(); + out.close(); + } + + protected void parseResponseHeaders(HttpURLConnection httpConn, HippyHttpResponse response) + throws Exception { + if (httpConn == null) { + return; + } + response.setStatusCode(httpConn.getResponseCode()); + response.setRspHeaderMap(httpConn.getHeaderFields()); + } + + public void destroyIfNeed() { + if (mExecutorService != null && !mExecutorService.isShutdown()) { + mExecutorService.shutdown(); + mExecutorService = null; + } + } + + protected void handleRequestCookie(HippyHttpRequest httpRequest) { + String url = httpRequest.getUrl(); + HippyArray requestCookies = httpRequest.getRequestCookies(); + if (url == null) { + return; + } + if (requestCookies != null) { + saveCookie2Manager(url, requestCookies); + } + CookieManager cookieManager = getCookieManager(); + if (cookieManager != null) { + String cookie = cookieManager.getCookie(url); + if (!TextUtils.isEmpty(cookie)) { + httpRequest.addHeader(HttpHeader.REQ.COOKIE, cookie); + } + } + } + + protected HippyHttpRequest generateHttpRequest(HippyMap initParams, Promise promise, + @Nullable Map nativeParams) { + if (initParams == null) { + promise.reject("invalid request param"); + return null; + } + String url = initParams.getString("url"); + final String method = initParams.getString("method"); + if (TextUtils.isEmpty(url) || TextUtils.isEmpty(method)) { + promise.reject("no valid url for request"); + return null; + } + HippyHttpRequest httpRequest = new HippyHttpRequest(); + httpRequest.setConnectTimeout(10 * 1000); + httpRequest.setReadTimeout(10 * 1000); + String redirect = initParams.getString("redirect"); + httpRequest.setInstanceFollowRedirects( + !TextUtils.isEmpty(redirect) && TextUtils.equals("follow", redirect)); + httpRequest.setUseCaches(false); + httpRequest.setMethod(method); + httpRequest.setUrl(url); + HippyMap headers = initParams.getMap("headers"); + if (headers != null) { + httpRequest.setRequestCookies(headers.getArray("Cookie")); + hippyMapToRequestHeaders(httpRequest, headers); + } + String body = initParams.getString("body"); + httpRequest.setBody(body); + httpRequest.setNativeParams(nativeParams); + httpRequest.setInitParams(initParams); + return httpRequest; + } + + protected void hippyMapToRequestHeaders(HippyHttpRequest request, HippyMap map) { + if (request == null || map == null) { + return; + } + Set keys = map.keySet(); + for (String oneKey : keys) { + Object valueObj = map.get(oneKey); + if (valueObj instanceof HippyArray) { + HippyArray oneHeaderArray = (HippyArray) valueObj; + List headerValueArray = new ArrayList<>(); + for (int i = 0; i < oneHeaderArray.size(); i++) { + Object oneHeaderValue = oneHeaderArray.get(i); + if (oneHeaderValue instanceof Number) { + headerValueArray.add(oneHeaderValue + ""); + } else if (oneHeaderValue instanceof Boolean) { + headerValueArray.add(oneHeaderValue + ""); + } else if (oneHeaderValue instanceof String) { + headerValueArray.add((String) oneHeaderValue); + } else { + LogUtils.e("hippy_console", "Unsupported Request Header List Type"); + } + } + + if (!headerValueArray.isEmpty()) { + request.addHeader(oneKey, headerValueArray); + } + } else { + LogUtils.e("hippy_console", + "Unsupported Request Header Type, Header Field Should All be an Array!!!"); } - } } + } - } + protected void saveCookie2Manager(String url, @NonNull HippyArray cookieArr) { + for (int i = 0; i < cookieArr.size(); i++) { + String cookies = (String) cookieArr.get(i); + saveCookie2Manager(url, cookies, null); + } } - } - void fillPostBody(HttpURLConnection connection, HippyHttpRequest request) throws IOException { - if (TextUtils.isEmpty(request.getBody())) { - return; + @NonNull + protected String resetCookieIfNeeded(@NonNull String cookie, @Nullable String expires) { + String[] kv = cookie.split("="); + if (kv.length == 1 || (kv.length >= 2 && kv[1].trim().length() == 0)) { + return kv[0] + "=;Max-Age=0"; + } + if (!TextUtils.isEmpty(expires)) { + return cookie + ";expires=" + expires; + } + return cookie; } - connection.setRequestProperty("Content-Length", request.getBody().getBytes().length + ""); - DataOutputStream out = new DataOutputStream(connection.getOutputStream()); - //TODO big stream will cause OOM; Progress callback is meaningless - out.write(request.getBody().getBytes()); - out.flush(); - out.close(); - } - void parseResponseHeaders(HttpURLConnection httpConn, HippyHttpResponse response) - throws Exception { - if (httpConn == null) { - return; + protected void clearCookie(@NonNull String url) { + CookieManager cookieManager = getCookieManager(); + if (cookieManager == null) { + return; + } + String cookies = cookieManager.getCookie(url); + if (TextUtils.isEmpty(cookies)) { + return; + } + String[] cookieItems = cookies.split(";"); + for (String cookie : cookieItems) { + cookieManager.setCookie(url, (cookie + ";Max-Age=0")); + } + syncCookie(); + } + + protected void saveCookie2Manager(@NonNull String url, @Nullable String cookies, @Nullable String expires) { + CookieManager cookieManager = getCookieManager(); + if (cookieManager == null || cookies == null) { + return; + } + cookies = cookies.replaceAll("\\s+", ""); + String[] cookieItems = cookies.split(";"); + for (String cookie : cookieItems) { + if (cookie != null && cookie.trim().length() > 0) { + String newCookie = resetCookieIfNeeded(cookie, expires); + cookieManager.setCookie(url, newCookie); + } + } + syncCookie(); + } + + protected void syncCookie() { + if (getCookieManager() != null) { + getCookieManager().flush(); + } + } + + @Nullable + protected CookieManager getCookieManager() { + CookieManager cookieManager; + try { + cookieManager = CookieManager.getInstance(); + if (!cookieManager.acceptCookie()) { + cookieManager.setAcceptCookie(true); + } + } catch (IllegalArgumentException ex) { + // https://bugs.chromium.org/p/chromium/issues/detail?id=559720 + return null; + } catch (Exception exception) { + LogUtils.w(TAG, "getCookieManager: " + exception.getMessage()); + // We cannot catch MissingWebViewPackageException as it is in a private / system API + // class. This validates the exception's message to ensure we are only handling this + // specific exception. + // The exception class doesn't always contain the correct name as it depends on the OEM + // and OS version. It is better to check the message for clues regarding the exception + // as that is somewhat consistent across OEMs. + // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/webkit/WebViewFactory.java#348 + return null; + } + return cookieManager; } - response.setStatusCode(httpConn.getResponseCode()); - response.setRspHeaderMap(httpConn.getHeaderFields()); - } + protected class HttpTaskCallbackImpl implements HippyHttpAdapter.HttpTaskCallback { - public void destroyIfNeed() { - if (mExecutorService != null && !mExecutorService.isShutdown()) { - mExecutorService.shutdown(); - mExecutorService = null; + private final Promise mPromise; + + public HttpTaskCallbackImpl(Promise promise) { + mPromise = promise; + } + + @SuppressWarnings("CharsetObjectCanBeUsed") + @Override + public void onTaskSuccess(HippyHttpRequest request, HippyHttpResponse response) + throws Exception { + String respBody = null; + if (response.getInputStream() != null) { + InputStream inputStream = response.getInputStream(); + if (isGzipRequest(request)) { + inputStream = new GZIPInputStream(inputStream); // gzip解压 + } + StringBuilder sb = new StringBuilder(); + String readLine; + BufferedReader bfReader = new BufferedReader( + new InputStreamReader(inputStream, "UTF-8")); + while ((readLine = bfReader.readLine()) != null) { + sb.append(readLine).append("\r\n"); + } + respBody = sb.toString(); + } + + CookieManager cookieManager = getCookieManager(); + HippyMap respMap = new HippyMap(); + respMap.pushInt("statusCode", response.getStatusCode()); + respMap.pushString("statusLine", response.getResponseMessage()); + + HippyMap headerMap = new HippyMap(); + if (response.getRspHeaderMaps() != null && !response.getRspHeaderMaps().isEmpty()) { + Set keys = response.getRspHeaderMaps().keySet(); + for (String oneKey : keys) { + List value = response.getRspHeaderMaps().get(oneKey); + HippyArray oneHeaderFiled = new HippyArray(); + if (value != null && !value.isEmpty()) { + boolean hasSetCookie = false; + for (int i = 0; i < value.size(); i++) { + String valueStr = value.get(i); + oneHeaderFiled.pushString(valueStr); + if (HttpHeader.RSP.SET_COOKIE.equalsIgnoreCase(oneKey)) { + if (cookieManager != null) { + hasSetCookie = true; + cookieManager.setCookie(request.getUrl(), valueStr); + } + } + } + if (hasSetCookie) { + syncCookie(); + } + } + headerMap.pushArray(oneKey, oneHeaderFiled); + } + } + respMap.pushMap("respHeaders", headerMap); + if (respBody == null) { + respBody = ""; + } + respMap.pushString("respBody", respBody); + mPromise.resolve(respMap); + } + + @Override + public void onTaskFailed(HippyHttpRequest request, Throwable error) { + if (error != null) { + mPromise.resolve(error.getMessage()); + } + } } - } - public void handleRequestCookie(String url, HippyArray requestCookies, HippyHttpRequest httpRequest) { - NetworkModule.saveCookie2Manager(url, requestCookies); - String cookie = NetworkModule.getCookieManager().getCookie(url); - if (!TextUtils.isEmpty(cookie)) { - httpRequest.addHeader(HttpHeader.REQ.COOKIE, cookie); + private boolean isGzipRequest(HippyHttpRequest request) { + if (request == null) { + return false; + } + Map headers = request.getHeaders(); + if (headers == null) { + return false; + } + for (Map.Entry header : headers.entrySet()) { + String key = header.getKey(); + if (key != null && key.equalsIgnoreCase(HttpHeader.REQ.ACCEPT_ENCODING)) { + Object value = header.getValue(); + if (value instanceof ArrayList) { + //noinspection unchecked + for (String valueItem : (ArrayList) value) { + if (valueItem.equalsIgnoreCase("gzip") || valueItem.equalsIgnoreCase( + "deflate")) { + return true; + } + } + } + } + } + return false; + } + + private URL toURL(String url) throws MalformedURLException { + URL _URL = new URL(url); + + // 有个别 URL 在 path 和 querystring 之间缺少 / 符号,需补上 + if (_URL.getPath() == null || "".equals(_URL.getPath())) { + if (_URL.getFile() != null && _URL.getFile().startsWith("?")) { + // 补斜杠符号 + int idx = url.indexOf('?'); + if (idx != -1) { + String sb = url.substring(0, idx) + + '/' + + url.substring(idx); + _URL = new URL(sb); + + // System.out.println("toURL : " + _URL.toString()); + } + } + + // 分支走到这里,没有path也没有file,证明为一个没有/的host,例如: + // http://m.cnbeta.com(注意:后面没有/) + if (_URL.getFile() == null || "".equals(_URL.getFile())) { + String sb = url + + "/"; + _URL = new URL(sb); + } + + } + return _URL; } - } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/http/HippyHttpAdapter.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/http/HippyHttpAdapter.java index 974c4e45672..2a86f702fbb 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/http/HippyHttpAdapter.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/http/HippyHttpAdapter.java @@ -13,23 +13,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.tencent.mtt.hippy.adapter.http; -import com.tencent.mtt.hippy.common.HippyArray; +import com.tencent.mtt.hippy.common.HippyMap; +import com.tencent.mtt.hippy.modules.Promise; +import java.util.Map; public interface HippyHttpAdapter { - void sendRequest(HippyHttpRequest request, HttpTaskCallback callback); + void destroyIfNeed(); + + void sendRequest(final HippyHttpRequest request, final HttpTaskCallback callback); + + void fetch(final HippyMap initParams, final Promise promise, Map nativeParams); - void destroyIfNeed(); + void getCookie(String url, Promise promise); - void handleRequestCookie(String url, HippyArray requestCookies, HippyHttpRequest httpRequest); + void setCookie(String url, String keyValue, String expires); - interface HttpTaskCallback { + interface HttpTaskCallback { - void onTaskSuccess(HippyHttpRequest request, HippyHttpResponse response) throws Exception; + void onTaskSuccess(HippyHttpRequest request, HippyHttpResponse response) throws Exception; - @SuppressWarnings("unused") - void onTaskFailed(HippyHttpRequest request, Throwable error); - } + @SuppressWarnings("unused") + void onTaskFailed(HippyHttpRequest request, Throwable error); + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/http/HippyHttpRequest.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/http/HippyHttpRequest.java index 0f0c3e5ec5e..37b16293f8f 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/http/HippyHttpRequest.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/http/HippyHttpRequest.java @@ -13,10 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.tencent.mtt.hippy.adapter.http; import android.os.Build; +import androidx.annotation.Nullable; + +import com.tencent.mtt.hippy.common.HippyArray; +import com.tencent.mtt.hippy.common.HippyMap; + import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -25,134 +31,151 @@ @SuppressWarnings({"unused"}) public class HippyHttpRequest { - public static final int DEFAULT_TIMEOUT_MS = 3000; - - private static String USER_AGENT = null; - private int mConnectTimeout = DEFAULT_TIMEOUT_MS; - private int mReadTimeout = DEFAULT_TIMEOUT_MS; - private final Map mHeaderMap; - private String mUrl; - private boolean mUseCaches = true; - private String mMethod = "GET"; - private boolean mInstanceFollowRedirects = false; - private String mBody; - - public HippyHttpRequest() { - //noinspection unchecked,rawtypes - mHeaderMap = new HashMap(); - initUserAgent(); - - if (USER_AGENT != null) { - addHeader(HttpHeader.REQ.USER_AGENT, USER_AGENT); - } else { - System.err.println("user_agent is null!"); - } - } - - public String getUrl() { - return mUrl; - } - - public void setUrl(String url) { - this.mUrl = url; - } - - public void addHeader(String name, String value) { - mHeaderMap.put(name, value); - } - - public void addHeader(String name, List value) { - mHeaderMap.put(name, value); - } - - public Map getHeaders() { - return mHeaderMap; - } - - public int getConnectTimeout() { - return mConnectTimeout; - } - - public void setConnectTimeout(int time) { - mConnectTimeout = time; - } - - public int getReadTimeout() { - return mReadTimeout; - } - - public void setReadTimeout(int time) { - mReadTimeout = time; - } - - public boolean isUseCaches() { - return mUseCaches; - } - - public void setUseCaches(boolean useCaches) { - this.mUseCaches = useCaches; - } - - public String getMethod() { - return mMethod; - } - - public void setMethod(String method) { - this.mMethod = method; - } - - public boolean isInstanceFollowRedirects() { - return mInstanceFollowRedirects; - } - - public void setInstanceFollowRedirects(boolean instanceFollowRedirects) { - this.mInstanceFollowRedirects = instanceFollowRedirects; - } - - public String getBody() { - return mBody; - } - - public void setBody(String body) { - this.mBody = body; - } - - private void initUserAgent() { - if (USER_AGENT == null) { - Locale locale = Locale.getDefault(); - StringBuffer buffer = new StringBuffer(); - // Add version - final String version = Build.VERSION.RELEASE; - if (version.length() > 0) { - buffer.append(version); - } else { - // default to "1.0" - buffer.append("1.0"); - } - buffer.append("; "); - final String language = locale.getLanguage(); - buffer.append(language.toLowerCase()); - final String country = locale.getCountry(); - buffer.append("-"); - buffer.append(country.toLowerCase()); - // add the model for the release build - if (android.os.Build.VERSION.SDK_INT > 3 && "REL".equals(Build.VERSION.CODENAME)) { - final String model = Build.MODEL; - if (model.length() > 0) { - buffer.append("; "); - buffer.append(model); + public static final int DEFAULT_TIMEOUT_MS = 3000; + + private static String USER_AGENT = null; + private int mConnectTimeout = DEFAULT_TIMEOUT_MS; + private int mReadTimeout = DEFAULT_TIMEOUT_MS; + private final Map mHeaderMap; + private String mUrl; + private boolean mUseCaches = true; + private String mMethod = "GET"; + private boolean mInstanceFollowRedirects = false; + private String mBody; + @Nullable + private HippyMap mInitParams; + @Nullable + private HippyArray mRequestCookies; + @Nullable + private Map mNativeParams; + + public HippyHttpRequest() { + //noinspection unchecked,rawtypes + mHeaderMap = new HashMap(); + initUserAgent(); + + if (USER_AGENT != null) { + addHeader(HttpHeader.REQ.USER_AGENT, USER_AGENT); + } else { + System.err.println("user_agent is null!"); } - } - final String id = Build.ID; - if (id.length() > 0) { - buffer.append(" Build/"); - buffer.append(id); - } + } - final String base = "Mozilla/5.0 (Linux; U; Android %s) AppleWebKit/533.1 (KHTML, like Gecko) Mobile Safari/533.1"; + @Nullable + public HippyMap getInitParams() { + return mInitParams; + } + + public void setInitParams(@Nullable HippyMap initParams) { + mInitParams = initParams; + } + + @Nullable + public HippyArray getRequestCookies() { + return mRequestCookies; + } + + public void setRequestCookies(@Nullable HippyArray requestCookies) { + mRequestCookies = requestCookies; + } + + @Nullable + public Map getNativeParams() { + return mNativeParams; + } + + public void setNativeParams(@Nullable Map nativeParams) { + mNativeParams = nativeParams; + } + + public String getUrl() { + return mUrl; + } + + public void setUrl(String url) { + this.mUrl = url; + } + + public void addHeader(String name, String value) { + mHeaderMap.put(name, value); + } + + public void addHeader(String name, List value) { + mHeaderMap.put(name, value); + } + + public Map getHeaders() { + return mHeaderMap; + } - USER_AGENT = String.format(base, buffer); + public int getConnectTimeout() { + return mConnectTimeout; } - } + public void setConnectTimeout(int time) { + mConnectTimeout = time; + } + + public int getReadTimeout() { + return mReadTimeout; + } + + public void setReadTimeout(int time) { + mReadTimeout = time; + } + + public boolean isUseCaches() { + return mUseCaches; + } + + public void setUseCaches(boolean useCaches) { + this.mUseCaches = useCaches; + } + + public String getMethod() { + return mMethod; + } + + public void setMethod(String method) { + this.mMethod = method; + } + + public boolean isInstanceFollowRedirects() { + return mInstanceFollowRedirects; + } + + public void setInstanceFollowRedirects(boolean instanceFollowRedirects) { + this.mInstanceFollowRedirects = instanceFollowRedirects; + } + + public String getBody() { + return mBody; + } + + public void setBody(String body) { + this.mBody = body; + } + + private void initUserAgent() { + if (USER_AGENT == null) { + Locale locale = Locale.getDefault(); + StringBuffer buffer = new StringBuffer(); + // Add version + final String version = Build.VERSION.RELEASE; + if (version.length() > 0) { + buffer.append(version); + } else { + // default to "1.0" + buffer.append("1.0"); + } + buffer.append("; "); + final String language = locale.getLanguage(); + buffer.append(language.toLowerCase()); + final String country = locale.getCountry(); + buffer.append("-"); + buffer.append(country.toLowerCase()); + final String base = "Mozilla/5.0 (Linux; U; Android %s) AppleWebKit/533.1 (KHTML, like Gecko) Mobile Safari/533.1"; + USER_AGENT = String.format(base, buffer); + } + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/image/HippyDrawable.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/image/HippyDrawable.java index 86b61b29311..0d54cb50cdb 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/image/HippyDrawable.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/image/HippyDrawable.java @@ -48,7 +48,7 @@ public void setDrawable(Drawable drawable) { * * @param rawData byte array raw data */ - public void setData(byte[] rawData) { + public boolean setData(byte[] rawData) { try { mGifMovie = Movie.decodeByteArray(rawData, 0, rawData.length); if (mGifMovie == null) { @@ -56,8 +56,10 @@ public void setData(byte[] rawData) { } else { mBitmap = null; } + return true; } catch (OutOfMemoryError | Exception e) { e.printStackTrace(); + return false; } } @@ -66,16 +68,17 @@ public void setData(byte[] rawData) { * * @param path file path of the image */ - public void setData(File path) { + public boolean setData(File path) { FileInputStream is = null; try { is = new FileInputStream(path); byte[] rawData = new byte[is.available()]; int total = is.read(rawData); LogUtils.d("HippyDrawable", "setData path: read total=" + total); - setData(rawData); + return setData(rawData); } catch (Exception e) { e.printStackTrace(); + return false; } finally { if (is != null) { try { @@ -111,17 +114,20 @@ public void setData(File path, boolean isGif) { } } - public void setDataForTarge28Assets(String assetsFile) { + public boolean setDataForTarge28Assets(String assetsFile) { ImageDecoder.Source source; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) { try { source = ImageDecoder.createSource(ContextHolder.getAppContext().getAssets(), assetsFile.substring("assets://".length())); mBitmap = ImageDecoder.decodeBitmap(source); + return true; } catch (IOException e) { e.printStackTrace(); + return false; } } + return false; } /** @@ -139,7 +145,7 @@ public void setData(Bitmap bmp) { * * @param source 不是原始数据,而是原始数据的来源:base64 / assets / file */ - public void setData(String source) { + public boolean setData(String source) { mSource = source; if (mSource.startsWith("data:")) { try { @@ -150,31 +156,34 @@ public void setData(String source) { String base64String = mSource.substring(base64Index); byte[] decode = Base64.decode(base64String, Base64.DEFAULT); if (decode != null) { - setData(decode); + return setData(decode); } } + return false; } catch (Exception e) { e.printStackTrace(); + return false; } } else if (mSource.startsWith("file://")) { // local file image String filePath = mSource.substring("file://".length()); - setData(new File(filePath)); + return setData(new File(filePath)); } else if (mSource.startsWith("assets://")) { InputStream is = null; try { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) { - setDataForTarge28Assets(mSource); + return setDataForTarge28Assets(mSource); } else { is = ContextHolder.getAppContext().getAssets() .open(mSource.substring("assets://".length())); byte[] rawData = new byte[is.available()]; int total = is.read(rawData); LogUtils.d("HippyDrawable", "setData source: read total=" + total); - setData(rawData); + return setData(rawData); } } catch (Exception e) { e.printStackTrace(); + return false; } finally { if (is != null) { try { @@ -185,6 +194,7 @@ public void setData(String source) { } } } + return false; } /** diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/image/HippyImageLoader.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/image/HippyImageLoader.java index 183963dcec6..9625bcd975a 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/image/HippyImageLoader.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/image/HippyImageLoader.java @@ -45,7 +45,9 @@ public HippyDrawable getImage(String source, Object param) { } } HippyDrawable drawable = new HippyDrawable(); - drawable.setData(source); + if (!drawable.setData(source)) { + return null; + } if (canCacheImage) { mWeakImageCache.put(imageCacheCode, new WeakReference<>(drawable)); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/monitor/DefaultEngineMonitorAdapter.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/monitor/DefaultEngineMonitorAdapter.java index aacf1a6a3d3..42e84ed9a1b 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/monitor/DefaultEngineMonitorAdapter.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/monitor/DefaultEngineMonitorAdapter.java @@ -15,52 +15,78 @@ */ package com.tencent.mtt.hippy.adapter.monitor; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.tencent.mtt.hippy.HippyRootView; +import com.tencent.mtt.hippy.bridge.HippyCallNativeParams; import java.util.List; @SuppressWarnings({"unused"}) public class DefaultEngineMonitorAdapter implements HippyEngineMonitorAdapter { - @Override - public void reportEngineLoadStart() { + @Override + public void reportEngineLoadStart() { - } + } - @Override - public void reportEngineLoadResult(int code, int loadTime, - List loadEvents, Throwable e) { + @Override + public void reportEngineLoadResult(int code, int loadTime, + List loadEvents, Throwable e) { - } + } - @Override - public void reportModuleLoadComplete(HippyRootView rootView, int loadTime, - List loadEvents) { + @Override + public void reportModuleLoadComplete(HippyRootView rootView, int loadTime, + List loadEvents) { - } + } - @Override - public boolean needReportBridgeANR() { - return false; - } + @Override + public boolean needReportBridgeANR() { + return false; + } - @Override - public void reportBridgeANR(String message) { + @Override + public void reportBridgeANR(String message) { - } + } - @Override - public void reportDoCallNatives(String moduleName, String moduleFunc) { + @Override + public void reportDoCallNatives(String moduleName, String moduleFunc) { - } + } - @Override - public void reportGestureEventCallStack(String funcName, String msg) { + @Override + public void reportGestureEventCallStack(String funcName, String msg) { - } + } - @Override - public void reportClickEvent(Object object, boolean isCustomEvent) { + @Override + public void reportClickEvent(Object object, boolean isCustomEvent) { - } + } + + @Override + public boolean onInterceptCallNative(@NonNull String componentName, + @NonNull HippyCallNativeParams params) { + return false; + } + + @Override + public void onCallNativeFinished(@NonNull String componentName, @NonNull HippyCallNativeParams params) { + } + + @Override + public boolean onInterceptPromiseCallback(@NonNull String componentName, + @NonNull String moduleName, + @NonNull String funcName, @NonNull String callBackId, @Nullable Object callbackResult) { + return false; + } + + @Override + public void reportCustomMonitorPoint(HippyRootView rootView, String eventName, + long timeMillis) { + + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/monitor/HippyEngineMonitorAdapter.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/monitor/HippyEngineMonitorAdapter.java index 53ff44613e1..52d41b60c21 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/monitor/HippyEngineMonitorAdapter.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/monitor/HippyEngineMonitorAdapter.java @@ -15,33 +15,45 @@ */ package com.tencent.mtt.hippy.adapter.monitor; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.tencent.mtt.hippy.HippyRootView; +import com.tencent.mtt.hippy.bridge.HippyCallNativeParams; import java.util.List; @SuppressWarnings({"EmptyMethod", "unused"}) public interface HippyEngineMonitorAdapter { - int ENGINE_LOAD_RESULT_SUCCESS = 0; - int ENGINE_LOAD_RESULT_ERROR = 1; - int ENGINE_LOAD_RESULE_TIMEOUT = 2; + int ENGINE_LOAD_RESULT_SUCCESS = 0; + int ENGINE_LOAD_RESULT_ERROR = 1; + int ENGINE_LOAD_RESULE_TIMEOUT = 2; - void reportEngineLoadStart(); + void reportEngineLoadStart(); - void reportEngineLoadResult(int code, int loadTime, List loadEvents, - Throwable e); + void reportEngineLoadResult(int code, int loadTime, List loadEvents, + Throwable e); - void reportModuleLoadComplete(HippyRootView rootView, int loadTime, - List loadEvents); + void reportModuleLoadComplete(HippyRootView rootView, int loadTime, + List loadEvents); - @SuppressWarnings("SameReturnValue") - boolean needReportBridgeANR(); + @SuppressWarnings("SameReturnValue") + boolean needReportBridgeANR(); - void reportBridgeANR(String message); + void reportBridgeANR(String message); - void reportDoCallNatives(String moduleName, String moduleFunc); + void reportDoCallNatives(String moduleName, String moduleFunc); - void reportGestureEventCallStack(String funcName, String msg); + void reportGestureEventCallStack(String funcName, String msg); - void reportClickEvent(Object object, boolean isCustomEvent); + void reportClickEvent(Object object, boolean isCustomEvent); + + boolean onInterceptCallNative(@NonNull String componentName, @NonNull HippyCallNativeParams params); + + void onCallNativeFinished(@NonNull String componentName, @NonNull HippyCallNativeParams params); + + boolean onInterceptPromiseCallback(@NonNull String componentName, @NonNull String moduleName, + @NonNull String funcName, @NonNull String callbackId, @Nullable Object callbackResult); + + void reportCustomMonitorPoint(HippyRootView rootView, String eventName, long timeMillis); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/monitor/HippyEngineMonitorEvent.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/monitor/HippyEngineMonitorEvent.java index 247b9ca411b..5473eb225f0 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/monitor/HippyEngineMonitorEvent.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/monitor/HippyEngineMonitorEvent.java @@ -27,6 +27,7 @@ public class HippyEngineMonitorEvent { public static final String MODULE_LOAD_EVENT_LOAD_BUNDLE = "loadBundle"; public static final String MODULE_LOAD_EVENT_RUN_BUNDLE = "runBundle"; public static final String MODULE_LOAD_EVENT_CREATE_VIEW = "createView"; + public static final String MODULE_LOAD_EVENT_RESTORE_INSTANCE_STATE = "restoreInstanceState"; public String eventName; public long startTime; public long endTime; diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/monitor/HippyEngineMonitorPoint.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/monitor/HippyEngineMonitorPoint.java new file mode 100644 index 00000000000..562ff2b0346 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/monitor/HippyEngineMonitorPoint.java @@ -0,0 +1,45 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.mtt.hippy.adapter.monitor; + +public enum HippyEngineMonitorPoint { + INIT_JS_FRAMEWORK_START("hippyInitJsFrameworkStart"), + INIT_JS_FRAMEWORK_END("hippyInitJsFrameworkEnd"), + COMMON_LOAD_SOURCE_START("hippyCommonLoadSourceStart"), + COMMON_LOAD_SOURCE_END("hippyCommonLoadSourceEnd"), + COMMON_EXECUTE_SOURCE_START("hippyCommonExecuteSourceStart"), + COMMON_EXECUTE_SOURCE_END("hippyCommonExecuteSourceEnd"), + SECONDARY_LOAD_SOURCE_START("hippySecondaryLoadSourceStart"), + SECONDARY_LOAD_SOURCE_END("hippySecondaryLoadSourceEnd"), + SECONDARY_EXECUTE_SOURCE_START("hippySecondaryExecuteSourceStart"), + SECONDARY_EXECUTE_SOURCE_END("hippySecondaryExecuteSourceEnd"), + BRIDGE_STARTUP_START("hippyBridgeStartupStart"), + BRIDGE_STARTUP_END("hippyBridgeStartupEnd"), + RUN_APPLICATION_START("hippyRunApplicationStart"), + RUN_APPLICATION_END("hippyRunApplicationEnd"), + FIRST_PAINT_START("hippyFirstPaintStart"), + FIRST_PAINT_END("hippyFirstPaintEnd"); + + private final String value; + + HippyEngineMonitorPoint(String value) { + this.value = value; + } + + public String value() { + return value; + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/thirdparty/HippyThirdPartyAdapter.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/thirdparty/HippyThirdPartyAdapter.java index f09586d7391..310b3625ea5 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/thirdparty/HippyThirdPartyAdapter.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/adapter/thirdparty/HippyThirdPartyAdapter.java @@ -1,14 +1,20 @@ package com.tencent.mtt.hippy.adapter.thirdparty; +import com.tencent.mtt.hippy.dom.node.DomNodeRecord; +import java.util.ArrayList; import org.json.JSONObject; @SuppressWarnings({"unused"}) public abstract class HippyThirdPartyAdapter { + public ArrayList domNodeRecordList = new ArrayList<>(); + public abstract void onRuntimeInit(long runtimeId); public abstract void onRuntimeDestroy(); + public abstract void saveInstanceState(ArrayList recordList, Object params); + public abstract String getPackageName(); public abstract String getAppVersion(); diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/annotation/HippyMethod.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/annotation/HippyMethod.java index f33d162563f..cfbd4ee1e8b 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/annotation/HippyMethod.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/annotation/HippyMethod.java @@ -31,4 +31,6 @@ String name() default ""; boolean isSync() default false; + + boolean useJSValueType() default false; } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyBridge.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyBridge.java index 6eb929afd3e..e3e958d322e 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyBridge.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyBridge.java @@ -1,5 +1,5 @@ /* Tencent is pleased to support the open source community by making Hippy available. - * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2018-2022 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,41 +13,51 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.tencent.mtt.hippy.bridge; import android.content.res.AssetManager; -import com.tencent.mtt.hippy.common.HippyArray; +import androidx.annotation.NonNull; + +import com.tencent.mtt.hippy.common.Callback; + import java.nio.ByteBuffer; public interface HippyBridge { - String URI_SCHEME_ASSETS = "asset:"; - String URI_SCHEME_FILE = "file:"; + String URI_SCHEME_ASSETS = "asset:"; + String URI_SCHEME_FILE = "file:"; + + void initJSBridge(String globalConfig, NativeCallback callback, int groupId); + + void runScript(@NonNull String script); - void initJSBridge(String gobalConfig, NativeCallback callback, int groupId); + void runInJsThread(Callback callback); - boolean runScriptFromUri(String uri, AssetManager assetManager, boolean canUseCodeCache, - String codeCacheTag, NativeCallback callback); + boolean runScriptFromUri(String uri, AssetManager assetManager, boolean canUseCodeCache, + String codeCacheTag, NativeCallback callback); - void onDestroy(); + void onDestroy(boolean isReload); - void destroy(NativeCallback callback); + void destroy(NativeCallback callback, boolean isReload); - void callFunction(String action, NativeCallback callback, ByteBuffer buffer); + void callFunction(String action, NativeCallback callback, ByteBuffer buffer); - void callFunction(String action, NativeCallback callback, byte[] buffer); + void callFunction(String action, NativeCallback callback, byte[] buffer); - void callFunction(String action, NativeCallback callback, byte[] buffer, int offset, int length); + void callFunction(String action, NativeCallback callback, byte[] buffer, int offset, + int length); - long getV8RuntimeId(); + long getV8RuntimeId(); - interface BridgeCallback { + interface BridgeCallback { - void callNatives(String moduleName, String moduleFunc, String callId, HippyArray params); + void callNatives(String moduleName, String moduleFunc, String callId, Object params); - void reportException(String message, String stackTrace); + void reportException(String message, String stackTrace); - void reportException(Throwable e); - } + void reportException(Throwable e); + } + public void connectDebugUrl(String wsDebugUrl); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyBridgeImpl.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyBridgeImpl.java index 21c1f3e7ffc..d78eb512439 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyBridgeImpl.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyBridgeImpl.java @@ -13,403 +13,496 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.tencent.mtt.hippy.bridge; +import android.content.Context; +import android.content.res.AssetManager; +import android.text.TextUtils; + +import com.tencent.mtt.hippy.HippyEngine; +import com.tencent.mtt.hippy.HippyEngine.V8InitParams; import com.tencent.mtt.hippy.HippyEngineContext; +import com.tencent.mtt.hippy.common.Callback; +import com.tencent.mtt.hippy.common.HippyArray; +import com.tencent.mtt.hippy.common.HippyMap; +import com.tencent.mtt.hippy.devsupport.DebugWebSocketClient; +import com.tencent.mtt.hippy.devsupport.DevRemoteDebugProxy; import com.tencent.mtt.hippy.devsupport.DevServerCallBack; import com.tencent.mtt.hippy.devsupport.DevSupportManager; +import com.tencent.mtt.hippy.devsupport.inspector.Inspector; +import com.tencent.mtt.hippy.modules.HippyModuleManager; +import com.tencent.mtt.hippy.modules.nativemodules.HippyNativeModuleInfo; +import com.tencent.mtt.hippy.serialization.PrimitiveValueDeserializer; import com.tencent.mtt.hippy.serialization.compatible.Deserializer; import com.tencent.mtt.hippy.serialization.nio.reader.BinaryReader; import com.tencent.mtt.hippy.serialization.nio.reader.SafeDirectReader; import com.tencent.mtt.hippy.serialization.nio.reader.SafeHeapReader; import com.tencent.mtt.hippy.serialization.string.InternalizedStringTable; -import com.tencent.mtt.hippy.devsupport.inspector.Inspector; +import com.tencent.mtt.hippy.utils.ArgumentUtils; +import com.tencent.mtt.hippy.utils.FileUtils; +import com.tencent.mtt.hippy.utils.LogUtils; +import com.tencent.mtt.hippy.utils.DimensionsUtil; import com.tencent.mtt.hippy.utils.UIThreadUtils; -import com.tencent.mtt.hippy.utils.UrlUtils; + import java.io.ByteArrayOutputStream; import java.io.File; import java.io.InputStream; -import java.io.UnsupportedEncodingException; +import java.lang.ref.WeakReference; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.util.Locale; - -import android.content.Context; -import android.content.res.AssetManager; -import android.text.TextUtils; -import com.tencent.mtt.hippy.common.HippyArray; -import com.tencent.mtt.hippy.devsupport.DebugWebSocketClient; -import com.tencent.mtt.hippy.devsupport.DevRemoteDebugProxy; -import com.tencent.mtt.hippy.utils.ArgumentUtils; -import com.tencent.mtt.hippy.utils.FileUtils; -import com.tencent.mtt.hippy.utils.LogUtils; -import java.nio.ByteOrder; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; @SuppressWarnings({"unused", "JavaJniMissingFunction"}) public class HippyBridgeImpl implements HippyBridge, DevRemoteDebugProxy.OnReceiveDataListener { - private static final Object sBridgeSyncLock; - - static { - sBridgeSyncLock = new Object(); - } - - private static volatile String mCodeCacheRootDir; - private long mV8RuntimeId = 0; - private BridgeCallback mBridgeCallback; - private boolean mInit = false; - private final boolean mIsDevModule; - private String mDebugServerHost; - private final boolean mSingleThreadMode; - private final boolean enableV8Serialization; - private DebugWebSocketClient mDebugWebSocketClient; - private String mDebugGlobalConfig; - private NativeCallback mDebugInitJSFrameworkCallback; - private final HippyEngineContext mContext; - private Deserializer deserializer; - private BinaryReader safeHeapReader; - private BinaryReader safeDirectReader; - - public HippyBridgeImpl(HippyEngineContext engineContext, BridgeCallback callback, - boolean singleThreadMode, - boolean enableV8Serialization, boolean isDevModule, String debugServerHost) { - this.mBridgeCallback = callback; - this.mSingleThreadMode = singleThreadMode; - this.enableV8Serialization = enableV8Serialization; - this.mIsDevModule = isDevModule; - this.mDebugServerHost = debugServerHost; - this.mContext = engineContext; - - synchronized (sBridgeSyncLock) { - if (mCodeCacheRootDir == null) { - Context context = mContext.getGlobalConfigs().getContext(); - File hippyFile = FileUtils.getHippyFile(context); - if (hippyFile != null) { - mCodeCacheRootDir = - hippyFile.getAbsolutePath() + File.separator + "codecache" + File.separator; + private static final Object sBridgeSyncLock; + + static { + sBridgeSyncLock = new Object(); + } + + private static volatile String mCodeCacheRootDir; + private long mV8RuntimeId = 0; + private BridgeCallback mBridgeCallback; + private boolean mInit = false; + private final HippyEngine.DebugMode mDebugMode; + private String mDebugServerHost; + private final boolean mSingleThreadMode; + private final boolean enableV8Serialization; + private DebugWebSocketClient mDebugWebSocketClient; + private String mDebugGlobalConfig; + private HippyEngineContext mContext; + @Nullable + private Deserializer mCompatibleDeserializer; + @Nullable + private com.tencent.mtt.hippy.serialization.recommend.Deserializer mRecommendDeserializer; + private BinaryReader mSafeHeapReader; + private BinaryReader mSafeDirectReader; + private final HippyEngine.V8InitParams v8InitParams; + private Inspector mInspector; + + public HippyBridgeImpl(HippyEngineContext engineContext, BridgeCallback callback, + boolean singleThreadMode, boolean enableV8Serialization, HippyEngine.DebugMode debugMode, + String debugServerHost, V8InitParams v8InitParams) { + this.mBridgeCallback = callback; + this.mSingleThreadMode = singleThreadMode; + this.enableV8Serialization = enableV8Serialization; + this.mDebugMode = debugMode; + this.mDebugServerHost = debugServerHost; + this.mContext = engineContext; + this.v8InitParams = v8InitParams; + synchronized (sBridgeSyncLock) { + if (mCodeCacheRootDir == null) { + Context context = mContext.getGlobalConfigs().getContext(); + File hippyFile = FileUtils.getHippyFile(context); + if (hippyFile != null) { + mCodeCacheRootDir = + hippyFile.getAbsolutePath() + File.separator + "codecache" + + File.separator; + } + } + } + + if (enableV8Serialization) { + mCompatibleDeserializer = new Deserializer(null, new InternalizedStringTable()); + mRecommendDeserializer = new com.tencent.mtt.hippy.serialization.recommend.Deserializer( + null, new InternalizedStringTable()); } - } } - if (enableV8Serialization) { - deserializer = new Deserializer(null, new InternalizedStringTable()); + public static int createSnapshotFromScript(String[] script, String basePath, String uri, Context context) { + HippyMap globalParams = new HippyMap(); + assert (context != null); + HippyMap dimensionMap = DimensionsUtil.getDimensions(-1, -1, context, false); + globalParams.pushMap("Dimensions", dimensionMap); + HippyMap platformParams = new HippyMap(); + platformParams.pushString("OS", "android"); + globalParams.pushMap("Platform", platformParams); + return createSnapshot(script, basePath, uri, ArgumentUtils.objectToJson(globalParams)); + }; + + @Override + public void initJSBridge(String globalConfig, final NativeCallback callback, final int groupId) { + mDebugGlobalConfig = globalConfig; + + if (mDebugMode == HippyEngine.DebugMode.Dev) { + createDebugSocketClient("", new DebugWebSocketClient.JSDebuggerCallback() { + @SuppressWarnings("unused") + @Override + public void onSuccess(String response) { + LogUtils.d("hippyCore", "js debug socket connect success"); + initJSEngine(groupId, callback); + } + + @SuppressWarnings("unused") + @Override + public void onFailure(final Throwable cause) { + LogUtils.e("hippyCore", "js debug socket connect failed"); + initJSEngine(groupId, callback); + } + }); + } else { + initJSEngine(groupId, callback); + } + } + + private void createDebugSocketClient(String wsDebugUrl, DebugWebSocketClient.JSDebuggerCallback cb) { + mDebugWebSocketClient = new DebugWebSocketClient(); + mDebugWebSocketClient.setOnReceiveDataCallback(this); + if (TextUtils.isEmpty(mDebugServerHost)) { + mDebugServerHost = "localhost:38989"; + } + DevSupportManager devSupportManager = mContext.getDevSupportManager(); + if (!"".equals(wsDebugUrl)) { + devSupportManager.setRemoteServerData(wsDebugUrl); + } + mInspector = devSupportManager.getInspector() + .setEngineContext(mContext, mDebugWebSocketClient); + String debugUrl = devSupportManager.createDebugUrl(mDebugServerHost); + mDebugWebSocketClient.connect(debugUrl, cb); } - } - - @Override - public void initJSBridge(String globalConfig, NativeCallback callback, final int groupId) { - mDebugGlobalConfig = globalConfig; - mDebugInitJSFrameworkCallback = callback; - - if (this.mIsDevModule) { - mDebugWebSocketClient = new DebugWebSocketClient(); - mDebugWebSocketClient.setOnReceiveDataCallback(this); - if (TextUtils.isEmpty(mDebugServerHost)) { - mDebugServerHost = "localhost:38989"; - } - String clientId = mContext.getDevSupportManager().getDevInstanceUUID(); // 方便区分不同的 Hippy 调试页面 - mDebugWebSocketClient.connect( - String.format(Locale.US, "ws://%s/debugger-proxy?role=android_client&clientId=%s", mDebugServerHost, clientId), - new DebugWebSocketClient.JSDebuggerCallback() { + + public void connectDebugUrl(String wsDebugUrl) { + createDebugSocketClient(wsDebugUrl, new DebugWebSocketClient.JSDebuggerCallback() { @SuppressWarnings("unused") @Override public void onSuccess(String response) { - LogUtils.d("hippyCore", "js debug socket connect success"); - initJSEngine(groupId); + LogUtils.d("hippyCore", "js debug socket connect success; from business"); } @SuppressWarnings("unused") @Override public void onFailure(final Throwable cause) { - LogUtils.e("hippyCore", "js debug socket connect failed"); - initJSEngine(groupId); + LogUtils.e("hippyCore", "js debug socket connect failed; from business"); } - }); - } else { - initJSEngine(groupId); + }); } - } - - private void initJSEngine(int groupId) { - synchronized (HippyBridgeImpl.class) { - try { - byte[] globalConfig = mDebugGlobalConfig.getBytes(StandardCharsets.UTF_16LE); - mV8RuntimeId = initJSFramework(globalConfig, mSingleThreadMode, enableV8Serialization, mIsDevModule, mDebugInitJSFrameworkCallback, groupId); - mInit = true; - } catch (Throwable e) { - if (mBridgeCallback != null) { - mBridgeCallback.reportException(e); + + private void initJSEngine(int groupId, final NativeCallback callback) { + synchronized (HippyBridgeImpl.class) { + try { + byte[] globalConfig = mDebugGlobalConfig.getBytes(StandardCharsets.UTF_16LE); + mV8RuntimeId = initJSFramework(globalConfig, mSingleThreadMode, + enableV8Serialization, + mDebugMode == HippyEngine.DebugMode.Dev || mDebugMode == HippyEngine.DebugMode.UserLocal, + callback, groupId, v8InitParams); + mInit = true; + } catch (Throwable e) { + if (mBridgeCallback != null) { + mBridgeCallback.reportException(e); + } + } } - } - } - } - - @Override - public long getV8RuntimeId() { - return mV8RuntimeId; - } - - @Override - public boolean runScriptFromUri(String uri, AssetManager assetManager, boolean canUseCodeCache, - String codeCacheTag, NativeCallback callback) { - if (!mInit) { - return false; } - if (!TextUtils.isEmpty(codeCacheTag) && !TextUtils.isEmpty(mCodeCacheRootDir)) { - String codeCacheDir = mCodeCacheRootDir + codeCacheTag + File.separator; - File codeCacheFile = new File(codeCacheDir); - if (!codeCacheFile.exists()) { - boolean ret = codeCacheFile.mkdirs(); - if (!ret) { - canUseCodeCache = false; - codeCacheDir = ""; + @Override + public long getV8RuntimeId() { + return mV8RuntimeId; + } + + @Override + public boolean runScriptFromUri(String uri, AssetManager assetManager, boolean canUseCodeCache, + String codeCacheTag, NativeCallback callback) { + if (!mInit) { + return false; } - } - - return runScriptFromUri(uri, assetManager, canUseCodeCache, codeCacheDir, mV8RuntimeId, - callback); - } else { - boolean ret = false; - LogUtils.d("HippyEngineMonitor", "runScriptFromAssets codeCacheTag is null"); - try { - ret = runScriptFromUri(uri, assetManager, false, "" + codeCacheTag + File.separator, - mV8RuntimeId, callback); - } catch (Throwable e) { - if (mBridgeCallback != null) { - mBridgeCallback.reportException(e); + + if (!TextUtils.isEmpty(codeCacheTag) && !TextUtils.isEmpty(mCodeCacheRootDir)) { + String codeCacheDir = mCodeCacheRootDir + codeCacheTag + File.separator; + File codeCacheFile = new File(codeCacheDir); + if (!codeCacheFile.exists()) { + boolean ret = codeCacheFile.mkdirs(); + if (!ret) { + canUseCodeCache = false; + codeCacheDir = ""; + } + } + + return runScriptFromUri(uri, assetManager, canUseCodeCache, codeCacheDir, mV8RuntimeId, + callback); + } else { + boolean ret = false; + LogUtils.d("HippyEngineMonitor", "runScriptFromAssets codeCacheTag is null"); + try { + ret = runScriptFromUri(uri, assetManager, false, "" + codeCacheTag + File.separator, + mV8RuntimeId, callback); + } catch (Throwable e) { + if (mBridgeCallback != null) { + mBridgeCallback.reportException(e); + } + } + return ret; } - } - return ret; } - } - @Override - public void callFunction(String action, NativeCallback callback, ByteBuffer buffer) { - if (!mInit || TextUtils.isEmpty(action) || buffer == null || buffer.limit() == 0) { - return; - } + @Override + public void callFunction(String action, NativeCallback callback, ByteBuffer buffer) { + if (!mInit || TextUtils.isEmpty(action) || buffer == null || buffer.limit() == 0) { + return; + } - int offset = buffer.position(); - int length = buffer.limit() - buffer.position(); - if (buffer.isDirect()) { - callFunction(action, mV8RuntimeId, callback, buffer, offset, length); - } else { - /* - * In Android's DirectByteBuffer implementation. - * - * {@link DirectByteBuffer#hb backing array} will be used to store buffer data, - * {@link DirectByteBuffer#offset} will be used to handle the alignment, - * it's already add to {@link DirectByteBuffer#address}, - * so the {@link DirectByteBuffer} has backing array and offset. - * - * In the other side, JNI method |void* GetDirectBufferAddress(JNIEnv*, jobject)| - * will be directly return {@link DirectByteBuffer#address} as the starting buffer address. - * - * So in this situation if, and only if, buffer is direct, - * {@link ByteBuffer#arrayOffset} will be ignored, treated as 0. - */ - offset += buffer.arrayOffset(); - callFunction(action, mV8RuntimeId, callback, buffer.array(), offset, length); + int offset = buffer.position(); + int length = buffer.limit() - buffer.position(); + if (buffer.isDirect()) { + callFunction(action, mV8RuntimeId, callback, buffer, offset, length); + } else { + /* + * In Android's DirectByteBuffer implementation. + * + * {@link DirectByteBuffer#hb backing array} will be used to store buffer data, + * {@link DirectByteBuffer#offset} will be used to handle the alignment, + * it's already add to {@link DirectByteBuffer#address}, + * so the {@link DirectByteBuffer} has backing array and offset. + * + * In the other side, JNI method |void* GetDirectBufferAddress(JNIEnv*, jobject)| + * will be directly return {@link DirectByteBuffer#address} as the starting buffer address. + * + * So in this situation if, and only if, buffer is direct, + * {@link ByteBuffer#arrayOffset} will be ignored, treated as 0. + */ + offset += buffer.arrayOffset(); + callFunction(action, mV8RuntimeId, callback, buffer.array(), offset, length); + } } - } - - @Override - public void callFunction(String action, NativeCallback callback, byte[] buffer) { - callFunction(action, callback, buffer, 0, buffer.length); - } - - @Override - public void callFunction(String action, NativeCallback callback, byte[] buffer, int offset, - int length) { - if (!mInit || TextUtils.isEmpty(action) || buffer == null || offset < 0 || length < 0 - || offset + length > buffer.length) { - return; + + @Override + public void callFunction(String action, NativeCallback callback, byte[] buffer) { + callFunction(action, callback, buffer, 0, buffer.length); } - callFunction(action, mV8RuntimeId, callback, buffer, offset, length); - } + @Override + public void callFunction(String action, NativeCallback callback, byte[] buffer, int offset, + int length) { + if (!mInit || TextUtils.isEmpty(action) || buffer == null || offset < 0 || length < 0 + || offset + length > buffer.length) { + return; + } - @Override - public void onDestroy() { - if (mDebugWebSocketClient != null) { - mDebugWebSocketClient.closeQuietly(); - mDebugWebSocketClient = null; + callFunction(action, mV8RuntimeId, callback, buffer, offset, length); } - if (!mInit) { - return; + @Override + public void onDestroy(boolean isReload) { + if (mDebugWebSocketClient != null) { + mDebugWebSocketClient.close(isReload ? Inspector.CLOSE_RELOAD : Inspector.CLOSE_DESTROY, ""); + mDebugWebSocketClient = null; + } + if (mInspector != null) { + mInspector.onDestroy(); + } + if (!mInit) { + return; + } + if (enableV8Serialization) { + if (mCompatibleDeserializer != null) { + mCompatibleDeserializer.getStringTable().release(); + } + if (mRecommendDeserializer != null) { + mRecommendDeserializer.getStringTable().release(); + } + } + mInit = false; + mV8RuntimeId = 0; + mContext = null; + mBridgeCallback = null; + } + + @Override + public void destroy(NativeCallback callback, boolean isReload) { + destroy(mV8RuntimeId, mSingleThreadMode, isReload, callback); } - mInit = false; - if (enableV8Serialization) { - deserializer.getStringTable().release(); + @Override + public void runScript(@NonNull String script) { + runScript(mV8RuntimeId, script); } - mV8RuntimeId = 0; - mBridgeCallback = null; - } + @Override + public void runInJsThread(Callback callback) { + runInJsThread(mV8RuntimeId, callback); + } - @Override - public void destroy(NativeCallback callback) { - destroy(mV8RuntimeId, mSingleThreadMode, callback); - } + public static native int createSnapshot(String[] script, String path, String uri, String config); - public native long initJSFramework(byte[] gobalConfig, boolean useLowMemoryMode, - boolean enableV8Serialization, boolean isDevModule, NativeCallback callback, long groupId); + public native long initJSFramework(byte[] globalConfig, boolean useLowMemoryMode, + boolean enableV8Serialization, boolean isDevModule, NativeCallback callback, + long groupId, V8InitParams v8InitParams); - public native boolean runScriptFromUri(String uri, AssetManager assetManager, - boolean canUseCodeCache, String codeCacheDir, long V8RuntimId, NativeCallback callback); + public native void runScript(long runtimeId, String script); - public native void destroy(long runtimeId, boolean useLowMemoryMode, NativeCallback callback); + public native boolean runScriptFromUri(String uri, AssetManager assetManager, + boolean canUseCodeCache, String codeCacheDir, long V8RuntimeId, NativeCallback callback); - public native void callFunction(String action, long V8RuntimId, NativeCallback callback, - ByteBuffer buffer, int offset, int length); + public native void destroy(long runtimeId, boolean useLowMemoryMode, boolean isReload, NativeCallback callback); - public native void callFunction(String action, long V8RuntimId, NativeCallback callback, - byte[] buffer, int offset, int length); + public native void callFunction(String action, long runtimeId, NativeCallback callback, + ByteBuffer buffer, int offset, int length); - public native void onResourceReady(ByteBuffer output, long runtimeId, long resId); + public native void callFunction(String action, long runtimeId, NativeCallback callback, + byte[] buffer, int offset, int length); - public void callNatives(String moduleName, String moduleFunc, String callId, byte[] buffer) { - callNatives(moduleName, moduleFunc, callId, ByteBuffer.wrap(buffer)); - } + public native void onResourceReady(ByteBuffer output, long runtimeId, long resId); - public void callNatives(String moduleName, String moduleFunc, String callId, ByteBuffer buffer) { - LogUtils.d("jni_callback", - "callNatives [moduleName:" + moduleName + " , moduleFunc: " + moduleFunc + "]"); + private native void runInJsThread(long runtimeId, Callback callback); - if (mBridgeCallback != null) { - HippyArray hippyParam = bytesToArgument(buffer); - mBridgeCallback.callNatives(moduleName, moduleFunc, callId, hippyParam); + public void callNatives(String moduleName, String moduleFunc, String callId, byte[] buffer) { + callNatives(moduleName, moduleFunc, callId, ByteBuffer.wrap(buffer)); } - } - public void InspectorChannel(byte[] params) { - String encoding = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN ? "UTF-16BE" : "UTF-16LE"; - String msg = new String(params, Charset.forName(encoding)); - if (mDebugWebSocketClient != null) { - mDebugWebSocketClient.sendMessage(msg); - } - } - - @SuppressWarnings("unused") - public void fetchResourceWithUri(final String uri, final long resId) { - UIThreadUtils.runOnUiThread(new Runnable() { - @Override - public void run() { - DevSupportManager devManager = mContext.getDevSupportManager(); - if (TextUtils.isEmpty(uri) || !UrlUtils.isWebUrl(uri) || devManager == null) { - LogUtils.e("HippyBridgeImpl", - "fetchResourceWithUri: can not call loadRemoteResource with " + uri); - return; + public void callNatives(String moduleName, String moduleFunc, String callId, + ByteBuffer buffer) { + LogUtils.d("jni_callback", + "callNatives [moduleName:" + moduleName + " , moduleFunc: " + moduleFunc + "]"); + if (mBridgeCallback != null) { + Object params = bytesToArgument(moduleName, moduleFunc, buffer); + mBridgeCallback.callNatives(moduleName, moduleFunc, callId, params); } + } - devManager.loadRemoteResource(uri, new DevServerCallBack() { - @Override - public void onDevBundleReLoad() { - } + public void InspectorChannel(byte[] params) { + String encoding = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN ? "UTF-16BE" : "UTF-16LE"; + String msg = new String(params, Charset.forName(encoding)); + if (mDebugWebSocketClient != null) { + mDebugWebSocketClient.sendMessage(msg); + } + } - @Override - public void onDevBundleLoadReady(InputStream inputStream) { - try { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - - byte[] b = new byte[2048]; - int size; - while ((size = inputStream.read(b)) > 0) { - output.write(b, 0, size); - } - - byte[] resBytes = output.toByteArray(); - final ByteBuffer buffer = ByteBuffer.allocateDirect(resBytes.length); - buffer.put(resBytes); - onResourceReady(buffer, mV8RuntimeId, resId); - } catch (Throwable e) { - if (mBridgeCallback != null) { - mBridgeCallback.reportException(e); - } - onResourceReady(null, mV8RuntimeId, resId); + @SuppressWarnings("unused") + public void fetchResourceWithUri(final String uri, final long resId) { + final WeakReference callbackWeakReference = new WeakReference<>(mBridgeCallback); + UIThreadUtils.runOnUiThread(new Runnable() { + @Override + public void run() { + if (mContext == null) { + return; + } + DevSupportManager devManager = mContext.getDevSupportManager(); + if (TextUtils.isEmpty(uri) || devManager == null) { + LogUtils.e("HippyBridgeImpl", + "fetchResourceWithUri: can not call loadRemoteResource with " + uri); + return; + } + + devManager.loadRemoteResource(uri, new DevServerCallBack() { + @Override + public void onDevBundleReLoad() { + } + + @Override + public void onDevBundleLoadReady(InputStream inputStream) { + try { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + + byte[] b = new byte[2048]; + int size; + while ((size = inputStream.read(b)) > 0) { + output.write(b, 0, size); + } + + byte[] resBytes = output.toByteArray(); + final ByteBuffer buffer = ByteBuffer.allocateDirect(resBytes.length); + buffer.put(resBytes); + onResourceReady(buffer, mV8RuntimeId, resId); + } catch (Throwable e) { + BridgeCallback callback = callbackWeakReference.get(); + if (callback != null) { + callback.reportException(e); + } + onResourceReady(null, mV8RuntimeId, resId); + } + } + + @Override + public void onInitDevError(Throwable e) { + LogUtils.e("hippy", "requireSubResource: " + e.getMessage()); + onResourceReady(null, mV8RuntimeId, resId); + } + }); } - } - - @Override - public void onInitDevError(Throwable e) { - LogUtils.e("hippy", "requireSubResource: " + e.getMessage()); - onResourceReady(null, mV8RuntimeId, resId); - } }); - } - }); - } - - private HippyArray bytesToArgument(ByteBuffer buffer) { - HippyArray hippyParam = null; - if (enableV8Serialization) { - LogUtils.d("hippy_bridge", "bytesToArgument using Buffer"); - Object paramObj; - try { + } + + private HippyArray parseJsonData(ByteBuffer buffer) { + byte[] bytes; + if (buffer.isDirect()) { + bytes = new byte[buffer.limit()]; + buffer.get(bytes); + } else { + bytes = buffer.array(); + } + return ArgumentUtils.parseToArray(new String(bytes)); + } + + @Nullable + private Object parseV8SerializeData(@NonNull String moduleName, @NonNull String moduleFunc, + ByteBuffer buffer) { + HippyModuleManager moduleManager = mContext.getModuleManager(); + HippyNativeModuleInfo moduleInfo = moduleManager.getModuleInfo(moduleName); + if (moduleInfo == null) { + return null; + } + HippyNativeModuleInfo.HippyNativeMethod method = moduleInfo.findMethod(moduleFunc); + PrimitiveValueDeserializer deserializer = mCompatibleDeserializer; + if (method != null && method.useJSValueType()) { + deserializer = mRecommendDeserializer; + } final BinaryReader binaryReader; if (buffer.isDirect()) { - if (safeDirectReader == null) { - safeDirectReader = new SafeDirectReader(); - } - binaryReader = safeDirectReader; + if (mSafeHeapReader == null) { + mSafeHeapReader = new SafeDirectReader(); + } + binaryReader = mSafeHeapReader; } else { - if (safeHeapReader == null) { - safeHeapReader = new SafeHeapReader(); - } - binaryReader = safeHeapReader; + if (mSafeDirectReader == null) { + mSafeDirectReader = new SafeHeapReader(); + } + binaryReader = mSafeDirectReader; } binaryReader.reset(buffer); deserializer.setReader(binaryReader); deserializer.reset(); deserializer.readHeader(); - paramObj = deserializer.readValue(); - } catch (Throwable e) { - e.printStackTrace(); - LogUtils.e("compatible.Deserializer", "Error Parsing Buffer", e); - return new HippyArray(); - } - if (paramObj instanceof HippyArray) { - hippyParam = (HippyArray) paramObj; - } - } else { - LogUtils.d("hippy_bridge", "bytesToArgument using JSON"); - byte[] bytes; - if (buffer.isDirect()) { - bytes = new byte[buffer.limit()]; - buffer.get(bytes); - } else { - bytes = buffer.array(); - } - hippyParam = ArgumentUtils.parseToArray(new String(bytes)); + return deserializer.readValue(); } - return hippyParam == null ? new HippyArray() : hippyParam; - } + private Object bytesToArgument(String moduleName, String moduleFunc, ByteBuffer buffer) { + Object result = null; + try { + if (enableV8Serialization) { + result = parseV8SerializeData(moduleName, moduleFunc, buffer); + } else { + result = parseJsonData(buffer); + } + } catch (Exception e) { + e.printStackTrace(); + } + return result == null ? new HippyArray() : result; + } - public void reportException(String message, String stackTrace) { - LogUtils.e("reportException", "!!!!!!!!!!!!!!!!!!!"); + public void reportException(String message, String stackTrace) { + LogUtils.e("reportException", "!!!!!!!!!!!!!!!!!!!"); - LogUtils.e("reportException", message); - LogUtils.e("reportException", stackTrace); + LogUtils.e("reportException", message); + LogUtils.e("reportException", stackTrace); - if (mBridgeCallback != null) { - mBridgeCallback.reportException(message, stackTrace); + if (mBridgeCallback != null) { + mBridgeCallback.reportException(message, stackTrace); + } } - } - - @Override - public void onReceiveData(String msg) { - if (this.mIsDevModule) { - boolean isInspectMsg = Inspector.getInstance(mContext) - .setWebSocketClient(mDebugWebSocketClient).dispatchReqFromFrontend(mContext, msg); - if (!isInspectMsg) { - callFunction("onWebsocketMsg", null, msg.getBytes(StandardCharsets.UTF_16LE)); - } + + @Override + public void onReceiveData(String msg) { + if (mDebugMode == HippyEngine.DebugMode.Dev || mDebugMode == HippyEngine.DebugMode.UserLocal) { + boolean isInspectMsg = + mInspector != null && mInspector.dispatchReqFromFrontend(mContext, msg); + if (!isInspectMsg) { + callFunction("onWebsocketMsg", null, msg.getBytes(StandardCharsets.UTF_16LE)); + } + } } - } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyBridgeManager.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyBridgeManager.java index dc48e70888d..519d9666b65 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyBridgeManager.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyBridgeManager.java @@ -1,5 +1,5 @@ /* Tencent is pleased to support the open source community by making Hippy available. - * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2018-2022 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package com.tencent.mtt.hippy.bridge; +import androidx.annotation.NonNull; import com.tencent.mtt.hippy.HippyEngine; import com.tencent.mtt.hippy.HippyEngine.BridgeTransferType; import com.tencent.mtt.hippy.HippyRootView; @@ -24,11 +25,17 @@ import com.tencent.mtt.hippy.common.HippyJsException; import com.tencent.mtt.hippy.common.HippyMap; +import java.nio.ByteBuffer; + @SuppressWarnings({"deprecation", "unused"}) public interface HippyBridgeManager { void initBridge(Callback callback); + void runScript(@NonNull String script); + + void runInJsThread(Callback callback); + void runBundle(int id, HippyBundleLoader loader, HippyEngine.ModuleListener listener, HippyRootView hippyRootView); @@ -44,7 +51,7 @@ void runBundle(int id, HippyBundleLoader loader, HippyEngine.ModuleListener list void execCallback(Object params, BridgeTransferType transferType); - void destroyBridge(Callback callback); + void destroyBridge(Callback callback, boolean isReload); void destroy(); @@ -52,4 +59,8 @@ void callJavaScriptModule(String mName, String name, Object params, BridgeTransferType transferType); HippyThirdPartyAdapter getThirdPartyAdapter(); + + long getV8RuntimeId(); + + void connectDebugUrl(String wsDebugUrl); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyBridgeManagerImpl.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyBridgeManagerImpl.java index 3a739b5318a..a50678f49db 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyBridgeManagerImpl.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyBridgeManagerImpl.java @@ -1,7 +1,7 @@ /* * Tencent is pleased to support the open source community by making Hippy * available. - * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2018-2022 THL A29 Limited, a Tencent company. All rights reserved. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -12,6 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.tencent.mtt.hippy.bridge; import android.content.Context; @@ -22,18 +23,20 @@ import android.os.Handler; import android.os.Message; import android.text.TextUtils; - +import androidx.annotation.Nullable; +import com.tencent.mtt.hippy.BuildConfig; import com.tencent.mtt.hippy.HippyEngine; import com.tencent.mtt.hippy.HippyEngine.BridgeTransferType; import com.tencent.mtt.hippy.HippyEngine.ModuleLoadStatus; +import com.tencent.mtt.hippy.HippyEngine.V8InitParams; import com.tencent.mtt.hippy.HippyEngineContext; import com.tencent.mtt.hippy.HippyRootView; import com.tencent.mtt.hippy.adapter.monitor.HippyEngineMonitorEvent; +import com.tencent.mtt.hippy.adapter.monitor.HippyEngineMonitorPoint; import com.tencent.mtt.hippy.adapter.thirdparty.HippyThirdPartyAdapter; import com.tencent.mtt.hippy.bridge.bundleloader.HippyBundleLoader; import com.tencent.mtt.hippy.bridge.jsi.TurboModuleManager; import com.tencent.mtt.hippy.common.Callback; -import com.tencent.mtt.hippy.common.HippyArray; import com.tencent.mtt.hippy.common.HippyJsException; import com.tencent.mtt.hippy.common.HippyMap; import com.tencent.mtt.hippy.modules.HippyModuleManager; @@ -45,619 +48,728 @@ import com.tencent.mtt.hippy.utils.ArgumentUtils; import com.tencent.mtt.hippy.utils.DimensionsUtil; import com.tencent.mtt.hippy.utils.I18nUtil; +import com.tencent.mtt.hippy.utils.TimeMonitor; import com.tencent.mtt.hippy.utils.UIThreadUtils; - -import org.json.JSONObject; - import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import org.json.JSONException; +import org.json.JSONObject; @SuppressWarnings({"unused", "deprecation"}) public class HippyBridgeManagerImpl implements HippyBridgeManager, HippyBridge.BridgeCallback, - Handler.Callback { - - static final int MSG_CODE_INIT_BRIDGE = 10; - static final int MSG_CODE_RUN_BUNDLE = 11; - static final int MSG_CODE_CALL_FUNCTION = 12; - static final int MSG_CODE_DESTROY_BRIDGE = 13; - - static final int FUNCTION_ACTION_LOAD_INSTANCE = 1; - static final int FUNCTION_ACTION_RESUME_INSTANCE = 2; - static final int FUNCTION_ACTION_PAUSE_INSTANCE = 3; - static final int FUNCTION_ACTION_DESTROY_INSTANCE = 4; - static final int FUNCTION_ACTION_CALLBACK = 5; - static final int FUNCTION_ACTION_CALL_JSMODULE = 6; - - public static final int BRIDGE_TYPE_SINGLE_THREAD = 2; - public static final int BRIDGE_TYPE_NORMAL = 1; - - final HippyEngineContext mContext; - final HippyBundleLoader mCoreBundleLoader; - HippyBridge mHippyBridge; - volatile boolean mIsInit = false; - Handler mHandler; - final int mBridgeType; - final boolean enableV8Serialization; - ArrayList mLoadedBundleInfo = null; - private final boolean mIsDevModule; - private final String mDebugServerHost; - private final int mGroupId; - private final HippyThirdPartyAdapter mThirdPartyAdapter; - private StringBuilder mStringBuilder; - private SafeHeapWriter safeHeapWriter; - private SafeDirectWriter safeDirectWriter; - private Serializer compatibleSerializer; - private com.tencent.mtt.hippy.serialization.recommend.Serializer recommendSerializer; - - - HippyEngine.ModuleListener mLoadModuleListener; - - private TurboModuleManager mTurboModuleManager; - - public HippyBridgeManagerImpl(HippyEngineContext context, HippyBundleLoader coreBundleLoader, - int bridgeType, - boolean enableV8Serialization, boolean isDevModule, String debugServerHost, int groupId, - HippyThirdPartyAdapter thirdPartyAdapter) { - mContext = context; - mCoreBundleLoader = coreBundleLoader; - mBridgeType = bridgeType; - mIsDevModule = isDevModule; - mDebugServerHost = debugServerHost; - mGroupId = groupId; - mThirdPartyAdapter = thirdPartyAdapter; - this.enableV8Serialization = enableV8Serialization; - - if (enableV8Serialization) { - compatibleSerializer = new Serializer(); - recommendSerializer = new com.tencent.mtt.hippy.serialization.recommend.Serializer(); - } else { - mStringBuilder = new StringBuilder(1024); + Handler.Callback { + + static final int MSG_CODE_INIT_BRIDGE = 10; + static final int MSG_CODE_RUN_BUNDLE = 11; + static final int MSG_CODE_CALL_FUNCTION = 12; + static final int MSG_CODE_DESTROY_BRIDGE = 13; + static final int MSG_CODE_RUN_SCRIPT = 14; + + static final int FUNCTION_ACTION_LOAD_INSTANCE = 1; + static final int FUNCTION_ACTION_RESUME_INSTANCE = 2; + static final int FUNCTION_ACTION_PAUSE_INSTANCE = 3; + static final int FUNCTION_ACTION_DESTROY_INSTANCE = 4; + static final int FUNCTION_ACTION_CALLBACK = 5; + static final int FUNCTION_ACTION_CALL_JSMODULE = 6; + + public static final long V8_RUNTIME_ID_EMPTY = -1; + + public static final int BRIDGE_TYPE_SINGLE_THREAD = 2; + public static final int BRIDGE_TYPE_NORMAL = 1; + + public static final int DESTROY_CLOSE = 0; + public static final int DESTROY_RELOAD = 1; + + final HippyEngineContext mContext; + final HippyBundleLoader mCoreBundleLoader; + HippyBridge mHippyBridge; + volatile boolean mIsInit = false; + Handler mHandler; + final int mBridgeType; + final boolean enableV8Serialization; + ArrayList mLoadedBundleInfo = null; + private final HippyEngine.DebugMode mDebugMode; + private final String mDebugServerHost; + private final int mGroupId; + private final HippyThirdPartyAdapter mThirdPartyAdapter; + private StringBuilder mStringBuilder; + private SafeHeapWriter safeHeapWriter; + private SafeDirectWriter safeDirectWriter; + private Serializer compatibleSerializer; + private com.tencent.mtt.hippy.serialization.recommend.Serializer recommendSerializer; + HippyEngine.ModuleListener mLoadModuleListener; + private TurboModuleManager mTurboModuleManager; + private HippyEngine.V8InitParams v8InitParams; + @Nullable + private NativeCallback mCallFunctionCallback; + + public HippyBridgeManagerImpl(HippyEngineContext context, HippyBundleLoader coreBundleLoader, + int bridgeType, boolean enableV8Serialization, HippyEngine.DebugMode debugMode, + String debugServerHost, + int groupId, HippyThirdPartyAdapter thirdPartyAdapter, V8InitParams v8InitParams) { + mContext = context; + mCoreBundleLoader = coreBundleLoader; + mBridgeType = bridgeType; + mDebugServerHost = debugServerHost; + mGroupId = groupId; + mThirdPartyAdapter = thirdPartyAdapter; + this.enableV8Serialization = enableV8Serialization; + this.v8InitParams = v8InitParams; + this.mDebugMode = debugMode; + + if (enableV8Serialization) { + compatibleSerializer = new Serializer(); + recommendSerializer = new com.tencent.mtt.hippy.serialization.recommend.Serializer(); + } else { + mStringBuilder = new StringBuilder(1024); + } } - } - private void handleCallFunction(Message msg) { - String action = null; - int instanceId = 0; - switch (msg.arg2) { - case FUNCTION_ACTION_LOAD_INSTANCE: { - if (msg.obj instanceof HippyMap) { - instanceId = ((HippyMap)msg.obj).getInt("id"); - HippyRootView rootView = mContext.getInstance(instanceId); - if (rootView != null) { - rootView.startMonitorEvent(HippyEngineMonitorEvent.MODULE_LOAD_EVENT_RUN_BUNDLE); - } - } - action = "loadInstance"; - break; - } - case FUNCTION_ACTION_RESUME_INSTANCE: { - action = "resumeInstance"; - break; - } - case FUNCTION_ACTION_PAUSE_INSTANCE: { - action = "pauseInstance"; - break; - } - case FUNCTION_ACTION_DESTROY_INSTANCE: { - action = "destroyInstance"; - break; - } - case FUNCTION_ACTION_CALLBACK: { - action = "callBack"; - break; - } - case FUNCTION_ACTION_CALL_JSMODULE: { - action = "callJsModule"; - break; - } + private NativeCallback generateCallback() { + return new NativeCallback(mHandler) { + @Override + public void callback(long result, String reason, @Nullable String payload) { + if ("loadInstance".equals(payload)) { + HippyRootView rootView = mContext.getInstance(); + TimeMonitor monitor = rootView == null ? null : rootView.getTimeMonitor(); + if (monitor != null) { + monitor.addPoint(HippyEngineMonitorPoint.RUN_APPLICATION_END); + } + } + if (result != 0) { + String info = "CallFunction error: action=" + payload + + ", result=" + result + ", reason=" + reason; + reportException(new Throwable(info)); + } + } + }; } - final int actinCode = msg.arg2; - final int rootViewId = instanceId; - NativeCallback callback = new NativeCallback(mHandler) { - @Override - public void Call(long result, Message message, String action, String reason) { - if (result != 0) { - String info = "CallFunction error: actinCode=" + actinCode - + ", result=" + result + ", reason=" + reason; - reportException(new Throwable(info)); - } else if (actinCode == FUNCTION_ACTION_LOAD_INSTANCE) { - HippyRootView rootView = mContext.getInstance(rootViewId); - if (rootView != null) { - rootView.startMonitorEvent(HippyEngineMonitorEvent.MODULE_LOAD_EVENT_CREATE_VIEW); - } + private void handleCallFunction(Message msg) { + if (mCallFunctionCallback == null) { + mCallFunctionCallback = generateCallback(); } - } - }; - PrimitiveValueSerializer serializer = (msg.obj instanceof JSValue) ? - recommendSerializer : compatibleSerializer; - - if (msg.arg1 == BridgeTransferType.BRIDGE_TRANSFER_TYPE_NIO.value()) { - ByteBuffer buffer; - if (enableV8Serialization) { - if (safeDirectWriter == null) { - safeDirectWriter = new SafeDirectWriter(SafeDirectWriter.INITIAL_CAPACITY, 0); - } else { - safeDirectWriter.reset(); + String action = null; + switch (msg.arg2) { + case FUNCTION_ACTION_LOAD_INSTANCE: { + HippyRootView rootView = mContext.getInstance(); + TimeMonitor monitor = rootView == null ? null : rootView.getTimeMonitor(); + if (monitor != null) { + monitor.startEvent(HippyEngineMonitorEvent.MODULE_LOAD_EVENT_RUN_BUNDLE); + } + action = "loadInstance"; + break; + } + case FUNCTION_ACTION_RESUME_INSTANCE: { + action = "resumeInstance"; + break; + } + case FUNCTION_ACTION_PAUSE_INSTANCE: { + action = "pauseInstance"; + break; + } + case FUNCTION_ACTION_DESTROY_INSTANCE: { + action = "destroyInstance"; + break; + } + case FUNCTION_ACTION_CALLBACK: { + action = "callBack"; + break; + } + case FUNCTION_ACTION_CALL_JSMODULE: { + action = "callJsModule"; + break; + } } - serializer.setWriter(safeDirectWriter); - serializer.reset(); - serializer.writeHeader(); - serializer.writeValue(msg.obj); - buffer = safeDirectWriter.chunked(); - } else { - mStringBuilder.setLength(0); - byte[] bytes = ArgumentUtils.objectToJsonOpt(msg.obj, mStringBuilder).getBytes( - StandardCharsets.UTF_16LE); - buffer = ByteBuffer.allocateDirect(bytes.length); - buffer.put(bytes); - } - - mHippyBridge.callFunction(action, callback, buffer); - } else { - if (enableV8Serialization) { - if (safeHeapWriter == null) { - safeHeapWriter = new SafeHeapWriter(); + + PrimitiveValueSerializer serializer = (msg.obj instanceof JSValue) ? + recommendSerializer : compatibleSerializer; + + if (msg.arg1 == BridgeTransferType.BRIDGE_TRANSFER_TYPE_NIO.value()) { + ByteBuffer buffer; + if (enableV8Serialization) { + if (safeDirectWriter == null) { + safeDirectWriter = new SafeDirectWriter(SafeDirectWriter.INITIAL_CAPACITY, 0); + } else { + safeDirectWriter.reset(); + } + serializer.setWriter(safeDirectWriter); + serializer.reset(); + serializer.writeHeader(); + serializer.writeValue(msg.obj); + buffer = safeDirectWriter.chunked(); + } else { + mStringBuilder.setLength(0); + byte[] bytes = ArgumentUtils.objectToJsonOpt(msg.obj, mStringBuilder).getBytes( + StandardCharsets.UTF_16LE); + buffer = ByteBuffer.allocateDirect(bytes.length); + buffer.put(bytes); + } + + mHippyBridge.callFunction(action, mCallFunctionCallback, buffer); } else { - safeHeapWriter.reset(); + if (enableV8Serialization) { + if (safeHeapWriter == null) { + safeHeapWriter = new SafeHeapWriter(); + } else { + safeHeapWriter.reset(); + } + serializer.setWriter(safeHeapWriter); + serializer.reset(); + serializer.writeHeader(); + serializer.writeValue(msg.obj); + ByteBuffer buffer = safeHeapWriter.chunked(); + int offset = buffer.arrayOffset() + buffer.position(); + int length = buffer.limit() - buffer.position(); + mHippyBridge.callFunction(action, mCallFunctionCallback, buffer.array(), offset, length); + } else { + mStringBuilder.setLength(0); + byte[] bytes = ArgumentUtils.objectToJsonOpt(msg.obj, mStringBuilder).getBytes( + StandardCharsets.UTF_16LE); + mHippyBridge.callFunction(action, mCallFunctionCallback, bytes); + } } - serializer.setWriter(safeHeapWriter); - serializer.reset(); - serializer.writeHeader(); - serializer.writeValue(msg.obj); - ByteBuffer buffer = safeHeapWriter.chunked(); - int offset = buffer.arrayOffset() + buffer.position(); - int length = buffer.limit() - buffer.position(); - mHippyBridge.callFunction(action, callback, buffer.array(), offset, length); - } else { - mStringBuilder.setLength(0); - byte[] bytes = ArgumentUtils.objectToJsonOpt(msg.obj, mStringBuilder).getBytes( - StandardCharsets.UTF_16LE); - mHippyBridge.callFunction(action, callback, bytes); - } - } - } - - private void handleDestroyBridge(Message msg) { - if (mThirdPartyAdapter != null) { - mThirdPartyAdapter.onRuntimeDestroy(); } - if (enableTurbo() && mTurboModuleManager != null) { - mTurboModuleManager.uninstall(mHippyBridge.getV8RuntimeId()); + private void handleRunScript(Message msg) { + final String script = (String) msg.obj; + mHippyBridge.runScript(script); } - @SuppressWarnings("unchecked") final com.tencent.mtt.hippy.common.Callback destroyCallback = (com.tencent.mtt.hippy.common.Callback) msg.obj; - mHippyBridge.destroy(new NativeCallback(mHandler) { - @Override - public void Call(long result, Message message, String action, String reason) { - boolean success = result == 0; - mHippyBridge.onDestroy(); - if (destroyCallback != null) { - RuntimeException exception = null; - if (!success) { - exception = new RuntimeException("destroy error: result=" + result + ", reason=" + reason); - } - - destroyCallback.callback(success, exception); + private void handleDestroyBridge(Message msg) { + if (mThirdPartyAdapter != null) { + mThirdPartyAdapter.onRuntimeDestroy(); } - } - }); - } + final boolean isReload = msg.arg1 == DESTROY_RELOAD; + @SuppressWarnings("unchecked") final com.tencent.mtt.hippy.common.Callback destroyCallback = (com.tencent.mtt.hippy.common.Callback) msg.obj; + mHippyBridge.destroy(new NativeCallback(mHandler) { + @Override + public void callback(long result, String reason, @Nullable String payload) { + boolean success = result == 0; + mHippyBridge.onDestroy(isReload); + if (destroyCallback != null) { + RuntimeException exception = null; + if (!success) { + exception = new RuntimeException( + "destroy error: result=" + result + ", reason=" + reason); + } - @Override - public boolean handleMessage(@SuppressWarnings("NullableProblems") Message msg) { - try { - switch (msg.what) { - case MSG_CODE_INIT_BRIDGE: { - mContext.getStartTimeMonitor() - .startEvent(HippyEngineMonitorEvent.ENGINE_LOAD_EVENT_INIT_BRIDGE); - @SuppressWarnings("unchecked") final com.tencent.mtt.hippy.common.Callback callback = (com.tencent.mtt.hippy.common.Callback) msg.obj; - try { - mHippyBridge = new HippyBridgeImpl(mContext, HippyBridgeManagerImpl.this, - mBridgeType == BRIDGE_TYPE_SINGLE_THREAD, enableV8Serialization, this.mIsDevModule, - this.mDebugServerHost); - - mHippyBridge.initJSBridge(getGlobalConfigs(), new NativeCallback(mHandler) { - @Override - public void Call(long result, Message message, String action, String reason) { - if (result != 0) { - String info = "initJSBridge error: result=" + result + ", reason=" + reason; - reportException(new Throwable(info)); + destroyCallback.callback(success, exception); } + mCallFunctionCallback = null; + } + }, isReload); + } - if (enableTurbo()) { - mTurboModuleManager = new TurboModuleManager(mContext); - mTurboModuleManager.install(mHippyBridge.getV8RuntimeId()); + @Override + public boolean handleMessage(@SuppressWarnings("NullableProblems") Message msg) { + try { + switch (msg.what) { + case MSG_CODE_INIT_BRIDGE: { + mContext.getStartTimeMonitor() + .startEvent(HippyEngineMonitorEvent.ENGINE_LOAD_EVENT_INIT_BRIDGE); + @SuppressWarnings("unchecked") final com.tencent.mtt.hippy.common.Callback callback = (com.tencent.mtt.hippy.common.Callback) msg.obj; + try { + mHippyBridge = new HippyBridgeImpl(mContext, HippyBridgeManagerImpl.this, + mBridgeType == BRIDGE_TYPE_SINGLE_THREAD, enableV8Serialization, + this.mDebugMode, this.mDebugServerHost, v8InitParams); + + mHippyBridge.initJSBridge(getGlobalConfigs(), new NativeCallback(mHandler) { + @Override + public void callback(long result, String reason, @Nullable String payload) { + if (result != 0) { + String info = + "initJSBridge error: result=" + result + ", reason=" + + reason; + RuntimeException exception = new RuntimeException(info); + if (callback != null) { + callback.callback(false, exception); + } + return; + } + if (enableTurbo()) { + mTurboModuleManager = new TurboModuleManager(mContext); + mTurboModuleManager.install(mHippyBridge.getV8RuntimeId()); + } + if (mThirdPartyAdapter != null) { + mThirdPartyAdapter.onRuntimeInit(mHippyBridge.getV8RuntimeId()); + } + TimeMonitor timeMonitor = mContext.getStartTimeMonitor(); + timeMonitor.addPoint(HippyEngineMonitorPoint.INIT_JS_FRAMEWORK_END); + timeMonitor.startEvent(HippyEngineMonitorEvent.ENGINE_LOAD_EVENT_LOAD_COMMONJS); + mIsInit = true; + loadCoreBundle(timeMonitor, callback); + } + }, mGroupId); + } catch (Throwable e) { + mIsInit = false; + callback.callback(false, e); + } + return true; + } + case MSG_CODE_RUN_SCRIPT: { + if (mIsInit) { + handleRunScript(msg); + } + return true; } + case MSG_CODE_RUN_BUNDLE: { + HippyRootView rootView = null; + if (msg.arg2 > 0) { + rootView = mContext.getInstance(msg.arg2); + if (rootView != null && rootView.getTimeMonitor() != null) { + rootView.getTimeMonitor() + .startEvent( + HippyEngineMonitorEvent.MODULE_LOAD_EVENT_LOAD_BUNDLE); + } + } + HippyBundleLoader loader = (HippyBundleLoader) msg.obj; + + if (!mIsInit) { + notifyModuleLoaded(ModuleLoadStatus.STATUS_ENGINE_UNINIT, + "load module error. HippyBridge mIsInit:" + mIsInit, null); + return true; + } + if (loader == null) { + notifyModuleLoaded(ModuleLoadStatus.STATUS_VARIABLE_NULL, + "load module error. loader:" + null, null); + return true; + } - if (mThirdPartyAdapter != null) { - mThirdPartyAdapter.onRuntimeInit(mHippyBridge.getV8RuntimeId()); + final String bundleUniKey = loader.getBundleUniKey(); + final HippyRootView localRootView = rootView; + + if (mLoadedBundleInfo != null && !TextUtils.isEmpty(bundleUniKey) + && mLoadedBundleInfo + .contains(bundleUniKey)) { + notifyModuleLoaded(ModuleLoadStatus.STATUS_REPEAT_LOAD, + "repeat load module. loader.getBundleUniKey=" + bundleUniKey, + localRootView); + return true; + } + + if (!TextUtils.isEmpty(bundleUniKey)) { + if (mLoadedBundleInfo == null) { + mLoadedBundleInfo = new ArrayList<>(); + } + mLoadedBundleInfo.add(bundleUniKey); + + loader.load(mHippyBridge, new NativeCallback(mHandler) { + @Override + public void callback(long result, String reason, @Nullable String payload) { + TimeMonitor timeMonitor = + localRootView == null ? null : localRootView.getTimeMonitor(); + if (timeMonitor != null && payload != null) { + try { + long ts = new JSONObject(payload).getLong("load_end_millis"); + timeMonitor.addPoint(HippyEngineMonitorPoint.SECONDARY_LOAD_SOURCE_END, ts); + timeMonitor.addPoint(HippyEngineMonitorPoint.SECONDARY_EXECUTE_SOURCE_START, ts); + } catch (JSONException ignored) { + // do nothing + } + } + if (result == 0) { + notifyModuleLoaded(ModuleLoadStatus.STATUS_OK, null, + localRootView); + } else { + notifyModuleLoaded(ModuleLoadStatus.STATUS_ERR_RUN_BUNDLE, + "load module error. loader.load failed. check the file!!", + null); + } + } + }); + } else { + notifyModuleLoaded(ModuleLoadStatus.STATUS_VARIABLE_NULL, + "can not load module. loader.getBundleUniKey=null", null); + } + + return true; } - mContext.getStartTimeMonitor() - .startEvent(HippyEngineMonitorEvent.ENGINE_LOAD_EVENT_LOAD_COMMONJS); - - if (mCoreBundleLoader != null) { - mCoreBundleLoader.load(mHippyBridge, new NativeCallback(mHandler) { - @Override - public void Call(long result, Message message, String action, String reason) { - mIsInit = result == 0; - RuntimeException exception = null; - if (!mIsInit) { - exception = new RuntimeException( - "load coreJsBundle failed, check your core jsBundle:" + reason); - } - callback.callback(mIsInit, exception); + case MSG_CODE_CALL_FUNCTION: { + if (mIsInit) { + handleCallFunction(msg); } - }); - } else { - mIsInit = true; - callback.callback(mIsInit, null); + + return true; + } + case MSG_CODE_DESTROY_BRIDGE: { + handleDestroyBridge(msg); + return true; } - } - }, mGroupId); - } catch (Throwable e) { - mIsInit = false; - callback.callback(false, e); - } - return true; - } - case MSG_CODE_RUN_BUNDLE: { - HippyRootView rootView = null; - if (msg.arg2 > 0) { - rootView = mContext.getInstance(msg.arg2); - if (rootView != null && rootView.getTimeMonitor() != null) { - rootView.getTimeMonitor() - .startEvent(HippyEngineMonitorEvent.MODULE_LOAD_EVENT_LOAD_BUNDLE); } - } - HippyBundleLoader loader = (HippyBundleLoader) msg.obj; + } catch (Throwable e) { + reportException(e); + } - if (!mIsInit) { - notifyModuleLoaded(ModuleLoadStatus.STATUS_ENGINE_UNINIT, - "load module error. HippyBridge mIsInit:" + mIsInit, null); - return true; - } - if (loader == null) { - notifyModuleLoaded(ModuleLoadStatus.STATUS_VARIABLE_NULL, - "load module error. loader:" + null, null); - return true; - } - - final String bundleUniKey = loader.getBundleUniKey(); - final HippyRootView localRootView = rootView; - - if (mLoadedBundleInfo != null && !TextUtils.isEmpty(bundleUniKey) && mLoadedBundleInfo - .contains(bundleUniKey)) { - notifyModuleLoaded(ModuleLoadStatus.STATUS_REPEAT_LOAD, - "repeat load module. loader.getBundleUniKey=" + bundleUniKey, localRootView); - return true; - } - - if (!TextUtils.isEmpty(bundleUniKey)) { - if (mLoadedBundleInfo == null) { - mLoadedBundleInfo = new ArrayList<>(); - } - mLoadedBundleInfo.add(bundleUniKey); + return false; + } + @Override + public void connectDebugUrl(String wsDebugUrl) { + mHippyBridge.connectDebugUrl(wsDebugUrl); + } - loader.load(mHippyBridge, new NativeCallback(mHandler) { - @Override - public void Call(long result, Message message, String action, String reason) { - if (result == 0) { - notifyModuleLoaded(ModuleLoadStatus.STATUS_OK, null, localRootView); - } else { - notifyModuleLoaded(ModuleLoadStatus.STATUS_ERR_RUN_BUNDLE, - "load module error. loader.load failed. check the file!!", null); + private void loadCoreBundle(TimeMonitor timeMonitor, Callback callback) { + if (mCoreBundleLoader != null) { + timeMonitor.addPoint(HippyEngineMonitorPoint.COMMON_LOAD_SOURCE_START); + mCoreBundleLoader.load(mHippyBridge, new NativeCallback(mHandler) { + @Override + public void callback(long result, String reason, @Nullable String payload) { + if (payload != null) { + try { + long ts = new JSONObject(payload).getLong("load_end_millis"); + timeMonitor.addPoint(HippyEngineMonitorPoint.COMMON_LOAD_SOURCE_END, ts); + timeMonitor.addPoint(HippyEngineMonitorPoint.COMMON_EXECUTE_SOURCE_START, ts); + } catch (JSONException ignored) { + // do nothing + } + } + RuntimeException exception = null; + boolean ret = (result == 0); + if (!ret) { + exception = new RuntimeException( + "load coreJsBundle failed, check your core jsBundle:" + + reason); + } + callback.callback(ret, exception); } - } }); - } else { - notifyModuleLoaded(ModuleLoadStatus.STATUS_VARIABLE_NULL, - "can not load module. loader.getBundleUniKey=null", null); - } - - return true; - } - case MSG_CODE_CALL_FUNCTION: { - if (mIsInit) { - handleCallFunction(msg); - } - - return true; - } - case MSG_CODE_DESTROY_BRIDGE: { - handleDestroyBridge(msg); - return true; + } else { + callback.callback(mIsInit, null); } - } - } catch (Throwable e) { - reportException(e); } - return false; - } + @Override + public void initBridge(Callback callback) { + mHandler = new Handler(mContext.getThreadExecutor().getJsThread().getLooper(), this); + Message message = mHandler.obtainMessage(MSG_CODE_INIT_BRIDGE, callback); + mHandler.sendMessage(message); + } - @Override - public void initBridge(Callback callback) { - mHandler = new Handler(mContext.getThreadExecutor().getJsThread().getLooper(), this); - Message message = mHandler.obtainMessage(MSG_CODE_INIT_BRIDGE, callback); - mHandler.sendMessage(message); - } + @Override + public void runScript(String script) { + if (!mIsInit || mHandler == null) { + return; + } + Message message = mHandler.obtainMessage(MSG_CODE_RUN_SCRIPT, script); + mHandler.sendMessage(message); + } - @Override - public void runBundle(int id, HippyBundleLoader loader, HippyEngine.ModuleListener listener, - HippyRootView hippyRootView) { - if (!mIsInit) { - mLoadModuleListener = listener; - notifyModuleLoaded(ModuleLoadStatus.STATUS_ENGINE_UNINIT, - "load module error. HippyBridge not initialized", hippyRootView); - return; + public void runInJsThread(Callback callback) { + mHippyBridge.runInJsThread(callback); } - mLoadModuleListener = listener; - Message message = mHandler.obtainMessage(MSG_CODE_RUN_BUNDLE, 0, id, loader); - mHandler.sendMessage(message); - } + @Override + public void runBundle(int id, HippyBundleLoader loader, HippyEngine.ModuleListener listener, + HippyRootView hippyRootView) { + if (!mIsInit) { + mLoadModuleListener = listener; + notifyModuleLoaded(ModuleLoadStatus.STATUS_ENGINE_UNINIT, + "load module error. HippyBridge not initialized", hippyRootView); + return; + } - public void notifyModuleJsException(final HippyJsException exception) { - if (UIThreadUtils.isOnUiThread()) { - if (mLoadModuleListener != null && mLoadModuleListener.onJsException(exception)) { - mLoadModuleListener = null; - } - } else { - UIThreadUtils.runOnUiThread(new Runnable() { - @Override - public void run() { - if (mLoadModuleListener != null && mLoadModuleListener.onJsException(exception)) { - mLoadModuleListener = null; - } + TimeMonitor monitor = hippyRootView == null ? null : hippyRootView.getTimeMonitor(); + if (monitor != null) { + monitor.addPoint(HippyEngineMonitorPoint.SECONDARY_LOAD_SOURCE_START); } - }); + mLoadModuleListener = listener; + Message message = mHandler.obtainMessage(MSG_CODE_RUN_BUNDLE, 0, id, loader); + mHandler.sendMessage(message); } - } - private void notifyModuleLoaded(final ModuleLoadStatus statusCode, final String msg, - final HippyRootView hippyRootView) { - if (UIThreadUtils.isOnUiThread()) { - if (mLoadModuleListener != null) { - mLoadModuleListener.onLoadCompleted(statusCode, msg, hippyRootView); - //mLoadModuleListener = null; - } - } else { - UIThreadUtils.runOnUiThread(new Runnable() { - @Override - public void run() { - if (mLoadModuleListener != null) { - mLoadModuleListener.onLoadCompleted(statusCode, msg, hippyRootView); - //mLoadModuleListener = null; - } + public void notifyModuleJsException(final HippyJsException exception) { + if (UIThreadUtils.isOnUiThread()) { + if (mLoadModuleListener != null && mLoadModuleListener.onJsException(exception)) { + mLoadModuleListener = null; + } + } else { + UIThreadUtils.runOnUiThread(new Runnable() { + @Override + public void run() { + if (mLoadModuleListener != null && mLoadModuleListener + .onJsException(exception)) { + mLoadModuleListener = null; + } + } + }); } - }); } - } - @Override - public void loadInstance(String name, int id, HippyMap params) { - if (!mIsInit) { - return; + private void notifyModuleLoaded(final ModuleLoadStatus statusCode, final String msg, + final HippyRootView hippyRootView) { + if (mLoadModuleListener != null) { + mLoadModuleListener.onLoadCompletedInCurrentThread(statusCode, msg, hippyRootView); + } + Runnable action = new Runnable() { + @Override + public void run() { + TimeMonitor timeMonitor = + hippyRootView == null ? null : hippyRootView.getTimeMonitor(); + if (timeMonitor != null) { + timeMonitor.addPoint( + HippyEngineMonitorPoint.SECONDARY_EXECUTE_SOURCE_END); + } + if (mLoadModuleListener != null) { + mLoadModuleListener.onLoadCompleted(statusCode, msg, hippyRootView); + //mLoadModuleListener = null; + } + } + }; + if (UIThreadUtils.isOnUiThread()) { + action.run(); + } else { + UIThreadUtils.runOnUiThread(action); + } } - HippyMap map = new HippyMap(); - map.pushString("name", name); - map.pushInt("id", id); - map.pushMap("params", params); - Message message = mHandler - .obtainMessage(MSG_CODE_CALL_FUNCTION, 0, FUNCTION_ACTION_LOAD_INSTANCE, map); - mHandler.sendMessage(message); - } - - @Override - public void resumeInstance(int id) { - if (!mIsInit) { - return; + @Override + public void loadInstance(String name, int id, HippyMap params) { + if (!mIsInit) { + return; + } + HippyRootView rootView = mContext.getInstance(id); + TimeMonitor monitor = rootView == null ? null : rootView.getTimeMonitor(); + if (monitor != null) { + monitor.addPoint(HippyEngineMonitorPoint.RUN_APPLICATION_START); + } + HippyMap map = new HippyMap(); + map.pushString("name", name); + map.pushInt("id", id); + map.pushMap("params", params); + Message message = mHandler + .obtainMessage(MSG_CODE_CALL_FUNCTION, 0, FUNCTION_ACTION_LOAD_INSTANCE, map); + mHandler.sendMessage(message); + mContext.getDevSupportManager().getInspector().updateContextName(name); } - Message message = mHandler - .obtainMessage(MSG_CODE_CALL_FUNCTION, 0, FUNCTION_ACTION_RESUME_INSTANCE, id); - mHandler.sendMessage(message); - } + @Override + public void resumeInstance(int id) { + if (!mIsInit) { + return; + } - @Override - public void pauseInstance(int id) { - if (!mIsInit) { - return; + Message message = mHandler + .obtainMessage(MSG_CODE_CALL_FUNCTION, 0, FUNCTION_ACTION_RESUME_INSTANCE, id); + mHandler.sendMessage(message); } - Message message = mHandler - .obtainMessage(MSG_CODE_CALL_FUNCTION, 0, FUNCTION_ACTION_PAUSE_INSTANCE, id); - mHandler.sendMessage(message); - } + @Override + public void pauseInstance(int id) { + if (!mIsInit) { + return; + } - @Override - public void destroyInstance(int id) { - if (!mIsInit) { - return; + Message message = mHandler + .obtainMessage(MSG_CODE_CALL_FUNCTION, 0, FUNCTION_ACTION_PAUSE_INSTANCE, id); + mHandler.sendMessage(message); } - Message message = mHandler - .obtainMessage(MSG_CODE_CALL_FUNCTION, 0, FUNCTION_ACTION_DESTROY_INSTANCE, id); - mHandler.sendMessage(message); - } + @Override + public void destroyInstance(int id) { + if (!mIsInit) { + return; + } - @Override - public void execCallback(Object params, BridgeTransferType transferType) { - Message message = mHandler - .obtainMessage(MSG_CODE_CALL_FUNCTION, transferType.value(), FUNCTION_ACTION_CALLBACK, - params); - mHandler.sendMessage(message); - } + Message message = mHandler + .obtainMessage(MSG_CODE_CALL_FUNCTION, 0, FUNCTION_ACTION_DESTROY_INSTANCE, id); + mHandler.sendMessage(message); + } - @Override - public void destroyBridge(Callback callback) { - assert (mHandler != null); - //noinspection ConstantConditions - if (mHandler == null) { - return; + @Override + public void execCallback(Object params, BridgeTransferType transferType) { + Message message = mHandler + .obtainMessage(MSG_CODE_CALL_FUNCTION, transferType.value(), + FUNCTION_ACTION_CALLBACK, + params); + mHandler.sendMessage(message); } - Message message = mHandler.obtainMessage(MSG_CODE_DESTROY_BRIDGE, callback); - mHandler.sendMessage(message); - } + @Override + public void destroyBridge(Callback callback, boolean isReload) { + assert (mHandler != null); + //noinspection ConstantConditions + if (mHandler == null) { + return; + } - @Override - public void destroy() { - mIsInit = false; - mLoadModuleListener = null; - if (mHandler != null) { - mHandler.removeMessages(MSG_CODE_INIT_BRIDGE); - mHandler.removeMessages(MSG_CODE_RUN_BUNDLE); - mHandler.removeMessages(MSG_CODE_CALL_FUNCTION); + Message message = mHandler.obtainMessage(MSG_CODE_DESTROY_BRIDGE, callback); + message.arg1 = isReload ? DESTROY_RELOAD : DESTROY_CLOSE; + mHandler.sendMessage(message); } - } - @Override - public void callJavaScriptModule(String moduleName, String methodName, Object param, - BridgeTransferType transferType) { - if (!mIsInit) { - return; + @Override + public void destroy() { + mIsInit = false; + mLoadModuleListener = null; + if (mHandler != null) { + mHandler.removeMessages(MSG_CODE_INIT_BRIDGE); + mHandler.removeMessages(MSG_CODE_RUN_BUNDLE); + mHandler.removeMessages(MSG_CODE_CALL_FUNCTION); + mHandler.removeMessages(MSG_CODE_RUN_SCRIPT); + } } - HippyMap map = new HippyMap(); - map.pushString("moduleName", moduleName); - map.pushString("methodName", methodName); - map.pushObject("params", param); + @Override + public void callJavaScriptModule(String moduleName, String methodName, Object param, + BridgeTransferType transferType) { + if (!mIsInit) { + return; + } - Message message = mHandler - .obtainMessage(MSG_CODE_CALL_FUNCTION, transferType.value(), FUNCTION_ACTION_CALL_JSMODULE, - map); - mHandler.sendMessage(message); - } + HippyMap map = new HippyMap(); + map.pushString("moduleName", moduleName); + map.pushString("methodName", methodName); + map.pushObject("params", param); - @Override - public void callNatives(String moduleName, String moduleFunc, String callId, HippyArray params) { - if (mContext != null && mContext.getModuleManager() != null) { - HippyModuleManager manager = mContext.getModuleManager(); - if (manager != null) { - HippyCallNativeParams callNativeParams = HippyCallNativeParams - .obtain(moduleName, moduleFunc, callId, params); - manager.callNatives(callNativeParams); - } + Message message = mHandler + .obtainMessage(MSG_CODE_CALL_FUNCTION, transferType.value(), + FUNCTION_ACTION_CALL_JSMODULE, + map); + mHandler.sendMessage(message); } - } - @Override - public void reportException(Throwable e) { - if (mContext == null || e == null) { - return; + @Override + public void callNatives(String moduleName, String moduleFunc, String callId, + Object params) { + if (mContext == null) { + return; + } + HippyModuleManager moduleManager = mContext.getModuleManager(); + if (moduleManager != null) { + HippyCallNativeParams callNativeParams = HippyCallNativeParams + .obtain(moduleName, moduleFunc, callId, params); + moduleManager.callNatives(callNativeParams); + } } - mContext.handleException(e); - } + @Override + public void reportException(Throwable e) { + if (mContext == null || e == null) { + return; + } - @Override - public void reportException(String message, String stackTrace) { - if (mContext == null) { - return; + mContext.handleException(e); } - mContext.handleException(new HippyJsException(message, stackTrace)); - } + @Override + public void reportException(String message, String stackTrace) { + if (mContext == null) { + return; + } - String getGlobalConfigs() { - Context context = mContext.getGlobalConfigs().getContext(); - assert (context != null); + mContext.handleException(new HippyJsException(message, stackTrace)); + } - HippyMap globalParams = new HippyMap(); - HippyMap dimensionMap = DimensionsUtil.getDimensions(-1, -1, context, false); + String getGlobalConfigs() { + Context context = mContext.getGlobalConfigs().getContext(); + assert (context != null); - if (mContext.getGlobalConfigs() != null - && mContext.getGlobalConfigs().getDeviceAdapter() != null) { - mContext.getGlobalConfigs().getDeviceAdapter() - .reviseDimensionIfNeed(context, dimensionMap, false, - false); - } - globalParams.pushMap("Dimensions", dimensionMap); - - String packageName = ""; - String versionName = ""; - String pageUrl = ""; - - HippyMap extraDataMap = new HippyMap(); - if (mThirdPartyAdapter != null) { - packageName = mThirdPartyAdapter.getPackageName(); - versionName = mThirdPartyAdapter.getAppVersion(); - pageUrl = mThirdPartyAdapter.getPageUrl(); - JSONObject jObject = mThirdPartyAdapter.getExtraData(); - extraDataMap.pushJSONObject(jObject); - } + HippyMap globalParams = new HippyMap(); + HippyMap dimensionMap = DimensionsUtil.getDimensions(-1, -1, context, false); + + if (mContext.getGlobalConfigs() != null + && mContext.getGlobalConfigs().getDeviceAdapter() != null) { + mContext.getGlobalConfigs().getDeviceAdapter() + .reviseDimensionIfNeed(context, dimensionMap, false, + false); + } + globalParams.pushMap("Dimensions", dimensionMap); + + String packageName = ""; + String versionName = ""; + String pageUrl = ""; + + HippyMap extraDataMap = new HippyMap(); + if (mThirdPartyAdapter != null) { + packageName = mThirdPartyAdapter.getPackageName(); + versionName = mThirdPartyAdapter.getAppVersion(); + pageUrl = mThirdPartyAdapter.getPageUrl(); + JSONObject jObject = mThirdPartyAdapter.getExtraData(); + extraDataMap.pushJSONObject(jObject); + } + + try { + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo( + context.getPackageName(), 0); + if (TextUtils.isEmpty(packageName)) { + packageName = packageInfo.packageName; + } + + if (TextUtils.isEmpty(versionName)) { + versionName = packageInfo.versionName; + } + } catch (Exception e) { + e.printStackTrace(); + } + + HippyMap platformParams = new HippyMap(); + platformParams.pushString("OS", "android"); + platformParams.pushString("PackageName", (packageName == null) ? "" : packageName); + platformParams.pushString("VersionName", (versionName == null) ? "" : versionName); + platformParams.pushInt("APILevel", Build.VERSION.SDK_INT); + platformParams.pushBoolean("NightMode", getNightMode()); + platformParams.pushString("SDKVersion", BuildConfig.LIBRARY_VERSION); + + HippyMap Localization = new HippyMap(); + Localization.pushString("language", I18nUtil.getLanguage()); + Localization.pushString("country", I18nUtil.getCountry()); + Localization.pushInt("direction", I18nUtil.getLayoutDirection()); + platformParams.pushMap("Localization", Localization); + + globalParams.pushMap("Platform", platformParams); + + if (mContext.getDevSupportManager().isSupportDev()) { + HippyMap debugParams = new HippyMap(); + debugParams.pushString("debugClientId", mContext.getDevSupportManager().getDebugInstanceId()); + globalParams.pushMap("Debug", debugParams); + } + + HippyMap tkd = new HippyMap(); + tkd.pushString("url", (pageUrl == null) ? "" : pageUrl); + tkd.pushString("appName", (packageName == null) ? "" : packageName); + tkd.pushString("appVersion", (versionName == null) ? "" : versionName); + tkd.pushMap("extra", extraDataMap); + globalParams.pushMap("tkd", tkd); - try { - PackageManager packageManager = context.getPackageManager(); - PackageInfo packageInfo = packageManager.getPackageInfo( - context.getPackageName(), 0); - if (TextUtils.isEmpty(packageName)) { - packageName = packageInfo.packageName; - } - - if (TextUtils.isEmpty(versionName)) { - versionName = packageInfo.versionName; - } - } catch (Exception e) { - e.printStackTrace(); + return ArgumentUtils.objectToJson(globalParams); } - HippyMap platformParams = new HippyMap(); - platformParams.pushString("OS", "android"); - platformParams.pushString("PackageName", (packageName == null) ? "" : packageName); - platformParams.pushString("VersionName", (versionName == null) ? "" : versionName); - platformParams.pushInt("APILevel", Build.VERSION.SDK_INT); - platformParams.pushBoolean("NightMode", getNightMode()); - - HippyMap Localization = new HippyMap(); - Localization.pushString("language", I18nUtil.getLanguage()); - Localization.pushString("country", I18nUtil.getCountry()); - Localization.pushInt("direction", I18nUtil.getLayoutDirection()); - platformParams.pushMap("Localization", Localization); - - globalParams.pushMap("Platform", platformParams); - - HippyMap tkd = new HippyMap(); - tkd.pushString("url", (pageUrl == null) ? "" : pageUrl); - tkd.pushString("appName", (packageName == null) ? "" : packageName); - tkd.pushString("appVersion", (versionName == null) ? "" : versionName); - tkd.pushMap("extra", extraDataMap); - globalParams.pushMap("tkd", tkd); - - return ArgumentUtils.objectToJson(globalParams); - } + private boolean getNightMode() { + int currentNightMode = + mContext.getGlobalConfigs().getContext().getResources().getConfiguration().uiMode + & Configuration.UI_MODE_NIGHT_MASK; + switch (currentNightMode) { + case Configuration.UI_MODE_NIGHT_UNDEFINED: + // We don't know what mode we're in, assume notnight + return false; + case Configuration.UI_MODE_NIGHT_NO: + // Night mode is not active, we're in day time + return false; + case Configuration.UI_MODE_NIGHT_YES: + // Night mode is active, we're at night! + return true; + default: + return false; + } + } - private boolean getNightMode() { - int currentNightMode = - mContext.getGlobalConfigs().getContext().getResources().getConfiguration().uiMode - & Configuration.UI_MODE_NIGHT_MASK; - switch (currentNightMode) { - case Configuration.UI_MODE_NIGHT_UNDEFINED: - // We don't know what mode we're in, assume notnight - return false; - case Configuration.UI_MODE_NIGHT_NO: - // Night mode is not active, we're in day time - return false; - case Configuration.UI_MODE_NIGHT_YES: - // Night mode is active, we're at night! - return true; - default: - return false; + @Override + public HippyThirdPartyAdapter getThirdPartyAdapter() { + return mThirdPartyAdapter; } - } @Override - public HippyThirdPartyAdapter getThirdPartyAdapter() { - return mThirdPartyAdapter; + public long getV8RuntimeId() { + if (!mIsInit || mHippyBridge == null) { + return V8_RUNTIME_ID_EMPTY; + } + return mHippyBridge.getV8RuntimeId(); } private boolean enableTurbo() { - return mContext.getGlobalConfigs() != null && mContext.getGlobalConfigs().enableTurbo(); - } + return mContext.getGlobalConfigs() != null && mContext.getGlobalConfigs().enableTurbo(); + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyCallNativeParams.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyCallNativeParams.java index 03fea3aef04..fa81129b883 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyCallNativeParams.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyCallNativeParams.java @@ -13,42 +13,41 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.tencent.mtt.hippy.bridge; -import com.tencent.mtt.hippy.common.HippyArray; import com.tencent.mtt.supportui.utils.struct.Pools; -@SuppressWarnings({"unused"}) public class HippyCallNativeParams { - private static final int POOL_SIZE = 20; - private static final Pools.SynchronizedPool INSTANCE_POOL = new Pools.SynchronizedPool<>( - POOL_SIZE); + private static final int POOL_SIZE = 20; + private static final Pools.SynchronizedPool INSTANCE_POOL = new Pools.SynchronizedPool<>( + POOL_SIZE); + + public String moduleName; + public String moduleFunc; + public String callId; + public Object params; - public String mModuleName; - public String mModuleFunc; - public String mCallId; - public HippyArray mParams; + public static HippyCallNativeParams obtain(String moduleName, String moduleFunc, String callId, + Object params) { + HippyCallNativeParams instance = INSTANCE_POOL.acquire(); + if (instance == null) { + instance = new HippyCallNativeParams(); + } + instance.init(moduleName, moduleFunc, callId, params); + return instance; + } + + private void init(String moduleName, String moduleFunc, String callId, Object params) { + this.moduleName = moduleName; + this.moduleFunc = moduleFunc; + this.callId = callId; + this.params = params; + } - public static HippyCallNativeParams obtain(String moduleName, String moduleFunc, String callId, - HippyArray params) { - HippyCallNativeParams instance = INSTANCE_POOL.acquire(); - if (instance == null) { - instance = new HippyCallNativeParams(); + public void onDispose() { + params = null; + INSTANCE_POOL.release(this); } - instance.init(moduleName, moduleFunc, callId, params); - return instance; - } - - private void init(String moduleName, String moduleFunc, String callId, HippyArray params) { - this.mModuleName = moduleName; - this.mModuleFunc = moduleFunc; - this.mCallId = callId; - this.mParams = params; - } - - public void onDispose() { - mParams = null; - INSTANCE_POOL.release(this); - } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyCoreAPI.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyCoreAPI.java index 0f1af9d6d78..eaa640e3ec6 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyCoreAPI.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/HippyCoreAPI.java @@ -25,7 +25,6 @@ import com.tencent.mtt.hippy.modules.nativemodules.animation.AnimationFrameModule; import com.tencent.mtt.hippy.modules.nativemodules.animation.AnimationModule; import com.tencent.mtt.hippy.modules.nativemodules.audio.AudioPlayerModule; -import com.tencent.mtt.hippy.modules.nativemodules.clipboard.ClipboardModule; import com.tencent.mtt.hippy.modules.nativemodules.debug.DevMenu; import com.tencent.mtt.hippy.modules.nativemodules.console.ConsoleModule; import com.tencent.mtt.hippy.modules.nativemodules.deviceevent.DeviceEventModule; @@ -34,6 +33,7 @@ import com.tencent.mtt.hippy.modules.nativemodules.netinfo.NetInfoModule; import com.tencent.mtt.hippy.modules.nativemodules.network.NetworkModule; import com.tencent.mtt.hippy.modules.nativemodules.network.WebSocketModule; +import com.tencent.mtt.hippy.modules.nativemodules.performance.PerformanceModule; import com.tencent.mtt.hippy.modules.nativemodules.storage.StorageModule; import com.tencent.mtt.hippy.modules.nativemodules.timer.TimerModule; import com.tencent.mtt.hippy.modules.nativemodules.uimanager.UIManagerModule; @@ -44,7 +44,6 @@ import com.tencent.mtt.hippy.views.hippylist.HippyRecyclerViewController; import com.tencent.mtt.hippy.views.image.HippyImageViewController; import com.tencent.mtt.hippy.views.list.HippyListItemViewController; -import com.tencent.mtt.hippy.views.list.HippyListViewController; import com.tencent.mtt.hippy.views.modal.HippyModalHostManager; import com.tencent.mtt.hippy.views.navigator.NavigatorController; import com.tencent.mtt.hippy.views.refresh.HippyPullFooterViewController; @@ -151,12 +150,6 @@ public HippyNativeModuleBase get() { return new UtilsModule(context); } }); - modules.put(ClipboardModule.class, new Provider() { - @Override - public HippyNativeModuleBase get() { - return new ClipboardModule(context); - } - }); modules.put(DevMenu.class, new Provider() { @Override public HippyNativeModuleBase get() { @@ -169,6 +162,12 @@ public HippyNativeModuleBase get() { return new AudioPlayerModule(context); } }); + modules.put(PerformanceModule.class, new Provider() { + @Override + public HippyNativeModuleBase get() { + return new PerformanceModule(context); + } + }); return modules; } @@ -186,7 +185,6 @@ public List> getControllers() { components.add(HippyTextViewController.class); components.add(HippyViewGroupController.class); components.add(HippyImageViewController.class); - components.add(HippyListViewController.class); components.add(HippyRecyclerViewController.class); components.add(HippyListItemViewController.class); components.add(HippyTextInputController.class); diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/NativeCallback.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/NativeCallback.java index 90f1bb06c17..79853924ce5 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/NativeCallback.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/NativeCallback.java @@ -1,54 +1,36 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.tencent.mtt.hippy.bridge; import android.os.Handler; -import android.os.Message; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; @SuppressWarnings({"unused"}) public abstract class NativeCallback { - public NativeCallback(Handler handler) { - mHandler = handler; - } + private final Handler mHandler; - public NativeCallback(Handler handler, Message msg, String action) { - mHandler = handler; - mMsg = msg; - mAction = action; - } - - public void Callback(long result, String reason) { - if (mHandler != null) { - NativeRunnable runnable = new NativeRunnable(this, result, mMsg, mAction, reason); - mHandler.post(runnable); + public NativeCallback(@NonNull Handler handler) { + mHandler = handler; } - } - - public abstract void Call(long result, Message message, String action, String reason); - - private final Handler mHandler; - private Message mMsg = null; - private String mAction = null; - public static class NativeRunnable implements Runnable { - - private final long result; - private final NativeCallback callback; - private final Message message; - private final String action; - private final String reason; - - public NativeRunnable(NativeCallback callback, long result, Message message, - String action, String reason) { - this.result = result; - this.callback = callback; - this.message = message; - this.action = action; - this.reason = reason; + public final void nativeCallback(long result, String reason, @Nullable String payload) { + mHandler.post(() -> callback(result, reason, payload)); } - @Override - public void run() { - callback.Call(result, message, action, reason); - } - } + public abstract void callback(long result, String reason, @Nullable String payload); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/libraryloader/LibraryLoader.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/libraryloader/LibraryLoader.java index a42fc7a5e1a..ed28b2d49bb 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/libraryloader/LibraryLoader.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/libraryloader/LibraryLoader.java @@ -16,7 +16,9 @@ package com.tencent.mtt.hippy.bridge.libraryloader; +import android.text.TextUtils; import com.tencent.mtt.hippy.BuildConfig; +import com.tencent.mtt.hippy.adapter.soloader.HippySoLoaderAdapter; public class LibraryLoader { @@ -25,18 +27,30 @@ public class LibraryLoader { "hippy", "flexbox" }; - public static synchronized void loadLibraryIfNeed() { + public static void loadLibraryIfNeeded(HippySoLoaderAdapter soLoaderAdapter) { if (hasLoaded || BuildConfig.ENABLE_SO_DOWNLOAD) { return; } - - try { - for (String name : SO_NAME_LIST) { - System.loadLibrary(name); + synchronized (LibraryLoader.class) { + if (hasLoaded) { + return; + } + try { + for (String name : SO_NAME_LIST) { + String tinkerSoPath = null; + if (soLoaderAdapter != null) { + tinkerSoPath = soLoaderAdapter.loadSoPath(name); + } + if (!TextUtils.isEmpty(tinkerSoPath)) { + System.load(tinkerSoPath); + } else { + System.loadLibrary(name); + } + } + hasLoaded = true; + } catch (Throwable e) { + e.printStackTrace(); } - hasLoaded = true; - } catch (Throwable e) { - e.printStackTrace(); } } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/serialization/delegate/DeserializerDelegate.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/serialization/delegate/DeserializerDelegate.java index c3a4c6e72e7..8616acaac47 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/serialization/delegate/DeserializerDelegate.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/serialization/delegate/DeserializerDelegate.java @@ -1,8 +1,26 @@ +/* + * Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ package com.tencent.mtt.hippy.bridge.serialization.delegate; import com.tencent.mtt.hippy.NativeAccess; import com.tencent.mtt.hippy.runtime.builtins.JSSharedArrayBuffer; import com.tencent.mtt.hippy.runtime.builtins.wasm.WasmModule; +import com.tencent.mtt.hippy.serialization.recommend.SharedValueConveyor; import com.tencent.mtt.hippy.serialization.exception.DataCloneDeserializationException; import com.tencent.mtt.hippy.serialization.recommend.Deserializer; @@ -33,4 +51,9 @@ public JSSharedArrayBuffer getSharedArrayBufferFromId(Deserializer deserializer, public WasmModule getWasmModuleFromId(Deserializer deserializer, int transfer_id) { return NativeAccess.getWasmModuleFromId(transfer_id); } + + @Override + public SharedValueConveyor getSharedValueConveyor(Deserializer deserializer) { + return null; + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/serialization/delegate/SerializerDelegate.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/serialization/delegate/SerializerDelegate.java index e4707ee6cfd..3b5560eab91 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/serialization/delegate/SerializerDelegate.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/serialization/delegate/SerializerDelegate.java @@ -1,9 +1,27 @@ +/* + * Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ package com.tencent.mtt.hippy.bridge.serialization.delegate; import com.tencent.mtt.hippy.exception.UnexpectedTypeException; import com.tencent.mtt.hippy.runtime.builtins.JSSharedArrayBuffer; import com.tencent.mtt.hippy.runtime.builtins.JSValue; import com.tencent.mtt.hippy.runtime.builtins.wasm.WasmModule; +import com.tencent.mtt.hippy.serialization.recommend.SharedValueConveyor; import com.tencent.mtt.hippy.serialization.recommend.Serializer; import java.util.Map; @@ -30,6 +48,11 @@ public boolean writeHostObject(Serializer serializer, Object object) { return false; } + @Override + public boolean adoptSharedValueConveyor(Serializer serializer, SharedValueConveyor conveyor) { + return false; + } + // region set & set private int getObjectId(T object) { int id = nextId.incrementAndGet(); diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/serialization/delegate/SerializerDelegateHost.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/serialization/delegate/SerializerDelegateHost.java index 1e7e5eb375c..68ff4162d28 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/serialization/delegate/SerializerDelegateHost.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/bridge/serialization/delegate/SerializerDelegateHost.java @@ -1,3 +1,20 @@ +/* + * Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ package com.tencent.mtt.hippy.bridge.serialization.delegate; import com.tencent.mtt.hippy.runtime.builtins.JSSharedArrayBuffer; diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DebugWebSocketClient.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DebugWebSocketClient.java index 7f78dc3fe21..a1a2f6d2e6c 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DebugWebSocketClient.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DebugWebSocketClient.java @@ -15,6 +15,8 @@ */ package com.tencent.mtt.hippy.devsupport; +import android.os.Handler; +import android.os.Looper; import com.tencent.mtt.hippy.utils.LogUtils; import com.tencent.mtt.hippy.websocket.WebSocketClient; @@ -27,13 +29,29 @@ public class DebugWebSocketClient implements WebSocketClient.WebSocketListener { private final ConcurrentHashMap mCallbacks = new ConcurrentHashMap<>(); WebSocketClient mWebSocket; private JSDebuggerCallback mConnectCallback; - + private Handler mHandler; private DevRemoteDebugProxy.OnReceiveDataListener mReceiveDataListener; + private static final int RECONNECT_TIME_DELAY = 3000; + private final Runnable mReconnectRunnable = new Runnable() { + @Override + public void run() { + if (mWebSocket == null || mWebSocket.isConnected()) { + return; + } + mWebSocket.connect(); + } + }; public void connect(String url, JSDebuggerCallback callback) { mConnectCallback = callback; mWebSocket = new WebSocketClient(URI.create(url), this, null); mWebSocket.connect(); + mHandler = new Handler(Looper.getMainLooper()); + } + + private void reconnect() { + mHandler.removeCallbacks(mReconnectRunnable); + mHandler.postDelayed(mReconnectRunnable, RECONNECT_TIME_DELAY); } public void setOnReceiveDataCallback(DevRemoteDebugProxy.OnReceiveDataListener l) { @@ -46,8 +64,14 @@ public void closeQuietly() { } } + public void close(int code, String reason) { + if (mWebSocket != null) { + mWebSocket.requestClose(code, reason); + } + } + public void sendMessage(String message) { - if (mWebSocket == null) { + if (mWebSocket == null || !mWebSocket.isConnected()) { LogUtils.e("sendMessage", "mWebSocket is null"); return; } @@ -79,12 +103,21 @@ public void onConnect() { @Override public void onDisconnect(int code, String reason) { - mWebSocket = null; + LogUtils.d("onDisconnect","code:" + code + ",reason:" + reason); + if (mConnectCallback != null) { + mConnectCallback.onFailure(new Exception(reason)); + mConnectCallback = null; + } + if (code == 0 && (WebSocketClient.DISCONNECT_REASON_EOF.equals(reason) + || WebSocketClient.DISCONNECT_REASON_CONNECT.equals(reason))) { + reconnect(); + } else { + mWebSocket = null; + mHandler.removeCallbacks(mReconnectRunnable); + } } private void abort(Throwable cause) { - closeQuietly(); - // Trigger failure callbacks if (mConnectCallback != null) { mConnectCallback.onFailure(cause); @@ -94,6 +127,8 @@ private void abort(Throwable cause) { callback.onFailure(cause); } mCallbacks.clear(); + + closeQuietly(); } public interface JSDebuggerCallback { diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevFactory.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevFactory.java index 12c0c6de8b5..a791c091fb8 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevFactory.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevFactory.java @@ -20,9 +20,9 @@ public class DevFactory { public static DevServerInterface create(HippyGlobalConfigs configs, boolean enableDev, - String serverHost, String bundleName) { + String serverHost, String bundleName, String remoteServerUrl) { if (enableDev) { - return new DevServerImpl(configs, serverHost, bundleName); + return new DevServerImpl(configs, serverHost, bundleName, remoteServerUrl); } else { return new DevServerImplDisable(configs, serverHost); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevRemoteServerData.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevRemoteServerData.java new file mode 100644 index 00000000000..a0b859987fc --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevRemoteServerData.java @@ -0,0 +1,125 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.mtt.hippy.devsupport; + +import android.text.TextUtils; +import com.tencent.mtt.hippy.utils.LogUtils; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLDecoder; +import java.util.regex.Pattern; + +public class DevRemoteServerData { + + private static String KEY_DEBUG_URL = "debugUrl"; + private String scheme; // protocol http or https + private String host; // host:port address + private String path; + private String versionId; + private String wsUrl; // ws debug url if remoteDebugUrl with + + public DevRemoteServerData(String remoteServerUrl) { + parseUrl(remoteServerUrl); + } + + /** + * parse url in remote debugging + *

url structure: http://host/versionId/index.bundle

+ * + * @param remoteServerUrl remote debugging url + */ + private void parseUrl(String remoteServerUrl) { + if (TextUtils.isEmpty(remoteServerUrl)) { + return; + } + String wsUrlPattern = "^wss?://.+"; + try { + if (Pattern.matches(wsUrlPattern, remoteServerUrl)) { + wsUrl = remoteServerUrl; + host = "fakeurl.com"; // just for the isValid rule + } else { + URL url = new URL(remoteServerUrl); + scheme = url.getProtocol(); + host = url.getHost(); + path = url.getPath(); + int port = url.getPort(); + if (port > 0) { + host = host + ":" + port; + } + wsUrl = parseQueryDebugUrl(url.getQuery()); + if (path.startsWith("/")) { + path = path.substring(1); // remove first character / + } + int index = path.indexOf("/"); + if (index >= 0) { + versionId = path.substring(0, index); + } + LogUtils.i("Hippy DevRemoteServerData", + String.format("parseUrl host:%s, versionId:%s", host, versionId)); + } + } catch (Exception e) { + LogUtils.e("Hippy DevRemoteServerData", "parseUrl error", e); + } + } + + /** + * parse the ws debug url when remoteDebugUrl with + * + * @param query query params + * @return debugUrl value + * @throws UnsupportedEncodingException not support encoding + */ + private String parseQueryDebugUrl(String query) throws UnsupportedEncodingException { + if (TextUtils.isEmpty(query)) { + return null; + } + String[] queryList = query.split("&"); + for (String queryItem : queryList) { + int idPosition = queryItem.indexOf("="); + if (idPosition >= 0) { + String findKey = queryItem.substring(0, idPosition); + if (KEY_DEBUG_URL.equals(findKey)) { + return URLDecoder.decode(queryItem.substring(idPosition + 1), "UTF-8"); + } + } + } + return null; + } + + public boolean isValid() { + return !TextUtils.isEmpty(host); + } + + public String getHost() { + return host; + } + + public String getVersionId() { + return versionId; + } + + public String getPath() { + return path; + } + + public String getScheme() { + return scheme; + } + + public String getWsUrl() { + return wsUrl; + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevServerConfig.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevServerConfig.java index c58c07f0089..7ed6c1954a6 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevServerConfig.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevServerConfig.java @@ -32,17 +32,24 @@ public class DevServerConfig { // Hippy Server JsBundle名字 private final String mServerHost; + private final String mBundleName; + @SuppressWarnings("unused") public DevServerConfig(String serverHost, String bundleName) { sharedPreferences = ContextHolder.getAppContext() .getSharedPreferences(HIPPYDEBUGPREF, Context.MODE_PRIVATE); mServerHost = serverHost; + mBundleName = bundleName; } public String getServerHost() { return mServerHost; } + public String getBundleName() { + return mBundleName; + } + public boolean enableRemoteDebug() { return sharedPreferences.getBoolean(JS_REMOTE_DEBUG, false); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevServerHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevServerHelper.java index 9c5bd4f0fba..f932c38dffe 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevServerHelper.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevServerHelper.java @@ -16,6 +16,8 @@ package com.tencent.mtt.hippy.devsupport; +import android.text.TextUtils; + import com.tencent.mtt.hippy.HippyGlobalConfigs; import com.tencent.mtt.hippy.adapter.http.HippyHttpAdapter; import com.tencent.mtt.hippy.adapter.http.HippyHttpRequest; @@ -27,23 +29,52 @@ @SuppressWarnings({"unused"}) public class DevServerHelper { - private static final String BUNDLE_URL_FORMAT = "http://%s/%s?platform=android&dev=%s&hot=%s&minify=%s"; + private static final String BUNDLE_URL_FORMAT = "%s://%s/%s?platform=android&dev=%s&hot=%s&minify=%s"; // --Commented out by Inspection (2021/5/4 20:09):private static final String LAUNCH_JS_DEVTOOLS_COMMAND_URL_FORMAT = "http://%s/launch-js-devtools"; // --Commented out by Inspection (2021/5/4 20:10):private static final String WEBSOCKET_PROXY_URL_FORMAT = "ws://%s/debugger-proxy?role=client"; private static final String WEBSOCKET_LIVERELOAD_URL_FORMAT = "ws://%s/debugger-live-reload"; // --Commented out by Inspection (2021/5/4 20:10):private static final String ONCHANGE_ENDPOINT_URL_FORMAT = "http://%s/onchange"; + private static final String DEBUG_URL_PREFIX = "ws://%s/debugger-proxy"; + private static final String DEBUG_URL_APPEND = "role=android_client&clientId=%s&hash=%s&contextName=%s"; + private static final String DEFAULT_BUNDLE_SCHEME = "http"; private final HippyGlobalConfigs mGlobalConfigs; private final String mServerHost; + DevRemoteServerData mRemoteServerData; - public DevServerHelper(HippyGlobalConfigs configs, String serverHost) { + public DevServerHelper(HippyGlobalConfigs configs, String serverHost, String remoteServerUrl) { mGlobalConfigs = configs; mServerHost = serverHost; + mRemoteServerData = new DevRemoteServerData(remoteServerUrl); + } + // 单独设置remoteServerUrl的解析 + public void setRemoteServerData(String wsDebugUrl) { + mRemoteServerData = new DevRemoteServerData(wsDebugUrl); } public String createBundleURL(String host, String bundleName, boolean devMode, boolean hmr, - boolean jsMinify) { - return String.format(Locale.US, BUNDLE_URL_FORMAT, host, bundleName, devMode, hmr, jsMinify); + boolean jsMinify) { + if (mRemoteServerData.isValid()) { + // remote debugging in non usb + return String.format(Locale.US, BUNDLE_URL_FORMAT, mRemoteServerData.getScheme(), mRemoteServerData.getHost(), mRemoteServerData.getPath(), + devMode, hmr, jsMinify); + } + return String.format(Locale.US, BUNDLE_URL_FORMAT, DEFAULT_BUNDLE_SCHEME, host, bundleName, devMode, hmr, jsMinify); + } + + public String createDebugURL(String host, String componentName, String clientId) { + String debugUrl = DEBUG_URL_PREFIX + "?" + DEBUG_URL_APPEND; + if (mRemoteServerData.isValid()) { + // remote debugging in non usb + if (!TextUtils.isEmpty(mRemoteServerData.getWsUrl())) { + // use the remoteServer ws url first + debugUrl = mRemoteServerData.getWsUrl() + (mRemoteServerData.getWsUrl().contains("?") ? "&" : "?") + DEBUG_URL_APPEND; + return String.format(Locale.US, debugUrl, clientId, mRemoteServerData.getVersionId(), componentName); + } + return String.format(Locale.US, debugUrl, mRemoteServerData.getHost(), clientId, + mRemoteServerData.getVersionId(), componentName); + } + return String.format(Locale.US, debugUrl, host, clientId, "", componentName); } public String getLiveReloadURL() { diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevServerImpl.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevServerImpl.java index ba118cd32e3..bffecaec812 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevServerImpl.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevServerImpl.java @@ -21,6 +21,7 @@ import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; +import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; @@ -33,6 +34,7 @@ import java.io.InputStream; import java.util.HashMap; import java.util.Stack; +import java.util.UUID; @SuppressWarnings({"unused"}) public class DevServerImpl implements View.OnClickListener, DevServerInterface, @@ -51,8 +53,9 @@ public class DevServerImpl implements View.OnClickListener, DevServerInterface, private final Stack mDebugButtonStack; private final LiveReloadController mLiveReloadController; - DevServerImpl(HippyGlobalConfigs configs, String serverHost, String bundleName) { - mFetchHelper = new DevServerHelper(configs, serverHost); + DevServerImpl(HippyGlobalConfigs configs, String serverHost, String bundleName, + String remoteServerUrl) { + mFetchHelper = new DevServerHelper(configs, serverHost, remoteServerUrl); mServerConfig = new DevServerConfig(serverHost, bundleName); mDebugButtonStack = new Stack<>(); mHostButtonMap = new HashMap<>(); @@ -114,6 +117,11 @@ public String createResourceUrl(String resName) { false, false); } + @Override + public void setRemoteServerData(String wsDebugUrl) { + mFetchHelper.setRemoteServerData(wsDebugUrl); + } + @Override public void loadRemoteResource(String url, final DevServerCallBack serverCallBack) { mFetchHelper.fetchBundleFromURL(new BundleFetchCallBack() { @@ -143,6 +151,12 @@ public void onFail(Exception exception) { }, url); } + @Override + public String createDebugUrl(String host, String debugClientId, String componentName) { + return mFetchHelper.createDebugURL(host, !TextUtils.isEmpty(componentName) ? componentName : + mServerConfig.getBundleName(), debugClientId); + } + @Override public void reload() { if (mServerCallBack != null) { diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevServerImplDisable.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevServerImplDisable.java index d78391a1f0b..e01a3d7b926 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevServerImplDisable.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevServerImplDisable.java @@ -25,7 +25,7 @@ public class DevServerImplDisable implements DevServerInterface { final DevServerHelper mFetchHelper; DevServerImplDisable(HippyGlobalConfigs configs, String serverHost) { - mFetchHelper = new DevServerHelper(configs, serverHost); + mFetchHelper = new DevServerHelper(configs, serverHost, null); } @Override @@ -57,6 +57,16 @@ public void onFail(Exception exception) { }, url); } + @Override + public void setRemoteServerData(String wsDebugUrl) { + + } + + @Override + public String createDebugUrl(String host, String debugClientId, String componentName) { + return null; + } + @Override public void setDevServerCallback(DevServerCallBack devServerCallback) { diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevServerInterface.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevServerInterface.java index 29180a8177c..c5b5aa2c043 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevServerInterface.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevServerInterface.java @@ -26,6 +26,8 @@ public interface DevServerInterface { void loadRemoteResource(String url, DevServerCallBack serverCallBack); + String createDebugUrl(String host, String debugClientId, String componentName); + void setDevServerCallback(DevServerCallBack devServerCallback); void attachToHost(HippyRootView view); @@ -33,4 +35,6 @@ public interface DevServerInterface { void detachFromHost(HippyRootView view); void handleException(Throwable throwable); + + void setRemoteServerData(String wsDebugUrl); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevSupportManager.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevSupportManager.java index 7d6d7dcdaab..07791764eb9 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevSupportManager.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/DevSupportManager.java @@ -17,6 +17,7 @@ import com.tencent.mtt.hippy.HippyGlobalConfigs; import com.tencent.mtt.hippy.HippyRootView; +import com.tencent.mtt.hippy.devsupport.inspector.Inspector; import java.util.UUID; @SuppressWarnings({"unused"}) @@ -24,12 +25,16 @@ public class DevSupportManager { final DevServerInterface mDevImp; final boolean mSupportDev; - private UUID mInstanceUUID = UUID.randomUUID(); + private final Inspector mInspector; + private String mDebugComponentName; + // to differ hippy page + private final UUID mInstanceUUID = UUID.randomUUID(); public DevSupportManager(HippyGlobalConfigs configs, boolean enableDev, String serverHost, - String bundleName) { - this.mDevImp = DevFactory.create(configs, enableDev, serverHost, bundleName); + String bundleName, String remoteServerUrl) { + this.mDevImp = DevFactory.create(configs, enableDev, serverHost, bundleName, remoteServerUrl); mSupportDev = enableDev; + mInspector = new Inspector(); } public DevServerInterface getDevImp() { @@ -40,6 +45,10 @@ public void setDevCallback(DevServerCallBack devCallback) { mDevImp.setDevServerCallback(devCallback); } + public void setRemoteServerData(String wsDebugUrl) { + mDevImp.setRemoteServerData(wsDebugUrl); + } + public void attachToHost(HippyRootView view) { mDevImp.attachToHost(view); } @@ -52,6 +61,10 @@ public String createResourceUrl(String resName) { return mDevImp.createResourceUrl(resName); } + public String createDebugUrl(String host) { + return mDevImp.createDebugUrl(host, mInstanceUUID.toString(), mDebugComponentName); + } + public void handleException(Throwable throwable) { mDevImp.handleException(throwable); } @@ -60,11 +73,19 @@ public void loadRemoteResource(String url, DevServerCallBack serverCallBack) { mDevImp.loadRemoteResource(url, serverCallBack); } - public String getDevInstanceUUID() { - return mInstanceUUID.toString(); - } - public boolean isSupportDev() { return mSupportDev; } + + public void setDebugComponentName(String debugComponentName) { + mDebugComponentName = debugComponentName; + } + + public String getDebugInstanceId() { + return mInstanceUUID.toString(); + } + + public Inspector getInspector() { + return mInspector; + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/Inspector.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/Inspector.java index 86323698954..eade40f1b70 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/Inspector.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/Inspector.java @@ -1,10 +1,31 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018-2022 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.tencent.mtt.hippy.devsupport.inspector; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.text.TextUtils; +import com.tencent.mtt.hippy.BuildConfig; import com.tencent.mtt.hippy.HippyEngineContext; import com.tencent.mtt.hippy.devsupport.DebugWebSocketClient; import com.tencent.mtt.hippy.devsupport.inspector.domain.CSSDomain; import com.tencent.mtt.hippy.devsupport.inspector.domain.DomDomain; +import com.tencent.mtt.hippy.devsupport.inspector.domain.InputDomain; import com.tencent.mtt.hippy.devsupport.inspector.domain.InspectorDomain; import com.tencent.mtt.hippy.devsupport.inspector.domain.PageDomain; import com.tencent.mtt.hippy.devsupport.inspector.model.InspectEvent; @@ -14,44 +35,55 @@ import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Map; +import org.json.JSONException; import org.json.JSONObject; public class Inspector implements BatchListener { private static final String TAG = "Inspector"; - + private static final String RENDERER_TYPE = "Native"; private static final String CHROME_SOCKET_CLOSED = "chrome_socket_closed"; - private static Inspector sInspector; + public static int CLOSE_DESTROY = 4003; + public static int CLOSE_RELOAD = 4004; private Map mDomainMap = new HashMap<>(); private DebugWebSocketClient mDebugWebSocketClient; private WeakReference mContextRef; private boolean needBatchUpdateDom = true; - public static synchronized Inspector getInstance(HippyEngineContext context) { - if (sInspector == null) { - sInspector = new Inspector(context); - } - return sInspector; - } - - private Inspector(HippyEngineContext context) { + public Inspector() { DomDomain domDomain = new DomDomain(this); CSSDomain cssDomain = new CSSDomain(this); PageDomain pageDomain = new PageDomain(this); + InputDomain inputDomain = new InputDomain(this); mDomainMap.put(domDomain.getDomainName(), domDomain); mDomainMap.put(cssDomain.getDomainName(), cssDomain); mDomainMap.put(pageDomain.getDomainName(), pageDomain); + mDomainMap.put(inputDomain.getDomainName(), inputDomain); + } + + public Inspector setEngineContext(HippyEngineContext context, DebugWebSocketClient client) { + mContextRef = new WeakReference<>(context); + mDebugWebSocketClient = client; DomManager domManager = context.getDomManager(); if (domManager != null) { domManager.setOnBatchListener(this); } + return this; } - public Inspector setWebSocketClient(DebugWebSocketClient client) { - mDebugWebSocketClient = client; - return this; + public void onDestroy() { + if (getContext() == null) { + return; + } + DomManager domManager = getContext().getDomManager(); + if (domManager != null) { + domManager.setOnBatchListener(null); + } + for (InspectorDomain domain: mDomainMap.values()) { + domain.onDestroy(); + } } public boolean dispatchReqFromFrontend(HippyEngineContext context, String msg) { @@ -80,11 +112,7 @@ public boolean dispatchReqFromFrontend(HippyEngineContext context, String msg) { String method = methodParamArray[1]; int id = msgObj.optInt("id"); JSONObject paramsObj = msgObj.optJSONObject("params"); - boolean shouldHandle = inspectorDomain.handleRequestFromBackend(context, method, id, paramsObj); - if (shouldHandle) { - mContextRef = new WeakReference<>(context); - } - return shouldHandle; + return inspectorDomain.handleRequestFromBackend(context, method, id, paramsObj); } } } @@ -137,6 +165,39 @@ public void setNeedBatchUpdateDom(boolean needBatchUpdate) { needBatchUpdateDom = needBatchUpdate; } + public void updateContextName(String name) { + if (getContext() == null) { + return; + } + try { + JSONObject contextObj = new JSONObject(); + contextObj.put("contextName", name); + + Context context = getContext().getGlobalConfigs().getContext(); + int moduleCount = getContext().getModuleManager().getNativeModuleCount(); + int viewCount = getContext().getRenderManager().getControllerManager().getControllerCount(); + + String packageName = ""; + String versionName = ""; + if (context != null) { + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); + packageName = packageInfo.packageName; + versionName = packageInfo.versionName; + } + contextObj.put("bundleId", packageName); + contextObj.put("hostVersion", versionName); + contextObj.put("sdkVersion", BuildConfig.LIBRARY_VERSION); + contextObj.put("rendererType", RENDERER_TYPE); + contextObj.put("viewCount", viewCount); + contextObj.put("moduleCount", moduleCount); + sendEventToFrontend(new InspectEvent("TDFRuntime.updateContextInfo", contextObj)); + } catch (Exception e) { + LogUtils.e(TAG, "updateContextName, exception:", e); + } + } + + @Override public void onBatch(boolean isAnimation) { if (needBatchUpdateDom && !isAnimation) { diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/domain/DomDomain.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/domain/DomDomain.java index e0f11c9e9fd..2eaf566914f 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/domain/DomDomain.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/domain/DomDomain.java @@ -16,6 +16,8 @@ public class DomDomain extends InspectorDomain { private static final String METHOD_GET_NODE_FOR_LOCATION = "getNodeForLocation"; private static final String METHOD_REMOVE_NODE = "removeNode"; private static final String METHOD_SET_INSPECT_NODE = "setInspectedNode"; + private static final String METHOD_PUSH_NODE_BY_PATH = "pushNodeByPathToFrontend"; + private static final String METHOD_PUSH_NODE_BY_BACKEND = "pushNodesByBackendIdsToFrontend"; private DomModel domModel; @@ -45,6 +47,12 @@ public boolean handleRequest(HippyEngineContext context, String method, int id, case METHOD_SET_INSPECT_NODE: handleSetInspectMode(context, id, paramsObj); break; + case METHOD_PUSH_NODE_BY_PATH: + handlePushNodeByPath(context, id, paramsObj); + break; + case METHOD_PUSH_NODE_BY_BACKEND: + handlePushNodesByBackendIds(context, id, paramsObj); + break; default: return false; } @@ -71,6 +79,18 @@ private void handleSetInspectMode(HippyEngineContext context, int id, JSONObject sendRspToFrontend(id, result); } + private void handlePushNodeByPath(HippyEngineContext context, int id, JSONObject paramsObj) { + JSONObject result = domModel.getNodeForPath(context, paramsObj); + sendRspToFrontend(id, result); + } + + private void handlePushNodesByBackendIds(HippyEngineContext context, int id, + JSONObject paramsObj) { + JSONObject result = domModel.getNodeByBackendIds(context, paramsObj); + sendRspToFrontend(id, result); + } + + public void sendUpdateEvent() { InspectEvent updateEvent = new InspectEvent("DOM.documentUpdated", new JSONObject()); sendEventToFrontend(updateEvent); diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/domain/InputDomain.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/domain/InputDomain.java new file mode 100644 index 00000000000..257ced67ef4 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/domain/InputDomain.java @@ -0,0 +1,111 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.devsupport.inspector.domain; + +import android.os.Handler; +import android.os.Looper; +import android.os.SystemClock; +import android.view.MotionEvent; +import com.tencent.mtt.hippy.HippyEngineContext; +import com.tencent.mtt.hippy.HippyRootView; +import com.tencent.mtt.hippy.devsupport.inspector.Inspector; +import com.tencent.mtt.hippy.utils.LogUtils; +import org.json.JSONException; +import org.json.JSONObject; + +public class InputDomain extends InspectorDomain { + + private static final String TAG = "InputDomain"; + private static final String DOMAIN_NAME = "Input"; + private static final String METHOD_EMULATE_TOUCH_FROM_MOUSE_EVENT = + "emulateTouchFromMouseEvent"; + private final Handler handler; + + public InputDomain(Inspector inspector) { + super(inspector); + handler = new Handler(Looper.getMainLooper()); + } + + @Override + public String getDomainName() { + return DOMAIN_NAME; + } + + @Override + protected boolean handleRequest(HippyEngineContext context, String method, int id, + JSONObject paramsObj) { + if (METHOD_EMULATE_TOUCH_FROM_MOUSE_EVENT.equals(method)) { + handleEmulateTouchFromMouseEvent(context, id, paramsObj); + return true; + } + return false; + } + + private void handleEmulateTouchFromMouseEvent(HippyEngineContext context, int id, + JSONObject paramsObj) { + try { + String type = paramsObj.getString("type"); + int x = paramsObj.getInt("x"); + int y = paramsObj.getInt("y"); + String button = paramsObj.getString("button"); + int modifiers = paramsObj.optInt("modifiers", 0); + long time = SystemClock.uptimeMillis(); + if ("mouseWheel".equals(type) && modifiers == 0) { + int deltaX = paramsObj.getInt("deltaX"); + int deltaY = paramsObj.getInt("deltaY"); + MotionEvent down = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, x, y, 0); + handler.post(() -> dispatchTouchEvent(context, down)); + MotionEvent move = MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, + x + deltaX, y + deltaY, 0); + handler.post(() -> dispatchTouchEvent(context, move)); + MotionEvent up = MotionEvent.obtain(time, time, MotionEvent.ACTION_UP, x + deltaX, + y + deltaY, 0); + handler.post(() -> dispatchTouchEvent(context, up)); + } else if ("left".equals(button) && modifiers == 0) { + int action; + switch (type) { + case "mousePressed": + action = MotionEvent.ACTION_DOWN; + break; + case "mouseMoved": + action = MotionEvent.ACTION_MOVE; + break; + case "mouseReleased": + default: + action = MotionEvent.ACTION_UP; + break; + } + MotionEvent event = MotionEvent.obtain(0, 0, action, x, y, 0); + handler.post(() -> dispatchTouchEvent(context, event)); + } + } catch (JSONException e) { + LogUtils.d(TAG, "parse error", e); + } + sendRspToFrontend(id, new JSONObject()); + } + + private void dispatchTouchEvent(HippyEngineContext context, MotionEvent event) { + try { + HippyRootView rootView = context.getInstance(); + if (rootView != null) { + rootView.dispatchTouchEvent(event); + } + } finally { + event.recycle(); + } + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/domain/InspectorDomain.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/domain/InspectorDomain.java index a12b72e3c02..430c1026575 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/domain/InspectorDomain.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/domain/InspectorDomain.java @@ -42,13 +42,8 @@ public boolean handleRequestFromBackend(HippyEngineContext context, String metho isEnable = false; inspector.rspToFrontend(id, null); return true; - } else { - if (isEnable) { - return handleRequest(context, method, id, paramsObj); - } else { - return false; - } } + return handleRequest(context, method, id, paramsObj); } /** @@ -102,4 +97,6 @@ protected void sendEventToFrontend(InspectEvent event) { public void onFrontendClosed(HippyEngineContext context) { } + public void onDestroy() {} + } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/domain/PageDomain.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/domain/PageDomain.java index 2e79403d583..c77e8e77086 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/domain/PageDomain.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/domain/PageDomain.java @@ -28,7 +28,7 @@ public class PageDomain extends InspectorDomain implements Handler.Callback, Pag private static final int MSG_SCREEN_CAST_ACK = 0x02; private static final long FRAME_CALLBACK_INTERVAL = 1000L; - private static final long DELAY_FOR_FRAME_UPDATE = 100L; + private static final long DELAY_FOR_FRAME_UPDATE = 200L; public static final String BUNDLE_KEY_PARAM = "params"; @@ -67,7 +67,9 @@ public boolean handleRequest(HippyEngineContext context, String method, int id, } private void handleStartScreenCast(HippyEngineContext context, int id, JSONObject paramsObj) { - mHandlerThread = new ScreenCastHandlerThread(this); + if (mHandlerThread == null) { + mHandlerThread = new ScreenCastHandlerThread(this); + } Handler hander = mHandlerThread.getHandler(); Message msg = hander.obtainMessage(MSG_START_SCREEN_CAST); msg.obj = context; @@ -85,8 +87,6 @@ private void handleStopScreenCast(HippyEngineContext context) { Handler hander = mHandlerThread.getHandler(); hander.removeMessages(MSG_START_SCREEN_CAST); hander.removeMessages(MSG_SCREEN_CAST_ACK); - mHandlerThread.quit(); - mHandlerThread = null; } } @@ -96,6 +96,15 @@ public void onFrontendClosed(HippyEngineContext context) { mPageModel.clear(); } + @Override + public void onDestroy() { + super.onDestroy(); + if (mHandlerThread != null) { + mHandlerThread.quit(); + mHandlerThread = null; + } + } + private void handleScreenFrameAck(final HippyEngineContext context, final JSONObject paramsObj) { if (mHandlerThread != null && paramsObj != null) { if (!mPageModel.canListenFrameUpdate()) { @@ -160,18 +169,21 @@ public boolean handleMessage(Message message) { } @Override - public void onFrameUpdate(HippyEngineContext context) { + public void onFrameUpdate() { mIsFrameUpdate = true; + HippyEngineContext context = null; if (mInspectorRef != null && mInspectorRef.get() != null && mInspectorRef.get().getContext() != null) { context = mInspectorRef.get().getContext(); } if (context != null && mHandlerThread != null && mLastSessionId != -1) { Handler hander = mHandlerThread.getHandler(); + if (hander.hasMessages(MSG_SCREEN_CAST_ACK)) { + return; + } Message msg = hander.obtainMessage(MSG_SCREEN_CAST_ACK); msg.obj = context; msg.arg1 = mLastSessionId; - hander.removeMessages(MSG_SCREEN_CAST_ACK); hander.sendMessageDelayed(msg, DELAY_FOR_FRAME_UPDATE); mIsFrameUpdate = false; } @@ -211,5 +223,4 @@ public Handler getHandler() { return mHandler; } } - } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/model/CSSModel.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/model/CSSModel.java index c7a3d3e1db9..6ba5ba28ffc 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/model/CSSModel.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/model/CSSModel.java @@ -1,10 +1,27 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.tencent.mtt.hippy.devsupport.inspector.model; import com.tencent.mtt.hippy.HippyEngineContext; import com.tencent.mtt.hippy.HippyRootView; import com.tencent.mtt.hippy.common.HippyMap; import com.tencent.mtt.hippy.devsupport.inspector.domain.CSSDomain; +import com.tencent.mtt.hippy.dom.node.DomDomainData; import com.tencent.mtt.hippy.dom.node.DomNode; +import com.tencent.mtt.hippy.dom.node.DomNodeRecord; import com.tencent.mtt.hippy.dom.node.NodeProps; import com.tencent.mtt.hippy.uimanager.RenderNode; import com.tencent.mtt.hippy.utils.LogUtils; @@ -22,6 +39,7 @@ public class CSSModel { private Map transformEnumMap = new HashMap<>(); private Set transformDoubleMap = new HashSet<>(); + private Set transformColorSet = new HashSet<>(); public CSSModel() { initTransformValue(); @@ -35,9 +53,12 @@ public CSSModel() { public JSONObject getMatchedStyles(HippyEngineContext context, int nodeId) { JSONObject matchedObject = new JSONObject(); try { - HippyMap style = context.getDomManager().getNode(nodeId).getDomainData().style; - if (style != null) { - matchedObject.put("inlineStyle", getCSSStyle(style, nodeId)); + DomNodeRecord domNodeRecord = context.getDomManager().getNode(nodeId).getDomNodeRecord(); + if (domNodeRecord instanceof DomDomainData) { + HippyMap style = ((DomDomainData) domNodeRecord).style; + if (style != null) { + matchedObject.put("inlineStyle", getCSSStyle(style, nodeId)); + } } } catch (Exception e) { LogUtils.e(TAG, "getMatchedStyles, Exception: ", e); @@ -63,8 +84,13 @@ public JSONObject getInlineStyles(HippyEngineContext context, int nodeId) { public JSONObject getComputedStyle(HippyEngineContext context, int nodeId) { JSONObject computedStyle = new JSONObject(); try { - HippyMap style = context.getDomManager().getNode(nodeId).getDomainData().style; - computedStyle.put("computedStyle", getComputedStyle(context, nodeId, style)); + DomNodeRecord domNodeRecord = context.getDomManager().getNode(nodeId).getDomNodeRecord(); + if (domNodeRecord instanceof DomDomainData) { + HippyMap style = ((DomDomainData) domNodeRecord).style; + if (style != null) { + computedStyle.put("computedStyle", getComputedStyle(context, nodeId, style)); + } + } } catch (Exception e) { LogUtils.e(TAG, "getComputedStyle, Exception: ", e); } @@ -141,11 +167,11 @@ private JSONObject setStyleText(HippyEngineContext context, JSONObject editObj) // set style int nodeId = editObj.optInt("styleSheetId"); DomNode node = context.getDomManager().getNode(nodeId); - if (node == null || node.getDomainData() == null) { + if (node == null || node.getDomNodeRecord() == null) { LogUtils.e(TAG, "setStyleText node is null"); return null; } - HippyRootView hippyRootView = context.getInstance(node.getDomainData().rootId); + HippyRootView hippyRootView = context.getInstance(node.getDomNodeRecord().rootId); if (hippyRootView == null) { LogUtils.e(TAG, "setStyleText hippyRootView is null"); return null; @@ -237,7 +263,8 @@ private JSONObject getStyleProperty(String name, String value) throws JSONExcept } private boolean isCanHandleStyle(String key) { - return transformDoubleMap.contains(key) || transformEnumMap.containsKey(key); + return transformDoubleMap.contains(key) || transformEnumMap.containsKey(key) + || transformColorSet.contains(key); } private static double getDoubleValue(String value) { @@ -249,6 +276,15 @@ private static double getDoubleValue(String value) { return 0; } + private static long getLongValue(String value) { + try { + return Long.parseLong(value); + } catch (Exception e) { + LogUtils.e(TAG, "getLongValue, Exception: ", e); + } + return 0; + } + private static String getEnumValue(String[] options, String value) { if (options == null) { return null; @@ -326,6 +362,17 @@ private void initTransformValue() { transformEnumMap.put(NodeProps.TEXT_ALIGN, new String[]{"left", "center", "right"}); transformEnumMap .put(NodeProps.RESIZE_MODE, new String[]{"cover", "contain", "stretch", "repeat", "center"}); + + transformColorSet.add("backgroundColor"); + transformColorSet.add("borderColor"); + transformColorSet.add("borderLeftColor"); + transformColorSet.add("borderTopColor"); + transformColorSet.add("borderRightColor"); + transformColorSet.add("borderBottomColor"); + transformColorSet.add("shadowColor"); + transformColorSet.add("color"); + transformColorSet.add("textShadowColor"); + transformColorSet.add("textDecorationColor"); } private Map getBoxModelRequireMap() { @@ -353,7 +400,7 @@ private Map getBoxModelRequireMap() { /** * 判断是否可以转换 * - * @param key css key + * @param key css key * @param value css value * @return 转换的 value */ @@ -364,6 +411,9 @@ private Object getTransformValue(String key, String value) { if (transformEnumMap.containsKey(key)) { return getEnumValue(transformEnumMap.get(key), value); } + if (transformColorSet.contains(key)) { + return getLongValue(value); + } return value; } @@ -380,8 +430,10 @@ public static String camelize(String str) { result.append(split); continue; } - result - .append(split.replaceFirst(split.substring(0, 1), split.substring(0, 1).toUpperCase())); + result.append(split.substring(0, 1).toUpperCase()); + if (split.length() > 1) { + result.append(split.substring(1)); + } } return result.toString(); } @@ -399,8 +451,10 @@ public static String unCamelize(String str) { result.append(split); continue; } - result.append( - split.replaceFirst(split.substring(0, 1), "-" + split.substring(0, 1).toLowerCase())); + result.append("-").append(split.substring(0, 1).toLowerCase()); + if (split.length() > 1) { + result.append(split.substring(1)); + } } return result.toString(); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/model/DomModel.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/model/DomModel.java index 33f7f9f8ec6..e0acf4ba976 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/model/DomModel.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/model/DomModel.java @@ -1,3 +1,18 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.tencent.mtt.hippy.devsupport.inspector.model; import android.app.Activity; @@ -8,9 +23,10 @@ import com.tencent.mtt.hippy.HippyEngineContext; import com.tencent.mtt.hippy.HippyRootView; import com.tencent.mtt.hippy.common.HippyMap; -import com.tencent.mtt.hippy.dom.node.DomDomainData; import com.tencent.mtt.hippy.dom.DomManager; +import com.tencent.mtt.hippy.dom.node.DomDomainData; import com.tencent.mtt.hippy.dom.node.DomNode; +import com.tencent.mtt.hippy.dom.node.DomNodeRecord; import com.tencent.mtt.hippy.dom.node.NodeProps; import com.tencent.mtt.hippy.uimanager.ControllerManager; import com.tencent.mtt.hippy.uimanager.RenderManager; @@ -18,6 +34,7 @@ import com.tencent.mtt.hippy.utils.LogUtils; import java.util.Map; import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; public class DomModel { @@ -25,6 +42,8 @@ public class DomModel { private static final String TAG = "DomModel"; private static final String DEFAULT_FRAME_ID = "main_frame"; + public static final String NODE_LOCATION_X = "x"; + public static final String NODE_LOCATION_Y = "y"; private DomNode mInspectNode; private JSONObject getNode(HippyEngineContext context, int nodeId) { @@ -37,10 +56,11 @@ private JSONObject getNode(HippyEngineContext context, int nodeId) { return null; } - DomDomainData domainData = domNode.getDomainData(); + DomNodeRecord domainData = domNode.getDomNodeRecord(); // rootNode domainData为空 - if (domainData != null && !TextUtils.isEmpty(domainData.text)) { - childrenArray.put(getTextNodeJson(domainData)); + if (domainData instanceof DomDomainData && !TextUtils + .isEmpty(((DomDomainData) domainData).text)) { + childrenArray.put(getTextNodeJson((DomDomainData) domainData)); } for (int i = 0, size = domNode.getChildCount(); i < size; i++) { @@ -49,7 +69,7 @@ private JSONObject getNode(HippyEngineContext context, int nodeId) { } try { - result = getNodeJson(domainData, NodeType.ELEMENT_NODE); + result = getNodeJson((DomDomainData) domainData, NodeType.ELEMENT_NODE); if (result == null) { result = new JSONObject(); } @@ -73,16 +93,20 @@ private JSONObject getTextNodeJson(DomDomainData domainData) { return result; } - private JSONObject getNodeJson(DomDomainData domainData, int nodeType) { + public static JSONObject getNodeJson(DomDomainData domainData, int nodeType) { if (domainData == null) { - return null; + return new JSONObject(); } JSONObject result = new JSONObject(); try { - result.put("nodeId", domainData.id); - result.put("backendNodeId", 0); + int nodeId = domainData.id; + if (nodeType == NodeType.TEXT_NODE) { // TextNode node Id is the negative id for the ELEMENT_NODE id + nodeId = -nodeId; + } + result.put("nodeId", nodeId); + result.put("backendNodeId", domainData.id); result.put("nodeType", nodeType); - result.put("localName", domainData.tagName); + result.put("localName", domainData.className); result.put("nodeName", domainData.tagName); result.put("nodeValue", domainData.text); result.put("parentId", domainData.pid); @@ -93,7 +117,7 @@ private JSONObject getNodeJson(DomDomainData domainData, int nodeType) { return result; } - private JSONArray getAttributeList(HippyMap attributes) { + private static JSONArray getAttributeList(HippyMap attributes) { JSONArray attributeList = new JSONArray(); try { for (Map.Entry entry : attributes.entrySet()) { @@ -114,7 +138,7 @@ private JSONArray getAttributeList(HippyMap attributes) { return attributeList; } - private String getInlineStyle(HippyMap data) { + private static String getInlineStyle(HippyMap data) { StringBuilder resultBuilder = new StringBuilder(); for (Map.Entry entry : data.entrySet()) { String key = entry.getKey(); @@ -298,7 +322,8 @@ public JSONObject getBoxModel(HippyEngineContext context, JSONObject paramsObj) if (domManager != null && renderManager != null) { DomNode domNode = domManager.getNode(nodeId); RenderNode renderNode = renderManager.getRenderNode(nodeId); - if (domNode != null && domNode.getDomainData() != null && renderNode != null) { + if (domNode != null && domNode.getDomNodeRecord() instanceof DomDomainData + && renderNode != null) { int[] viewLocation = getRenderViewLocation(context, renderNode); // 没找到view,还未创建 if (viewLocation == null) { @@ -307,7 +332,7 @@ public JSONObject getBoxModel(HippyEngineContext context, JSONObject paramsObj) JSONArray border = getBorder(viewLocation[0], viewLocation[1], renderNode.getWidth(), renderNode.getHeight()); - HippyMap style = domNode.getDomainData().style; + HippyMap style = ((DomDomainData) domNode.getDomNodeRecord()).style; JSONArray padding = getPadding(border, style); JSONArray content = getContent(padding, style); JSONArray margin = getMargin(border, style); @@ -329,7 +354,7 @@ public JSONObject getBoxModel(HippyEngineContext context, JSONObject paramsObj) return new JSONObject(); } - private int[] getRenderViewLocation(HippyEngineContext context, RenderNode renderNode) { + private static int[] getRenderViewLocation(HippyEngineContext context, RenderNode renderNode) { int[] viewLocation = new int[2]; viewLocation[0] = renderNode.getX(); viewLocation[1] = renderNode.getY(); @@ -352,7 +377,7 @@ private int[] getRenderViewLocation(HippyEngineContext context, RenderNode rende return viewLocation; } - private boolean isLocationHitRenderNode(HippyEngineContext context, int x, int y, + private static boolean isLocationHitRenderNode(HippyEngineContext context, int x, int y, RenderNode renderNode) { if (renderNode == null) { return false; @@ -405,7 +430,7 @@ private boolean isTranslucentStatus(HippyEngineContext context) { * @param newNode * @return */ - private RenderNode getSmallerAreaRenderNode(RenderNode oldNode, RenderNode newNode) { + private static RenderNode getSmallerAreaRenderNode(RenderNode oldNode, RenderNode newNode) { if (oldNode == null) { return newNode; } @@ -426,7 +451,7 @@ private RenderNode getSmallerAreaRenderNode(RenderNode oldNode, RenderNode newNo * @param rootNode * @return */ - private RenderNode getMaxDepthAndMinAreaHitRenderNode(HippyEngineContext context, int x, int y, + private static RenderNode getMaxDepthAndMinAreaHitRenderNode(HippyEngineContext context, int x, int y, RenderNode rootNode) { if (rootNode == null || !isLocationHitRenderNode(context, x, y, rootNode)) { return null; @@ -443,40 +468,45 @@ private RenderNode getMaxDepthAndMinAreaHitRenderNode(HippyEngineContext context return hitNode != null ? hitNode : rootNode; } - public JSONObject getNodeForLocation(HippyEngineContext context, JSONObject paramsObj) { if (context == null || paramsObj == null) { return new JSONObject(); } - try { - int x = paramsObj.optInt("x", 0); - int y = paramsObj.optInt("y", 0); - DomManager domManager = context.getDomManager(); - RenderManager renderManager = context.getRenderManager(); - if (domManager != null && renderManager != null) { - int rootId = domManager.getRootNodeId(); - RenderNode renderNode = renderManager.getRenderNode(rootId); - if (renderNode.getChildCount() > 0) { - RenderNode rootNode = getRootRenderNode(renderNode, x, y); - if (rootNode != null) { - RenderNode hitRenderNode = getMaxDepthAndMinAreaHitRenderNode(context, x, y, rootNode); - JSONObject result = new JSONObject(); - if (hitRenderNode != null) { - result.put("backendId", hitRenderNode.getId()); - result.put("frameId", DEFAULT_FRAME_ID); - result.put("nodeId", hitRenderNode.getId()); - } - return result; - } - } + int x = paramsObj.optInt(NODE_LOCATION_X, 0); + int y = paramsObj.optInt(NODE_LOCATION_Y, 0); + RenderNode hitRenderNode = getHitNodeForLocation(context, x, y); + JSONObject result = new JSONObject(); + if (hitRenderNode != null) { + try { + result.put("backendId", hitRenderNode.getId()); + result.put("frameId", DEFAULT_FRAME_ID); + result.put("nodeId", hitRenderNode.getId()); + } catch (Exception e) { + LogUtils.e(TAG, "getNodeForLocation, exception:", e); } - } catch (Exception e) { - LogUtils.e(TAG, "getDocument, exception:", e); } - return new JSONObject(); + return result; } - private RenderNode getRootRenderNode(RenderNode rootNode, int x, int y) { + public static RenderNode getHitNodeForLocation(HippyEngineContext context, int x, int y) { + if (context == null) { + return null; + } + DomManager domManager = context.getDomManager(); + RenderManager renderManager = context.getRenderManager(); + if (domManager != null && renderManager != null) { + int rootId = domManager.getRootNodeId(); + RenderNode renderNode = renderManager.getRenderNode(rootId); + if (renderNode.getChildCount() > 0) { + RenderNode rootNode = getRootRenderNode(renderNode, x, y); + if (rootNode != null) { + return getMaxDepthAndMinAreaHitRenderNode(context, x, y, rootNode); + } + } + } + return null; + } + private static RenderNode getRootRenderNode(RenderNode rootNode, int x, int y) { if (rootNode.getWidth() > 0 && rootNode.getHeight() > 0) { return rootNode; } @@ -513,6 +543,80 @@ public JSONObject setInspectMode(HippyEngineContext context, JSONObject paramsOb return new JSONObject(); } + /** + * find node by path, the path will this: 1,HTML,1,BODY,1,MAIN,1,SECTION,0,DIV,1,P + * the order will like this (the child number, the child tag name) + * + * @param context Hippy Context + * @param paramsObj params from devtools + * @return the find node json object + */ + public JSONObject getNodeForPath(HippyEngineContext context, JSONObject paramsObj) { + JSONObject nodeObject = new JSONObject(); + if (context == null || paramsObj == null) { + return nodeObject; + } + DomManager domManager = context.getDomManager(); + if (domManager == null) { + return nodeObject; + } + int rootId = domManager.getRootNodeId(); + DomNode rootNode = domManager.getNode(rootId); + if (rootNode == null) { + return nodeObject; + } + DomNode findNode = rootNode; + try { + String path = paramsObj.optString("path"); + String[] pathArrays = path.split(","); + for (int i = 0; i < pathArrays.length; i += 2) { + int childNumber = Integer.parseInt(pathArrays[i]); + String childName = pathArrays[i + 1]; + if (childNumber >= findNode.getChildCount()) { + return nodeObject; + } + DomNode node = findNode.getChildAt(childNumber); + if (node != null && node.getDomNodeRecord() != null && childName + .equals(node.getDomNodeRecord().tagName)) { + findNode = node; + } + } + nodeObject.put("nodeId", findNode.getId()); + } catch (JSONException e) { + LogUtils.e(TAG, "getNodeForPath, exception:", e); + } + return nodeObject; + } + + /** + * find node id by backend node id where getDocument return backendNodeId + * + * @param context Hippy Context + * @param paramsObj params from devtools + * @return the find node json object + */ + public JSONObject getNodeByBackendIds(HippyEngineContext context, JSONObject paramsObj) { + JSONObject nodeObject = new JSONObject(); + if (context == null || paramsObj == null) { + return nodeObject; + } + try { + JSONArray backendNodeIds = paramsObj.optJSONArray("backendNodeIds"); + if (backendNodeIds == null) { + return nodeObject; + } + JSONArray nodeIdArrays = new JSONArray(); + for (int i = 0; i < backendNodeIds.length(); i++) { + int backendNodeId = backendNodeIds.optInt(i); + nodeIdArrays.put(backendNodeId); + } + nodeObject.put("nodeIds", nodeIdArrays); + } catch (JSONException e) { + LogUtils.e(TAG, "getNodeByBackendIds, exception:", e); + } + return nodeObject; + } + private static HippyRootView getRootView(HippyEngineContext context) { int rootId = context.getDomManager().getRootNodeId(); return context.getInstance(rootId); diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/model/PageModel.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/model/PageModel.java index 95028f169a4..4d94affde6a 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/model/PageModel.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/devsupport/inspector/model/PageModel.java @@ -2,6 +2,7 @@ import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Matrix; import android.os.Build; import android.text.TextUtils; @@ -34,6 +35,7 @@ public class PageModel { private Bitmap screenBitmap; private WeakReference mFrameUpdateListenerRef; private ViewTreeObserver.OnDrawListener mOnDrawListener; + private static float MINI_SCALE = 0.4f; public JSONObject startScreenCast(HippyEngineContext context, final JSONObject paramsObj) { isFramingScreenCast = true; @@ -65,11 +67,10 @@ private void listenFrameUpdate(final HippyEngineContext context) { mOnDrawListener = new ViewTreeObserver.OnDrawListener() { @Override public void onDraw() { - LogUtils.d(TAG, "HippyRootView, onDraw"); if (mFrameUpdateListenerRef != null) { FrameUpdateListener listener = mFrameUpdateListenerRef.get(); if (listener != null) { - listener.onFrameUpdate(context); + listener.onFrameUpdate(); } } } @@ -101,7 +102,9 @@ public void stopScreenCast(HippyEngineContext context) { LogUtils.e(TAG, "stopScreenCast error none hippyRootView"); return; } - hippyRootView.getViewTreeObserver().removeOnDrawListener(mOnDrawListener); + if (mOnDrawListener != null) { + hippyRootView.getViewTreeObserver().removeOnDrawListener(mOnDrawListener); + } } } @@ -141,7 +144,7 @@ private JSONObject getScreenCastData(HippyEngineContext context) { if (paramObj != null) { float scaleX = (float) this.maxWidth / (float) viewWidth; float scaleY = (float) this.maxHeight / (float) viewHeight; - scale = Math.min(scaleX, scaleY); + scale = Math.max(Math.min(scaleX, scaleY), MINI_SCALE); // select the proper scale which not less than MINI_SCALE } Bitmap bitmap = screenBitmap; if (bitmap == null) { @@ -149,6 +152,7 @@ private JSONObject getScreenCastData(HippyEngineContext context) { screenBitmap = bitmap; } Canvas canvas = new Canvas(bitmap); + canvas.drawColor(Color.WHITE); hippyRootView.draw(canvas); if (scale != 1.0f) { Matrix matrix = new Matrix(); @@ -221,6 +225,6 @@ private String bitmapToBase64Str(Bitmap bitmap) { } public interface FrameUpdateListener { - void onFrameUpdate(HippyEngineContext context); + void onFrameUpdate(); } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/DomManager.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/DomManager.java index 917b42167dd..1dde1593f60 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/DomManager.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/DomManager.java @@ -16,6 +16,7 @@ package com.tencent.mtt.hippy.dom; +import android.os.SystemClock; import android.text.Layout; import android.text.TextUtils; import android.util.SparseBooleanArray; @@ -30,6 +31,7 @@ import com.tencent.mtt.hippy.dom.node.*; import com.tencent.mtt.hippy.modules.Promise; import com.tencent.mtt.hippy.modules.javascriptmodules.EventDispatcher; +import com.tencent.mtt.hippy.runtime.builtins.JSObject; import com.tencent.mtt.hippy.uimanager.DiffUtils; import com.tencent.mtt.hippy.uimanager.RenderManager; import com.tencent.mtt.hippy.utils.LogUtils; @@ -46,6 +48,8 @@ public class DomManager implements HippyInstanceLifecycleEventListener, HippyEngineLifecycleEventListener { private static final String TAG = "DomManager"; + private static final long FRAME_TIME_LIMIT_FOREGROUND_MILLIS = 500; + private static final long FRAME_TIME_LIMIT_BACKGROUND_MILLIS = 4000; protected final DispatchUIFrameCallback mDispatchUIFrameCallback; private final SparseBooleanArray mTagsWithLayoutVisited = new SparseBooleanArray(); protected volatile boolean mIsDispatchUIFrameCallbackEnqueued; @@ -81,13 +85,23 @@ public DomManager(HippyEngineContext context) { } + private static boolean hasCollapsable(HippyMap props) { + if (props == null) { + return false; + } + if (props.get(NodeProps.COLLAPSABLE) != null && !((Boolean) props + .get(NodeProps.COLLAPSABLE))) { + return true; + } + return false; + } + private static boolean jsJustLayout(HippyMap props) { if (props == null) { return true; } - if (props.get(NodeProps.COLLAPSABLE) != null && !((Boolean) props - .get(NodeProps.COLLAPSABLE))) { + if (hasCollapsable(props)) { return false; } @@ -118,6 +132,16 @@ public void removeActionInterceptor(DomActionInterceptor interceptor) { } } + public void createFakeRootNode(int rootId, int width, int height) { + DomNode node = new StyleNode(); + node.setId(rootId); + node.setViewClassName(NodeProps.ROOT_NODE); + node.setStyleWidth(width); + node.setStyleHeight(height); + addRootNode(node); + mRenderManager.createRootNode(rootId); + } + public void createRootNode(int instanceId) { HippyRootView view = mContext.getInstance(instanceId); if (view != null) { @@ -136,6 +160,9 @@ public void createRootNode(int instanceId) { @Override public void onInstanceLoad(final int instanceId) { + if (instanceId < 0) { + return; + } mContext.getThreadExecutor().postOnDomThread(new Runnable() { @Override public void run() { @@ -201,17 +228,14 @@ private void markTextNodeDirty(DomNode domNode) { markTextNodeDirty(domNode.getChildAt(i)); } if (domNode instanceof TextNode) { - TextNode textNode = (TextNode) domNode; - if (textNode.enableScale()) { - textNode.dirty(); - } + domNode.markUpdated(); } } } - public void forceUpdateNode(int rootId) { - DomNode node = mNodeRegistry.getNode(rootId); - markTextNodeDirty(node); + public void onFontChanged(int rootId) { + DomNode rootNode = mNodeRegistry.getNode(rootId); + markTextNodeDirty(rootNode); if (!mRenderBatchStarted) { batch(); } @@ -239,6 +263,10 @@ public void renderBatchStart() { mRenderBatchStarted = true; } + public void renderBatchStop() { + mRenderBatchStarted = false; + } + public void renderBatchEnd() { LogUtils.d(TAG, "renderBatchEnd"); mRenderBatchStarted = false; @@ -256,8 +284,15 @@ public DomNode getNode(final int id) { } public int getRootNodeId() { - int count = mNodeRegistry.getRootNodeCount(); - return count >= 1 ? mNodeRegistry.getRootTag(0) : 0; + int id = 0; + for (int i = 0; i < mNodeRegistry.getRootNodeCount(); i++) { + int tempId = mNodeRegistry.getRootTag(i); + if (tempId >= 0) { + id = tempId; + break; + } + } + return id; } public void createNode(final HippyRootView hippyRootView, int rootId, final int id, int pid, int index, @@ -287,14 +322,12 @@ public void createNode(final HippyRootView hippyRootView, int rootId, final int .isControllerLazy(className)); node.setProps(map); - if (mContext.getDevSupportManager().isSupportDev()) { - node.setDomainData(new DomDomainData(id, rootId, pid, className, tagName, map)); - } + node.setDomNodeRecord(new DomDomainData(rootId, id, pid, index, className, tagName, map)); // boolean isLayoutOnly=false; boolean isLayoutOnly = (NodeProps.VIEW_CLASS_NAME.equals(node.getViewClass())) && jsJustLayout( - (HippyMap) props.get(NodeProps.STYLE)) + (HippyMap) props.get(NodeProps.STYLE)) && !hasCollapsable(props) && !isTouchEvent(props); LogUtils.d(TAG, "dom create node id: " + id + " mClassName " + className + " pid " + pid + " mIndex:" @@ -327,7 +360,7 @@ public void createNode(final HippyRootView hippyRootView, int rootId, final int final HippyMap newProps = map; //this is create view ahead in every doframe - if (!node.isLazy()) { + if (!node.isLazy() && id >= 0) { synchronized (mDispatchLock) { addDispatchTask(new IDomExecutor() { @Override @@ -446,7 +479,7 @@ public void updateNode(final int id, HippyMap map, HippyRootView hippyRootView) mDomStyleUpdateManager.updateStyle(node, hippyMap); boolean layoutOnlyHasChanged = - node.isJustLayout() && (!jsJustLayout((HippyMap) props.get(NodeProps.STYLE)) + node.isJustLayout() && (!jsJustLayout((HippyMap) props.get(NodeProps.STYLE)) || hasCollapsable(props) || isTouchEvent(props)); if (layoutOnlyHasChanged) { @@ -465,8 +498,6 @@ public void exec() { } } else { LogUtils.d(TAG, "update error node is null id " + id); - - mContext.getGlobalConfigs().getLogAdapter().log(TAG, "update error node is null id " + id); } } @@ -773,27 +804,53 @@ public void batch() { batch(false); } - public void batch(boolean isAnimation) { - int rootNodeCount = mNodeRegistry.getRootNodeCount(); + public void screenshotBatchStop(boolean isSync) { + mRenderBatchStarted = false; + if (isSync) { + mPaddingNulUITasks.clear(); + mUITasks.clear(); + } + } + public void screenshotBatchEnd(boolean isSync) { + if (!isSync) { + renderBatchEnd(); + return; + } + mRenderBatchStarted = false; + doLayout(); + mTagsWithLayoutVisited.clear(); + synchronized (mDispatchLock) { + if (mIsDestroyed) { + return; + } + for (int i = 0; i < mUITasks.size(); i++) { + mDispatchRunnable.add(mUITasks.get(i)); + } + for (int i = 0; i < mPaddingNulUITasks.size(); i++) { + mDispatchRunnable.add(mPaddingNulUITasks.get(i)); + } + } + mPaddingNulUITasks.clear(); + mUITasks.clear(); + } + + private void doLayout() { + int rootNodeCount = mNodeRegistry.getRootNodeCount(); for (int i = 0; i < rootNodeCount; i++) { int rootTag = mNodeRegistry.getRootTag(i); DomNode rootNode = mNodeRegistry.getNode(rootTag); if (rootNode != null) { applyLayoutBefore(rootNode); - - LogUtils.d(TAG, " dom start calculateLayout"); - rootNode.calculateLayout(); - applyLayoutAfter(rootNode); - applyLayoutUpdateRecursive(rootNode); - - LogUtils.d(TAG, "dom end calculateLayout"); - // LogUtils.l(TAG, rootNode.toString()); } } + } + + public void batch(boolean isAnimation) { + doLayout(); mTagsWithLayoutVisited.clear(); LogUtils.d(TAG, "dom batch complete"); @@ -816,7 +873,7 @@ public void batch(boolean isAnimation) { } } - void flushPendingBatches() { + public void flushPendingBatches() { if (mEnginePaused) { mIsDispatchUIFrameCallbackEnqueued = false; @@ -827,7 +884,7 @@ void flushPendingBatches() { synchronized (mDispatchLock) { Iterator iterator = mDispatchRunnable.iterator(); boolean shouldBatch = mDispatchRunnable.size() > 0; - long startTime = System.currentTimeMillis(); + long startTime = SystemClock.elapsedRealtime(); while (iterator.hasNext()) { IDomExecutor iDomExecutor = iterator.next(); if (iDomExecutor != null && !mIsDestroyed) { @@ -840,7 +897,13 @@ void flushPendingBatches() { } iterator.remove(); if (mIsDispatchUIFrameCallbackEnqueued) { - if (System.currentTimeMillis() - startTime > 500) { + if (SystemClock.elapsedRealtime() - startTime > FRAME_TIME_LIMIT_FOREGROUND_MILLIS) { + break; + } + } else { + if (SystemClock.elapsedRealtime() - startTime > FRAME_TIME_LIMIT_BACKGROUND_MILLIS) { + mIsDispatchUIFrameCallbackEnqueued = true; + HippyChoreographer.getInstance().postFrameCallback(mDispatchUIFrameCallback); break; } } @@ -862,11 +925,11 @@ public void exec() { }); } - public void measureInWindow(final int id, final Promise promise) { + public void measureInWindow(final int id, final JSObject options, final Promise promise) { addNulUITask(new IDomExecutor() { @Override public void exec() { - mRenderManager.measureInWindow(id, promise); + mRenderManager.measureInWindow(id, options, promise); } }); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/flex/FlexAlign.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/flex/FlexAlign.java index 97eb15f349c..0eba4d17a35 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/flex/FlexAlign.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/flex/FlexAlign.java @@ -24,6 +24,7 @@ public enum FlexAlign { BASELINE, SPACE_BETWEEN, SPACE_AROUND, + SPACE_EVENLY, ; @SuppressWarnings("unused") @@ -45,9 +46,10 @@ public static FlexAlign fromInt(int value) { return SPACE_BETWEEN; case 7: return SPACE_AROUND; + case 8: + return SPACE_EVENLY; default: throw new IllegalArgumentException("Unknown enum value: " + value); } } - } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/flex/FlexJustify.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/flex/FlexJustify.java index 7ab2ece0164..60ac40c4216 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/flex/FlexJustify.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/flex/FlexJustify.java @@ -20,7 +20,8 @@ public enum FlexJustify { CENTER, FLEX_END, SPACE_BETWEEN, - SPACE_AROUND; + SPACE_AROUND, + SPACE_EVENLY; @SuppressWarnings("unused") public static FlexJustify fromInt(int value) { @@ -35,6 +36,8 @@ public static FlexJustify fromInt(int value) { return SPACE_BETWEEN; case 4: return SPACE_AROUND; + case 5: + return SPACE_EVENLY; default: throw new IllegalArgumentException("Unknown enum value: " + value); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/DomDomainData.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/DomDomainData.java index 3ede1a68770..84edf3e6ff9 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/DomDomainData.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/DomDomainData.java @@ -3,19 +3,20 @@ import com.tencent.mtt.hippy.common.HippyMap; import com.tencent.mtt.hippy.dom.node.NodeProps; -public class DomDomainData { +public class DomDomainData extends DomNodeRecord { - public DomDomainData(int id, int rootId, int pid, String className, String tagName, - HippyMap map) { - this.id = id; + public DomDomainData(int rootId, int id, int pid, int index, + final String className, final String tagName, final HippyMap props) { this.rootId = rootId; + this.id = id; this.pid = pid; - this.name = className; + this.index = index; + this.className = className; this.tagName = tagName; - if (map != null) { - this.style = map.getMap(NodeProps.STYLE); - this.text = map.getString("text"); - this.attributes = map.getMap(NodeProps.ATTRIBUTES); + if (props != null) { + this.style = props.getMap(NodeProps.STYLE); + this.text = props.getString("text"); + this.attributes = props.getMap(NodeProps.ATTRIBUTES); } } @@ -26,11 +27,6 @@ public void updateLayout(double layoutX, double layoutY, double width, double he this.height = height; } - public int id; - public int rootId; - public int pid; - public String name; - public String tagName; public double layoutX; public double layoutY; public double width; diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/DomNode.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/DomNode.java index 621eae1da97..ddf2a0c12d1 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/DomNode.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/DomNode.java @@ -35,7 +35,7 @@ public class DomNode extends FlexNode { boolean mIsLazy = false; - private DomDomainData mDomainData; + private DomNodeRecord domNodeRecord; public void setLazy(boolean lazy) { this.mIsLazy = lazy; @@ -107,17 +107,17 @@ public void setProps(HippyMap props) { mTotalProps = props; } - public void setDomainData(DomDomainData domainData) { - mDomainData = domainData; + public void setDomNodeRecord(DomNodeRecord domNodeRecord) { + this.domNodeRecord = domNodeRecord; } - public DomDomainData getDomainData() { - return mDomainData; + public DomNodeRecord getDomNodeRecord() { + return domNodeRecord; } private void updateDomainData() { - if (mDomainData != null) { - mDomainData.updateLayout(mLastX, mLastY, mLastWidth, mLastHeight); + if (domNodeRecord instanceof DomDomainData) { + ((DomDomainData)domNodeRecord).updateLayout(mLastX, mLastY, mLastWidth, mLastHeight); } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/DomNodeRecord.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/DomNodeRecord.java new file mode 100644 index 00000000000..59837e61046 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/DomNodeRecord.java @@ -0,0 +1,13 @@ +package com.tencent.mtt.hippy.dom.node; + +import com.tencent.mtt.hippy.common.HippyMap; + +public class DomNodeRecord { + public int rootId; + public int id; + public int pid; + public int index; + public String className; + public String tagName; + public HippyMap props; +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/HippyForegroundColorSpan.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/HippyForegroundColorSpan.java new file mode 100644 index 00000000000..87e224df2d4 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/HippyForegroundColorSpan.java @@ -0,0 +1,24 @@ +package com.tencent.mtt.hippy.dom.node; + +import android.text.style.ForegroundColorSpan; +import androidx.annotation.Nullable; + +public final class HippyForegroundColorSpan extends ForegroundColorSpan { + + // Support host set custom colors data, such as change skin or night mode. + @Nullable + private Object mCustomColors; + + public HippyForegroundColorSpan(int color, Object customColors) { + super(color); + mCustomColors = customColors; + } + + public HippyForegroundColorSpan(int color) { + this(color, null); + } + + public Object getCustomColors() { + return mCustomColors; + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/HippyImageSpan.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/HippyImageSpan.java index e08bd42ad71..4aa78b1c3e0 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/HippyImageSpan.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/HippyImageSpan.java @@ -20,14 +20,22 @@ import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; import android.graphics.Movie; import android.graphics.Paint; +import android.graphics.Paint.FontMetricsInt; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.os.SystemClock; import android.text.TextUtils; import android.text.style.DynamicDrawableSpan; import android.text.style.ImageSpan; - +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.tencent.mtt.hippy.HippyEngineContext; import com.tencent.mtt.hippy.adapter.image.HippyDrawable; import com.tencent.mtt.hippy.adapter.image.HippyImageLoader; @@ -36,8 +44,8 @@ import com.tencent.mtt.hippy.uimanager.HippyViewEvent; import com.tencent.mtt.hippy.utils.UIThreadUtils; import com.tencent.mtt.hippy.utils.UrlUtils; - import com.tencent.mtt.hippy.views.image.HippyImageView.ImageEvent; +import com.tencent.smtt.flexbox.FlexNodeStyle.Edge; import java.lang.ref.WeakReference; import java.lang.reflect.Field; @@ -48,27 +56,77 @@ public class HippyImageSpan extends ImageSpan { public final static int STATE_LOADING = 1; public final static int STATE_LOADED = 2; + private final boolean mUseLegacy; + private final String mVerticalAlign; + @Deprecated private int mLeft; + @Deprecated private int mTop; private int mWidth; private int mHeight; + private int mMeasuredWidth; + private int mMeasuredHeight; private String mUrl; private final WeakReference mImageNodeWeakRefrence; private int mImageLoadState = STATE_UNLOAD; - private int mVerticalAlignment; - private final HippyImageLoader mImageAdapter; - private final HippyEngineContext engineContext; + private final WeakReference mImageAdapterRef; + private final WeakReference engineContextRef; + private Drawable mSrcDrawable = null; private Movie mGifMovie = null; - private int mGifProgress = 0; + private Paint mGifPaint = null; + private long mGifProgress = 0; private long mGifLastPlayTime = -1; + private float mHeightRate = 0; + private Paint mBackgroundPaint = null; + private int mMarginLeft; + private int mMarginTop; + private int mMarginRight; + private int mMarginBottom; + + @Deprecated + private LegacyIAlignConfig alignConfig; public HippyImageSpan(Drawable d, String source, ImageNode node, HippyImageLoader imageAdapter, HippyEngineContext context) { super(d, source, node.getVerticalAlignment()); - engineContext = context; + engineContextRef = new WeakReference<>(context); mImageNodeWeakRefrence = new WeakReference<>(node); - mImageAdapter = imageAdapter; + mImageAdapterRef = new WeakReference<>(imageAdapter); + mVerticalAlign = node.getVerticalAlign(); + mUseLegacy = TextUtils.isEmpty(mVerticalAlign); + mWidth = Math.round(node.getStyleWidth()); + mHeight = Math.round(node.getStyleHeight()); setUrl(source); + if (node.hasBackgroundColor()) { + mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mBackgroundPaint.setColor(node.getBackgroundColor()); + } + if (mUseLegacy) { + alignConfig = LegacyIAlignConfig.fromVerticalAlignment(node.getVerticalAlignment()); + } else { + initMargin(node); + } + } + + @Deprecated + public void setDesiredSize(int width, int height) { + if (mUseLegacy) { + alignConfig.setDesiredSize(width, height); + } + } + + public void setActiveSizeWithRate(float heightRate) { + mHeightRate = heightRate; + if (mUseLegacy) { + alignConfig.setActiveSizeWithRate(heightRate); + } + } + + @Deprecated + public void setMargin(int marginLeft, int marginRight) { + if (mUseLegacy) { + alignConfig.setMargin(marginLeft, marginRight); + } } private void updateBoundsAttribute() { @@ -86,7 +144,6 @@ private void updateBoundsAttribute() { mTop = top; mWidth = width; mHeight = height; - mVerticalAlignment = node.getVerticalAlignment(); } } } @@ -103,18 +160,21 @@ private void loadImageWithUrl(String url) { mUrl = url; mImageLoadState = STATE_UNLOAD; - updateBoundsAttribute(); + if (mUseLegacy) { + updateBoundsAttribute(); + } - if (mImageAdapter != null) { + HippyImageLoader imageAdapter = mImageAdapterRef.get(); + if (imageAdapter != null) { if (shouldUseFetchImageMode(mUrl)) { final HippyMap props = new HippyMap(); props.pushBoolean(NodeProps.CUSTOM_PROP_ISGIF, false); props.pushInt(NodeProps.WIDTH, mWidth); props.pushInt(NodeProps.HEIGHT, mHeight); - doFetchImage(mUrl, props, mImageAdapter); + doFetchImage(mUrl, props, imageAdapter); } else { - HippyDrawable hippyDrawable = mImageAdapter.getImage(mUrl, null); + HippyDrawable hippyDrawable = imageAdapter.getImage(mUrl, null); shouldReplaceDrawable(hippyDrawable); } } @@ -137,7 +197,7 @@ public void run() { } } - private void drawGIF(Canvas canvas, float left, float top, int width, int height) { + private void updateGifTime() { if (mGifMovie == null) { return; } @@ -147,10 +207,10 @@ private void drawGIF(Canvas canvas, float left, float top, int width, int height duration = 1000; } - long now = System.currentTimeMillis(); + long now = SystemClock.elapsedRealtime(); if (mGifLastPlayTime != -1) { - mGifProgress += now - mGifLastPlayTime; + mGifProgress += (now - mGifLastPlayTime); if (mGifProgress > duration) { mGifProgress = 0; @@ -158,23 +218,122 @@ private void drawGIF(Canvas canvas, float left, float top, int width, int height } mGifLastPlayTime = now; + int progress = mGifProgress > Integer.MAX_VALUE ? 0 : (int) mGifProgress; + mGifMovie.setTime(progress); + } + + private void legacyDrawGIF(Canvas canvas, float left, float top, int width, int height) { + if (mGifMovie == null) { + return; + } + updateGifTime(); + + if (mBackgroundPaint != null) { + canvas.drawRect(left, top, left + width, top + height, mBackgroundPaint); + } float mGifScaleX = width / (float) mGifMovie.width(); float mGifScaleY = height / (float) mGifMovie.height(); float x = (mGifScaleX != 0) ? left / mGifScaleX : left; float y = (mGifScaleY != 0) ? top / mGifScaleY : top; - - mGifMovie.setTime(mGifProgress); canvas.save(); canvas.scale(mGifScaleX, mGifScaleY); - mGifMovie.draw(canvas, x, y); + mGifMovie.draw(canvas, x, y, mGifPaint); canvas.restore(); postInvalidateDelayed(40); } + @Override + public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, + @Nullable FontMetricsInt fm) { + if (mUseLegacy) { + return legacyGetSize(paint, text, start, end, fm); + } + if (mHeightRate > 0) { + if (mHeight == 0) { + mMeasuredWidth = mMeasuredHeight = 0; + } else { + int textSize = (int) paint.getTextSize(); + mMeasuredHeight = (int) (textSize * mHeightRate); + mMeasuredWidth = mWidth * mMeasuredHeight / mHeight; + } + } else { + mMeasuredWidth = mWidth; + mMeasuredHeight = mHeight; + } + if (fm != null) { + fm.top = fm.ascent = -(mMeasuredHeight + mMarginTop + mMarginBottom); + fm.leading = fm.bottom = fm.descent = 0; + } + + return mMeasuredWidth + mMarginLeft + mMarginRight; + } + + private int legacyGetSize(@NonNull Paint paint, CharSequence text, int start, int end, + @Nullable FontMetricsInt fm) { + if (mGifMovie!=null) { + return super.getSize(paint, text, start, end, fm); + } else { + Drawable drawable = getDrawable(); + return alignConfig.getSize(paint, + text, start, end, + fm, drawable); + } + } + @Override public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { + if (mUseLegacy) { + legacyDraw(canvas, text, start, end, x, top, y, bottom, paint); + return; + } + if (mMeasuredWidth == 0 || mMeasuredHeight == 0) { + return; + } + canvas.save(); + int transY; + switch (mVerticalAlign) { + case TextNode.V_ALIGN_TOP: + transY = top + mMarginTop; + break; + case TextNode.V_ALIGN_MIDDLE: + transY = top + (bottom - top) / 2 - mMeasuredHeight / 2; + break; + case TextNode.V_ALIGN_BOTTOM: + transY = bottom - mMeasuredHeight - mMarginBottom; + break; + case TextNode.V_ALIGN_BASELINE: + default: + transY = y - mMeasuredHeight - mMarginBottom; + break; + } + + canvas.translate(x + mMarginLeft, transY); + if (mBackgroundPaint != null) { + canvas.drawRect(0, 0, mMeasuredWidth, mMeasuredHeight, mBackgroundPaint); + } + if (mGifMovie != null) { + updateGifTime(); + float scaleX = mMeasuredWidth / (float) mGifMovie.width(); + float scaleY = mMeasuredHeight / (float) mGifMovie.height(); + canvas.scale(scaleX, scaleY, 0, 0); + mGifMovie.draw(canvas, 0, 0, mGifPaint); + postInvalidateDelayed(40); + } else { + Drawable drawable = mSrcDrawable == null ? super.getDrawable() : mSrcDrawable; + Rect rect = drawable.getBounds(); + float scaleX = mMeasuredWidth / (float) rect.right; + float scaleY = mMeasuredHeight / (float) rect.bottom; + canvas.scale(scaleX, scaleY, 0, 0); + drawable.draw(canvas); + } + canvas.restore(); + } + + private void legacyDraw(Canvas canvas, CharSequence text, + int start, int end, float x, + int top, int y, int bottom, Paint paint) { int transY; Paint.FontMetricsInt fm = paint.getFontMetricsInt(); if (mGifMovie != null) { @@ -182,19 +341,14 @@ public void draw(Canvas canvas, CharSequence text, int height = (mHeight == 0) ? mGifMovie.height() : mHeight; transY = (y + fm.descent + y + fm.ascent) / 2 - height / 2; - drawGIF(canvas, x + mLeft, transY + mTop, width, height); - } else if (mVerticalAlignment == ImageSpan.ALIGN_BASELINE) { - Drawable b = getDrawable(); - - transY = (y + fm.descent + y + fm.ascent) / 2 - - b.getBounds().bottom / 2; - - canvas.save(); - canvas.translate(x + mLeft, transY + mTop); - b.draw(canvas); - canvas.restore(); + legacyDrawGIF(canvas, x + mLeft, transY + mTop, width, height); } else { - super.draw(canvas, text, start, end, x, top, y, bottom, paint); + Drawable drawable = getDrawable(); + alignConfig.draw(canvas, + text, start, end, + x, top, y, bottom, + paint, + drawable, mBackgroundPaint); } } @@ -211,10 +365,50 @@ private void postInvalidateDelayed(long delayMilliseconds) { } private void shouldReplaceDrawable(HippyDrawable hippyDrawable) { + if (mUseLegacy) { + legacyShouldReplaceDrawable(hippyDrawable); + return; + } + mSrcDrawable = null; + mGifMovie = null; + mGifPaint = null; + if (hippyDrawable != null) { + Bitmap bitmap = hippyDrawable.getBitmap(); + if (bitmap != null) { + BitmapDrawable drawable = new BitmapDrawable(bitmap); + ImageNode node = mImageNodeWeakRefrence.get(); + if (node != null && node.hasTintColor()) { + drawable.setColorFilter(new PorterDuffColorFilter(node.getTintColor(), PorterDuff.Mode.SRC_ATOP)); + } + drawable.setBounds(0, 0, mWidth, mHeight); + mSrcDrawable = drawable; + mImageLoadState = STATE_LOADED; + } else if (hippyDrawable.isAnimated()) { + mGifMovie = hippyDrawable.getGIF(); + ImageNode node = mImageNodeWeakRefrence.get(); + if (node != null && node.hasTintColor()) { + mGifPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mGifPaint.setColorFilter(new PorterDuffColorFilter(node.getTintColor(), PorterDuff.Mode.SRC_ATOP)); + } + mImageLoadState = STATE_LOADED; + } else { + mImageLoadState = STATE_UNLOAD; + } + postInvalidateDelayed(0); + } else { + mImageLoadState = STATE_UNLOAD; + } + } + + private void legacyShouldReplaceDrawable(HippyDrawable hippyDrawable) { if (hippyDrawable != null) { Bitmap bitmap = hippyDrawable.getBitmap(); if (bitmap != null) { BitmapDrawable drawable = new BitmapDrawable(bitmap); + ImageNode node = mImageNodeWeakRefrence.get(); + if (node != null && node.hasTintColor()) { + drawable.setColorFilter(new PorterDuffColorFilter(node.getTintColor(), PorterDuff.Mode.SRC_ATOP)); + } int w = (mWidth == 0) ? drawable.getIntrinsicWidth() : mWidth; int h = (mHeight == 0) ? drawable.getIntrinsicHeight() : mHeight; @@ -238,6 +432,11 @@ private void shouldReplaceDrawable(HippyDrawable hippyDrawable) { mImageLoadState = STATE_LOADED; } else if (hippyDrawable.isAnimated()) { mGifMovie = hippyDrawable.getGIF(); + ImageNode node = mImageNodeWeakRefrence.get(); + if (node != null && node.hasTintColor()) { + mGifPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mGifPaint.setColorFilter(new PorterDuffColorFilter(node.getTintColor(), PorterDuff.Mode.SRC_ATOP)); + } mImageLoadState = STATE_LOADED; } else { mImageLoadState = STATE_UNLOAD; @@ -267,7 +466,7 @@ private void sendImageLoadEvent(ImageEvent eventType) { if (!TextUtils.isEmpty(eventName) && node.isEnableImageEvent(eventType)) { HippyViewEvent event = new HippyViewEvent(eventName); - event.send(node.getId(), engineContext, null); + event.send(node.getId(), engineContextRef.get(), null); } } @@ -292,4 +491,57 @@ public void onRequestFail(Throwable throwable, String source) { } }, props); } + + public void setTintColor(int tintColor) { + Runnable action = () -> { + ColorFilter colorFilter = tintColor == Color.TRANSPARENT ? null + : new PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_ATOP); + if (mSrcDrawable != null) { + mSrcDrawable.setColorFilter(colorFilter); + } else if (mGifMovie != null) { + mGifPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mGifPaint.setColorFilter(colorFilter); + } else if (mUseLegacy) { + Drawable drawable = getDrawable(); + drawable.setColorFilter(colorFilter); + } + }; + if (UIThreadUtils.isOnUiThread()) { + action.run(); + } else { + UIThreadUtils.runOnUiThread(action); + } + } + + public void setBackgroundColor(int color) { + Runnable action = () -> { + if (color == Color.TRANSPARENT) { + mBackgroundPaint = null; + } else { + if (mBackgroundPaint == null) { + mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + } + mBackgroundPaint.setColor(color); + } + }; + if (UIThreadUtils.isOnUiThread()) { + action.run(); + } else { + UIThreadUtils.runOnUiThread(action); + } + } + + private void initMargin(ImageNode node) { + int margin = Math.round(node.getMargin(Edge.EDGE_ALL.ordinal())); + int marginHorizontal = getValue(Math.round(node.getMargin(Edge.EDGE_HORIZONTAL.ordinal())), margin); + int marginVertical = getValue(Math.round(node.getMargin(Edge.EDGE_VERTICAL.ordinal())), margin); + mMarginLeft = getValue(Math.round(node.getMargin(Edge.EDGE_LEFT.ordinal())), marginHorizontal); + mMarginRight = getValue(Math.round(node.getMargin(Edge.EDGE_RIGHT.ordinal())), marginHorizontal); + mMarginTop = getValue(Math.round(node.getMargin(Edge.EDGE_TOP.ordinal())), marginVertical); + mMarginBottom = getValue(Math.round(node.getMargin(Edge.EDGE_BOTTOM.ordinal())), marginVertical); + } + + private static int getValue(int primary, int secondary) { + return primary == 0 ? secondary : primary; + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/HippyLineHeightSpan.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/HippyLineHeightSpan.java index 914c2514726..cf109ac22ab 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/HippyLineHeightSpan.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/HippyLineHeightSpan.java @@ -43,8 +43,8 @@ public void chooseHeight(CharSequence text, int start, int end, int spanstartv, fm.top = fm.bottom - mHeight; } else { final int additional = mHeight - (-fm.top + fm.bottom); - fm.top -= Math.ceil(additional / 2.0f); - fm.bottom += Math.floor(additional / 2.0f); + fm.top -= (int) Math.ceil(additional / 2.0f); + fm.bottom += (int) Math.floor(additional / 2.0f); fm.ascent = fm.top; fm.descent = fm.bottom; } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/HippyNativeGestureSpan.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/HippyNativeGestureSpan.java index 7e876d35b31..b0463aed3ed 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/HippyNativeGestureSpan.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/HippyNativeGestureSpan.java @@ -152,6 +152,11 @@ public boolean needHandle(String type) { return false; } + @Override + public void handle(String type, MotionEvent event) { + + } + @Override public void handle(String type, float x, float y) { if (TextUtils.equals(type, NodeProps.ON_PRESS_IN)) { diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/HippyStyleSpan.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/HippyStyleSpan.java index fe07c79e797..710162b032d 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/HippyStyleSpan.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/HippyStyleSpan.java @@ -18,6 +18,7 @@ import android.text.TextPaint; import android.text.style.MetricAffectingSpan; import com.tencent.mtt.hippy.adapter.font.HippyFontScaleAdapter; +import java.lang.ref.WeakReference; @SuppressWarnings({"unused"}) public class HippyStyleSpan extends MetricAffectingSpan { @@ -25,24 +26,24 @@ public class HippyStyleSpan extends MetricAffectingSpan { private final int mStyle; private final int mWeight; private final String mFontFamily; - private final HippyFontScaleAdapter fontAdapter; + private final WeakReference fontAdapterRef; public HippyStyleSpan(int fontStyle, int fontWeight, String fontFamily, HippyFontScaleAdapter adapter) { mStyle = fontStyle; mWeight = fontWeight; mFontFamily = fontFamily; - fontAdapter = adapter; + fontAdapterRef = new WeakReference<>(adapter); } @Override public void updateDrawState(TextPaint ds) { - TypeFaceUtil.apply(ds, mStyle, mWeight, mFontFamily, fontAdapter); + TypeFaceUtil.apply(ds, mStyle, mWeight, mFontFamily, fontAdapterRef.get()); } @Override public void updateMeasureState(TextPaint paint) { - TypeFaceUtil.apply(paint, mStyle, mWeight, mFontFamily, fontAdapter); + TypeFaceUtil.apply(paint, mStyle, mWeight, mFontFamily, fontAdapterRef.get()); } public int getStyle() { diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/HippyVerticalAlignSpan.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/HippyVerticalAlignSpan.java new file mode 100644 index 00000000000..cb81b23f974 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/HippyVerticalAlignSpan.java @@ -0,0 +1,67 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.mtt.hippy.dom.node; + +import android.graphics.Paint.FontMetricsInt; +import android.text.TextPaint; +import android.text.style.CharacterStyle; + +/** + * A span that change the vertical align of the text. Caution: It depends on the + * TextPaint's FontMetrics, which may be changed by other span, so this span should be added with a + * lowest priority. + */ +public class HippyVerticalAlignSpan extends CharacterStyle { + + private final FontMetricsInt mReusableFontMetricsInt = new FontMetricsInt(); + private final String mVerticalAlign; + private int mLineTop; + private int mLineBottom; + + public HippyVerticalAlignSpan(String verticalAlign) { + this.mVerticalAlign = verticalAlign; + } + + public void setLineMetrics(int top, int bottom) { + mLineTop = top; + mLineBottom = bottom; + } + + @Override + public void updateDrawState(TextPaint tp) { + if (mLineTop != 0 || mLineBottom != 0) { + final FontMetricsInt fmi = mReusableFontMetricsInt; + switch (mVerticalAlign) { + case TextNode.V_ALIGN_TOP: + tp.getFontMetricsInt(fmi); + tp.baselineShift = mLineTop - fmi.top; + break; + case TextNode.V_ALIGN_MIDDLE: + tp.getFontMetricsInt(fmi); + tp.baselineShift = (mLineTop + mLineBottom - fmi.top - fmi.bottom) / 2; + break; + case TextNode.V_ALIGN_BOTTOM: + tp.getFontMetricsInt(fmi); + tp.baselineShift = mLineBottom - fmi.bottom; + break; + case TextNode.V_ALIGN_BASELINE: + default: + break; + } + } + } + +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/ImageNode.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/ImageNode.java index c30155c33e3..abc2fa04cf4 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/ImageNode.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/ImageNode.java @@ -16,21 +16,25 @@ package com.tencent.mtt.hippy.dom.node; +import android.graphics.Color; import android.text.style.ImageSpan; - import com.tencent.mtt.hippy.annotation.HippyControllerProps; - import com.tencent.mtt.hippy.views.image.HippyImageView.ImageEvent; import java.util.ArrayList; @SuppressWarnings({"unused"}) public class ImageNode extends StyleNode { + @Deprecated public static final String PROP_VERTICAL_ALIGNMENT = "verticalAlignment"; private final boolean mIsVirtual; private HippyImageSpan mImageSpan = null; + @Deprecated private int mVerticalAlignment = ImageSpan.ALIGN_BASELINE; + private String mVerticalAlign; + private int mTintColor = Color.TRANSPARENT; + private int mBackgroundColor = Color.TRANSPARENT; private final boolean[] shouldSendImageEvent; private ArrayList mGestureTypes = null; @@ -48,10 +52,25 @@ public boolean isEnableImageEvent(ImageEvent event) { return shouldSendImageEvent[event.ordinal()]; } + /** + * @deprecated use {@link #getVerticalAlign} instead + */ + @Deprecated public int getVerticalAlignment() { return mVerticalAlignment; } + public String getVerticalAlign() { + if (mVerticalAlign != null) { + return mVerticalAlign; + } + DomNode parent = getParent(); + if (parent instanceof TextNode) { + return ((TextNode) parent).getVerticalAlign(); + } + return null; + } + public boolean isVirtual() { return mIsVirtual; } @@ -140,11 +159,34 @@ public void touchCancelable(boolean flag) { } } + /** + * @deprecated use {@link #setVerticalAlign} instead + */ + @Deprecated @HippyControllerProps(name = PROP_VERTICAL_ALIGNMENT, defaultType = HippyControllerProps.NUMBER, defaultNumber = ImageSpan.ALIGN_BASELINE) public void setVerticalAlignment(int verticalAlignment) { mVerticalAlignment = verticalAlignment; } + @HippyControllerProps(name = TextNode.PROP_VERTICAL_ALIGN, defaultType = HippyControllerProps.STRING) + public void setVerticalAlign(String align) { + switch (align) { + case HippyControllerProps.DEFAULT: + // reset to legacy mode + mVerticalAlign = null; + break; + case TextNode.V_ALIGN_TOP: + case TextNode.V_ALIGN_MIDDLE: + case TextNode.V_ALIGN_BASELINE: + case TextNode.V_ALIGN_BOTTOM: + mVerticalAlign = align; + break; + default: + mVerticalAlign = TextNode.V_ALIGN_BASELINE; + break; + } + } + @HippyControllerProps(name = "src", defaultType = HippyControllerProps.STRING) public void setUrl(String url) { if (mImageSpan != null) { @@ -163,4 +205,36 @@ public void setOnLoadEnd(boolean enable) { public void setOnError(boolean enable) { shouldSendImageEvent[ImageEvent.ONERROR.ordinal()] = enable; } + + @HippyControllerProps(name = "tintColor", defaultType = HippyControllerProps.NUMBER) + public void setTintColor(int tintColor) { + mTintColor = tintColor; + if (mImageSpan != null) { + mImageSpan.setTintColor(tintColor); + } + } + + public boolean hasTintColor() { + return mTintColor != Color.TRANSPARENT; + } + + public int getTintColor() { + return mTintColor; + } + + @HippyControllerProps(name = NodeProps.BACKGROUND_COLOR, defaultType = HippyControllerProps.NUMBER) + public void setBackgroundColor(int color) { + mBackgroundColor = color; + if (mImageSpan != null) { + mImageSpan.setBackgroundColor(color); + } + } + + public boolean hasBackgroundColor() { + return mBackgroundColor != Color.TRANSPARENT; + } + + public int getBackgroundColor() { + return mBackgroundColor; + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/LayoutHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/LayoutHelper.java index e3353dc1bc4..e2fa777f191 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/LayoutHelper.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/LayoutHelper.java @@ -24,12 +24,15 @@ import com.tencent.mtt.hippy.common.HippyHandlerThread; import com.tencent.mtt.hippy.common.HippyThreadRunnable; import com.tencent.mtt.hippy.utils.LogUtils; +import java.util.concurrent.atomic.AtomicInteger; @SuppressWarnings({"unused"}) public class LayoutHelper { + private static final int MAX_QUEUE_SIZE = 200; private HippyHandlerThread mHandlerThread; private final Picture mPicture = new Picture(); + private final AtomicInteger mQueueSize = new AtomicInteger(0); public LayoutHelper() { mHandlerThread = new HippyHandlerThread("text-warm-thread"); @@ -43,11 +46,13 @@ public void release() { } public void postWarmLayout(Layout layout) { - if (mHandlerThread != null && mHandlerThread.isThreadAlive()) { + if (mHandlerThread != null && mHandlerThread.isThreadAlive() && mQueueSize.get() < MAX_QUEUE_SIZE) { + mQueueSize.getAndIncrement(); mHandlerThread.runOnQueue(new HippyThreadRunnable(layout) { @Override public void run(Layout param) { warmUpLayout(param); + mQueueSize.getAndDecrement(); } }); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/LegacyIAlignConfig.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/LegacyIAlignConfig.java new file mode 100644 index 00000000000..2987dd68ee0 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/LegacyIAlignConfig.java @@ -0,0 +1,314 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.mtt.hippy.dom.node; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Paint.FontMetricsInt; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +@Deprecated +interface LegacyIAlignConfig { + + int ALIGN_BOTTOM = 0; + int ALIGN_BASELINE = 1; + int ALIGN_CENTER = 2; + int ALIGN_TOP = 3; + + static LegacyIAlignConfig fromVerticalAlignment(int verticalAlignment) { + switch (verticalAlignment) { + case ALIGN_BASELINE: + return new AlignBaselineConfig(); + case ALIGN_CENTER: + return new AlignCenterConfig(); + case ALIGN_TOP: + return new AlignTopConfig(); + case ALIGN_BOTTOM: + default: + return new AlignBottomConfig(); + } + } + + void setDesiredSize(int desiredDrawableWidth, int desiredDrawableHeight); + + void setActiveSizeWithRate(float heightRate); + + void setMargin(int marginLeft, int marginRight); + + int getSize(@NonNull Paint paint, + CharSequence text, int start, int end, + @Nullable FontMetricsInt fm, + Drawable drawable); + + void draw(@NonNull Canvas canvas, + CharSequence text, int start, int end, + float baseLineX, int lineTop, int baselineY, int lintBottom, + @NonNull Paint paint, + Drawable drawable, @Nullable Paint backgroundPaint); + + abstract class BaseAlignConfig implements LegacyIAlignConfig { + + private final int[] size = new int[2]; + private int desiredDrawableWidth; + private int desiredDrawableHeight; + private float heightRate; + private int marginLeft; + private int marginRight; + + private static int adjustTransY( + int transY, + int lineTop, + int lineBottom, + int drawableHeight + ) { + if (drawableHeight + transY > lineBottom) { + transY = lineBottom - drawableHeight; + } + if (transY < lineTop) { + transY = lineTop; + } + return transY; + } + + @Override + public void setDesiredSize(int desiredDrawableWidth, int desiredDrawableHeight) { + this.desiredDrawableWidth = desiredDrawableWidth; + this.desiredDrawableHeight = desiredDrawableHeight; + + heightRate = 0; + } + + @Override + public void setActiveSizeWithRate(float heightRate) { + this.heightRate = heightRate; + + desiredDrawableWidth = 0; + desiredDrawableHeight = 0; + } + + @Override + public void setMargin(int marginLeft, int marginRight) { + this.marginLeft = marginLeft; + this.marginRight = marginRight; + } + + private void calDrawableSize(Rect drawableBounds, Paint paint) { + int dWidth; + int dHeight; + if (heightRate > 0) { + int textSize = (int) paint.getTextSize(); + dHeight = (int) (textSize * heightRate); + dWidth = drawableBounds.right * dHeight / drawableBounds.bottom; + } else { + dHeight = desiredDrawableHeight; + dWidth = desiredDrawableWidth; + } + + if (dWidth <= 0 || dHeight <= 0) { + dWidth = drawableBounds.right; + dHeight = drawableBounds.bottom; + } + + size[0] = dWidth; + size[1] = dHeight; + } + + @Override + public int getSize(@NonNull Paint paint, + CharSequence text, int start, int end, + @Nullable FontMetricsInt fm, + Drawable drawable) { + + calDrawableSize(drawable.getBounds(), paint); + int dWidth = size[0]; + int dHeight = size[1]; + + int deltaTop = 0; + int deltaBottom = 0; + if (fm != null) { + deltaTop = fm.top - fm.ascent; + deltaBottom = fm.bottom - fm.descent; + } + + int size = getCustomSize(paint, + text, start, end, + fm, dWidth, dHeight); + if (fm != null) { + fm.top = fm.ascent + deltaTop; + fm.bottom = fm.descent + deltaBottom; + } + return marginLeft + size + marginRight; + } + + @Override + public void draw(@NonNull Canvas canvas, + CharSequence text, int start, int end, + float baseLineX, int lineTop, int baselineY, int lineBottom, + @NonNull Paint paint, + Drawable drawable, @Nullable Paint backgroundPaint) { + Rect drawableBounds = drawable.getBounds(); + + int dWidth = size[0]; + int dHeight = size[1]; + + FontMetricsInt fontMetricsInt = paint.getFontMetricsInt(); + + int transY = getTransY(canvas, + text, start, end, + baseLineX, lineTop, baselineY, lineBottom, + paint, fontMetricsInt, + dWidth, dHeight); + transY = adjustTransY(transY, lineTop, lineBottom, dHeight); + + float scaleX = (float) dWidth / drawableBounds.right; + float scaleY = (float) dHeight / drawableBounds.bottom; + + canvas.save(); + canvas.translate(baseLineX + marginLeft, transY); + canvas.scale(scaleX, scaleY); + if (backgroundPaint != null) { + canvas.drawRect(0, 0, dWidth, dHeight, backgroundPaint); + } + drawable.draw(canvas); + canvas.restore(); + } + + abstract int getCustomSize(@NonNull Paint paint, + CharSequence text, int start, int end, + @Nullable FontMetricsInt fm, + int drawableWidth, int drawableHeight); + + abstract int getTransY(@NonNull Canvas canvas, + CharSequence text, int start, int end, + float baseLineX, int lineTop, int baselineY, int lineBottom, + @NonNull Paint paint, FontMetricsInt fontMetricsInt, + int drawableWidth, int drawableHeight); + } + + class AlignBaselineConfig extends BaseAlignConfig { + + @Override + public int getCustomSize(@NonNull Paint paint, + CharSequence text, int start, int end, + @Nullable FontMetricsInt fm, + int drawableWidth, int drawableHeight) { + if (fm != null) { + fm.ascent = -drawableHeight; + } + return drawableWidth; + } + + @Override + public int getTransY(@NonNull Canvas canvas, + CharSequence text, int start, int end, + float baseLineX, int lineTop, int baselineY, int lineBottom, + @NonNull Paint paint, FontMetricsInt fontMetricsInt, + int drawableWidth, int drawableHeight) { + return baselineY - drawableHeight; + } + } + + class AlignBottomConfig extends AlignBaselineConfig { + + @Override + public int getCustomSize(@NonNull Paint paint, + CharSequence text, int start, int end, + @Nullable FontMetricsInt fm, + int drawableWidth, int drawableHeight) { + if (fm != null) { + fm.ascent = fm.descent - drawableHeight; + } + return drawableWidth; + } + + @Override + public int getTransY(@NonNull Canvas canvas, + CharSequence text, int start, int end, + float baseLineX, int lineTop, int baselineY, int lineBottom, + @NonNull Paint paint, FontMetricsInt fontMetricsInt, + int drawableWidth, int drawableHeight) { + return super.getTransY(canvas, + text, start, end, + baseLineX, lineTop, baselineY, lineBottom, + paint, fontMetricsInt, + drawableWidth, drawableHeight) + fontMetricsInt.descent; + } + } + + class AlignCenterConfig extends AlignBottomConfig { + + @Override + public int getCustomSize(@NonNull Paint paint, + CharSequence text, int start, int end, + @Nullable FontMetricsInt fm, + int drawableWidth, int drawableHeight) { + if (fm != null) { + int textAreaHeight = fm.descent - fm.ascent; + if (textAreaHeight < drawableHeight) { + int oldSumOfAscentAndDescent = fm.ascent + fm.descent; + fm.ascent = oldSumOfAscentAndDescent - drawableHeight >> 1; + fm.descent = oldSumOfAscentAndDescent + drawableHeight >> 1; + } + + } + return drawableWidth; + } + + @Override + public int getTransY(@NonNull Canvas canvas, + CharSequence text, int start, int end, + float baseLineX, int lineTop, int baselineY, int lineBottom, + @NonNull Paint paint, FontMetricsInt fontMetricsInt, + int drawableWidth, int drawableHeight) { + int transY = super.getTransY(canvas, + text, start, end, + baseLineX, lineTop, baselineY, lineBottom, + paint, fontMetricsInt, + drawableWidth, drawableHeight); + + int fontHeight = fontMetricsInt.descent - fontMetricsInt.ascent; + transY = transY - (fontHeight >> 1) + (drawableHeight >> 1); + + return transY; + } + } + + class AlignTopConfig extends BaseAlignConfig { + + @Override + public int getCustomSize(@NonNull Paint paint, + CharSequence text, int start, int end, + @Nullable FontMetricsInt fm, + int drawableWidth, int drawableHeight) { + if (fm != null) { + fm.descent = drawableHeight + fm.ascent; + } + return drawableWidth; + } + + @Override + public int getTransY(@NonNull Canvas canvas, + CharSequence text, int start, int end, + float baseLineX, int lineTop, int baselineY, int lintBottom, + @NonNull Paint paint, FontMetricsInt fontMetricsInt, + int drawableWidth, int drawableHeight) { + return baselineY + fontMetricsInt.ascent; + } + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/NodeProps.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/NodeProps.java index e7914d3e028..921d19a28b5 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/NodeProps.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/NodeProps.java @@ -110,6 +110,8 @@ public class NodeProps { public static final String FONT_STYLE = "fontStyle"; public static final String FONT_FAMILY = "fontFamily"; public static final String LINE_HEIGHT = "lineHeight"; + public static final String LINE_SPACING_MULTIPLIER = "lineSpacingMultiplier"; + public static final String LINE_SPACING_EXTRA = "lineSpacingExtra"; public static final String NUMBER_OF_LINES = "numberOfLines"; public static final String ELLIPSIZE_MODE = "ellipsizeMode"; public static final String ON = "on"; @@ -119,6 +121,7 @@ public class NodeProps { public static final String TEXT_ALIGN = "textAlign"; public static final String TEXT_ALIGN_VERTICAL = "textAlignVertical"; public static final String TEXT_DECORATION_LINE = "textDecorationLine"; + public static final String BREAK_STRATEGY = "breakStrategy"; public static final String ON_CLICK = "onClick"; public static final String ON_LONG_CLICK = "onLongClick"; public static final String ON_PRESS_IN = "onPressIn"; @@ -163,8 +166,11 @@ public class NodeProps { public static final String REQUEST_FOCUS = "requestFocus"; public static final String VISIBLE = "visible"; + public static final String HIDDEN = "hidden"; public static final String REPEAT_COUNT = "repeatCount"; public static final String ATTRIBUTES = "attributes"; + public static final String BACKGROUND_RIPPLE = "nativeBackgroundAndroid"; + public static final String OVER_PULL = "bounces"; private static final HashSet JUST_LAYOUT_PROPS = new HashSet<>( Arrays.asList(ALIGN_SELF, ALIGN_ITEMS, COLLAPSABLE, FLEX, FLEX_DIRECTION, FLEX_WRAP, diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/StyleNode.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/StyleNode.java index bb83cc002e3..327960b1074 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/StyleNode.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/StyleNode.java @@ -85,7 +85,7 @@ public void setShadowRadius(float shadowRadius) { } } - @HippyControllerProps(name = NodeProps.DIRECTION) + @HippyControllerProps(name = NodeProps.DIRECTION, defaultType = HippyControllerProps.STRING, defaultString = "ltr") public void setDirection(String direction) { if (TextUtils.isEmpty(direction)) { return; @@ -115,7 +115,7 @@ public void setFlexDirection(String flexDirection) { @HippyControllerProps(name = NodeProps.FLEX_WRAP) public void setFlexWrap(String flexWrap) { - setWrap(flexWrap == null ? FlexWrap.NOWRAP : FlexWrap.valueOf(flexWrap.toUpperCase(Locale.US))); + setWrap(flexWrap == null ? FlexWrap.NOWRAP : FlexWrap.valueOf(flexWrap.toUpperCase(Locale.US).replace("-", "_"))); } @HippyControllerProps(name = NodeProps.ALIGN_SELF) @@ -144,7 +144,7 @@ public void setOverflow(String overflow) { } @SuppressWarnings("SwitchStatementWithTooFewBranches") - @HippyControllerProps(name = NodeProps.DISPLAY) + @HippyControllerProps(name = NodeProps.DISPLAY, defaultType = HippyControllerProps.STRING) public void setDisplay(String display) { FlexNodeStyle.Display flexDisplay = FlexNodeStyle.Display.DISPLAY_FLEX; switch (display) { diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/TextNode.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/TextNode.java index 6bfa67a926d..227f043047d 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/TextNode.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/TextNode.java @@ -22,22 +22,36 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; -import android.text.*; -import android.text.Layout.Alignment; -import android.text.style.*; - +import android.text.BidiFormatter; +import android.text.BoringLayout; +import android.text.Layout; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.SpannedString; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.text.TextUtils; +import android.text.style.AbsoluteSizeSpan; +import android.text.style.BackgroundColorSpan; +import android.text.style.ImageSpan; +import android.text.style.StrikethroughSpan; +import android.text.style.UnderlineSpan; +import androidx.annotation.RequiresApi; import com.tencent.mtt.hippy.HippyEngineContext; import com.tencent.mtt.hippy.adapter.font.HippyFontScaleAdapter; import com.tencent.mtt.hippy.adapter.image.HippyDrawable; import com.tencent.mtt.hippy.adapter.image.HippyImageLoader; import com.tencent.mtt.hippy.annotation.HippyControllerProps; import com.tencent.mtt.hippy.common.HippyMap; -import com.tencent.mtt.hippy.dom.flex.*; +import com.tencent.mtt.hippy.dom.flex.FlexMeasureMode; +import com.tencent.mtt.hippy.dom.flex.FlexNodeAPI; +import com.tencent.mtt.hippy.dom.flex.FlexOutput; +import com.tencent.mtt.hippy.dom.flex.FlexSpacing; import com.tencent.mtt.hippy.utils.I18nUtil; import com.tencent.mtt.hippy.utils.LogUtils; import com.tencent.mtt.hippy.utils.PixelUtil; import com.tencent.mtt.hippy.views.text.HippyTextView; - import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; @@ -48,16 +62,33 @@ public class TextNode extends StyleNode { SpannableStringBuilder mSpanned; public final static int UNSET = -1; + public final static String MODE_HEAD = "head"; + public final static String MODE_MIDDLE = "middle"; + public final static String MODE_TAIL = "tail"; + public final static String MODE_CLIP = "clip"; + public final static String STRATEGY_SIMPLE = "simple"; + public final static String STRATEGY_HIGH_QUALITY = "high_quality"; + public final static String STRATEGY_BALANCED = "balanced"; + public static final String PROP_VERTICAL_ALIGN = "verticalAlign"; + + /*package*/ final static String V_ALIGN_TOP = "top"; + /*package*/ final static String V_ALIGN_MIDDLE = "middle"; + /*package*/ final static String V_ALIGN_BASELINE = "baseline"; + /*package*/ final static String V_ALIGN_BOTTOM = "bottom"; + CharSequence mText; protected int mNumberOfLines = UNSET; + private String mEllipsizeMode = MODE_TAIL; + private String mBreakStrategy = STRATEGY_SIMPLE; protected int mFontSize = (int) Math.ceil(PixelUtil.dp2px(NodeProps.FONT_SIZE_SP)); private float mLineHeight = UNSET; private float mLetterSpacing = UNSET; + protected float mLineSpacingMultiplier = UNSET; + protected float mLineSpacingExtra; - private int mColor = Color.BLACK; - private final boolean mIsBackgroundColorSet = false; - private int mBackgroundColor; + protected int mColor = Color.BLACK; + private int mBackgroundColor = Color.TRANSPARENT; private String mFontFamily = null; public static final int DEFAULT_TEXT_SHADOW_COLOR = 0x55000000; @@ -87,14 +118,16 @@ public class TextNode extends StyleNode { public static final String IMAGE_SPAN_TEXT = "[img]"; - final TextPaint sTextPaintInstance = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + final TextPaint mTextPaintInstance; + // 这个TextPaint用于兼容2.13.x及之前版本对于空Text节点的layout高度 + private TextPaint mTextPaintForEmpty; private final boolean mIsVirtual; protected boolean mEnableScale = false; private WeakReference mTextViewWeakRefrence = null; - + private String mVerticalAlign; public TextNode(boolean mIsVirtual) { this.mIsVirtual = mIsVirtual; @@ -105,6 +138,9 @@ public TextNode(boolean mIsVirtual) { if (I18nUtil.isRTL()) { mTextAlign = Layout.Alignment.ALIGN_OPPOSITE; } + + mTextPaintInstance = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + mTextPaintInstance.setTextSize(mFontSize); } public void setTextView(HippyTextView view) { @@ -145,7 +181,7 @@ public void letterSpacing(float letterSpace) { } @SuppressWarnings("unused") - @HippyControllerProps(name = NodeProps.COLOR, defaultType = HippyControllerProps.NUMBER, defaultNumber = 0) + @HippyControllerProps(name = NodeProps.COLOR, defaultType = HippyControllerProps.NUMBER, defaultNumber = Color.BLACK) public void color(Integer color) { mColor = color; markUpdated(); @@ -164,6 +200,7 @@ public Spannable getSpan() { @HippyControllerProps(name = NodeProps.FONT_SIZE, defaultType = HippyControllerProps.NUMBER, defaultNumber = NodeProps.FONT_SIZE_SP) public void fontSize(float fontSize) { this.mFontSize = (int) Math.ceil(PixelUtil.dp2px(fontSize)); + mTextPaintInstance.setTextSize(mFontSize); markUpdated(); } @@ -174,15 +211,6 @@ public void fontFamily(String fontFamily) { markUpdated(); } - @Override - public void updateProps(HippyMap props) { - super.updateProps(props); - HippyMap styleMap = (HippyMap) props.get(NodeProps.STYLE); - if (styleMap != null && styleMap.get(NodeProps.COLOR) == null) { - styleMap.pushInt(NodeProps.COLOR, Color.BLACK); - } - } - private static int parseArgument(String wight) { return wight.length() == 3 && wight.endsWith("00") && wight.charAt(0) <= '9' && wight.charAt(0) >= '1' ? 100 * (wight.charAt(0) - '0') : -1; @@ -263,6 +291,20 @@ public void lineHeight(int lineHeight) { markUpdated(); } + @SuppressWarnings("unused") + @HippyControllerProps(name = NodeProps.LINE_SPACING_MULTIPLIER, defaultType = HippyControllerProps.NUMBER, defaultNumber = UNSET) + public void lineSpacingMultiplier(float lineSpacingMultiplier) { + mLineSpacingMultiplier = lineSpacingMultiplier; + markUpdated(); + } + + @SuppressWarnings("unused") + @HippyControllerProps(name = NodeProps.LINE_SPACING_EXTRA, defaultType = HippyControllerProps.NUMBER, defaultNumber = 0.0f) + public void lineSpacingExtra(float lineSpacingExtra) { + mLineSpacingExtra = PixelUtil.dp2px(lineSpacingExtra); + markUpdated(); + } + @SuppressWarnings("unused") @HippyControllerProps(name = NodeProps.TEXT_ALIGN, defaultType = HippyControllerProps.STRING, defaultString = "left") public void setTextAlign(String textAlign) { @@ -397,35 +439,63 @@ public void setNumberOfLines(int numberOfLines) { markUpdated(); } - protected HippyFontScaleAdapter mFontScaleAdapter; - protected HippyEngineContext engineContext; - protected HippyImageLoader mImageAdapter; + @HippyControllerProps(name = NodeProps.ELLIPSIZE_MODE, defaultType = HippyControllerProps.STRING, defaultString = MODE_TAIL) + public void setEllipsizeMode(String mode) { + if (TextUtils.isEmpty(mode)) { + mode = MODE_TAIL; + } + if (!mEllipsizeMode.equals(mode)) { + if (MODE_TAIL.equals(mode) || MODE_CLIP.equals(mode) || MODE_MIDDLE.equals(mode) || MODE_HEAD.equals(mode)) { + mEllipsizeMode = mode; + markUpdated(); + } else { + throw new RuntimeException("Invalid ellipsizeMode: " + mode); + } + } + } + + @HippyControllerProps(name = NodeProps.BREAK_STRATEGY, defaultType = HippyControllerProps.STRING, defaultString = STRATEGY_SIMPLE) + public void setBreakStrategy(String strategy) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return; + } + if (TextUtils.isEmpty(strategy)) { + strategy = STRATEGY_SIMPLE; + } + if (!mBreakStrategy.equals(strategy)) { + if (STRATEGY_SIMPLE.equals(strategy) || STRATEGY_HIGH_QUALITY.equals(strategy) || STRATEGY_BALANCED.equals(strategy)) { + mBreakStrategy = strategy; + markUpdated(); + } else { + throw new RuntimeException("Invalid breakStrategy: " + strategy); + } + } + } + + @HippyControllerProps(name = NodeProps.BACKGROUND_COLOR, defaultType = HippyControllerProps.NUMBER) + public void setBackgroundColor(int backgroundColor) { + mBackgroundColor = backgroundColor; + } @Override public void layoutBefore(HippyEngineContext context) { super.layoutBefore(context); - engineContext = context; - if (mFontScaleAdapter == null) { - mFontScaleAdapter = context.getGlobalConfigs().getFontScaleAdapter(); - } - - if (mImageAdapter == null) { - mImageAdapter = context.getGlobalConfigs().getImageLoaderAdapter(); - } + HippyFontScaleAdapter fontScaleAdapter = context.getGlobalConfigs().getFontScaleAdapter(); + HippyImageLoader imageAdapter = context.getGlobalConfigs().getImageLoaderAdapter(); if (mIsVirtual) { return; } - if (mFontScaleAdapter != null && !TextUtils.isEmpty(mText)) { - CharSequence s = mFontScaleAdapter.getEmoticonText(mText, mFontSize); + if (fontScaleAdapter != null && !TextUtils.isEmpty(mText)) { + CharSequence s = fontScaleAdapter.getEmoticonText(mText, mFontSize); if (s != null) { mText = s; } } - mSpanned = createSpan(mText, true); + mSpanned = createSpan(mText, true, context, fontScaleAdapter, imageAdapter); } @SuppressWarnings({"EmptyMethod", "unused"}) @@ -433,11 +503,12 @@ protected void createCustomSpan(CharSequence text, Spannable spannableText) { } - private SpannableStringBuilder createSpan(CharSequence text, boolean useChild) { + private SpannableStringBuilder createSpan(CharSequence text, boolean useChild, HippyEngineContext context, + HippyFontScaleAdapter fontScaleAdapter, HippyImageLoader imageAdapter) { if (text != null) { SpannableStringBuilder spannable = new SpannableStringBuilder(); List ops = new ArrayList<>(); - createSpanOperations(ops, spannable, this, text, useChild); + createSpanOperations(ops, spannable, this, text, useChild, context, fontScaleAdapter, imageAdapter); for (int i = ops.size() - 1; i >= 0; i--) { SpanOperation op = ops.get(i); @@ -453,7 +524,7 @@ private SpannableStringBuilder createSpan(CharSequence text, boolean useChild) { } private void createImageSpanOperation(List ops, SpannableStringBuilder sb, - ImageNode imageNode) { + ImageNode imageNode, HippyEngineContext context, HippyImageLoader imageAdapter) { String url = null; String defaultSource = null; HippyMap props = imageNode.getTotalProps(); @@ -463,9 +534,9 @@ private void createImageSpanOperation(List ops, SpannableStringBu } Drawable drawable = null; - if (!TextUtils.isEmpty(defaultSource) && mImageAdapter != null) { + if (!TextUtils.isEmpty(defaultSource) && imageAdapter != null) { assert defaultSource != null; - HippyDrawable hippyDrawable = mImageAdapter.getImage(defaultSource, null); + HippyDrawable hippyDrawable = imageAdapter.getImage(defaultSource, null); Bitmap bitmap = hippyDrawable.getBitmap(); if (bitmap != null) { drawable = new BitmapDrawable(bitmap); @@ -480,8 +551,7 @@ private void createImageSpanOperation(List ops, SpannableStringBu int height = Math.round(imageNode.getStyleHeight()); drawable.setBounds(0, 0, width, height); - HippyImageSpan imageSpan = new HippyImageSpan(drawable, url, imageNode, mImageAdapter, - engineContext); + HippyImageSpan imageSpan = new HippyImageSpan(drawable, url, imageNode, imageAdapter, context); imageNode.setImageSpan(imageSpan); int start = sb.length(); @@ -497,14 +567,21 @@ private void createImageSpanOperation(List ops, SpannableStringBu } private void createSpanOperations(List ops, SpannableStringBuilder sb, - TextNode textNode, CharSequence text, boolean useChild) { + TextNode textNode, CharSequence text, boolean useChild, HippyEngineContext context, + HippyFontScaleAdapter fontScaleAdapter, HippyImageLoader imageAdapter) { int start = sb.length(); sb.append(text); int end = sb.length(); if (start <= end) { + String verticalAlign = textNode.getVerticalAlign(); + if (verticalAlign != null) { + HippyVerticalAlignSpan span = new HippyVerticalAlignSpan(verticalAlign); + ops.add(new SpanOperation(start, end, span, SpanOperation.PRIORITY_LOWEST)); + } - ops.add(new SpanOperation(start, end, new ForegroundColorSpan(textNode.mColor))); - if (textNode.mIsBackgroundColorSet) { + ops + .add(new SpanOperation(start, end, createForegroundColorSpan(textNode.mColor, textNode))); + if (textNode.isVirtual() && textNode.mBackgroundColor != Color.TRANSPARENT) { ops.add(new SpanOperation(start, end, new BackgroundColorSpan(textNode.mBackgroundColor))); } if (textNode.mLetterSpacing != UNSET) { @@ -516,17 +593,18 @@ private void createSpanOperations(List ops, SpannableStringBuilde if (textNode.mFontSize != UNSET) { int fontSize = textNode.mFontSize; - if (textNode.mFontScaleAdapter != null && textNode.mEnableScale) { - fontSize = (int) (fontSize * textNode.mFontScaleAdapter.getFontScale()); + if (fontScaleAdapter != null && textNode.mEnableScale) { + fontSize = (int) (fontSize * fontScaleAdapter.getFontScale()); } ops.add(new SpanOperation(start, end, new AbsoluteSizeSpan(fontSize))); } - - if (textNode.mFontStyle != UNSET || textNode.mFontWeight != UNSET - || textNode.mFontFamily != null) { + String fontFamily = textNode.mFontFamily; + if (fontFamily == null && fontScaleAdapter != null) { + fontFamily = fontScaleAdapter.getCustomDefaultFontFamily(); + } + if (textNode.mFontStyle != UNSET || textNode.mFontWeight != UNSET || fontFamily != null) { ops.add(new SpanOperation(start, end, - new HippyStyleSpan(textNode.mFontStyle, textNode.mFontWeight, textNode.mFontFamily, - mFontScaleAdapter))); + new HippyStyleSpan(textNode.mFontStyle, textNode.mFontWeight, fontFamily, fontScaleAdapter))); } if (textNode.mIsUnderlineTextDecorationSet) { ops.add(new SpanOperation(start, end, new UnderlineSpan())); @@ -539,11 +617,13 @@ private void createSpanOperations(List ops, SpannableStringBuilde new HippyShadowSpan(textNode.mTextShadowOffsetDx, textNode.mTextShadowOffsetDy, textNode.mTextShadowRadius, textNode.mTextShadowColor))); } - if (textNode.mLineHeight != UNSET) { + if (textNode.mLineHeight != UNSET + && mLineSpacingMultiplier == UNSET + && mLineSpacingExtra == 0) { float lineHeight = textNode.mLineHeight; - if (textNode.mFontScaleAdapter != null && textNode.mEnableScale) { - lineHeight = (lineHeight * textNode.mFontScaleAdapter.getFontScale()); + if (fontScaleAdapter != null && textNode.mEnableScale) { + lineHeight = (lineHeight * fontScaleAdapter.getFontScale()); } ops.add(new SpanOperation(start, end, new HippyLineHeightSpan(lineHeight))); } @@ -561,16 +641,16 @@ private void createSpanOperations(List ops, SpannableStringBuilde if (domNode instanceof TextNode) { TextNode tempNode = (TextNode) domNode; CharSequence tempText = tempNode.mText; - if (mFontScaleAdapter != null && !TextUtils.isEmpty(tempText)) { - CharSequence s = mFontScaleAdapter.getEmoticonText(tempText, tempNode.mFontSize); + if (fontScaleAdapter != null && !TextUtils.isEmpty(tempText)) { + CharSequence s = fontScaleAdapter.getEmoticonText(tempText, tempNode.mFontSize); if (s != null) { tempText = s; } } //noinspection ConstantConditions - createSpanOperations(ops, sb, tempNode, tempText, useChild); + createSpanOperations(ops, sb, tempNode, tempText, useChild, context, fontScaleAdapter, imageAdapter); } else if (domNode instanceof ImageNode) { - createImageSpanOperation(ops, sb, (ImageNode) domNode); + createImageSpanOperation(ops, sb, (ImageNode) domNode, context, imageAdapter); } else { throw new RuntimeException(domNode.getViewClass() + "is not support in Text"); } @@ -580,6 +660,10 @@ private void createSpanOperations(List ops, SpannableStringBuilde } } + protected HippyForegroundColorSpan createForegroundColorSpan(int color, TextNode textNode) { + return new HippyForegroundColorSpan(color); + } + private static final FlexNodeAPI.MeasureFunction TEXT_MEASURE_FUNCTION = new FlexNodeAPI.MeasureFunction() { @SuppressWarnings("rawtypes") @Override @@ -625,6 +709,25 @@ public void layoutAfter(HippyEngineContext context) { } + protected float getLineSpacingMultiplier() { + return mLineSpacingMultiplier <= 0 ? 1.0f : mLineSpacingMultiplier; + } + + @RequiresApi(api = Build.VERSION_CODES.M) + private int getBreakStrategy() { + final String strategy = mBreakStrategy; + switch (strategy) { + case STRATEGY_SIMPLE: + return Layout.BREAK_STRATEGY_SIMPLE; + case STRATEGY_HIGH_QUALITY: + return Layout.BREAK_STRATEGY_HIGH_QUALITY; + case STRATEGY_BALANCED: + return Layout.BREAK_STRATEGY_BALANCED; + default: + throw new RuntimeException("Invalid breakStrategy: " + strategy); + } + } + private StaticLayout buildStaticLayout(CharSequence source, TextPaint paint, int width) { Layout.Alignment textAlign = mTextAlign; if (I18nUtil.isRTL()) { @@ -634,12 +737,21 @@ private StaticLayout buildStaticLayout(CharSequence source, TextPaint paint, int } } - return new StaticLayout(source, paint, width, textAlign, 1.f, 0.f, - true); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return StaticLayout.Builder.obtain(source, 0, source.length(), paint, width) + .setAlignment(textAlign) + .setLineSpacing(mLineSpacingExtra, getLineSpacingMultiplier()) + .setIncludePad(true) + .setBreakStrategy(getBreakStrategy()) + .build(); + } else { + return new StaticLayout(source, paint, width, textAlign, getLineSpacingMultiplier(), + mLineSpacingExtra, true); + } } - private Layout createLayout(float width, FlexMeasureMode widthMode) { - TextPaint textPaint = sTextPaintInstance; + protected Layout createLayout(float width, FlexMeasureMode widthMode) { + final TextPaint textPaint = getTextPaint(); Layout layout; Spanned text = mSpanned == null ? new SpannedString("") : mSpanned; BoringLayout.Metrics boring = null; @@ -648,102 +760,277 @@ private Layout createLayout(float width, FlexMeasureMode widthMode) { } catch (Throwable e) { LogUtils.d("TextNode", "createLayout: " + e.getMessage()); } - float desiredWidth = boring == null ? Layout.getDesiredWidth(text, textPaint) : Float.NaN; boolean unconstrainedWidth = widthMode == FlexMeasureMode.UNDEFINED || width < 0; - if (boring == null && (unconstrainedWidth || (!FlexConstants.isUndefined(desiredWidth) - && desiredWidth <= width))) { - layout = new StaticLayout(text, textPaint, (int)Math.ceil(desiredWidth), mTextAlign, 1.f, - 0.f, true); - } else if (boring != null && (unconstrainedWidth || boring.width <= width)) { - layout = BoringLayout.make(text, textPaint, boring.width, mTextAlign, 1.f, 0.f, boring, true); + if (boring != null && (unconstrainedWidth || boring.width <= width)) { + layout = BoringLayout.make(text, textPaint, boring.width, mTextAlign, getLineSpacingMultiplier(), mLineSpacingExtra, boring, true); } else { - layout = buildStaticLayout(text, textPaint, (int)Math.ceil(width)); - } - if (mNumberOfLines != UNSET && mNumberOfLines > 0) { - if (layout.getLineCount() > mNumberOfLines) { - int lastLineStart = layout.getLineStart(mNumberOfLines - 1); - int lastLineEnd = layout.getLineEnd(mNumberOfLines - 1); - if (lastLineStart < lastLineEnd) { - layout = createLayoutWithNumberOfLine(lastLineStart, layout.getWidth()); + float desiredWidth = Layout.getDesiredWidth(text, textPaint); + if (!unconstrainedWidth && (widthMode == FlexMeasureMode.EXACTLY || desiredWidth > width)) { + desiredWidth = width; + } + layout = buildStaticLayout(text, textPaint, (int) Math.ceil(desiredWidth)); + if (mNumberOfLines != UNSET && mNumberOfLines > 0) { + if (layout.getLineCount() > mNumberOfLines) { + int lastLineStart = layout.getLineStart(mNumberOfLines - 1); + int lastLineEnd = layout.getLineEnd(mNumberOfLines - 1); + if (lastLineStart < lastLineEnd) { + int measureWidth = (int)Math.ceil(unconstrainedWidth ? desiredWidth : width); + layout = truncateLayoutWithNumberOfLine(layout, measureWidth, mNumberOfLines); + } } } } assert layout != null; - layout.getPaint().setTextSize(mFontSize); + CharSequence layoutText = layout.getText(); + if (layoutText instanceof Spanned) { + Spanned spanned = (Spanned) layoutText; + HippyVerticalAlignSpan[] spans = spanned.getSpans(0, spanned.length(), HippyVerticalAlignSpan.class); + for (HippyVerticalAlignSpan span : spans) { + int offset = spanned.getSpanStart(span); + int line = layout.getLineForOffset(offset); + int baseline = layout.getLineBaseline(line); + span.setLineMetrics(layout.getLineTop(line) - baseline, layout.getLineBottom(line) - baseline); + } + } return layout; } - private StaticLayout createLayoutWithNumberOfLine(int lastLineStart, int width) { - if (mSpanned == null) { - return null; + private TextPaint getTextPaint() { + if (TextUtils.isEmpty(mText)) { + if (mTextPaintForEmpty == null) { + mTextPaintForEmpty = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + } + return mTextPaintForEmpty; } - String text = mSpanned.toString(); - SpannableStringBuilder temp = (SpannableStringBuilder) mSpanned.subSequence(0, text.length()); - String ellipsizeStr = (String) TextUtils - .ellipsize(text.substring(lastLineStart), sTextPaintInstance, width, - TextUtils.TruncateAt.END); - String newString = text.subSequence(0, lastLineStart).toString() - + truncate(ellipsizeStr, sTextPaintInstance, width, mTruncateAt); - - int start = Math.max(newString.length() - 1, 0); - CharacterStyle[] hippyStyleSpans = temp.getSpans(start, text.length(), CharacterStyle.class); - if (hippyStyleSpans != null && hippyStyleSpans.length > 0) { - for (CharacterStyle hippyStyleSpan : hippyStyleSpans) { - if (temp.getSpanStart(hippyStyleSpan) >= start) { - temp.removeSpan(hippyStyleSpan); + return mTextPaintInstance; + } + + private StaticLayout truncateLayoutWithNumberOfLine(Layout preLayout, int width, int numberOfLines) { + int lineCount = preLayout.getLineCount(); + assert lineCount >= 2; + CharSequence origin = preLayout.getText(); + TextPaint paint = preLayout.getPaint(); + + CharSequence truncated; + if (MODE_CLIP.equals(mEllipsizeMode)) { + int end = preLayout.getLineEnd(numberOfLines - 1); + if (origin.charAt(end - 1) == '\n') { + // there will be an unexpected blank line, if ends with a new line char, trim it + --end; + } + truncated = origin.subSequence(0, end); + } else { + TextPaint measurePaint = new TextPaint(); + measurePaint.set(paint); + int start = preLayout.getLineStart(numberOfLines - 1); + CharSequence formerLines; + if (start > 0) { + boolean newline = origin.charAt(start - 1) != '\n'; + if (origin instanceof Spanned) { + formerLines = new SpannableStringBuilder().append(origin, 0, start); + if (newline) { + ((SpannableStringBuilder) formerLines).append('\n'); + } + } else { + formerLines = new StringBuilder().append(origin, 0, start); + if (newline) { + ((StringBuilder) formerLines).append('\n'); + } } + } else { + formerLines = null; + } + CharSequence lastLine; + if (MODE_HEAD.equals(mEllipsizeMode)) { + float formerTextSize = numberOfLines >= 2 ? getLineHeight(preLayout, numberOfLines - 2) : paint.getTextSize(); + float latterTextSize = Math.max(getLineHeight(preLayout, lineCount - 2), getLineHeight(preLayout, lineCount - 1)); + measurePaint.setTextSize(Math.max(formerTextSize, latterTextSize)); + lastLine = ellipsizeHead(origin, measurePaint, width, start); + } else if (MODE_MIDDLE.equals(mEllipsizeMode)) { + measurePaint.setTextSize(Math.max(getLineHeight(preLayout, numberOfLines - 1), getLineHeight(preLayout, lineCount - 1))); + lastLine = ellipsizeMiddle(origin, measurePaint, width, start); + } else /*if (MODE_TAIL.equals(mEllipsizeMode))*/ { + measurePaint.setTextSize(getLineHeight(preLayout, numberOfLines - 1)); + int end = preLayout.getLineEnd(numberOfLines); + lastLine = ellipsizeTail(origin, measurePaint, width, start, end); } + // concat everything + truncated = formerLines == null ? lastLine : formerLines instanceof SpannableStringBuilder + ? ((SpannableStringBuilder) formerLines).append(lastLine) + : ((StringBuilder) formerLines).append(lastLine); } - return buildStaticLayout(temp.replace(start, text.length(), ELLIPSIS), sTextPaintInstance, width); + return buildStaticLayout(truncated, paint, width); } - private static final String ELLIPSIS = "\u2026"; + private float getLineHeight(Layout layout, int line) { + return layout.getLineTop(line + 1) - layout.getLineTop(line); + } - public String truncate(String source, TextPaint paint, int desired, - TextUtils.TruncateAt truncateAt) { - if (!TextUtils.isEmpty(source)) { - StringBuilder builder; - Spanned spanned; - StaticLayout layout; - for (int i = source.length(); i > 0; i--) { - builder = new StringBuilder(i + 1); - if (truncateAt != null) { - builder.append(source, 0, i > 1 ? i - 1 : i); - builder.append(ELLIPSIS); - } else { - builder.append(source, 0, i); + private CharSequence ellipsizeHead(CharSequence origin, TextPaint paint, int width, int start) { + start = Math.max(start, TextUtils.lastIndexOf(origin, '\n') + 1); + // "…${last line of the rest part}" + CharSequence tmp; + if (origin instanceof Spanned) { + tmp = new SpannableStringBuilder() + .append(ELLIPSIS) + .append(origin, start, origin.length()); + } else { + tmp = new StringBuilder(ELLIPSIS.length() + origin.length() - start) + .append(ELLIPSIS) + .append(origin, start, origin.length()); + } + CharSequence result = TextUtils.ellipsize(tmp, paint, width, TextUtils.TruncateAt.START); + if (result instanceof Spannable) { + // make spans cover the "…" + Spannable sp = (Spannable) result; + int spanStart = ELLIPSIS.length(); + Object[] spans = sp.getSpans(spanStart, spanStart, Object.class); + for (Object span : spans) { + if (!(span instanceof ImageSpan) && sp.getSpanStart(span) == spanStart) { + int flag = sp.getSpanFlags(span); + int spanEnd = sp.getSpanEnd(span); + sp.removeSpan(span); + sp.setSpan(span, 0, spanEnd, flag); + } + } + } + return result; + } + + private CharSequence ellipsizeMiddle(CharSequence origin, TextPaint paint, int width, int start) { + int leftEnd, rightStart; + if ((leftEnd = TextUtils.indexOf(origin, '\n', start)) != -1) { + rightStart = TextUtils.lastIndexOf(origin, '\n') + 1; + assert leftEnd < rightStart; + // "${first line of the rest part}…${last line of the rest part}" + CharSequence tmp; + if (origin instanceof Spanned) { + tmp = new SpannableStringBuilder() + .append(origin, start, leftEnd) + .append(ELLIPSIS) + .append(origin, rightStart, origin.length()); + } else { + tmp = new StringBuilder(leftEnd - start + ELLIPSIS.length() + origin.length() - rightStart) + .append(origin, start, leftEnd) + .append(ELLIPSIS) + .append(origin, rightStart, origin.length()); + } + final int[] outRange = new int[2]; + TextUtils.EllipsizeCallback callback = new TextUtils.EllipsizeCallback() { + @Override + public void ellipsized(int l, int r) { + outRange[0] = l; + outRange[1] = r; } - spanned = createSpan(builder.toString(), false); - layout = buildStaticLayout(spanned, paint, desired); - if (layout.getLineCount() <= 1) { - return spanned.toString(); + }; + CharSequence line = TextUtils.ellipsize(tmp, paint, width, TextUtils.TruncateAt.MIDDLE, false, callback); + if (line != tmp) { + int pos0 = leftEnd - start; + int pos1 = pos0 + ELLIPSIS.length(); + if (outRange[0] > pos0) { + line = tmp instanceof SpannableStringBuilder + ? ((SpannableStringBuilder) tmp).replace(pos0, outRange[1], ELLIPSIS) + : ((StringBuilder) tmp).replace(pos0, outRange[1], ELLIPSIS); + } else if (outRange[1] < pos1) { + line = tmp instanceof SpannableStringBuilder + ? ((SpannableStringBuilder) tmp).replace(outRange[0], pos1, ELLIPSIS) + : ((StringBuilder) tmp).replace(outRange[0], pos1, ELLIPSIS); } } + return line; + } else { + // "${only one line of the rest part}" + CharSequence tmp = origin.subSequence(start, origin.length()); + return TextUtils.ellipsize(tmp, paint, width, TextUtils.TruncateAt.MIDDLE); + } + } + + private CharSequence ellipsizeTail(CharSequence origin, TextPaint paint, int width, int start, int end) { + int index = TextUtils.indexOf(origin, '\n', start, end); + if (index != -1) { + end = index; } - return ""; + // "${first line of the rest part}…" + CharSequence tmp; + if (origin instanceof Spanned) { + tmp = new SpannableStringBuilder() + .append(origin, start, end).append(ELLIPSIS); + } else { + tmp = new StringBuilder(end - start + ELLIPSIS.length()) + .append(origin, start, end).append(ELLIPSIS); + } + return TextUtils.ellipsize(tmp, paint, width, TextUtils.TruncateAt.END); + } + + @HippyControllerProps(name = PROP_VERTICAL_ALIGN, defaultType = HippyControllerProps.STRING) + public void setVerticalAlign(String align) { + switch (align) { + case HippyControllerProps.DEFAULT: + // reset to default + mVerticalAlign = null; + break; + case TextNode.V_ALIGN_TOP: + case TextNode.V_ALIGN_MIDDLE: + case TextNode.V_ALIGN_BASELINE: + case TextNode.V_ALIGN_BOTTOM: + mVerticalAlign = align; + break; + default: + mVerticalAlign = TextNode.V_ALIGN_BASELINE; + break; + } + } + + public String getVerticalAlign() { + if (mVerticalAlign != null) { + return mVerticalAlign; + } + DomNode parent = getParent(); + if (parent instanceof TextNode) { + return ((TextNode) parent).getVerticalAlign(); + } + return null; } + private static final String ELLIPSIS = "\u2026"; + private static class SpanOperation { + public static final int PRIORITY_DEFAULT = 1; + public static final int PRIORITY_LOWEST = 0; protected final int start; protected final int end; protected final Object what; + protected final int priority; @SuppressWarnings("unused") SpanOperation(int start, int end, Object what) { this.start = start; this.end = end; this.what = what; + this.priority = PRIORITY_DEFAULT; + } + + SpanOperation(int start, int end, Object what, int priority) { + this.start = start; + this.end = end; + this.what = what; + this.priority = priority; } public void execute(SpannableStringBuilder sb) { - int spanFlags = Spannable.SPAN_EXCLUSIVE_INCLUSIVE; - if (start == 0) { + int spanFlags; + if (what instanceof ImageSpan) { + spanFlags = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE; + } else if (start == 0) { spanFlags = Spannable.SPAN_INCLUSIVE_INCLUSIVE; + } else { + spanFlags = Spannable.SPAN_EXCLUSIVE_INCLUSIVE; } + spanFlags |= (priority << Spannable.SPAN_PRIORITY_SHIFT) & Spannable.SPAN_PRIORITY; try { sb.setSpan(what, start, end, spanFlags); diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/TypeFaceUtil.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/TypeFaceUtil.java index e34259f4375..d55327f6521 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/TypeFaceUtil.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/dom/node/TypeFaceUtil.java @@ -81,6 +81,20 @@ private static Typeface createTypeface(String fontFamilyName, int style, } } + if (typeface == null) { + for (String fileExtension : FONT_EXTENSIONS) { + String fileName = FONTS_PATH + fontFamilyName + fileExtension; + try { + typeface = Typeface.createFromAsset(ContextHolder.getAppContext().getAssets(), fileName); + if (typeface != null) { + typeface = Typeface.create(typeface, style); + } + } catch (Exception e) { + LogUtils.e("TypeFaceUtil", "createTypeface: " + e.getMessage()); + } + } + } + if (typeface == null) { typeface = Typeface.create(fontFamilyName, style); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/HippyModuleManager.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/HippyModuleManager.java index b506215604f..6167fb0e6ea 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/HippyModuleManager.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/HippyModuleManager.java @@ -13,26 +13,44 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.tencent.mtt.hippy.modules; +import androidx.annotation.NonNull; +import com.tencent.mtt.hippy.HippyAPIProvider; import com.tencent.mtt.hippy.bridge.HippyCallNativeParams; import com.tencent.mtt.hippy.common.Provider; import com.tencent.mtt.hippy.modules.javascriptmodules.HippyJavaScriptModule; import com.tencent.mtt.hippy.modules.nativemodules.HippyNativeModuleBase; +import com.tencent.mtt.hippy.modules.nativemodules.HippyNativeModuleInfo; + +import java.util.List; -/** - * FileName: HippyModuleManager Description: History: - */ public interface HippyModuleManager { - void callNatives(HippyCallNativeParams params); + void callNatives(HippyCallNativeParams params); + + void destroy(); + + HippyNativeModuleInfo getModuleInfo(@NonNull String moduleName); + + T getJavaScriptModule(Class cls); - void destroy(); + T getNativeModule(Class cls); - T getJavaScriptModule(Class cls); + void addNativeModule(Class cls, Provider provider); - T getNativeModule(Class cls); + /** + * Add native modules and java script modules defined in {@link HippyAPIProvider}. + * + * @param apiProviders API providers need to be added. + */ + void addModules(List apiProviders); - void addNativeModule(Class cls, Provider provider); + /** + * Get the number of native module, use for data report + * @return native module count + */ + int getNativeModuleCount(); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/HippyModuleManagerImpl.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/HippyModuleManagerImpl.java index 1199a775353..db7eda792bb 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/HippyModuleManagerImpl.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/HippyModuleManagerImpl.java @@ -13,17 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.tencent.mtt.hippy.modules; import android.os.Handler; import android.os.Looper; import android.os.Message; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.tencent.mtt.hippy.HippyAPIProvider; import com.tencent.mtt.hippy.HippyEngineContext; +import com.tencent.mtt.hippy.adapter.monitor.HippyEngineMonitorAdapter; import com.tencent.mtt.hippy.annotation.HippyNativeModule; import com.tencent.mtt.hippy.bridge.HippyCallNativeParams; -import com.tencent.mtt.hippy.common.HippyArray; import com.tencent.mtt.hippy.common.Provider; import com.tencent.mtt.hippy.modules.javascriptmodules.HippyJavaScriptModule; import com.tencent.mtt.hippy.modules.javascriptmodules.HippyJavaScriptModuleInvocationHandler; @@ -38,299 +42,324 @@ @SuppressWarnings({"unchecked", "unused", "rawtypes"}) public class HippyModuleManagerImpl implements HippyModuleManager, Handler.Callback { - private static final int MSG_CODE_CALL_NATIVES = 1; - private static final int MSG_CODE_DESTROY_MODULE = 2; - private final ConcurrentHashMap mNativeModuleInfo; - //Only multi-threaded read - private final HashMap, HippyJavaScriptModule> mJsModules; - private final HippyEngineContext mContext; - private boolean isDestroyed = false; - private volatile Handler mUIThreadHandler; - private volatile Handler mBridgeThreadHandler; - private volatile Handler mDomThreadHandler; - private final HippyModuleANRMonitor mANRMonitor; + private static final int MSG_CODE_CALL_NATIVES = 1; + private static final int MSG_CODE_DESTROY_MODULE = 2; + private final ConcurrentHashMap mNativeModuleInfo; + //Only multi-threaded read + private final HashMap, HippyJavaScriptModule> mJsModules; + private final HippyEngineContext mContext; + private boolean isDestroyed = false; + private volatile Handler mUIThreadHandler; + private volatile Handler mBridgeThreadHandler; + private volatile Handler mDomThreadHandler; - public HippyModuleManagerImpl(HippyEngineContext context, List packages) { - this.mContext = context; - mANRMonitor = new HippyModuleANRMonitor(mContext); - mNativeModuleInfo = new ConcurrentHashMap<>(); - mJsModules = new HashMap<>(); - for (HippyAPIProvider pckg : packages) { - Map, Provider> nativeModules = pckg - .getNativeModules(context); - if (nativeModules != null && nativeModules.size() > 0) { - Set> keys = nativeModules.keySet(); - for (Class cls : keys) { - HippyNativeModuleInfo moduleInfo = new HippyNativeModuleInfo(cls, nativeModules.get(cls)); - String[] names = moduleInfo.getNames(); - if (names != null && names.length > 0) { - for (String name : names) { - if (!mNativeModuleInfo.containsKey(name)) { - mNativeModuleInfo.put(name, moduleInfo); - } - } - } + public HippyModuleManagerImpl(HippyEngineContext context, List packages) { + this.mContext = context; + mNativeModuleInfo = new ConcurrentHashMap<>(); + mJsModules = new HashMap<>(); + addModules(packages); + } - if (!mNativeModuleInfo.containsKey(moduleInfo.getName())) { - mNativeModuleInfo.put(moduleInfo.getName(), moduleInfo); - //throw new RuntimeException("There is already a native module named : " + moduleInfo.getName()); - } + /** + * Add native modules and java script modules defined in {@link HippyAPIProvider}. + * + * @param apiProviders API providers need to be added. + */ + public synchronized void addModules(List apiProviders) { + if (apiProviders == null) { + return; } - } + for (HippyAPIProvider provider : apiProviders) { + Map, Provider> + nativeModules = provider.getNativeModules(mContext); + if (nativeModules != null && nativeModules.size() > 0) { + Set> keys = nativeModules.keySet(); + for (Class cls : keys) { + addNativeModule(cls, nativeModules.get(cls)); + } + } - List> jsModules = pckg.getJavaScriptModules(); - if (jsModules != null && jsModules.size() > 0) { - for (Class cls : jsModules) { - String name = getJavaScriptModuleName(cls); - //noinspection SuspiciousMethodCalls - if (mJsModules.containsKey(name)) { - throw new RuntimeException("There is already a javascript module named : " + name); - } - mJsModules.put(cls, null); + List> jsModules = provider + .getJavaScriptModules(); + if (jsModules != null && jsModules.size() > 0) { + for (Class cls : jsModules) { + String name = getJavaScriptModuleName(cls); + // noinspection SuspiciousMethodCalls + if (mJsModules.containsKey(name)) { + throw new RuntimeException( + "There is already a javascript module named : " + name); + } + mJsModules.put(cls, null); + } + } } - } - } - } - @Override - public synchronized void addNativeModule(Class cls, Provider provider) { - assert provider != null; - if (provider == null) { - return; - } + @Override + public synchronized void addNativeModule(Class cls, + Provider provider) { + if (provider == null) { + return; + } + HippyNativeModuleInfo moduleInfo = new HippyNativeModuleInfo(cls, provider); + String[] names = moduleInfo.getNames(); + if (names != null && names.length > 0) { + for (String name : names) { + if (!mNativeModuleInfo.containsKey(name)) { + mNativeModuleInfo.put(name, moduleInfo); + } + } + } - HippyNativeModuleInfo moduleInfo = new HippyNativeModuleInfo(cls, provider); - String[] names = moduleInfo.getNames(); - if (names != null && names.length > 0) { - for (String name : names) { - if (!mNativeModuleInfo.containsKey(name)) { - mNativeModuleInfo.put(name, moduleInfo); + if (!mNativeModuleInfo.containsKey(moduleInfo.getName())) { + mNativeModuleInfo.put(moduleInfo.getName(), moduleInfo); } - } } - if (!mNativeModuleInfo.containsKey(moduleInfo.getName())) { - mNativeModuleInfo.put(moduleInfo.getName(), moduleInfo); - } - } + @Override + public void destroy() { + if (mBridgeThreadHandler != null) { + mBridgeThreadHandler.removeMessages(MSG_CODE_CALL_NATIVES); + } + if (mDomThreadHandler != null) { + mDomThreadHandler.removeMessages(MSG_CODE_CALL_NATIVES); + } + if (mUIThreadHandler != null) { + mUIThreadHandler.removeMessages(MSG_CODE_CALL_NATIVES); + } - @Override - public void destroy() { - if (mBridgeThreadHandler != null) { - mBridgeThreadHandler.removeMessages(MSG_CODE_CALL_NATIVES); - } - if (mDomThreadHandler != null) { - mDomThreadHandler.removeMessages(MSG_CODE_CALL_NATIVES); - } - if (mUIThreadHandler != null) { - mUIThreadHandler.removeMessages(MSG_CODE_CALL_NATIVES); + //Must be thrown bridge thread to complete + isDestroyed = true; + Iterator> iterator = mNativeModuleInfo.entrySet() + .iterator(); + Map.Entry entry; + HippyNativeModuleInfo moduleInfo; + while (iterator.hasNext()) { + entry = iterator.next(); + if (entry != null) { + moduleInfo = entry.getValue(); + if (moduleInfo != null && moduleInfo.shouldDestroy()) { + moduleInfo.onDestroy(); + if (moduleInfo.getThread() == HippyNativeModule.Thread.DOM) { + if (mDomThreadHandler != null) { + Message msg = mDomThreadHandler + .obtainMessage(MSG_CODE_DESTROY_MODULE, moduleInfo); + mDomThreadHandler.sendMessage(msg); + } + } else if (moduleInfo.getThread() == HippyNativeModule.Thread.MAIN) { + if (mUIThreadHandler != null) { + Message msg = mUIThreadHandler + .obtainMessage(MSG_CODE_DESTROY_MODULE, moduleInfo); + mUIThreadHandler.sendMessage(msg); + } + } else { + if (mBridgeThreadHandler != null) { + Message msg = mBridgeThreadHandler + .obtainMessage(MSG_CODE_DESTROY_MODULE, moduleInfo); + mBridgeThreadHandler.sendMessage(msg); + } + } + } + } + } + mNativeModuleInfo.clear(); } - if (mANRMonitor != null) { - mANRMonitor.checkMonitor(); - } + @Override + public void callNatives(HippyCallNativeParams params) { + if (isDestroyed) { + return; + } + HippyNativeModuleInfo moduleInfo = mNativeModuleInfo.get(params.moduleName); + if (moduleInfo == null) { + PromiseImpl promise = new PromiseImpl(mContext, params.moduleName, params.moduleFunc, + params.callId); + promise.doCallback(PromiseImpl.PROMISE_CODE_NORMAN_ERROR, "module can not be found"); + return; + } - //Must be thrown bridge thread to complete - isDestroyed = true; - Iterator> iterator = mNativeModuleInfo.entrySet() - .iterator(); - Map.Entry entry; - HippyNativeModuleInfo moduleInfo; - while (iterator.hasNext()) { - entry = iterator.next(); - if (entry != null) { - moduleInfo = entry.getValue(); - if (moduleInfo != null && moduleInfo.shouldDestroy()) { - moduleInfo.onDestroy(); - if (moduleInfo.getThread() == HippyNativeModule.Thread.DOM) { - if (mDomThreadHandler != null) { - Message msg = mDomThreadHandler.obtainMessage(MSG_CODE_DESTROY_MODULE, moduleInfo); - mDomThreadHandler.sendMessage(msg); - } - } else if (moduleInfo.getThread() == HippyNativeModule.Thread.MAIN) { - if (mUIThreadHandler != null) { - Message msg = mUIThreadHandler.obtainMessage(MSG_CODE_DESTROY_MODULE, moduleInfo); - mUIThreadHandler.sendMessage(msg); - } - } else { - if (mBridgeThreadHandler != null) { - Message msg = mBridgeThreadHandler.obtainMessage(MSG_CODE_DESTROY_MODULE, moduleInfo); - mBridgeThreadHandler.sendMessage(msg); - } - } + if (moduleInfo.getThread() == HippyNativeModule.Thread.DOM) { + Handler handler = getDomThreadHandler(); + Message msg = handler.obtainMessage(MSG_CODE_CALL_NATIVES, params); + handler.sendMessage(msg); + } else if (moduleInfo.getThread() == HippyNativeModule.Thread.MAIN) { + Handler handler = getUIThreadHandler(); + Message msg = handler.obtainMessage(MSG_CODE_CALL_NATIVES, params); + handler.sendMessage(msg); + } else { + Handler handler = getBridgeThreadHandler(); + Message msg = handler.obtainMessage(MSG_CODE_CALL_NATIVES, params); + handler.sendMessage(msg); } - } } - mNativeModuleInfo.clear(); - } - @Override - public void callNatives(HippyCallNativeParams params) { - if (isDestroyed) { - return; - } - HippyNativeModuleInfo moduleInfo = mNativeModuleInfo.get(params.mModuleName); - if (moduleInfo == null) { - PromiseImpl promise = new PromiseImpl(mContext, params.mModuleName, params.mModuleFunc, - params.mCallId); - promise.doCallback(PromiseImpl.PROMISE_CODE_NORMAN_ERROR, "module can not be found"); - return; + @Override + public synchronized T getJavaScriptModule(Class cls) { + HippyJavaScriptModule module = mJsModules.get(cls); + if (module != null) { + return (T) module; + } + ClassLoader clsLoader = cls.getClassLoader(); + if (clsLoader == null) { + return null; + } + HippyJavaScriptModule moduleProxy = (HippyJavaScriptModule) Proxy + .newProxyInstance(clsLoader, new Class[]{cls}, + new HippyJavaScriptModuleInvocationHandler(mContext, + getJavaScriptModuleName(cls))); + mJsModules.remove(cls); + mJsModules.put(cls, moduleProxy); + return (T) moduleProxy; } - if (moduleInfo.getThread() == HippyNativeModule.Thread.DOM) { - Handler handler = getDomThreadHandler(); - Message msg = handler.obtainMessage(MSG_CODE_CALL_NATIVES, params); - handler.sendMessage(msg); - } else if (moduleInfo.getThread() == HippyNativeModule.Thread.MAIN) { - Handler handler = getUIThreadHandler(); - Message msg = handler.obtainMessage(MSG_CODE_CALL_NATIVES, params); - handler.sendMessage(msg); - } else { - Handler handler = getBridgeThreadHandler(); - Message msg = handler.obtainMessage(MSG_CODE_CALL_NATIVES, params); - handler.sendMessage(msg); + @Override + public synchronized T getNativeModule(Class cls) { + HippyNativeModule annotation = cls.getAnnotation(HippyNativeModule.class); + if (annotation != null) { + String name = annotation.name(); + HippyNativeModuleInfo moduleInfo = mNativeModuleInfo.get(name); + if (moduleInfo != null) { + return (T) moduleInfo.getInstance(); + } + } + return null; } - } - @Override - public synchronized T getJavaScriptModule(Class cls) { - HippyJavaScriptModule module = mJsModules.get(cls); - if (module != null) { - return (T) module; - } - ClassLoader clsLoader = cls.getClassLoader(); - if (clsLoader == null) { - return null; + @Override + public int getNativeModuleCount() { + return mNativeModuleInfo.size(); } - HippyJavaScriptModule moduleProxy = (HippyJavaScriptModule) Proxy - .newProxyInstance(clsLoader, new Class[]{cls}, - new HippyJavaScriptModuleInvocationHandler(mContext, getJavaScriptModuleName(cls))); - mJsModules.remove(cls); - mJsModules.put(cls, moduleProxy); - return (T) moduleProxy; - } - @Override - public synchronized T getNativeModule(Class cls) { - HippyNativeModule annotation = cls.getAnnotation(HippyNativeModule.class); - if (annotation != null) { - String name = annotation.name(); - HippyNativeModuleInfo moduleInfo = mNativeModuleInfo.get(name); - if (moduleInfo != null) { - return (T) moduleInfo.getInstance(); - } + @Nullable + public HippyNativeModuleInfo getModuleInfo(@NonNull String moduleName) { + return mNativeModuleInfo.get(moduleName); } - return null; - } - - void doCallNatives(String moduleName, String moduleFunc, String callId, HippyArray params) { - PromiseImpl promise = new PromiseImpl(mContext, moduleName, moduleFunc, callId); - try { - HippyNativeModuleInfo moduleInfo = mNativeModuleInfo.get(moduleName); - if (moduleInfo == null) { - promise.doCallback(PromiseImpl.PROMISE_CODE_NORMAN_ERROR, "module can not be found"); - return; - } - moduleInfo.initialize(); - HippyNativeModuleInfo.HippyNativeMethod method = moduleInfo.findMethod(moduleFunc); - if (method == null || method.isSync()) { - promise - .doCallback(PromiseImpl.PROMISE_CODE_NORMAN_ERROR, "module function can not be found"); - return; - } - method.invoke(mContext, moduleInfo.getInstance(), params, promise); - } catch (Throwable e) { - promise.doCallback(PromiseImpl.PROMISE_CODE_NORMAN_ERROR, e.getMessage()); - mContext.getGlobalConfigs().getExceptionHandler().handleNativeException(new RuntimeException(e), true); + void doCallNatives(@NonNull HippyCallNativeParams params) { + PromiseImpl promise = new PromiseImpl(mContext, params.moduleName, params.moduleFunc, + params.callId); + try { + HippyNativeModuleInfo moduleInfo = mNativeModuleInfo.get(params.moduleName); + if (moduleInfo == null) { + promise.doCallback(PromiseImpl.PROMISE_CODE_NORMAN_ERROR, + "module can not be found"); + return; + } + moduleInfo.initialize(); + HippyNativeModuleInfo.HippyNativeMethod method = moduleInfo + .findMethod(params.moduleFunc); + if (method == null || method.isSync()) { + promise + .doCallback(PromiseImpl.PROMISE_CODE_NORMAN_ERROR, + "module function can not be found"); + return; + } + method.invoke(moduleInfo.getInstance(), params.params, promise); + } catch (Throwable e) { + promise.doCallback(PromiseImpl.PROMISE_CODE_NORMAN_ERROR, e.getMessage()); + mContext.getGlobalConfigs().getExceptionHandler() + .handleNativeException(new RuntimeException(e), true); + } } - } - private String getJavaScriptModuleName(Class cls) { - String name = cls.getSimpleName(); - int dollarSignIndex = name.lastIndexOf('$'); - if (dollarSignIndex != -1) { - name = name.substring(dollarSignIndex + 1); + private String getJavaScriptModuleName(Class cls) { + String name = cls.getSimpleName(); + int dollarSignIndex = name.lastIndexOf('$'); + if (dollarSignIndex != -1) { + name = name.substring(dollarSignIndex + 1); + } + return name; } - return name; - } - private Handler getDomThreadHandler() { - if (mDomThreadHandler == null) { - synchronized (HippyModuleManagerImpl.class) { + private Handler getDomThreadHandler() { if (mDomThreadHandler == null) { - mDomThreadHandler = new Handler(mContext.getThreadExecutor().getDomThread().getLooper(), - this); + synchronized (HippyModuleManagerImpl.class) { + if (mDomThreadHandler == null) { + mDomThreadHandler = new Handler( + mContext.getThreadExecutor().getDomThread().getLooper(), + this); + } + } } - } + return mDomThreadHandler; } - return mDomThreadHandler; - } - private Handler getUIThreadHandler() { - if (mUIThreadHandler == null) { - synchronized (HippyModuleManagerImpl.class) { + private Handler getUIThreadHandler() { if (mUIThreadHandler == null) { - mUIThreadHandler = new Handler(Looper.getMainLooper(), this); + synchronized (HippyModuleManagerImpl.class) { + if (mUIThreadHandler == null) { + mUIThreadHandler = new Handler(Looper.getMainLooper(), this); + } + } } - } + return mUIThreadHandler; } - return mUIThreadHandler; - } - private Handler getBridgeThreadHandler() { - if (mBridgeThreadHandler == null) { - synchronized (HippyModuleManagerImpl.class) { + private Handler getBridgeThreadHandler() { if (mBridgeThreadHandler == null) { - mBridgeThreadHandler = new Handler( - mContext.getThreadExecutor().getJsBridgeThread().getLooper(), this); + synchronized (HippyModuleManagerImpl.class) { + if (mBridgeThreadHandler == null) { + mBridgeThreadHandler = new Handler( + mContext.getThreadExecutor().getJsBridgeThread().getLooper(), this); + } + } } - } + return mBridgeThreadHandler; } - return mBridgeThreadHandler; - } - @Override - public boolean handleMessage(Message msg) { + private boolean onInterceptCallNative(@Nullable HippyCallNativeParams params) { + HippyEngineMonitorAdapter adapter = mContext.getGlobalConfigs().getEngineMonitorAdapter(); + if (adapter == null || params == null) { + return false; + } + return adapter.onInterceptCallNative(mContext.getComponentName(), params); + } - switch (msg.what) { - case MSG_CODE_CALL_NATIVES: { - HippyCallNativeParams param = null; - int id = -1; - try { - param = (HippyCallNativeParams) msg.obj; - HippyArray array = param.mParams; + private void onCallNativeFinished(@Nullable HippyCallNativeParams params) { + HippyEngineMonitorAdapter adapter = mContext.getGlobalConfigs().getEngineMonitorAdapter(); + if (adapter == null || params == null) { + return; + } + adapter.onCallNativeFinished(mContext.getComponentName(), params); + } - id = mANRMonitor.startMonitor(param.mModuleName, param.mModuleFunc); - doCallNatives(param.mModuleName, param.mModuleFunc, param.mCallId, array); - } catch (Throwable e) { - LogUtils.d("HippyModuleManagerImpl", "handleMessage: " + e.getMessage()); - } finally { - if (param != null) { - param.onDispose(); - } + @Override + public boolean handleMessage(Message msg) { - if (id >= 0) { - mANRMonitor.endMonitor(id); - } - } - return true; - } - case MSG_CODE_DESTROY_MODULE: { - try { - HippyNativeModuleInfo moduleInfo = (HippyNativeModuleInfo) msg.obj; - moduleInfo.destroy(); - } catch (Throwable e) { - LogUtils.d("HippyModuleManagerImpl", "handleMessage: " + e.getMessage()); + switch (msg.what) { + case MSG_CODE_CALL_NATIVES: { + HippyCallNativeParams params = null; + int id = -1; + try { + params = (HippyCallNativeParams) msg.obj; + boolean shouldInterceptCallNative = onInterceptCallNative(params); + if (!shouldInterceptCallNative) { + doCallNatives(params); + } + } catch (Throwable e) { + e.printStackTrace(); + } finally { + onCallNativeFinished(params); + if (params != null) { + params.onDispose(); + } + } + return true; + } + case MSG_CODE_DESTROY_MODULE: { + try { + HippyNativeModuleInfo moduleInfo = (HippyNativeModuleInfo) msg.obj; + moduleInfo.destroy(); + } catch (Throwable e) { + LogUtils.d("HippyModuleManagerImpl", "handleMessage: " + e.getMessage()); + } + return true; + } } - return true; - } + return false; } - return false; - } - public ConcurrentHashMap getNativeModuleInfo() { - return mNativeModuleInfo; - } + public ConcurrentHashMap getNativeModuleInfo() { + return mNativeModuleInfo; + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/PromiseImpl.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/PromiseImpl.java index d828d625f37..06b29a7ce68 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/PromiseImpl.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/PromiseImpl.java @@ -17,93 +17,107 @@ import android.text.TextUtils; - import com.tencent.mtt.hippy.HippyEngine.BridgeTransferType; import com.tencent.mtt.hippy.HippyEngineContext; +import com.tencent.mtt.hippy.adapter.monitor.HippyEngineMonitorAdapter; import com.tencent.mtt.hippy.common.HippyMap; -import com.tencent.mtt.hippy.runtime.builtins.JSMap; import com.tencent.mtt.hippy.runtime.builtins.JSObject; import com.tencent.mtt.hippy.runtime.builtins.JSValue; -import java.util.HashMap; +import java.lang.ref.WeakReference; @SuppressWarnings({"deprecation", "unused"}) public class PromiseImpl implements Promise { - public static final int PROMISE_CODE_SUCCESS = 0; - public static final int PROMISE_CODE_NORMAN_ERROR = 1; - public static final int PROMISE_CODE_OTHER_ERROR = 2; - private static final String CALL_ID_NO_CALLBACK = "-1"; - private HippyEngineContext mContext; - private final String mModuleName; - private final String mModuleFunc; - private final String mCallId; - private boolean mNeedResolveBySelf = true; - private BridgeTransferType transferType = BridgeTransferType.BRIDGE_TRANSFER_TYPE_NORMAL; - - public PromiseImpl(HippyEngineContext context, String moduleName, String moduleFunc, - String callId) { - this.mContext = context; - this.mModuleName = moduleName; - this.mModuleFunc = moduleFunc; - this.mCallId = callId; - } + public static final int PROMISE_CODE_SUCCESS = 0; + public static final int PROMISE_CODE_NORMAN_ERROR = 1; + public static final int PROMISE_CODE_OTHER_ERROR = 2; + private static final String CALL_ID_NO_CALLBACK = "-1"; + private WeakReference mContextRef; + private final String mModuleName; + private final String mModuleFunc; + private final String mCallId; + private boolean mNeedResolveBySelf = true; + private BridgeTransferType transferType = BridgeTransferType.BRIDGE_TRANSFER_TYPE_NORMAL; + + public PromiseImpl(HippyEngineContext context, String moduleName, String moduleFunc, + String callId) { + this.mContextRef = new WeakReference<>(context); + this.mModuleName = moduleName; + this.mModuleFunc = moduleFunc; + this.mCallId = callId; + } - public void setContext(HippyEngineContext context) { - mContext = context; - } + public void setContext(HippyEngineContext context) { + mContextRef = new WeakReference<>(context); + } - public String getCallId() { - return mCallId; - } + public String getCallId() { + return mCallId; + } - public boolean isCallback() { - return !TextUtils.equals(mCallId, CALL_ID_NO_CALLBACK); - } + public boolean isCallback() { + return !TextUtils.equals(mCallId, CALL_ID_NO_CALLBACK); + } - @Override - public void setTransferType(BridgeTransferType type) { - transferType = type; - } + @Override + public void setTransferType(BridgeTransferType type) { + transferType = type; + } - @Override - public void resolve(Object value) { - doCallback(PROMISE_CODE_SUCCESS, value); - } + @Override + public void resolve(Object value) { + doCallback(PROMISE_CODE_SUCCESS, value); + } - @Override - public void reject(Object error) { - doCallback(PROMISE_CODE_OTHER_ERROR, error); - } + @Override + public void reject(Object error) { + doCallback(PROMISE_CODE_OTHER_ERROR, error); + } - public void setNeedResolveBySelf(boolean falg) { - mNeedResolveBySelf = falg; - } + public void setNeedResolveBySelf(boolean falg) { + mNeedResolveBySelf = falg; + } - public boolean needResolveBySelf() { - return mNeedResolveBySelf; - } + public boolean needResolveBySelf() { + return mNeedResolveBySelf; + } - public void doCallback(int code, Object obj) { - if (TextUtils.equals(CALL_ID_NO_CALLBACK, mCallId)) { - return; + private boolean onInterceptPromiseCallBack(Object resultObject) { + final HippyEngineContext context = mContextRef.get(); + if (context == null) { + return false; + } + HippyEngineMonitorAdapter adapter = context.getGlobalConfigs().getEngineMonitorAdapter(); + if (adapter == null) { + return false; + } + return adapter + .onInterceptPromiseCallback(context.getComponentName(), mModuleName, mModuleFunc, + mCallId, resultObject); } - if (obj instanceof JSValue) { - JSObject jsObject = new JSObject(); - jsObject.set("result", code); - jsObject.set("moduleName", mModuleName); - jsObject.set("moduleFunc", mModuleFunc); - jsObject.set("callId", mCallId); - jsObject.set("params", obj); - mContext.getBridgeManager().execCallback(jsObject, transferType); - } else { - HippyMap hippyMap = new HippyMap(); - hippyMap.pushInt("result", code); - hippyMap.pushString("moduleName", mModuleName); - hippyMap.pushString("moduleFunc", mModuleFunc); - hippyMap.pushString("callId", mCallId); - hippyMap.pushObject("params", obj); - mContext.getBridgeManager().execCallback(hippyMap, transferType); + public void doCallback(int code, Object resultObject) { + final HippyEngineContext context = mContextRef.get(); + if (context == null || onInterceptPromiseCallBack(resultObject) || TextUtils + .equals(CALL_ID_NO_CALLBACK, mCallId)) { + return; + } + if (resultObject instanceof JSValue) { + JSObject jsObject = new JSObject(); + jsObject.set("result", code); + jsObject.set("moduleName", mModuleName); + jsObject.set("moduleFunc", mModuleFunc); + jsObject.set("callId", mCallId); + jsObject.set("params", resultObject); + context.getBridgeManager().execCallback(jsObject, transferType); + } else { + HippyMap hippyMap = new HippyMap(); + hippyMap.pushInt("result", code); + hippyMap.pushString("moduleName", mModuleName); + hippyMap.pushString("moduleFunc", mModuleFunc); + hippyMap.pushString("callId", mCallId); + hippyMap.pushObject("params", resultObject); + context.getBridgeManager().execCallback(hippyMap, transferType); + } } - } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/HippyNativeModuleInfo.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/HippyNativeModuleInfo.java index a0069b29c02..4d314ff3a31 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/HippyNativeModuleInfo.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/HippyNativeModuleInfo.java @@ -13,191 +13,218 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.tencent.mtt.hippy.modules.nativemodules; import android.text.TextUtils; -import com.tencent.mtt.hippy.HippyEngineContext; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.tencent.mtt.hippy.annotation.HippyMethod; import com.tencent.mtt.hippy.annotation.HippyNativeModule; +import com.tencent.mtt.hippy.annotation.HippyNativeModule.Thread; import com.tencent.mtt.hippy.common.HippyArray; import com.tencent.mtt.hippy.common.Provider; import com.tencent.mtt.hippy.modules.Promise; import com.tencent.mtt.hippy.modules.PromiseImpl; +import com.tencent.mtt.hippy.runtime.builtins.array.JSDenseArray; import com.tencent.mtt.hippy.utils.ArgumentUtils; +import com.tencent.mtt.hippy.utils.LogUtils; import java.lang.reflect.Method; import java.lang.reflect.Type; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; -@SuppressWarnings({"unused"}) public final class HippyNativeModuleInfo { - private final String mName; - - private final String[] mNames; - - private final HippyNativeModule.Thread mThread; - - private final Provider mProvider; - - private final Class mClass; - - private Map mMethods; - - private HippyNativeModuleBase mInstance; - - private boolean mInit = false; - - private boolean mIsDestroyed = false; - - public HippyNativeModuleInfo(Class cls, Provider provider) { - HippyNativeModule annotation = cls.getAnnotation(HippyNativeModule.class); - assert annotation != null; - this.mName = annotation.name(); - this.mNames = annotation.names(); - this.mClass = cls; - this.mThread = annotation.thread(); - mProvider = provider; - initImmediately(annotation); - - } - - private void initImmediately(HippyNativeModule annotation) { - if (annotation.init()) { - try { - initialize(); - } catch (Throwable e) { - e.printStackTrace(); - } - + private static final String TAG = "HippyNativeModuleInfo"; + private String mName; + private String[] mNames; + private HippyNativeModule.Thread mThread = Thread.BRIDGE; + private final Provider mProvider; + private final Class mClass; + @Nullable + private Map mMethods; + private HippyNativeModuleBase mInstance; + private boolean mInit = false; + private boolean mIsDestroyed = false; + + public HippyNativeModuleInfo(@NonNull Class cls, + Provider provider) { + HippyNativeModule annotation = cls.getAnnotation(HippyNativeModule.class); + mClass = cls; + mProvider = provider; + if (annotation != null) { + mName = annotation.name(); + mNames = annotation.names(); + mThread = annotation.thread(); + initImmediately(annotation); + } } - } - - public boolean shouldDestroy() { - return !mIsDestroyed; - } - public void onDestroy() { - mIsDestroyed = true; - } + private void initImmediately(@NonNull HippyNativeModule annotation) { + if (annotation.init()) { + try { + initialize(); + } catch (Throwable e) { + e.printStackTrace(); + } + } + } - public String getName() { - return mName; - } + public boolean shouldDestroy() { + return !mIsDestroyed; + } - public String[] getNames() { - return mNames; - } + public void onDestroy() { + mIsDestroyed = true; + } - public HippyNativeModuleBase getInstance() { - return mInstance; - } + public String getName() { + return mName; + } - public HippyNativeModule.Thread getThread() { - return mThread; - } + public String[] getNames() { + return mNames; + } - public Map getMethods() { - return mMethods; - } + public HippyNativeModuleBase getInstance() { + return mInstance; + } - public void initialize() { - if (mInit) { - return; + public HippyNativeModule.Thread getThread() { + return mThread; } - mMethods = new HashMap<>(); - Method[] targetMethods = mClass.getMethods(); - for (Method targetMethod : targetMethods) { - HippyMethod hippyMethod = targetMethod.getAnnotation(HippyMethod.class); - if (hippyMethod != null) { - String methodName = hippyMethod.name(); - if (TextUtils.isEmpty(methodName)) { - methodName = targetMethod.getName(); + + private void checkModuleMethods() { + if (mMethods != null) { + return; } - if (mMethods.containsKey(methodName)) { - throw new RuntimeException( - "Java Module " + mName + " method name already registered: " + methodName); + synchronized (this) { + if (mMethods != null) { + return; + } + mMethods = new ConcurrentHashMap<>(); + Method[] targetMethods = mClass.getMethods(); + for (Method targetMethod : targetMethods) { + HippyMethod hippyMethod = targetMethod.getAnnotation(HippyMethod.class); + if (hippyMethod == null) { + continue; + } + String methodName = hippyMethod.name(); + if (TextUtils.isEmpty(methodName)) { + methodName = targetMethod.getName(); + } + if (mMethods.containsKey(methodName)) { + LogUtils.e(TAG, + "Register the same method twice, moduleName=" + mName + ", methodName=" + + methodName); + continue; + } + mMethods.put(methodName, new HippyNativeMethod(targetMethod, hippyMethod.isSync(), + hippyMethod.useJSValueType())); + } } - mMethods.put(methodName, new HippyNativeMethod(targetMethod, hippyMethod.isSync())); - } } - mInstance = mProvider.get(); - mInstance.initialize(); - mInit = true; - } - - public void destroy() { - if (mInstance != null) { - mInstance.destroy(); + public void initialize() { + if (mInit) { + return; + } + checkModuleMethods(); + mInstance = mProvider.get(); + mInstance.initialize(); + mInit = true; } - } - public HippyNativeMethod findMethod(String moduleFunc) { - if (mMethods == null) { - return null; + public void destroy() { + if (mInstance != null) { + mInstance.destroy(); + } } - return mMethods.get(moduleFunc); - } + @Nullable + public HippyNativeMethod findMethod(String moduleFunc) { + checkModuleMethods(); + return mMethods.get(moduleFunc); + } - public static class HippyNativeMethod { - - private final Method mMethod; + public static class HippyNativeMethod { - private final Type[] mParamTypes; + @NonNull + private final Method mMethod; + @Nullable + private final Type[] mParamTypes; + private final boolean mIsSync; + private final boolean mUseJSValueType; - private boolean mIsSync; + public HippyNativeMethod(@NonNull Method method, boolean isSync, boolean useJSValueType) { + mMethod = method; + mIsSync = isSync; + mUseJSValueType = useJSValueType; + mParamTypes = method.getGenericParameterTypes(); + } - public HippyNativeMethod(Method method, boolean isSync) { - this.mMethod = method; - this.mIsSync = isSync; - this.mParamTypes = method.getGenericParameterTypes(); - } + public boolean isSync() { + return mIsSync; + } - public void invoke(HippyEngineContext context, Object receiver, HippyArray args, - PromiseImpl promise) throws Exception { - Object[] params = prepareArguments(context, mParamTypes, args, promise); - mMethod.invoke(receiver, params); - if (promise.needResolveBySelf()) { - promise.resolve(""); - } - } + public boolean useJSValueType() { + return mUseJSValueType; + } - private Object[] prepareArguments(HippyEngineContext context, Type[] paramClss, HippyArray args, - PromiseImpl promise) { - if (paramClss == null || paramClss.length <= 0) { - return new Object[0]; - } - Object[] params = new Object[paramClss.length]; - if (args == null) { - throw new RuntimeException("method argument list not match"); - } - Type paramCls; - int index = 0; - - for (int i = 0; i < paramClss.length; i++) { - paramCls = paramClss[i]; - if (paramCls == Promise.class) { - params[i] = promise; - promise.setNeedResolveBySelf(false); - } else { - if (args.size() <= index) { - throw new RuntimeException("method argument list not match"); - } - params[i] = ArgumentUtils.parseArgument(paramCls, args, index); - index++; + public void invoke(Object receiver, @Nullable Object args, + PromiseImpl promise) throws Exception { + Object[] params = null; + if (args != null) { + params = prepareArguments(args, promise); + } + mMethod.invoke(receiver, params); + if (promise.needResolveBySelf()) { + promise.resolve(""); + } } - } - return params; - } + private boolean checkArgumentType(@NonNull Object args) { + if (mUseJSValueType && args instanceof JSDenseArray) { + return true; + } + return !mUseJSValueType && args instanceof HippyArray; + } - public boolean isSync() { - return mIsSync; + @Nullable + private Object[] prepareArguments(@NonNull Object args, PromiseImpl promise) + throws IllegalArgumentException { + if (mParamTypes == null || mParamTypes.length <= 0) { + return null; + } + if (!checkArgumentType(args)) { + throw new IllegalArgumentException("The data type of parameters mismatch!"); + } + Object[] params = new Object[mParamTypes.length]; + int index = 0; + int size = mUseJSValueType ? ((JSDenseArray) args).size() : ((HippyArray) args).size(); + for (int i = 0; i < mParamTypes.length; i++) { + Type paramCls = mParamTypes[i]; + if (paramCls == Promise.class) { + params[i] = promise; + promise.setNeedResolveBySelf(false); + } else { + if (size <= index) { + throw new IllegalArgumentException( + "The number of parameters does not match"); + } + if (mUseJSValueType) { + params[i] = ((JSDenseArray) args).get(index); + } else { + params[i] = ArgumentUtils.parseArgument(paramCls, (HippyArray) args, index); + } + index++; + } + } + return params; + } } - - } - } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/animation/AnimationModule.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/animation/AnimationModule.java index 8d0f5171200..00140499a57 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/animation/AnimationModule.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/animation/AnimationModule.java @@ -62,6 +62,7 @@ public class AnimationModule extends HippyNativeModuleBase implements DomActionI private long mLastUpdateTime; private final Set mNeedUpdateAnimationNodes; private Set mWaitUpdateAnimationNodes; + private boolean mInitialized = false; public AnimationModule(HippyEngineContext context) { @@ -71,6 +72,9 @@ public AnimationModule(HippyEngineContext context) { @Override public boolean handleMessage(Message msg) { + if (!mInitialized) { + return true; + } int what = msg.what; switch (what) { case MSG_CHANGE_ANIMATION_STATUS: { @@ -103,14 +107,21 @@ public void initialize() { if (mContext.getDomManager() != null) { mContext.getDomManager().addActionInterceptor(this); } + mInitialized = true; } @Override public void destroy() { + mInitialized = false; mContext.removeEngineLifecycleEventListener(this); if (mContext.getDomManager() != null) { mContext.getDomManager().removeActionInterceptor(this); } + for (int i = 0, size = mAnimations.size(); i < size; ++i) { + Animation animation = mAnimations.valueAt(i); + animation.stop(); + } + mAnimations.clear(); super.destroy(); } @@ -151,7 +162,7 @@ public void onAnimationUpdate(Animation animation) { mNeedUpdateAnimationNodes.addAll(nodeIds); - if (!mHandler.hasMessages(MSG_CHANGE_ANIMATION_STATUS)) { + if (mInitialized && !mHandler.hasMessages(MSG_CHANGE_ANIMATION_STATUS)) { mHandler.sendEmptyMessage(MSG_CHANGE_ANIMATION_STATUS); } } @@ -315,7 +326,7 @@ public HippyMap onUpdateNode(int tagId, HippyRootView rootView, HippyMap props) @Override public void onEngineResume() { - if (mHandler != null) { + if (mInitialized && mHandler != null) { mHandler.post(new Runnable() { @Override public void run() { @@ -335,7 +346,7 @@ public void run() { @Override public void onEnginePause() { - if (mHandler != null) { + if (mInitialized && mHandler != null) { mHandler.post(new Runnable() { @Override public void run() { diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/animation/TimingAnimation.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/animation/TimingAnimation.java index 9759adbbc7f..870332b004a 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/animation/TimingAnimation.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/animation/TimingAnimation.java @@ -43,8 +43,8 @@ public class TimingAnimation extends Animation implements ValueAnimator.Animator private static final String TIMING_FUNCTION_EASE_IN_OUT = "ease-in-out"; private static final String TIMING_FUNCTION_EASE_BEZIER = "ease_bezier"; private static final Pattern TIMING_FUNCTION_CUBIC_BEZIER_PATTERN = Pattern.compile("^cubic-bezier\\(([^,]*),([^,]*),([^,]*),([^,]*)\\)$"); - protected float mStartValue; - protected float mToValue; + protected Number mStartValue = 0; + protected Number mToValue = 0; protected int mDuration; protected String mTimingFunction; protected final ValueAnimator mAnimator; @@ -140,12 +140,14 @@ public void parseFromData(HippyMap param) { } if (param.containsKey("startValue")) { - mStartValue = (float) param.getDouble("startValue"); + Object value = param.get("startValue"); + mStartValue = value instanceof Number ? (Number) value : 0; } mAnimationValue = mStartValue; if (param.containsKey("toValue")) { - mToValue = (float) param.getDouble("toValue"); + Object value = param.get("toValue"); + mToValue = value instanceof Number ? (Number) value : 0; } if (param.containsKey("duration")) { @@ -174,10 +176,10 @@ public void parseFromData(HippyMap param) { } if (!TextUtils.isEmpty(mValueType) && mValueType.equals(VALUE_TYPE_COLOR)) { - mAnimator.setIntValues((int) mStartValue, (int) mToValue); + mAnimator.setIntValues(mStartValue.intValue(), mToValue.intValue()); mAnimator.setEvaluator(new ArgbEvaluator()); } else { - mAnimator.setFloatValues(mStartValue, mToValue); + mAnimator.setFloatValues(mStartValue.floatValue(), mToValue.floatValue()); } mAnimator.setDuration(mDuration); diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/clipboard/ClipboardModule.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/clipboard/ClipboardModule.java deleted file mode 100644 index 27ec7ef200c..00000000000 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/clipboard/ClipboardModule.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.tencent.mtt.hippy.modules.nativemodules.clipboard; - -import android.content.ClipData; -import android.content.Context; - -import com.tencent.mtt.hippy.HippyEngineContext; -import com.tencent.mtt.hippy.annotation.HippyMethod; -import com.tencent.mtt.hippy.annotation.HippyNativeModule; -import com.tencent.mtt.hippy.modules.Promise; -import com.tencent.mtt.hippy.modules.nativemodules.HippyNativeModuleBase; -import android.content.ClipboardManager; - -@HippyNativeModule(name = "ClipboardModule") -public class ClipboardModule extends HippyNativeModuleBase { - - private final ClipboardManager mClipboardManager; - - public ClipboardModule(HippyEngineContext context) { - super(context); - mClipboardManager = (ClipboardManager) (mContext.getGlobalConfigs()).getContext() - .getSystemService(Context.CLIPBOARD_SERVICE); - } - - - private ClipboardManager getClipboardService() { - return mClipboardManager; - } - - @SuppressWarnings("unused") - @HippyMethod(name = "getString") - public void getString(Promise promise) { - try { - ClipboardManager clipboard = getClipboardService(); - ClipData clipData = clipboard.getPrimaryClip(); - if (clipData != null && clipData.getItemCount() >= 1) { - ClipData.Item firstItem = clipboard.getPrimaryClip().getItemAt(0); - promise.resolve("" + firstItem.getText()); - } else { - promise.resolve(""); - } - } catch (Exception e) { - promise.reject(e); - } - } - - @SuppressWarnings("unused") - @HippyMethod(name = "setString") - public void setString(String text) { - ClipData clipdata = ClipData.newPlainText(null, text); - ClipboardManager clipboard = getClipboardService(); - clipboard.setPrimaryClip(clipdata); - } -} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/netinfo/NetInfoModule.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/netinfo/NetInfoModule.java index d581a14fea6..a167d75132a 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/netinfo/NetInfoModule.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/netinfo/NetInfoModule.java @@ -30,6 +30,8 @@ import com.tencent.mtt.hippy.modules.nativemodules.HippyNativeModuleBase; import com.tencent.mtt.hippy.utils.LogUtils; +import java.util.concurrent.RejectedExecutionException; + @SuppressWarnings({"deprecation", "unused"}) @HippyNativeModule(name = "NetInfo") public class NetInfoModule extends HippyNativeModuleBase { @@ -101,23 +103,18 @@ private void registerReceiver() { mConnectivityReceiver = new ConnectivityReceiver(); } - if (!mConnectivityReceiver.isRegistered()) { - try { - IntentFilter filter = new IntentFilter(); - filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); - mContext.getGlobalConfigs().getContext().registerReceiver(mConnectivityReceiver, filter); - mConnectivityReceiver.setRegistered(true); - } catch (Throwable e) { - LogUtils.d("NetInfoModule", "registerReceiver: " + e.getMessage()); - } + try { + Context context = mContext.getGlobalConfigs().getContext(); + mConnectivityReceiver.register(context); + } catch (Throwable e) { + LogUtils.d("NetInfoModule", "registerReceiver: " + e.getMessage()); } } private void unregisterReceiver() { try { - if (mConnectivityReceiver != null && mConnectivityReceiver.isRegistered()) { - mContext.getGlobalConfigs().getContext().unregisterReceiver(mConnectivityReceiver); - mConnectivityReceiver.setRegistered(false); + if (mConnectivityReceiver != null) { + mConnectivityReceiver.unregister(); mConnectivityReceiver = null; } } catch (Throwable e) { @@ -128,38 +125,57 @@ private void unregisterReceiver() { private class ConnectivityReceiver extends BroadcastReceiver { private final String EVENT_NAME = "networkStatusDidChange"; - private boolean isRegistered = false; + private boolean isRegistered; private String mCurrentConnectivity; - public boolean isRegistered() { - return isRegistered; + private Context context; + + void register(Context context) { + if (isRegistered) { + return; + } + + this.context = context; + if (context != null) { + IntentFilter filter = new IntentFilter(); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + context.registerReceiver(this, filter); + isRegistered = true; + } } - public void setRegistered(boolean registered) { - isRegistered = registered; + void unregister() { + if (!isRegistered) { + return; + } + if (context != null) { + context.unregisterReceiver(this); + context = null; + } } @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) { - mContext.getGlobalConfigs().getExecutorSupplierAdapter().getBackgroundTaskExecutor() - .execute(new Runnable() { - @Override - public void run() { - String currentConnectivity = getCurrentConnectionType(); - if (!currentConnectivity.equalsIgnoreCase(mCurrentConnectivity)) { - try { - mCurrentConnectivity = currentConnectivity; - HippyMap data = new HippyMap(); - data.pushString("network_info", mCurrentConnectivity); - mContext.getModuleManager().getJavaScriptModule(EventDispatcher.class) - .receiveNativeEvent(EVENT_NAME, data); - } catch (Throwable e) { - LogUtils.d("ConnectivityReceiver", "onReceive: " + e.getMessage()); - } - } + try { + mContext.getGlobalConfigs().getExecutorSupplierAdapter() + .getBackgroundTaskExecutor().execute(() -> { + String currentConnectivity = getCurrentConnectionType(); + if (!currentConnectivity.equalsIgnoreCase(mCurrentConnectivity)) { + try { + mCurrentConnectivity = currentConnectivity; + HippyMap data = new HippyMap(); + data.pushString("network_info", mCurrentConnectivity); + mContext.getModuleManager().getJavaScriptModule(EventDispatcher.class) + .receiveNativeEvent(EVENT_NAME, data); + } catch (Throwable e) { + LogUtils.d("NetInfoModule", "onReceive sendEvent: ", e); } - }); + } + }); + } catch (RejectedExecutionException e) { + LogUtils.d("NetInfoModule", "onReceive execute: ", e); + } } } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/network/NetworkModule.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/network/NetworkModule.java index 4b13ff43c5d..0a4df9d2779 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/network/NetworkModule.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/network/NetworkModule.java @@ -15,277 +15,45 @@ */ package com.tencent.mtt.hippy.modules.nativemodules.network; -import android.os.Build; -import android.text.TextUtils; -import android.webkit.CookieManager; -import android.webkit.CookieSyncManager; - import com.tencent.mtt.hippy.HippyEngineContext; -import com.tencent.mtt.hippy.HippyGlobalConfigs; import com.tencent.mtt.hippy.adapter.http.HippyHttpAdapter; -import com.tencent.mtt.hippy.adapter.http.HippyHttpRequest; -import com.tencent.mtt.hippy.adapter.http.HippyHttpResponse; -import com.tencent.mtt.hippy.adapter.http.HttpHeader; import com.tencent.mtt.hippy.annotation.HippyMethod; import com.tencent.mtt.hippy.annotation.HippyNativeModule; -import com.tencent.mtt.hippy.common.HippyArray; import com.tencent.mtt.hippy.common.HippyMap; import com.tencent.mtt.hippy.modules.Promise; import com.tencent.mtt.hippy.modules.nativemodules.HippyNativeModuleBase; -import com.tencent.mtt.hippy.utils.ContextHolder; -import com.tencent.mtt.hippy.utils.LogUtils; - -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.zip.GZIPInputStream; @SuppressWarnings({"deprecation", "unused"}) @HippyNativeModule(name = "network") public class NetworkModule extends HippyNativeModuleBase { - // 使用CookieManager之前,需要初始化CookieSyncManager,单例的 - static CookieSyncManager mCookieSyncManager; - - public NetworkModule(HippyEngineContext context) { - super(context); - } + private static final String TAG = "NetworkModule"; - private void hippyMapToRequestHeaders(HippyHttpRequest request, HippyMap map) { - if (request == null || map == null) { - return; + public NetworkModule(HippyEngineContext context) { + super(context); } - Set keys = map.keySet(); - for (String oneKey : keys) { - Object valueObj = map.get(oneKey); - if (valueObj instanceof HippyArray) { - HippyArray oneHeaderArray = (HippyArray) valueObj; - List headerValueArray = new ArrayList<>(); - for (int i = 0; i < oneHeaderArray.size(); i++) { - Object oneHeaderValue = oneHeaderArray.get(i); - if (oneHeaderValue instanceof Number) { - headerValueArray.add(oneHeaderValue + ""); - } else if (oneHeaderValue instanceof Boolean) { - headerValueArray.add(oneHeaderValue + ""); - } else if (oneHeaderValue instanceof String) { - headerValueArray.add((String) oneHeaderValue); - } else { - LogUtils.e("hippy_console", "Unsupported Request Header List Type"); - } + @HippyMethod(name = "fetch") + public void fetch(final HippyMap request, final Promise promise) { + HippyHttpAdapter adapter = mContext.getGlobalConfigs().getHttpAdapter(); + if (adapter != null) { + adapter.fetch(request, promise, mContext.getNativeParams()); } - - if (!headerValueArray.isEmpty()) { - request.addHeader(oneKey, headerValueArray); - } - } else { - LogUtils.e("hippy_console", - "Unsupported Request Header Type, Header Field Should All be an Array!!!"); - } - } - } - - @HippyMethod(name = "fetch") - public void fetch(final HippyMap request, final Promise promise) { - if (request == null) { - promise.reject("invalid request param"); - return; - } - - String url = request.getString("url"); - final String method = request.getString("method"); - if (TextUtils.isEmpty(url) || TextUtils.isEmpty(method)) { - promise.reject("no valid url for request"); - return; - } - - HippyHttpRequest httpRequest = new HippyHttpRequest(); - httpRequest.setConnectTimeout(10 * 1000); - httpRequest.setReadTimeout(10 * 1000); - String redirect = request.getString("redirect"); - httpRequest.setInstanceFollowRedirects( - !TextUtils.isEmpty(redirect) && TextUtils.equals("follow", redirect)); - httpRequest.setUseCaches(false); - httpRequest.setMethod(method); - httpRequest.setUrl(url); - HippyMap headers = request.getMap("headers"); - HippyArray requestCookies = null; - if (headers != null) { - requestCookies = headers.getArray("Cookie"); - hippyMapToRequestHeaders(httpRequest, headers); - } - String body = request.getString("body"); - httpRequest.setBody(body); - - HippyGlobalConfigs configs = mContext.getGlobalConfigs(); - HippyHttpAdapter adapter; - if (configs != null && configs.getHttpAdapter() != null) { - adapter = configs.getHttpAdapter(); - adapter.handleRequestCookie(url, requestCookies, httpRequest); - adapter.sendRequest(httpRequest, new HttpTaskCallbackImpl(promise)); - } - } - - @HippyMethod(name = "getCookie") - public void getCookie(String url, Promise promise) { - String cookie = getCookieManager().getCookie(url); - promise.resolve(cookie); - } - - @HippyMethod(name = "setCookie") - public void setCookie(String url, String keyValue, String expires) { - if (!TextUtils.isEmpty(url) && !TextUtils.isEmpty(keyValue)) { - if (!TextUtils.isEmpty(expires)) { - keyValue = keyValue + ";expires=" + expires; - getCookieManager().setCookie(url, keyValue); - syncCookie(); - } else { - saveCookie2Manager(url, keyValue); - } - } - } - - public static void saveCookie2Manager(String url, HippyArray cookies) { - if (cookies != null) { - CookieManager cookieManager = getCookieManager(); - for (int i = 0; i < cookies.size(); i++) { - String cookieStr = (String) cookies.get(i); - saveCookie2Manager(url, cookieStr); - } - } - } - - public static void saveCookie2Manager(String url, String cookies) { - if (cookies != null) { - cookies = cookies.replaceAll("\\s+", ""); - String[] cookieItems = cookies.split(";"); - CookieManager cookieManager = getCookieManager(); - for (String cookie : cookieItems) { - cookieManager.setCookie(url, cookie); - } - - syncCookie(); } - } - public static CookieManager getCookieManager() { - if (mCookieSyncManager == null) { - mCookieSyncManager = CookieSyncManager.createInstance(ContextHolder.getAppContext()); - - CookieManager cookieManager = CookieManager.getInstance(); - cookieManager.setAcceptCookie(true); - } - return CookieManager.getInstance(); - } - - private static void syncCookie() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - CookieManager.getInstance().flush(); - } else if (mCookieSyncManager != null) { - mCookieSyncManager.sync(); - } - } - - private static class HttpTaskCallbackImpl implements HippyHttpAdapter.HttpTaskCallback { - - private final Promise mPromise; - - public HttpTaskCallbackImpl(Promise promise) { - mPromise = promise; - } - - @SuppressWarnings("CharsetObjectCanBeUsed") - @Override - public void onTaskSuccess(HippyHttpRequest request, HippyHttpResponse response) - throws Exception { - String respBody = null; - if (response.getInputStream() != null) { - InputStream inputStream = response.getInputStream(); - if (isGzipRequest(request)) { - inputStream = new GZIPInputStream(inputStream); // gzip解压 - } - StringBuilder sb = new StringBuilder(); - String readLine; - BufferedReader bfReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); - while ((readLine = bfReader.readLine()) != null) { - sb.append(readLine).append("\r\n"); + @HippyMethod(name = "getCookie") + public void getCookie(String url, Promise promise) { + HippyHttpAdapter adapter = mContext.getGlobalConfigs().getHttpAdapter(); + if (adapter != null) { + adapter.getCookie(url, promise); } - respBody = sb.toString(); - } - - HippyMap respMap = new HippyMap(); - respMap.pushInt("statusCode", response.getStatusCode()); - respMap.pushString("statusLine", response.getResponseMessage()); - - HippyMap headerMap = new HippyMap(); - if (response.getRspHeaderMaps() != null && !response.getRspHeaderMaps().isEmpty()) { - Set keys = response.getRspHeaderMaps().keySet(); - for (String oneKey : keys) { - List value = response.getRspHeaderMaps().get(oneKey); - HippyArray oneHeaderFiled = new HippyArray(); - if (value != null && !value.isEmpty()) { - boolean hasSetCookie = false; - for (int i = 0; i < value.size(); i++) { - String valueStr = value.get(i); - oneHeaderFiled.pushString(valueStr); - if (HttpHeader.RSP.SET_COOKIE.equalsIgnoreCase(oneKey)) { - hasSetCookie = true; - getCookieManager().setCookie(request.getUrl(), valueStr); - } - } - if (hasSetCookie) { - syncCookie(); - } - } - - headerMap.pushArray(oneKey, oneHeaderFiled); - } - } - - respMap.pushMap("respHeaders", headerMap); - if (respBody == null) { - respBody = ""; - } - - respMap.pushString("respBody", respBody); - - mPromise.resolve(respMap); } - @Override - public void onTaskFailed(HippyHttpRequest request, Throwable error) { - if (error != null) { - mPromise.resolve(error.getMessage()); - } - } - } - - // 检查是否是gzip/deflate压缩的请求 - private static boolean isGzipRequest(HippyHttpRequest request) { - if (request == null) { - return false; - } - Map headers = request.getHeaders(); - if (headers != null) { - for (Map.Entry header : headers.entrySet()) { - String key = header.getKey(); - if (key != null && key.equalsIgnoreCase(HttpHeader.REQ.ACCEPT_ENCODING)) { - Object value = header.getValue(); - if (value instanceof ArrayList) { - //noinspection unchecked - for (String valueItem : (ArrayList) value) { - if (valueItem.equalsIgnoreCase("gzip") || valueItem.equalsIgnoreCase("deflate")) { - return true; - } - } - } + @HippyMethod(name = "setCookie") + public void setCookie(String url, String keyValue, String expires) { + HippyHttpAdapter adapter = mContext.getGlobalConfigs().getHttpAdapter(); + if (adapter != null) { + adapter.setCookie(url, keyValue, expires); } - } } - return false; - } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/network/WebSocketModule.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/network/WebSocketModule.java index 272789e459e..1f6cd1f75db 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/network/WebSocketModule.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/network/WebSocketModule.java @@ -28,6 +28,7 @@ import com.tencent.mtt.hippy.websocket.Header; import com.tencent.mtt.hippy.websocket.WebSocketClient; +import java.lang.ref.WeakReference; import java.net.URI; import java.util.ArrayList; import java.util.List; @@ -215,20 +216,21 @@ private static class HippyWebSocketListener implements WebSocketClient.WebSocket private final int mWebSocketId; - private final HippyEngineContext mContext; + private final WeakReference mContextRef; private final WebSocketModule mWebSocketModule; private boolean mDisconnected; public HippyWebSocketListener(int websocketID, HippyEngineContext context, WebSocketModule socketModule) { mWebSocketId = websocketID; - mContext = context; + mContextRef = new WeakReference<>(context); mWebSocketModule = socketModule; mDisconnected = false; } private void sendWebSocketEvent(String eventType, HippyMap data) { - if (mDisconnected) { + final HippyEngineContext context; + if (mDisconnected || (context = mContextRef.get()) == null) { return; } @@ -236,7 +238,7 @@ private void sendWebSocketEvent(String eventType, HippyMap data) { eventParams.pushInt(PARAM_KEY_SOCKET_ID, mWebSocketId); eventParams.pushString(PARAM_KEY_TYPE, eventType); eventParams.pushObject(PARAM_KEY_DATA, data); - mContext.getModuleManager().getJavaScriptModule(EventDispatcher.class) + context.getModuleManager().getJavaScriptModule(EventDispatcher.class) .receiveNativeEvent(WEB_SOCKET_EVENT_NAME, eventParams); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/performance/PerformanceModule.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/performance/PerformanceModule.java new file mode 100644 index 00000000000..3b24147b3ac --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/performance/PerformanceModule.java @@ -0,0 +1,75 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.modules.nativemodules.performance; + +import android.os.Handler; +import android.os.Looper; +import com.tencent.mtt.hippy.HippyEngineContext; +import com.tencent.mtt.hippy.HippyRootView; +import com.tencent.mtt.hippy.adapter.monitor.HippyEngineMonitorAdapter; +import com.tencent.mtt.hippy.annotation.HippyMethod; +import com.tencent.mtt.hippy.annotation.HippyNativeModule; +import com.tencent.mtt.hippy.common.HippyArray; +import com.tencent.mtt.hippy.common.HippyMap; +import com.tencent.mtt.hippy.modules.nativemodules.HippyNativeModuleBase; +import com.tencent.mtt.hippy.utils.TimeMonitor; +import java.util.Iterator; +import java.util.Map; + +@HippyNativeModule(name = "PerformanceModule") +public class PerformanceModule extends HippyNativeModuleBase { + + private final Handler mHandler; + + public PerformanceModule(HippyEngineContext context) { + super(context); + mHandler = new Handler(Looper.getMainLooper()); + } + + @HippyMethod(isSync = true) + public void mark(final String eventName, final long timeMillis) { + HippyRootView rootView = mContext.getInstance(); + TimeMonitor monitor = rootView.getTimeMonitor(); + if (monitor != null) { + monitor.addCustomPoint(eventName, timeMillis); + } + HippyEngineMonitorAdapter adapter = mContext.getGlobalConfigs().getEngineMonitorAdapter(); + if (adapter != null) { + mHandler.post(() -> adapter.reportCustomMonitorPoint(rootView, eventName, timeMillis)); + } + } + + @HippyMethod(isSync = true) + public HippyArray getEntries() { + HippyArray result = new HippyArray(); + HippyRootView rootView = mContext.getInstance(); + TimeMonitor monitor = rootView.getTimeMonitor(); + if (monitor == null) { + return result; + } + HippyMap entry = new HippyMap(); + entry.pushString("name", rootView.getName()); + entry.pushString("entryType", "navigation"); + Map points = monitor.getAllPoints(); + for (Map.Entry point : points.entrySet()) { + entry.pushLong(point.getKey(), point.getValue()); + } + result.pushMap(entry); + return result; + } + +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/uimanager/UIManagerModule.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/uimanager/UIManagerModule.java index 39177c637a0..238fb433c5f 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/uimanager/UIManagerModule.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/uimanager/UIManagerModule.java @@ -21,9 +21,16 @@ import com.tencent.mtt.hippy.annotation.HippyNativeModule; import com.tencent.mtt.hippy.common.HippyArray; import com.tencent.mtt.hippy.common.HippyMap; +import com.tencent.mtt.hippy.devsupport.inspector.model.DomModel; +import com.tencent.mtt.hippy.devsupport.inspector.model.DomModel.NodeType; import com.tencent.mtt.hippy.dom.DomManager; +import com.tencent.mtt.hippy.dom.node.DomDomainData; +import com.tencent.mtt.hippy.dom.node.DomNode; +import com.tencent.mtt.hippy.dom.node.DomNodeRecord; import com.tencent.mtt.hippy.modules.Promise; import com.tencent.mtt.hippy.modules.nativemodules.HippyNativeModuleBase; +import com.tencent.mtt.hippy.runtime.builtins.JSObject; +import com.tencent.mtt.hippy.uimanager.RenderNode; import com.tencent.mtt.hippy.utils.LogUtils; @SuppressWarnings({"deprecation", "unused"}) @@ -146,9 +153,25 @@ public void callUIFunction(HippyArray hippyArray, Promise promise) { public void measureInWindow(int id, Promise promise) { DomManager domManager = this.mContext.getDomManager(); if (domManager != null) { - domManager.measureInWindow(id, promise); + JSObject options = new JSObject(); + options.set(RenderNode.KEY_COMPATIBLE, true); + domManager.measureInWindow(id, options, promise); } - LogUtils.d("UIManagerModule", id + "" + promise); + LogUtils.d("UIManagerModule", "measureInWindow" + id + " " + promise); + } + + @HippyMethod(name = "getBoundingClientRect", useJSValueType = true) + public void getBoundingClientRect(int id, JSObject options, Promise promise) { + DomManager domManager = this.mContext.getDomManager(); + if (domManager == null) { + JSObject result = new JSObject(); + result.set(RenderNode.KEY_ERR_MSG, "DomManager is null"); + promise.resolve(result); + return; + } + domManager.measureInWindow(id, options, promise); + LogUtils.d("UIManagerModule", "getBoundingClientRect" + id + " " + options + " " + + promise); } @HippyMethod(name = "startBatch") @@ -167,4 +190,22 @@ public void endBatch() { } } + @HippyMethod(name = "getNodeForLocation") + public void getNodeForLocation(HippyMap hippyMap, final Promise promise) { + int x = ((Number)hippyMap.get(DomModel.NODE_LOCATION_X)).intValue(); + int y = ((Number)hippyMap.get(DomModel.NODE_LOCATION_Y)).intValue(); + RenderNode node = DomModel.getHitNodeForLocation(mContext, x, y); + DomManager domManager = mContext.getDomManager(); + if (domManager == null || node == null) { + promise.resolve(null); + return; + } + DomNode domNode = domManager.getNode(node.getId()); + if (domNode == null) { + promise.resolve(null); + return; + } + DomNodeRecord domainData = domNode.getDomNodeRecord(); + promise.resolve(DomModel.getNodeJson((DomDomainData) domainData, NodeType.ELEMENT_NODE).toString()); + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/runtime/builtins/JSDataView.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/runtime/builtins/JSDataView.java index 4edfa616c5b..4e266d70940 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/runtime/builtins/JSDataView.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/runtime/builtins/JSDataView.java @@ -32,17 +32,21 @@ public enum DataViewKind { UINT32_ARRAY, // kUint32Array FLOAT32_ARRAY, // kFloat32Array FLOAT64_ARRAY, // kFloat64Array + BIGINT64_ARRAY, // kBigInt64Array + BIGUINT64_ARRAY, // kBigUint64Arra DATA_VIEW // kDataView } private T bufferObject; private DataViewKind kind; + private final int flags; private final String BYTE_OFFSET = "byteOffset"; private final String BYTE_LENGTH = "byteLength"; - public JSDataView(T bufferObject, DataViewKind kind, int byteOffset, int byteLength) { + public JSDataView(T bufferObject, DataViewKind kind, int byteOffset, int byteLength, int flags) { this.bufferObject = bufferObject; this.kind = kind; + this.flags = flags; set(BYTE_OFFSET, byteOffset); set(BYTE_LENGTH, byteLength); } @@ -51,6 +55,10 @@ public DataViewKind getKind() { return kind; } + public int getFlags() { + return flags; + } + public T getBufferObject() { return bufferObject; } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/ArrayBufferViewTag.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/ArrayBufferViewTag.java index c3b76b09197..af403e7c8e0 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/ArrayBufferViewTag.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/ArrayBufferViewTag.java @@ -25,11 +25,12 @@ public enum ArrayBufferViewTag { UINT32_ARRAY('D'), // kUint32Array FLOAT32_ARRAY('f'), // kFloat32Array FLOAT64_ARRAY('F'), // kFloat64Array + BIGINT64_ARRAY('q'), // kBigInt64Array + BIGUINT64_ARRAY('Q'), // kBigUint64Array DATA_VIEW('?'); // kDataView private final byte tag; - @SuppressWarnings("unused") ArrayBufferViewTag(char tag) { this.tag = (byte) tag; } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/ErrorTag.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/ErrorTag.java index 756b4751c63..2c88f5e23d1 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/ErrorTag.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/ErrorTag.java @@ -18,7 +18,6 @@ /** * Error-related serialization tags. */ -@SuppressWarnings({"unused"}) public enum ErrorTag { EVAL_ERROR('E'), // kEvalErrorPrototype RANGE_ERROR('R'), // kRangeErrorPrototype diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/PrimitiveValueDeserializer.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/PrimitiveValueDeserializer.java index 0ee30dd73c7..f6155a00766 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/PrimitiveValueDeserializer.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/PrimitiveValueDeserializer.java @@ -57,7 +57,6 @@ public abstract class PrimitiveValueDeserializer extends SharedSerialization { protected PrimitiveValueDeserializer(BinaryReader reader, StringTable stringTable) { super(); - this.reader = reader; if (stringTable == null) { @@ -66,39 +65,7 @@ protected PrimitiveValueDeserializer(BinaryReader reader, StringTable stringTabl this.stringTable = stringTable; } - protected abstract Object readJSBoolean(boolean value); - - protected abstract Object readJSNumber(); - - protected abstract Object readJSBigInt(); - - protected abstract Object readJSString(StringLocation location, Object relatedKey); - - protected abstract Object readJSArrayBuffer(); - - protected abstract Object readJSRegExp(); - - protected abstract Object readJSObject(); - - protected abstract Object readJSMap(); - - protected abstract Object readJSSet(); - - protected abstract Object readDenseArray(); - - protected abstract Object readSparseArray(); - - protected abstract Object readJSError(); - - protected abstract Object readHostObject(); - - protected abstract Object readTransferredJSArrayBuffer(); - - protected abstract Object readSharedArrayBuffer(); - - protected abstract Object readTransferredWasmModule(); - - protected abstract Object readTransferredWasmMemory(); + protected abstract int getSupportedVersion(); /** * Set current binary reader @@ -142,7 +109,8 @@ public void reset() { public void readHeader() { if (readTag() == SerializationTag.VERSION) { version = (int) reader.getVarint(); - if (version > LATEST_VERSION) { + int supportedVersion = getSupportedVersion(); + if (supportedVersion > 0 && version > supportedVersion) { throw new UnsupportedOperationException( "Unable to deserialize cloned data due to invalid or unsupported version."); } @@ -159,113 +127,60 @@ public Object readValue() { } protected Object readValue(StringLocation location, Object relatedKey) { - SerializationTag tag = readTag(); + byte tag = readTag(); return readValue(tag, location, relatedKey); } - protected Object readValue(SerializationTag tag, StringLocation location, Object relatedKey) { + protected Object readValue(byte tag, StringLocation location, Object relatedKey) { switch (tag) { - case TRUE: + case SerializationTag.TRUE: return Boolean.TRUE; - case FALSE: + case SerializationTag.FALSE: return Boolean.FALSE; - case THE_HOLE: + case SerializationTag.THE_HOLE: return Hole; - case UNDEFINED: + case SerializationTag.UNDEFINED: return Undefined; - case NULL: + case SerializationTag.NULL: return Null; - case INT32: + case SerializationTag.INT32: return readZigZag(); - case UINT32: + case SerializationTag.UINT32: return reader.getVarint(); - case DOUBLE: + case SerializationTag.DOUBLE: return readDoubleWithRectification(); - case BIG_INT: + case SerializationTag.BIG_INT: return readBigInt(); - case ONE_BYTE_STRING: + case SerializationTag.ONE_BYTE_STRING: return readOneByteString(location, relatedKey); - case TWO_BYTE_STRING: + case SerializationTag.TWO_BYTE_STRING: return readTwoByteString(location, relatedKey); - case UTF8_STRING: + case SerializationTag.UTF8_STRING: return readUTF8String(location, relatedKey); - case DATE: + case SerializationTag.DATE: return readDate(); - case TRUE_OBJECT: - return readJSBoolean(true); - case FALSE_OBJECT: - return readJSBoolean(false); - case NUMBER_OBJECT: - return readJSNumber(); - case BIG_INT_OBJECT: - return readJSBigInt(); - case STRING_OBJECT: - return readJSString(location, relatedKey); - case REGEXP: - return readJSRegExp(); - case ARRAY_BUFFER: - return readJSArrayBuffer(); - case ARRAY_BUFFER_TRANSFER: - return readTransferredJSArrayBuffer(); - case SHARED_ARRAY_BUFFER: - return readSharedArrayBuffer(); - case BEGIN_JS_OBJECT: - return readJSObject(); - case BEGIN_JS_MAP: - return readJSMap(); - case BEGIN_JS_SET: - return readJSSet(); - case BEGIN_DENSE_JS_ARRAY: - return readDenseArray(); - case BEGIN_SPARSE_JS_ARRAY: - return readSparseArray(); - case OBJECT_REFERENCE: + case SerializationTag.OBJECT_REFERENCE: return readObjectReference(); - case WASM_MODULE_TRANSFER: - return readTransferredWasmModule(); - case HOST_OBJECT: - return readHostObject(); - case WASM_MEMORY_TRANSFER: - return readTransferredWasmMemory(); - case ERROR: - return readJSError(); - default: { - // Before there was an explicit tag for host objects, all unknown tags - // were delegated to the host. - if (version < 13) { - reader.position(-1); - return readHostObject(); - } - - // Unsupported Tag treated as Undefined - return Undefined; - } + default: + return Nothing; } } - protected SerializationTag readTag() { - SerializationTag tag; + protected byte readTag() { + byte tag; do { - tag = SerializationTag.fromTag(reader.getByte()); + tag = reader.getByte(); } while (tag == SerializationTag.PADDING); return tag; } - protected SerializationTag peekTag() { + protected byte peekTag() { if (reader.position() < reader.length()) { - SerializationTag tag = SerializationTag.fromTag(reader.getByte()); + byte tag = reader.getByte(); reader.position(-1); return tag; } - return null; - } - - protected ArrayBufferViewTag readArrayBufferViewTag() { - return ArrayBufferViewTag.fromTag(reader.getByte()); - } - - protected ErrorTag readErrorTag() { - return ErrorTag.fromTag(reader.getByte()); + return SerializationTag.VOID; } protected int readZigZag() { @@ -338,13 +253,13 @@ public ByteBuffer readBytes(int length) { } protected String readString(StringLocation location, Object relatedKey) { - SerializationTag tag = readTag(); + byte tag = readTag(); switch (tag) { - case ONE_BYTE_STRING: + case SerializationTag.ONE_BYTE_STRING: return readOneByteString(location, relatedKey); - case TWO_BYTE_STRING: + case SerializationTag.TWO_BYTE_STRING: return readTwoByteString(location, relatedKey); - case UTF8_STRING: + case SerializationTag.UTF8_STRING: return readUTF8String(location, relatedKey); default: throw new UnreachableCodeException(); @@ -421,8 +336,8 @@ protected T assignId(T object) { } /** - * Reads the underlying wire format version. Likely mostly to be useful to legacy code reading old - * wire format versions. + * Reads the underlying wire format version. + * Likely mostly to be useful to legacy code reading old wire format versions. * * @return wire format version */ diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/PrimitiveValueSerializer.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/PrimitiveValueSerializer.java index a461c4762d9..e6f6a550bf4 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/PrimitiveValueSerializer.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/PrimitiveValueSerializer.java @@ -21,6 +21,7 @@ import com.tencent.mtt.hippy.serialization.nio.writer.BinaryWriter; import java.math.BigInteger; +import java.util.Date; import java.util.IdentityHashMap; import java.util.Map; @@ -34,6 +35,10 @@ public abstract class PrimitiveValueSerializer extends SharedSerialization { * Writer used for write buffer. */ protected BinaryWriter writer; + /** + * The version of the data used for the serialization. + */ + private final int version; /** * ID of the next serialized object. **/ @@ -50,15 +55,24 @@ public abstract class PrimitiveValueSerializer extends SharedSerialization { * Unsigned int max value. */ private static final long MAX_UINT32_VALUE = 4294967295L; + /** + * Unsigned int min value. + */ + private static final long MIN_UINT32_VALUE = 0L; /** * Small string max length, used for SSO(Short / Small String Optimization). */ private static final int SSO_SMALL_STRING_MAX_LENGTH = 32; + /** + * ISO-8859-1(Latin1) max char + */ + private static final char ISO_8859_1_MAX_CHAR = 0xff; - protected PrimitiveValueSerializer(BinaryWriter writer) { + protected PrimitiveValueSerializer(BinaryWriter writer, int version) { super(); this.writer = writer; + this.version = version; } /** @@ -93,19 +107,11 @@ public void reset() { */ public void writeHeader() { writeTag(SerializationTag.VERSION); - writer.putVarint(LATEST_VERSION); - } - - protected void writeTag(SerializationTag tag) { - writer.putByte(tag.getTag()); + writer.putVarint(version); } - protected void writeTag(ArrayBufferViewTag tag) { - writer.putByte(tag.getTag()); - } - - protected void writeTag(ErrorTag tag) { - writer.putByte(tag.getTag()); + protected void writeTag(byte tag) { + writer.putByte(tag); } /** @@ -122,7 +128,7 @@ public boolean writeValue(Object value) { writeInt32((int) value); } else if (value instanceof Long) { long longValue = (long) value; - if (longValue <= MAX_UINT32_VALUE) { + if (longValue <= MAX_UINT32_VALUE && longValue >= MIN_UINT32_VALUE) { writeTag(SerializationTag.UINT32); writer.putVarint(longValue); } else { @@ -151,6 +157,9 @@ public boolean writeValue(Object value) { if (id != null) { writeTag(SerializationTag.OBJECT_REFERENCE); writer.putVarint(id); + } else if (value instanceof Date) { + assignId(value); + writeDate((Date) value); } else { return false; } @@ -304,11 +313,11 @@ protected void writeString(@NonNull String value) { // Designed to take advantage of // https://wiki.openjdk.java.net/display/HotSpot/RangeCheckElimination if (length > SSO_SMALL_STRING_MAX_LENGTH) { - for (char c; i < length && (c = stringWriteBuffer[i]) < 0x80; i++) { + for (char c; i < length && (c = stringWriteBuffer[i]) <= ISO_8859_1_MAX_CHAR; i++) { writer.putByte((byte) c); } } else { - for (char c; i < length && (c = value.charAt(i)) < 0x80; i++) { + for (char c; i < length && (c = value.charAt(i)) <= ISO_8859_1_MAX_CHAR; i++) { writer.putByte((byte) c); } } @@ -320,7 +329,7 @@ protected void writeString(@NonNull String value) { // region two byte string, universal path writeTag(SerializationTag.TWO_BYTE_STRING); - writer.putVarint(length * 2); + writer.putVarint(length * 2L); if (length > SSO_SMALL_STRING_MAX_LENGTH) { for (i = 0; i < length; i++) { char c = stringWriteBuffer[i]; @@ -361,7 +370,21 @@ protected void writeBigIntContents(@NonNull BigInteger bigInteger) { } } + private void writeDate(@NonNull Date date) { + writeTag(SerializationTag.DATE); + writer.putDouble(date.getTime()); + } + protected void assignId(Object object) { objectMap.put(object, nextId++); } + + /** + * Get the version of the data used for the serialization. + * + * @return version + */ + public int getVersion() { + return version; + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/SerializationTag.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/SerializationTag.java index e7b77933d97..1338d22c10b 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/SerializationTag.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/SerializationTag.java @@ -18,65 +18,47 @@ /** * A tag that determines the type of the serialized value. */ -@SuppressWarnings({"unused"}) -public enum SerializationTag { - VERSION((char) 0xFF), - TRUE('T'), // kTrue - FALSE('F'), // kFalse - UNDEFINED('_'), // kUndefined - NULL('0'), // kNull - INT32('I'), // kInt32 - UINT32('U'), // kUint32 - DOUBLE('N'), // kDouble - BIG_INT('Z'), // kBigInt - UTF8_STRING('S'), // kUtf8String - ONE_BYTE_STRING('"'), // kOneByteString - TWO_BYTE_STRING('c'), // kTwoByteString - PADDING('\0'), // kPadding - DATE('D'), // kDate - TRUE_OBJECT('y'), // kTrueObject - FALSE_OBJECT('x'), // kFalseObject - NUMBER_OBJECT('n'), // kNumberObject - BIG_INT_OBJECT('z'), // kBigIntObject - STRING_OBJECT('s'), // kStringObject - REGEXP('R'), // kRegExp - ARRAY_BUFFER('B'), // kArrayBuffer - SHARED_ARRAY_BUFFER('u'), // kSharedArrayBuffer - ARRAY_BUFFER_TRANSFER('t'), // kArrayBufferTransfer - ARRAY_BUFFER_VIEW('V'), // kArrayBufferView - BEGIN_JS_MAP(';'), // kBeginJSMap - END_JS_MAP(':'), // kEndJSMap - BEGIN_JS_SET('\''), // kBeginJSSet - END_JS_SET(','), // kEndJSSet - BEGIN_JS_OBJECT('o'), // kBeginJSObject - END_JS_OBJECT('{'), // kEndJSObject - BEGIN_SPARSE_JS_ARRAY('a'), // kBeginSparseJSArray - END_SPARSE_JS_ARRAY('@'), // kEndSparseJSArray - BEGIN_DENSE_JS_ARRAY('A'), // kBeginDenseJSArray - END_DENSE_JS_ARRAY('$'), // kEndDenseJSArray - THE_HOLE('-'), // kTheHole - OBJECT_REFERENCE('^'), // kObjectReference - WASM_MODULE_TRANSFER('w'), // kWasmModuleTransfer - HOST_OBJECT('\\'), // kHostObject - WASM_MEMORY_TRANSFER('m'), // kWasmMemoryTransfer - ERROR('r'); // kError - - private final byte tag; - - SerializationTag(char tag) { - this.tag = (byte) tag; - } - - public byte getTag() { - return tag; - } - - public static SerializationTag fromTag(byte tag) { - for (SerializationTag t : values()) { - if (t.tag == tag) { - return t; - } - } - return null; - } +public interface SerializationTag { + byte VOID = 0; + byte VERSION = (byte) 0xFF; + byte TRUE = (byte) 'T'; // kTrue + byte FALSE = (byte) 'F'; // kFalse + byte UNDEFINED = (byte) '_'; // kUndefined + byte NULL = (byte) '0'; // kNull + byte INT32 = (byte) 'I'; // kInt32 + byte UINT32 = (byte) 'U'; // kUint32 + byte DOUBLE = (byte) 'N'; // kDouble + byte BIG_INT = (byte) 'Z'; // kBigInt + byte UTF8_STRING = (byte) 'S'; // kUtf8String + byte ONE_BYTE_STRING = (byte) '"'; // kOneByteString + byte TWO_BYTE_STRING = (byte) 'c'; // kTwoByteString + byte PADDING = (byte) '\0'; // kPadding + byte DATE = (byte) 'D'; // kDate + byte TRUE_OBJECT = (byte) 'y'; // kTrueObject + byte FALSE_OBJECT = (byte) 'x'; // kFalseObject + byte NUMBER_OBJECT = (byte) 'n'; // kNumberObject + byte BIG_INT_OBJECT = (byte) 'z'; // kBigIntObject + byte STRING_OBJECT = (byte) 's'; // kStringObject + byte REGEXP = (byte) 'R'; // kRegExp + byte ARRAY_BUFFER = (byte) 'B'; // kArrayBuffer + byte SHARED_ARRAY_BUFFER = (byte) 'u'; // kSharedArrayBuffer + byte ARRAY_BUFFER_TRANSFER = (byte) 't'; // kArrayBufferTransfer + byte ARRAY_BUFFER_VIEW = (byte) 'V'; // kArrayBufferView + byte BEGIN_JS_MAP = (byte) ';'; // kBeginJSMap + byte END_JS_MAP = (byte) ':'; // kEndJSMap + byte BEGIN_JS_SET = (byte) '\''; // kBeginJSSet + byte END_JS_SET = (byte) ','; // kEndJSSet + byte BEGIN_JS_OBJECT = (byte) 'o'; // kBeginJSObject + byte END_JS_OBJECT = (byte) '{'; // kEndJSObject + byte BEGIN_SPARSE_JS_ARRAY = (byte) 'a'; // kBeginSparseJSArray + byte END_SPARSE_JS_ARRAY = (byte) '@'; // kEndSparseJSArray + byte BEGIN_DENSE_JS_ARRAY = (byte) 'A'; // kBeginDenseJSArray + byte END_DENSE_JS_ARRAY = (byte) '$'; // kEndDenseJSArray + byte SHARED_OBJECT = (byte) 'p'; // kSharedObject + byte THE_HOLE = (byte) '-'; // kTheHole + byte OBJECT_REFERENCE = (byte) '^'; // kObjectReference + byte WASM_MODULE_TRANSFER = (byte) 'w'; // kWasmModuleTransfer + byte HOST_OBJECT = (byte) '\\'; // kHostObject + byte WASM_MEMORY_TRANSFER = (byte) 'm'; // kWasmMemoryTransfer + byte ERROR = (byte) 'r'; // kError } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/SharedSerialization.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/SharedSerialization.java index 10644975f39..443471742b7 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/SharedSerialization.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/SharedSerialization.java @@ -20,12 +20,10 @@ */ @SuppressWarnings({"unused"}) public abstract class SharedSerialization { - - static protected final byte LATEST_VERSION = (byte) 13; // kLatestVersion - protected final Object Null; protected final Object Undefined; protected final Object Hole; + protected static final Object Nothing = new Object(); SharedSerialization() { Null = getNull(); diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/compatible/Deserializer.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/compatible/Deserializer.java index 8f8dc8cd2c8..0cb402d664b 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/compatible/Deserializer.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/compatible/Deserializer.java @@ -36,6 +36,11 @@ @SuppressWarnings({"unused", "deprecation"}) public class Deserializer extends PrimitiveValueDeserializer { + /** + * Version 13 compatibility mode + */ + private boolean version13BrokenDataMode = false; + public Deserializer(BinaryReader reader) { this(reader, null); } @@ -44,6 +49,11 @@ public Deserializer(BinaryReader reader, StringTable stringTable) { super(reader, stringTable); } + @Override + protected int getSupportedVersion() { + return 15; + } + @Override protected Object getUndefined() { return ConstantValue.Undefined; @@ -60,27 +70,120 @@ protected Object getHole() { } @Override - protected Boolean readJSBoolean(boolean value) { - return assignId(value); + @SuppressWarnings("fallthrough") + protected Object readValue(byte tag, StringLocation location, Object relatedKey) { + Object object = super.readValue(tag, location, relatedKey); + if (object != Nothing) { + return object; + } + + switch (tag) { + case SerializationTag.TRUE_OBJECT: + return readJSBoolean(true); + case SerializationTag.FALSE_OBJECT: + return readJSBoolean(false); + case SerializationTag.NUMBER_OBJECT: + return readJSNumber(); + case SerializationTag.BIG_INT_OBJECT: + return readJSBigInt(); + case SerializationTag.STRING_OBJECT: + return readJSString(location, relatedKey); + case SerializationTag.REGEXP: + return readJSRegExp(); + case SerializationTag.ARRAY_BUFFER: + return readJSArrayBuffer(); + case SerializationTag.ARRAY_BUFFER_TRANSFER: + return readTransferredJSArrayBuffer(); + case SerializationTag.SHARED_ARRAY_BUFFER: + return readSharedArrayBuffer(); + case SerializationTag.BEGIN_JS_OBJECT: + return readJSObject(); + case SerializationTag.BEGIN_JS_MAP: + return readJSMap(); + case SerializationTag.BEGIN_JS_SET: + return readJSSet(); + case SerializationTag.BEGIN_DENSE_JS_ARRAY: + return readDenseArray(); + case SerializationTag.BEGIN_SPARSE_JS_ARRAY: + return readSparseArray(); + case SerializationTag.WASM_MODULE_TRANSFER: + return readTransferredWasmModule(); + case SerializationTag.HOST_OBJECT: + return readHostObject(); + case SerializationTag.WASM_MEMORY_TRANSFER: + return readTransferredWasmMemory(); + case SerializationTag.ERROR: + return readJSError(); + case SerializationTag.SHARED_OBJECT: { + if (getWireFormatVersion() >= 15) { + return readSharedObject(); + } + // If the data doesn't support shared values because it is from an older + // version, treat the tag as unknown. + // [[fallthrough]] + } + default: { + // Before there was an explicit tag for host objects, all unknown tags + // were delegated to the host. + if (getWireFormatVersion() < 13) { + reader.position(-1); + return readHostObject(); + } + + // Unsupported Tag treated as Undefined + return Undefined; + } + } } + /** + * Deserializes a JavaScript delegate object from the buffer. + * + * @return JavaScript delegate object + */ @Override - protected Number readJSNumber() { + public Object readValue() { + /* + * The `V8` engine has a bug which produced invalid version 13 data (see https://crbug.com/1284506). + * + * This compatibility mode tries to first read the data normally, + * and if it fails, and the version is 13, tries to read the broken format. + */ + int originalPosition = reader.position(); + try { + return super.readValue(); + } catch (Exception e) { + if (getWireFormatVersion() == 13) { + reader.position(originalPosition); + version13BrokenDataMode = true; + return super.readValue(); + } else { + throw e; + } + } + } + + private ErrorTag readErrorTag() { + return ErrorTag.fromTag((byte) reader.getVarint()); + } + + private Boolean readJSBoolean(boolean value) { + return assignId(value); + } + + private Number readJSNumber() { return assignId(reader.getDouble()); } - @Override - protected BigInteger readJSBigInt() { + private BigInteger readJSBigInt() { return assignId(readBigInt()); } - @Override - protected String readJSString(StringLocation location, Object relatedKey) { + private String readJSString(StringLocation location, Object relatedKey) { return assignId(readString(location, relatedKey)); } - @Override - protected Object readJSArrayBuffer() { + private Object readJSArrayBuffer() { int byteLength = (int) reader.getVarint(); if (byteLength < 0) { throw new DataCloneOutOfRangeException(byteLength); @@ -95,15 +198,13 @@ protected Object readJSArrayBuffer() { return null; } - @Override - protected Object readJSRegExp() { + private Object readJSRegExp() { readString(StringLocation.VOID, null); reader.getVarint(); return assignId(Undefined); } - @Override - protected HippyMap readJSObject() { + private HippyMap readJSObject() { HippyMap object = new HippyMap(); assignId(object); int read = readJSProperties(object, SerializationTag.END_JS_OBJECT); @@ -114,7 +215,7 @@ protected HippyMap readJSObject() { return object; } - private int readJSProperties(HippyMap object, SerializationTag endTag) { + private int readJSProperties(HippyMap object, byte endTag) { final StringLocation keyLocation, valueLocation; if (endTag == SerializationTag.END_DENSE_JS_ARRAY) { keyLocation = StringLocation.DENSE_ARRAY_KEY; @@ -126,7 +227,7 @@ private int readJSProperties(HippyMap object, SerializationTag endTag) { throw new UnreachableCodeException(); } - SerializationTag tag; + byte tag; int count = 0; while ((tag = readTag()) != endTag) { count++; @@ -150,11 +251,10 @@ private int readJSProperties(HippyMap object, SerializationTag endTag) { return count; } - @Override - protected HippyMap readJSMap() { + private HippyMap readJSMap() { HippyMap object = new HippyMap(); assignId(object); - SerializationTag tag; + byte tag; int read = 0; while ((tag = readTag()) != SerializationTag.END_JS_MAP) { read++; @@ -176,11 +276,10 @@ protected HippyMap readJSMap() { return object; } - @Override - protected HippyArray readJSSet() { + private HippyArray readJSSet() { HippyArray array = new HippyArray(); assignId(array); - SerializationTag tag; + byte tag; int read = 0; while ((tag = readTag()) != SerializationTag.END_JS_SET) { read++; @@ -194,8 +293,7 @@ protected HippyArray readJSSet() { return array; } - @Override - protected HippyArray readDenseArray() { + private HippyArray readDenseArray() { int length = (int) reader.getVarint(); if (length < 0) { throw new DataCloneOutOfRangeException(length); @@ -203,7 +301,7 @@ protected HippyArray readDenseArray() { HippyArray array = new HippyArray(); assignId(array); for (int i = 0; i < length; i++) { - SerializationTag tag = readTag(); + byte tag = readTag(); if (tag != SerializationTag.THE_HOLE) { array.pushObject(readValue(tag, StringLocation.DENSE_ARRAY_ITEM, i)); } @@ -232,13 +330,12 @@ protected HippyArray readDenseArray() { * * @return array */ - @Override - protected HippyArray readSparseArray() { + private HippyArray readSparseArray() { long length = reader.getVarint(); HippyArray array = new HippyArray(); assignId(array); - SerializationTag tag; + byte tag; int read = 0; while ((tag = readTag()) != SerializationTag.END_SPARSE_JS_ARRAY) { read++; @@ -282,19 +379,21 @@ protected HippyArray readSparseArray() { } private void readJSArrayBufferView() { - SerializationTag arrayBufferViewTag = readTag(); + byte arrayBufferViewTag = readTag(); if (arrayBufferViewTag != SerializationTag.ARRAY_BUFFER_VIEW) { throw new AssertionError("ArrayBufferViewTag: " + arrayBufferViewTag); } reader.getVarint(); reader.getVarint(); - readArrayBufferViewTag(); + if (getWireFormatVersion() >= 14 || version13BrokenDataMode) { + reader.getVarint(); + } + reader.getVarint(); assignId(Undefined); } - @Override - protected HippyMap readJSError() { + private HippyMap readJSError() { String message = null; String stack = null; String errorType = null; @@ -346,13 +445,16 @@ protected HippyMap readJSError() { return error; } - @Override - protected Object readHostObject() { + private Object readHostObject() { return assignId(Undefined); } - @Override - public Object readTransferredJSArrayBuffer() { + private Object readSharedObject() { + reader.getVarint(); + return assignId(Undefined); + } + + private Object readTransferredJSArrayBuffer() { reader.getVarint(); assignId(Undefined); if (peekTag() == SerializationTag.ARRAY_BUFFER_VIEW) { @@ -361,8 +463,7 @@ public Object readTransferredJSArrayBuffer() { return null; } - @Override - public Object readSharedArrayBuffer() { + private Object readSharedArrayBuffer() { reader.getVarint(); assignId(Undefined); if (peekTag() == SerializationTag.ARRAY_BUFFER_VIEW) { @@ -371,15 +472,13 @@ public Object readSharedArrayBuffer() { return null; } - @Override - protected Object readTransferredWasmModule() { + private Object readTransferredWasmModule() { reader.getVarint(); assignId(Undefined); return null; } - @Override - protected Object readTransferredWasmMemory() { + private Object readTransferredWasmMemory() { reader.getVarint(); readSharedArrayBuffer(); assignId(Undefined); diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/compatible/Serializer.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/compatible/Serializer.java index 5f79f8e408b..c6f0b28691f 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/compatible/Serializer.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/compatible/Serializer.java @@ -33,11 +33,11 @@ public class Serializer extends PrimitiveValueSerializer { public Serializer() { - super(null); + super(null, 13); } - public Serializer(BinaryWriter writer) { - super(writer); + public Serializer(BinaryWriter writer, int version) { + super(writer, version); } @Override diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/nio/writer/SafeHeapWriter.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/nio/writer/SafeHeapWriter.java index ebd4f5fb078..3809dc6e7bd 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/nio/writer/SafeHeapWriter.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/nio/writer/SafeHeapWriter.java @@ -141,7 +141,7 @@ public int length(int length) { } @Override - public final ByteBuffer chunked() { + public ByteBuffer chunked() { ByteBuffer chunked = ByteBuffer.wrap(value, 0, count); reset(); return chunked; diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/recommend/Deserializer.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/recommend/Deserializer.java index b08e2c24333..48ee70a4ed8 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/recommend/Deserializer.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/recommend/Deserializer.java @@ -17,6 +17,7 @@ import androidx.annotation.NonNull; +import com.tencent.mtt.hippy.serialization.ArrayBufferViewTag; import com.tencent.mtt.hippy.serialization.exception.DataCloneOutOfRangeException; import com.tencent.mtt.hippy.serialization.exception.DataCloneOutOfValueException; import com.tencent.mtt.hippy.exception.UnexpectedException; @@ -83,13 +84,22 @@ public interface Delegate { /** * Get a {@link WasmModule} given a transfer_id previously provided by - * erializer.Delegate#getWasmModuleTransferId + * Serializer.Delegate#getWasmModuleTransferId * * @param deserializer current deserializer * @param transfer_id transfer id * @return WebAssembly Module */ WasmModule getWasmModuleFromId(Deserializer deserializer, int transfer_id); + + /** + * Get the SharedValueConveyor previously provided by + * Serializer.Delegate#adoptSharedValueConveyor + * + * @param deserializer current deserializer + * @return SharedValueConveyor + */ + SharedValueConveyor getSharedValueConveyor(Deserializer deserializer); } /** @@ -100,6 +110,14 @@ public interface Delegate { * Maps transfer ID to the transferred {@link JSArrayBuffer}s. */ private Map arrayBufferTransferMap; + /** + * Version 13 compatibility mode + */ + private boolean version13BrokenDataMode = false; + /** + * Shared value conveyors to keep JS shared values alive in transit when serialized. + */ + private SharedValueConveyor sharedObjectConveyor; public Deserializer(BinaryReader reader) { this(reader, null, null); @@ -118,6 +136,11 @@ public Deserializer(BinaryReader reader, StringTable stringTable, Delegate deleg this.delegate = delegate; } + @Override + public int getSupportedVersion() { + return 15; + } + @Override protected Object getHole() { return JSOddball.Hole; @@ -134,30 +157,127 @@ protected Object getNull() { } @Override - protected Object readJSBoolean(boolean value) { - return assignId(value ? JSBooleanObject.True : JSBooleanObject.False); + @SuppressWarnings("fallthrough") + protected Object readValue(byte tag, StringLocation location, Object relatedKey) { + Object object = super.readValue(tag, location, relatedKey); + if (object != Nothing) { + return object; + } + + switch (tag) { + case SerializationTag.TRUE_OBJECT: + return readJSBoolean(true); + case SerializationTag.FALSE_OBJECT: + return readJSBoolean(false); + case SerializationTag.NUMBER_OBJECT: + return readJSNumber(); + case SerializationTag.BIG_INT_OBJECT: + return readJSBigInt(); + case SerializationTag.STRING_OBJECT: + return readJSString(location, relatedKey); + case SerializationTag.REGEXP: + return readJSRegExp(); + case SerializationTag.ARRAY_BUFFER: + return readJSArrayBuffer(); + case SerializationTag.ARRAY_BUFFER_TRANSFER: + return readTransferredJSArrayBuffer(); + case SerializationTag.SHARED_ARRAY_BUFFER: + return readSharedArrayBuffer(); + case SerializationTag.BEGIN_JS_OBJECT: + return readJSObject(); + case SerializationTag.BEGIN_JS_MAP: + return readJSMap(); + case SerializationTag.BEGIN_JS_SET: + return readJSSet(); + case SerializationTag.BEGIN_DENSE_JS_ARRAY: + return readDenseArray(); + case SerializationTag.BEGIN_SPARSE_JS_ARRAY: + return readSparseArray(); + case SerializationTag.WASM_MODULE_TRANSFER: + return readTransferredWasmModule(); + case SerializationTag.HOST_OBJECT: + return readHostObject(); + case SerializationTag.WASM_MEMORY_TRANSFER: + return readTransferredWasmMemory(); + case SerializationTag.ERROR: + return readJSError(); + case SerializationTag.SHARED_OBJECT: { + if (getWireFormatVersion() >= 15) { + return readSharedObject(); + } + // If the data doesn't support shared values because it is from an older + // version, treat the tag as unknown. + // [[fallthrough]] + } + default: { + // Before there was an explicit tag for host objects, all unknown tags + // were delegated to the host. + if (getWireFormatVersion() < 13) { + reader.position(-1); + return readHostObject(); + } + + // Unsupported Tag treated as Undefined + return Undefined; + } + } } + /** + * Deserializes a JavaScript delegate object from the buffer. + * + * @return JavaScript delegate object + */ @Override - protected JSNumberObject readJSNumber() { + public Object readValue() { + /* + * The `V8` engine has a bug which produced invalid version 13 data (see https://crbug.com/1284506). + * + * This compatibility mode tries to first read the data normally, + * and if it fails, and the version is 13, tries to read the broken format. + */ + int originalPosition = reader.position(); + try { + return super.readValue(); + } catch (Exception e) { + if (getWireFormatVersion() == 13) { + reader.position(originalPosition); + version13BrokenDataMode = true; + return super.readValue(); + } else { + throw e; + } + } + } + + private ArrayBufferViewTag readArrayBufferViewTag() { + return ArrayBufferViewTag.fromTag((byte) reader.getVarint()); + } + + private ErrorTag readErrorTag() { + return ErrorTag.fromTag((byte) reader.getVarint()); + } + + private Object readJSBoolean(boolean value) { + return assignId(value ? JSBooleanObject.True : JSBooleanObject.False); + } + + private JSNumberObject readJSNumber() { double value = reader.getDouble(); return assignId(new JSNumberObject(value)); } - @Override - protected JSBigintObject readJSBigInt() { + private JSBigintObject readJSBigInt() { BigInteger value = readBigInt(); return assignId(new JSBigintObject(value)); } - @Override - protected JSStringObject readJSString(StringLocation location, Object relatedKey) { + private JSStringObject readJSString(StringLocation location, Object relatedKey) { String value = readString(location, relatedKey); return assignId(new JSStringObject(value)); } - @Override - protected Object readJSArrayBuffer() { + private Object readJSArrayBuffer() { int byteLength = (int) reader.getVarint(); if (byteLength < 0) { throw new DataCloneOutOfRangeException(byteLength); @@ -170,8 +290,7 @@ protected Object readJSArrayBuffer() { arrayBufferObject) : arrayBuffer; } - @Override - protected JSRegExp readJSRegExp() { + private JSRegExp readJSRegExp() { String pattern = readString(StringLocation.REGEXP, null); int flags = (int) reader.getVarint(); if (flags < 0) { @@ -180,8 +299,7 @@ protected JSRegExp readJSRegExp() { return assignId(new JSRegExp(pattern, flags)); } - @Override - protected JSObject readJSObject() { + private JSObject readJSObject() { JSObject object = new JSObject(); assignId(object); int read = readJSProperties(object, SerializationTag.END_JS_OBJECT); @@ -192,20 +310,20 @@ protected JSObject readJSObject() { return object; } - private int readJSProperties(@NonNull JSObject object, SerializationTag endTag) { + private int readJSProperties(@NonNull JSObject object, byte endTag) { final StringLocation keyLocation, valueLocation; switch (endTag) { - case END_DENSE_JS_ARRAY: { + case SerializationTag.END_DENSE_JS_ARRAY: { keyLocation = StringLocation.DENSE_ARRAY_KEY; valueLocation = StringLocation.DENSE_ARRAY_ITEM; break; } - case END_SPARSE_JS_ARRAY: { + case SerializationTag.END_SPARSE_JS_ARRAY: { keyLocation = StringLocation.SPARSE_ARRAY_KEY; valueLocation = StringLocation.SPARSE_ARRAY_ITEM; break; } - case END_JS_OBJECT: { + case SerializationTag.END_JS_OBJECT: { keyLocation = StringLocation.OBJECT_KEY; valueLocation = StringLocation.OBJECT_VALUE; break; @@ -215,7 +333,7 @@ private int readJSProperties(@NonNull JSObject object, SerializationTag endTag) } } - SerializationTag tag; + byte tag; int count = 0; while ((tag = readTag()) != endTag) { count++; @@ -237,11 +355,10 @@ private int readJSProperties(@NonNull JSObject object, SerializationTag endTag) return count; } - @Override - protected JSMap readJSMap() { + private JSMap readJSMap() { JSMap map = new JSMap(); assignId(map); - SerializationTag tag; + byte tag; int read = 0; HashMap internalMap = map.getInternalMap(); while ((tag = readTag()) != SerializationTag.END_JS_MAP) { @@ -257,11 +374,10 @@ protected JSMap readJSMap() { return map; } - @Override - protected JSSet readJSSet() { + private JSSet readJSSet() { JSSet set = new JSSet(); assignId(set); - SerializationTag tag; + byte tag; int read = 0; HashSet internalSet = set.getInternalSet(); while ((tag = readTag()) != SerializationTag.END_JS_SET) { @@ -276,8 +392,7 @@ protected JSSet readJSSet() { return set; } - @Override - protected JSDenseArray readDenseArray() { + private JSDenseArray readDenseArray() { int length = (int) reader.getVarint(); if (length < 0) { throw new DataCloneOutOfRangeException(length); @@ -285,7 +400,7 @@ protected JSDenseArray readDenseArray() { JSDenseArray array = new JSDenseArray(length); assignId(array); for (int i = 0; i < length; i++) { - SerializationTag tag = readTag(); + byte tag = readTag(); array.push(readValue(tag, StringLocation.DENSE_ARRAY_ITEM, i)); } @@ -301,8 +416,7 @@ protected JSDenseArray readDenseArray() { return array; } - @Override - protected JSSparseArray readSparseArray() { + private JSSparseArray readSparseArray() { long length = reader.getVarint(); JSSparseArray array = new JSSparseArray(); assignId(array); @@ -319,24 +433,29 @@ protected JSSparseArray readSparseArray() { } private JSDataView readJSArrayBufferView(JSArrayBuffer arrayBuffer) { - SerializationTag arrayBufferViewTag = readTag(); + byte arrayBufferViewTag = readTag(); if (arrayBufferViewTag != SerializationTag.ARRAY_BUFFER_VIEW) { throw new AssertionError("ArrayBufferViewTag: " + arrayBufferViewTag); } - int offset = (int) reader.getVarint(); - if (offset < 0) { - throw new DataCloneOutOfValueException(offset); - } - int byteLength = (int) reader.getVarint(); - if (byteLength < 0) { - throw new DataCloneOutOfValueException(byteLength); - } + + ArrayBufferViewTag tag = readArrayBufferViewTag(); JSDataView.DataViewKind kind; - switch (readArrayBufferViewTag()) { + if (tag == null) { + tag = ArrayBufferViewTag.INT8_ARRAY; + } + switch (tag) { case DATA_VIEW: { kind = JSDataView.DataViewKind.DATA_VIEW; break; } + case BIGUINT64_ARRAY: { + kind = JSDataView.DataViewKind.BIGUINT64_ARRAY; + break; + } + case BIGINT64_ARRAY: { + kind = JSDataView.DataViewKind.BIGINT64_ARRAY; + break; + } case FLOAT32_ARRAY: { kind = JSDataView.DataViewKind.FLOAT32_ARRAY; break; @@ -377,13 +496,30 @@ private JSDataView readJSArrayBufferView(JSArrayBuffer arrayBuffe throw new UnreachableCodeException(); } } - JSDataView view = new JSDataView<>(arrayBuffer, kind, offset, byteLength); + + int offset = (int) reader.getVarint(); + if (offset < 0) { + throw new DataCloneOutOfValueException(offset); + } + int byteLength = (int) reader.getVarint(); + if (byteLength < 0) { + throw new DataCloneOutOfValueException(byteLength); + } + + int flags = 0; + if (getWireFormatVersion() >= 14 || version13BrokenDataMode) { + flags = (int) reader.getVarint(); + if (flags < 0) { + throw new DataCloneOutOfValueException(flags); + } + } + + JSDataView view = new JSDataView<>(arrayBuffer, kind, offset, byteLength, flags); assignId(view); return view; } - @Override - protected JSError readJSError() { + private JSError readJSError() { JSError.ErrorType errorType = JSError.ErrorType.Error; String message = null; String stack = null; @@ -441,14 +577,33 @@ protected JSError readJSError() { return error; } - @Override - protected Object readHostObject() { + private Object readHostObject() { if (delegate == null) { throw new DataCloneDeserializationException(); } return assignId(delegate.readHostObject(this)); } + private SharedValueConveyor.SharedValue readSharedObject() { + if (delegate == null) { + throw new DataCloneDeserializationException(); + } + + if (sharedObjectConveyor == null) { + sharedObjectConveyor = delegate.getSharedValueConveyor(this); + if (sharedObjectConveyor == null) { + return assignId(new SharedValueConveyor.SharedValue() { + }); + } + } + + int id = (int) reader.getVarint(); + if (id < 0) { + throw new DataCloneOutOfValueException(id); + } + return assignId(sharedObjectConveyor.getPersisted(id)); + } + /** * Accepts the {@link JSArrayBuffer} corresponding to the one passed previously to {@link * Serializer#transferArrayBuffer(int, JSArrayBuffer)} @@ -463,8 +618,7 @@ public void transferArrayBuffer(int transferId, @NonNull JSArrayBuffer arrayBuff arrayBufferTransferMap.put(transferId, arrayBuffer); } - @Override - protected Object readTransferredJSArrayBuffer() { + private Object readTransferredJSArrayBuffer() { int id = (int) reader.getVarint(); if (id < 0) { throw new DataCloneOutOfValueException(id); @@ -481,8 +635,7 @@ protected Object readTransferredJSArrayBuffer() { : arrayBuffer; } - @Override - protected Object readSharedArrayBuffer() { + private Object readSharedArrayBuffer() { if (delegate == null) { throw new DataCloneDeserializationException(); } @@ -496,8 +649,7 @@ protected Object readSharedArrayBuffer() { sharedArrayBuffer) : sharedArrayBuffer; } - @Override - protected Object readTransferredWasmModule() { + private Object readTransferredWasmModule() { if (delegate == null) { throw new DataCloneDeserializationException(); } @@ -509,8 +661,7 @@ protected Object readTransferredWasmModule() { return assignId(wasmModule); } - @Override - protected Object readTransferredWasmMemory() { + private Object readTransferredWasmMemory() { long maximumPages = reader.getVarint(); JSValue memory = (JSValue) readSharedArrayBuffer(); if (!memory.isSharedArrayBuffer()) { diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/recommend/Serializer.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/recommend/Serializer.java index a89342485c0..292a86aca13 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/recommend/Serializer.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/recommend/Serializer.java @@ -97,6 +97,21 @@ public interface Delegate { * @return ID */ int getWasmModuleTransferId(Serializer serializer, WasmModule module); + + /** + * Called when the first shared value is serialized. All subsequent shared + * values will use the same conveyor. + * + * The implement must ensure the lifetime of the conveyor matches the + * lifetime of the serialized data. + * + * This method is called at most once per serializer. + * + * @param serializer current serializer + * @param conveyor SharedValueConveyor + * @return return true if supports serializing shared values, otherwise return false. + */ + boolean adoptSharedValueConveyor(Serializer serializer, SharedValueConveyor conveyor); } /** @@ -111,21 +126,17 @@ public interface Delegate { * Determines whether {@link JSArrayBuffer}s should be serialized as host objects. */ private boolean treatArrayBufferViewsAsHostObjects; + /** + * Shared value conveyors to keep JS shared values alive in transit when serialized. + */ + private SharedValueConveyor sharedObjectConveyor; public Serializer() { - this(null, null); - } - - public Serializer(Delegate delegate) { - this(null, delegate); + this(null, null, 13); } - public Serializer(BinaryWriter writer) { - this(writer, null); - } - - public Serializer(BinaryWriter writer, Delegate delegate) { - super(writer); + public Serializer(BinaryWriter writer, Delegate delegate, int version) { + super(writer, version); this.delegate = delegate; } @@ -161,6 +172,12 @@ public boolean writeValue(Object object) { return true; } + if (getVersion() >= 15 && object instanceof SharedValueConveyor.SharedValue) { + assignId(object); + writeSharedObject((SharedValueConveyor.SharedValue) object); + return true; + } + if (!treatArrayBufferViewsAsHostObjects && JSValue.is(object) && ((JSValue) object) .isDataView()) { JSDataView view = (JSDataView) object; @@ -172,11 +189,7 @@ public boolean writeValue(Object object) { } } - if (object instanceof Date) { - assignId(object); - writeTag(SerializationTag.DATE); - writeDate((Date) object); - } else if (JSValue.is(object)) { + if (JSValue.is(object)) { assignId(object); JSValue value = (JSValue) object; @@ -220,6 +233,14 @@ public boolean writeValue(Object object) { return true; } + private void writeTag(ArrayBufferViewTag tag) { + writer.putVarint(tag.getTag()); + } + + private void writeTag(ErrorTag tag) { + writer.putVarint(tag.getTag()); + } + private void writeDate(@NonNull Date date) { writer.putDouble(date.getTime()); } @@ -302,7 +323,7 @@ private void writeJSMap(@NonNull JSMap value) { writeValue(entry.getValue()); } writeTag(SerializationTag.END_JS_MAP); - writer.putVarint(2 * count); + writer.putVarint(2L * count); } private void writeJSSet(@NonNull JSSet value) { @@ -356,6 +377,14 @@ private void writeJSArrayBufferView(@NonNull JSDataView value) { tag = ArrayBufferViewTag.DATA_VIEW; break; } + case BIGINT64_ARRAY: { + tag = ArrayBufferViewTag.BIGINT64_ARRAY; + break; + } + case BIGUINT64_ARRAY: { + tag = ArrayBufferViewTag.BIGUINT64_ARRAY; + break; + } case FLOAT32_ARRAY: { tag = ArrayBufferViewTag.FLOAT32_ARRAY; break; @@ -399,6 +428,9 @@ private void writeJSArrayBufferView(@NonNull JSDataView value) { writeTag(tag); writer.putVarint(value.getByteOffset()); writer.putVarint(value.getByteLength()); + if (getVersion() >= 14) { + writer.putVarint(value.getFlags()); + } } } @@ -425,30 +457,37 @@ private void writeErrorTypeTag(@NonNull JSError error) { JSError.ErrorType errorType = error.getType(); ErrorTag tag; switch (errorType) { - case EvalError: + case EvalError: { tag = ErrorTag.EVAL_ERROR; break; - case RangeError: + } + case RangeError: { tag = ErrorTag.RANGE_ERROR; break; - case ReferenceError: + } + case ReferenceError: { tag = ErrorTag.REFERENCE_ERROR; break; - case SyntaxError: + } + case SyntaxError: { tag = ErrorTag.SYNTAX_ERROR; break; - case TypeError: + } + case TypeError: { tag = ErrorTag.TYPE_ERROR; break; - case URIError: + } + case URIError: { tag = ErrorTag.URI_ERROR; break; - default: + } + default: { tag = null; if (errorType != JSError.ErrorType.Error && errorType != JSError.ErrorType.AggregateError) { throw new UnreachableCodeException(); } break; + } } if (tag != null) { writeTag(tag); @@ -463,6 +502,21 @@ private boolean writeHostObject(Object object) { return delegate.writeHostObject(this, object); } + private void writeSharedObject(SharedValueConveyor.SharedValue object) { + if (delegate == null) { + throw new DataCloneException(object); + } + if (sharedObjectConveyor == null) { + sharedObjectConveyor = new SharedValueConveyor(); + if (!delegate.adoptSharedValueConveyor(this, sharedObjectConveyor)) { + sharedObjectConveyor = null; + return; + } + } + writeTag(SerializationTag.SHARED_OBJECT); + writer.putVarint(sharedObjectConveyor.persist(object)); + } + /** * Marks an {@link JSArrayBuffer} as having its contents transferred out of band. Pass the * corresponding {@link JSArrayBuffer} in the deserializing context to {@link diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/recommend/SharedValueConveyor.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/recommend/SharedValueConveyor.java new file mode 100644 index 00000000000..be8c5289a0d --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/serialization/recommend/SharedValueConveyor.java @@ -0,0 +1,52 @@ +/* + * Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.tencent.mtt.hippy.serialization.recommend; + +import java.util.ArrayList; +import java.util.List; + +/** + * The {@link SharedValueConveyor} keep shared values while serializing + * + * This class is not directly constructible and is always passed to + * Serializer and Deserializer. + * + * The user must keep the SharedValueConveyor instance until the associated + * serialized data will no longer be deserialized. + */ +public final class SharedValueConveyor { + /** + * All shared values MUST be implement ${@link SharedValue} interface. + */ + public interface SharedValue { + } + + private final List sharedObjects = new ArrayList<>(); + + SharedValueConveyor() { + } + + SharedValue getPersisted(int index) { + return sharedObjects.get(index); + } + + int persist(SharedValue value) { + sharedObjects.add(value); + return sharedObjects.size() - 1; + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/ControllerManager.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/ControllerManager.java index 891399ac672..f0cf200cc60 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/ControllerManager.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/ControllerManager.java @@ -21,6 +21,10 @@ import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; + +import androidx.annotation.NonNull; + +import androidx.annotation.Nullable; import com.tencent.mtt.hippy.HippyEngineContext; import com.tencent.mtt.hippy.HippyInstanceLifecycleEventListener; import com.tencent.mtt.hippy.HippyAPIProvider; @@ -32,7 +36,9 @@ import com.tencent.mtt.hippy.dom.node.NodeProps; import com.tencent.mtt.hippy.dom.node.StyleNode; import com.tencent.mtt.hippy.modules.Promise; +import com.tencent.mtt.hippy.runtime.builtins.JSObject; import com.tencent.mtt.hippy.utils.ContextHolder; +import com.tencent.mtt.hippy.utils.DimensionsUtil; import com.tencent.mtt.hippy.utils.LogUtils; import com.tencent.mtt.hippy.utils.PixelUtil; import com.tencent.mtt.hippy.utils.UIThreadUtils; @@ -63,19 +69,37 @@ public ControllerManager(HippyEngineContext context, List hipp } private void processControllers(List hippyPackages) { + addControllers(hippyPackages); + mControllerRegistry.addControllerHolder(NodeProps.ROOT_NODE, + new ControllerHolder(new HippyViewGroupController(), false)); + } + + public int getControllerCount() { + return mControllerRegistry.getControllersCount(); + } + + /** + * Add view controllers defined in {@link HippyAPIProvider}. + * + * @param hippyPackages API providers need to be added. + */ + public void addControllers(List hippyPackages) { + if (hippyPackages == null) { + return; + } for (HippyAPIProvider hippyPackage : hippyPackages) { List> components = hippyPackage.getControllers(); if (components != null) { for (Class hippyComponent : components) { HippyController hippyNativeModule = (HippyController) hippyComponent - .getAnnotation(HippyController.class); + .getAnnotation(HippyController.class); assert hippyNativeModule != null; String name = hippyNativeModule.name(); String[] names = hippyNativeModule.names(); boolean lazy = hippyNativeModule.isLazyLoad(); try { ControllerHolder holder = new ControllerHolder( - (HippyViewController) hippyComponent.newInstance(), lazy); + (HippyViewController) hippyComponent.newInstance(), lazy); mControllerRegistry.addControllerHolder(name, holder); if (names.length > 0) { for (String s : names) { @@ -88,8 +112,6 @@ private void processControllers(List hippyPackages) { } } } - mControllerRegistry.addControllerHolder(NodeProps.ROOT_NODE, - new ControllerHolder(new HippyViewGroupController(), false)); } public void destroy() { @@ -175,6 +197,10 @@ public void updateLayout(String name, int id, int x, int y, int width, int heigh component.updateLayout(id, x, y, width, height, mControllerRegistry); } + public void addFakeRootView(@NonNull HippyRootView rootView) { + mControllerRegistry.addRootView(rootView); + } + @Override public void onInstanceLoad(int instanceId) { if (mContext != null && mContext.getInstance(instanceId) != null) { @@ -278,7 +304,13 @@ public void onBatchComplete(String className, int id) { hippyViewController.onBatchComplete(view); } } - + public void onBatchStart(String className, int id) { + HippyViewController hippyViewController = mControllerRegistry.getViewController(className); + View view = mControllerRegistry.getView(id); + if (view != null) { + hippyViewController.onBatchStart(view); + } + } public void deleteChildRecursive(ViewGroup viewParent, View child, int childIndex) { if (viewParent == null || child == null) { return; @@ -338,54 +370,28 @@ public void deleteChild(int pId, int childId, int childIndex) { if (parentView instanceof ViewGroup && childView != null) { deleteChildRecursive((ViewGroup) parentView, childView, childIndex); } -// else -// { -// mContext.getGlobalConfigs().getLogAdapter().log(TAG, "deleteChild error pId: " + pId + " childId: " + childId +(parentView instanceof ViewGroup)+ (childView != null)); -// } } - private static int statusBarHeight = -1; - - public static int getStatusBarHeightFromSystem() { - if (statusBarHeight > 0) { - return statusBarHeight; - } - - Class c; - Object obj; - Field field; - int x; - try { - c = Class.forName("com.android.internal.R$dimen"); - obj = c.newInstance(); - field = c.getField("status_bar_height"); - //noinspection ConstantConditions - x = Integer.parseInt(field.get(obj).toString()); - statusBarHeight = ContextHolder.getAppContext().getResources().getDimensionPixelSize(x); - } catch (Exception e1) { - statusBarHeight = -1; - e1.printStackTrace(); - } - - if (statusBarHeight < 1) { - try { - int statebarH_id = ContextHolder.getAppContext().getResources() - .getIdentifier("statebar_height", "dimen", - ContextHolder.getAppContext().getPackageName()); - statusBarHeight = Math - .round(ContextHolder.getAppContext().getResources().getDimension(statebarH_id)); - } catch (NotFoundException e) { - LogUtils.d("ControllerManager", "getStatusBarHeightFromSystem: " + e.getMessage()); + public void removeViewFromRegistry(int id) { + View view = mControllerRegistry.getView(id); + if (view instanceof ViewGroup) { + for (int i = ((ViewGroup) view).getChildCount() - 1; i >= 0; i--) { + View child = ((ViewGroup) view).getChildAt(i); + if (child != null) { + removeViewFromRegistry(child.getId()); + } } } - return statusBarHeight; + if (view != null) { + mControllerRegistry.removeView(view.getId()); + } } @SuppressLint("Range") public void measureInWindow(int id, Promise promise) { View v = mControllerRegistry.getView(id); if (v == null) { - promise.reject("this view is null"); + promise.resolve("this view is null"); } else { int[] outputBuffer = new int[4]; int statusBarHeight; @@ -394,7 +400,7 @@ public void measureInWindow(int id, Promise promise) { // We need to remove the status bar from the height. getLocationOnScreen will include the // status bar. - statusBarHeight = getStatusBarHeightFromSystem(); + statusBarHeight = DimensionsUtil.getStatusBarHeight(); if (statusBarHeight > 0) { outputBuffer[1] -= statusBarHeight; } @@ -403,7 +409,7 @@ public void measureInWindow(int id, Promise promise) { outputBuffer[2] = v.getWidth(); outputBuffer[3] = v.getHeight(); } catch (Throwable e) { - promise.reject("exception" + e.getMessage()); + promise.resolve("exception" + e.getMessage()); e.printStackTrace(); return; } @@ -425,6 +431,52 @@ public void measureInWindow(int id, Promise promise) { } + /** + * @param id view id + * @param rootView + * @param relToContainer true is relative to the rootView, otherwise relative to the app frame + * @param promise + */ + public void getBoundingClientRect(int id, HippyRootView rootView, boolean relToContainer, Promise promise) { + View v = mControllerRegistry.getView(id); + if (v == null) { + JSObject result = new JSObject(); + result.set(RenderNode.KEY_ERR_MSG, "this view is null"); + promise.resolve(result); + return; + } + int x; + int y; + int width = v.getWidth(); + int height = v.getHeight(); + int[] pair = new int[2]; + if (relToContainer) { + if (rootView == null) { + JSObject result = new JSObject(); + result.set(RenderNode.KEY_ERR_MSG, "container is null"); + promise.resolve(result); + return; + } + + v.getLocationInWindow(pair); + x = pair[0]; + y = pair[1]; + rootView.getLocationInWindow(pair); + x -= pair[0]; + y -= pair[1]; + } else { + v.getLocationOnScreen(pair); + x = pair[0]; + y = pair[1]; + } + JSObject result = new JSObject(); + result.set("x", PixelUtil.px2dp(x)); + result.set("y", PixelUtil.px2dp(y)); + result.set("width", PixelUtil.px2dp(width)); + result.set("height", PixelUtil.px2dp(height)); + promise.resolve(result); + } + public void onManageChildComplete(String className, int id) { HippyViewController hippyViewController = mControllerRegistry.getViewController(className); View view = mControllerRegistry.getView(id); diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/ControllerRegistry.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/ControllerRegistry.java index 27006a77407..ae197a60205 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/ControllerRegistry.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/ControllerRegistry.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; public class ControllerRegistry { @@ -35,7 +36,7 @@ public class ControllerRegistry { public ControllerRegistry(HippyEngineContext context) { mViews = new SparseArray<>(); mRoots = new SparseArray<>(); - mControllers = new HashMap<>(); + mControllers = new ConcurrentHashMap<>(); engineContext = context; } @@ -47,6 +48,10 @@ public ControllerHolder getControllerHolder(String className) { return mControllers.get(className); } + public int getControllersCount() { + return mControllers.size(); + } + @SuppressWarnings({"rawtypes"}) public HippyViewController getViewController(String className) { try { diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/DiffUtils.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/DiffUtils.java index f70ef79651a..5791a02e928 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/DiffUtils.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/DiffUtils.java @@ -164,61 +164,32 @@ public static HippyMap diffProps(HippyMap from, HippyMap to, int diffLevel) { Object fromValue = from.get(fromKey); Object toValue = to.get(fromKey); if (fromValue instanceof Boolean) { - boolean fromBool = (boolean) fromValue; - if (toValue != null && fromBool == (boolean) toValue) { - LogUtils.d("DiffUtils", "don't do anything for bool value"); - } else { + if (!equalsBoolean((Boolean) fromValue, toValue)) { updateProps.pushObject(fromKey, toValue); } } else if (fromValue instanceof Number) { - boolean isSame = false; - double fromDoubleValue = ((Number) fromValue).doubleValue(); - if (toValue instanceof Number) { - double toDoubleValue = ((Number) toValue).doubleValue(); - isSame = (fromDoubleValue == toDoubleValue); - } - // if toValue is null, push null to trigger default value - if (!isSame) { + if (!equalsNumber((Number) fromValue, toValue)) { updateProps.pushObject(fromKey, toValue); } } else if (fromValue instanceof String) { - if (toValue != null && TextUtils.equals(fromValue.toString(), toValue.toString())) { - LogUtils.d("DiffUtils", "don't do anything for same value"); - } else { + if (toValue == null || !equalsString((String) fromValue, toValue.toString())) { updateProps.pushObject(fromKey, toValue); } } else if (fromValue instanceof HippyArray) { - if (toValue instanceof HippyArray) { - HippyArray diffResult = diffArray((HippyArray) fromValue, (HippyArray) toValue, - diffLevel + 1); - //tintColor复用的时候必须要强制更新 - if (fromKey.equals("tintColors") || fromKey.equals("tintColor")) { - diffResult = (HippyArray) toValue; - } - //这里diffResult == null标识属性没有更新 - if (diffResult != null /* && diffResult.size() > 0*/) { - updateProps.pushObject(fromKey, toValue); - } - } else { // toValue(Array)没有的时候,要给个默认值 - updateProps.pushObject(fromKey, null); + //tintColor复用的时候必须要强制更新 + if (fromKey.equals("tintColors") || fromKey.equals("tintColor") || + !equalsArray((HippyArray) fromValue, toValue)) { + updateProps.pushObject(fromKey, toValue); } } else if (fromValue instanceof HippyMap) { - if (toValue instanceof HippyMap) { - - HippyMap diffResult = diffProps((HippyMap) fromValue, (HippyMap) toValue, diffLevel + 1); - if (diffResult != null && diffResult.size() > 0) { - if (diffLevel == 0 && fromKey.equals(NodeProps.STYLE)) { - updateProps.pushObject(fromKey, diffResult); - } else { - updateProps.pushObject(fromKey, toValue); - } + if (diffLevel == 0 && fromKey.equals(NodeProps.STYLE)) { + if (!(toValue instanceof HippyMap)) { + toValue = new HippyMap(); } - } else if (diffLevel == 0 && fromKey.equals(NodeProps.STYLE)) { - //style is null - HippyMap diffResult = diffProps((HippyMap) fromValue, new HippyMap(), diffLevel + 1); + HippyMap diffResult = diffProps((HippyMap) fromValue, (HippyMap) toValue, diffLevel + 1); updateProps.pushMap(fromKey, diffResult); - } else { // toValue没有的时候,要给个默认值 - updateProps.pushObject(fromKey, null); + } else if (!equalsMap((HippyMap) fromValue, toValue)) { + updateProps.pushObject(fromKey, toValue); } } } @@ -240,60 +211,76 @@ public static HippyMap diffProps(HippyMap from, HippyMap to, int diffLevel) { return updateProps; } + private static boolean equalsBoolean(Boolean from, Object to) { + return from.equals(to); + } - private static HippyArray diffArray(HippyArray fromValue, HippyArray toValue, int diffLevel) { + private static boolean equalsNumber(Number from, Object to) { + return to instanceof Number && from.doubleValue() == ((Number) to).doubleValue(); + } - if (fromValue.size() != toValue.size()) { - return toValue; - } - int size = fromValue.size(); + private static boolean equalsString(String from, Object to) { + return from.equals(to); + } - for (int i = 0; i < size; i++) { - Object from = fromValue.getObject(i); - Object to = toValue.getObject(i); - // 这里默认from & to的类型相同 + private static boolean equalsObject(Object from, Object to) { + if (from == to) { + return true; + } if (from instanceof Boolean) { - if ((boolean) from != (boolean) to) { - return toValue; - } + return equalsBoolean((Boolean) from, to); } else if (from instanceof Number) { - - boolean isSame = false; - - double fromDoubleValue = ((Number) from).doubleValue(); - if (to instanceof Number) { - double toDoubleValue = ((Number) to).doubleValue(); - isSame = (fromDoubleValue == toDoubleValue); - } - // if to is null, push null to trigger default value - - if (!isSame) { - return toValue; - } - + return equalsNumber((Number) from, to); } else if (from instanceof String) { - if (!TextUtils.equals((String) from, (String) to)) { - return toValue; - } + return equalsString((String) from, to); } else if (from instanceof HippyArray) { - if (to instanceof HippyArray) { - HippyArray diffResult = diffArray((HippyArray) from, (HippyArray) to, diffLevel); - if (diffResult != null) { - return toValue; - } - } + return equalsArray((HippyArray) from, to); } else if (from instanceof HippyMap) { - if (to instanceof HippyMap) { - HippyMap diffResult = diffProps((HippyMap) from, (HippyMap) to, diffLevel); - if (diffResult != null) { - return toValue; - } - } + return equalsMap((HippyMap) from, to); } + return false; + } + + private static boolean equalsArray(HippyArray from, Object to) { + if (from == to) { + return true; } - return null; + int size = from.size(); + if (!(to instanceof HippyArray) || size != ((HippyArray) to).size()) { + return false; + } + + for (int i = 0; i < size; i++) { + Object fromValue = from.getObject(i); + Object toValue = ((HippyArray) to).getObject(i); + if (!equalsObject(fromValue, toValue)) { + return false; + } + } + return true; } + private static boolean equalsMap(HippyMap from, Object to) { + if (from == to) { + return true; + } + if (!(to instanceof HippyMap) || from.size() != ((HippyMap) to).size()) { + return false; + } + for (String fromKey : from.keySet()) { + Object fromValue = from.get(fromKey); + Object toValue = ((HippyMap) to).get(fromKey); + if (!equalsObject(fromValue, toValue)) { + return false; + } + if (fromValue == null && !((HippyMap) to).containsKey(fromKey)) { + // since null could be either a null value or a non-existent key, + // let's confirm that it's the former case. + return false; + } + } + return true; + } public static class CreatePatch extends Patch { diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/DrawableFactory.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/DrawableFactory.java new file mode 100644 index 00000000000..07ae2704596 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/DrawableFactory.java @@ -0,0 +1,92 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.uimanager; + +import static android.graphics.drawable.RippleDrawable.RADIUS_AUTO; + +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.RippleDrawable; +import android.os.Build; + +import com.tencent.mtt.hippy.annotation.HippyMethod; +import com.tencent.mtt.hippy.common.HippyMap; +import com.tencent.mtt.hippy.utils.PixelUtil; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +import java.util.Map; + +public class DrawableFactory { + + private static final String PROPERTY_RIPPLE_COLOR = "color"; + private static final String PROPERTY_RIPPLE_RADIUS = "rippleRadius"; + private static final String PROPERTY_RIPPLE_BORDERLESS = "borderless"; + + public enum DrawableType { + DRAWABLE_TYPE_RIPPLE + } + + @Nullable + public static Drawable createDrawable(DrawableType type, @Nullable HippyMap params) { + Drawable drawable = null; + switch (type) { + case DRAWABLE_TYPE_RIPPLE: + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + drawable = createRippleDrawable(params); + } + break; + default: + } + + return drawable; + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @NonNull + private static Drawable createRippleDrawable(@Nullable HippyMap params) { + int color = Color.BLUE; + int radius = 0; + Drawable mask = null; + if (params != null) { + if (params.containsKey(PROPERTY_RIPPLE_COLOR) + && !params.isNull(PROPERTY_RIPPLE_COLOR)) { + color = params.getInt(PROPERTY_RIPPLE_COLOR); + } + if (params.containsKey(PROPERTY_RIPPLE_RADIUS)) { + double rd = params.getDouble(PROPERTY_RIPPLE_RADIUS); + radius = (int) (PixelUtil.dp2px(rd) + 0.5); + } + if (!params.containsKey(PROPERTY_RIPPLE_BORDERLESS) + || params.isNull(PROPERTY_RIPPLE_BORDERLESS) + || !params.getBoolean(PROPERTY_RIPPLE_BORDERLESS)) { + mask = new ColorDrawable(Color.WHITE); + } + } + ColorStateList colorStateList = + new ColorStateList(new int[][]{new int[]{}}, new int[]{color}); + RippleDrawable drawable = new RippleDrawable(colorStateList, null, mask); + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && radius > 0) { + drawable.setRadius(radius); + } + return drawable; + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/HippyViewController.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/HippyViewController.java index ec64685b5b5..3953ad11afe 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/HippyViewController.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/HippyViewController.java @@ -36,6 +36,12 @@ import com.tencent.mtt.hippy.utils.LogUtils; import com.tencent.mtt.hippy.utils.PixelUtil; import com.tencent.mtt.hippy.views.common.CommonBorder; +import com.tencent.mtt.hippy.views.common.HippyNestedScrollComponent; +import com.tencent.mtt.hippy.views.common.HippyNestedScrollComponent.Priority; +import com.tencent.mtt.hippy.views.common.HippyNestedScrollHelper; +import com.tencent.mtt.hippy.views.hippylist.HippyRecyclerViewWrapper; +import com.tencent.mtt.hippy.views.list.HippyListView; +import com.tencent.mtt.hippy.views.scroll.HippyScrollView; import com.tencent.mtt.hippy.views.view.HippyViewGroupController; import com.tencent.mtt.supportui.views.IGradient; import com.tencent.mtt.supportui.views.IShadow; @@ -48,8 +54,8 @@ public abstract class HippyViewController implements View.OnFocusChangeListener { + public static final String FAKE_NODE_TAG = "fake"; private static final String TAG = "HippyViewController"; - private static final MatrixUtil.MatrixDecompositionContext sMatrixDecompositionContext = new MatrixUtil.MatrixDecompositionContext(); private static final double[] sTransformDecompositionArray = new double[16]; private boolean bUserChageFocus = false; @@ -59,7 +65,9 @@ public View createView(HippyRootView rootView, int id, HippyEngineContext hippyE String className, HippyMap initialProps) { View view = null; - + if (id < 0) { + initialProps.pushBoolean(FAKE_NODE_TAG, true); + } if (rootView != null) { Context rootViewContext = rootView.getContext(); if (rootViewContext instanceof HippyInstanceContext) { @@ -80,6 +88,20 @@ public View createView(HippyRootView rootView, int id, HippyEngineContext hippyE } } + if (id < 0) { + if (view instanceof HippyRecyclerViewWrapper) { + ((HippyRecyclerViewWrapper)view).setScrollEnable(false); + } + + if (view instanceof HippyListView) { + ((HippyListView)view).setScrollEnable(false); + } + + if (view instanceof HippyScrollView) { + ((HippyScrollView)view).setScrollEnabled(false); + } + } + LogUtils.d(TAG, "createView id " + id); view.setId(id); //view.setTag(className); @@ -208,6 +230,26 @@ public void setZIndex(T view, int zIndex) { } } + /** + * @deprecated use {@link #setVisibility} instead + */ + @Deprecated + @HippyControllerProps(name = "setVisible", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = true) + public void setVisible(T view, boolean flag) { + view.setVisibility(flag ? View.VISIBLE : View.INVISIBLE); + } + + @HippyControllerProps(name = "visibility", defaultType = HippyControllerProps.STRING, defaultString = NodeProps.VISIBLE) + public void setVisibility(T view, String value) { + if (TextUtils.isEmpty(value) || NodeProps.VISIBLE.equals(value)) { + view.setVisibility(View.VISIBLE); + } else if (NodeProps.HIDDEN.equals(value)) { + view.setVisibility(View.INVISIBLE); + } else { + throw new RuntimeException("Invalid visibility: " + value); + } + } + /** * color/border/alpha **/ @@ -298,6 +340,7 @@ public void setNextFocusRightId(T view, int id) { @HippyControllerProps(name = NodeProps.FOCUSABLE, defaultType = HippyControllerProps.BOOLEAN) public void setFocusable(T view, boolean focusable) { view.setFocusable(focusable); + view.setFocusableInTouchMode(focusable); if (focusable) { view.setOnFocusChangeListener(this); } else { @@ -338,7 +381,14 @@ public void onFocusChange(View v, boolean hasFocus) { @HippyControllerProps(name = NodeProps.LINEAR_GRADIENT, defaultType = HippyControllerProps.MAP) public void setLinearGradient(T view, HippyMap linearGradient) { - if (linearGradient != null && view instanceof IGradient) { + if (view instanceof IGradient) { + if (linearGradient == null) { + // reset linear gradient + ((IGradient)view).setGradientAngle(null); + ((IGradient)view).setGradientColors(null); + ((IGradient)view).setGradientPositions(null); + return; + } String angle = linearGradient.getString("angle"); HippyArray colorStopList = linearGradient.getArray("colorStopList"); @@ -600,6 +650,55 @@ public void setRenderToHardwareTexture(T view, boolean useHWTexture) { view.setLayerType(useHWTexture ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE, null); } + @HippyControllerProps(name = HippyNestedScrollComponent.PROP_PRIORITY, defaultType = + HippyControllerProps.STRING, defaultString = HippyNestedScrollComponent.PRIORITY_SELF) + public void setNestedScrollPriority(T view, String priorityName) { + if (view instanceof HippyNestedScrollComponent) { + HippyNestedScrollComponent sc = (HippyNestedScrollComponent) view; + HippyNestedScrollComponent.Priority priority = HippyNestedScrollHelper.priorityOf(priorityName); + if (priority == Priority.NOT_SET) { + priority = Priority.SELF; + } + sc.setNestedScrollPriority(HippyNestedScrollComponent.DIRECTION_ALL, priority); + } + } + + @HippyControllerProps(name = HippyNestedScrollComponent.PROP_LEFT_PRIORITY, defaultType = + HippyControllerProps.STRING) + public void setNestedScrollLeftPriority(T view, String priorityName) { + if (view instanceof HippyNestedScrollComponent) { + HippyNestedScrollComponent.Priority priority = HippyNestedScrollHelper.priorityOf(priorityName); + ((HippyNestedScrollComponent) view).setNestedScrollPriority(HippyNestedScrollComponent.DIRECTION_LEFT, priority); + } + } + + @HippyControllerProps(name = HippyNestedScrollComponent.PROP_TOP_PRIORITY, defaultType = + HippyControllerProps.STRING) + public void setNestedScrollTopPriority(T view, String priorityName) { + if (view instanceof HippyNestedScrollComponent) { + HippyNestedScrollComponent.Priority priority = HippyNestedScrollHelper.priorityOf(priorityName); + ((HippyNestedScrollComponent) view).setNestedScrollPriority(HippyNestedScrollComponent.DIRECTION_TOP, priority); + } + } + + @HippyControllerProps(name = HippyNestedScrollComponent.PROP_RIGHT_PRIORITY, defaultType = + HippyControllerProps.STRING) + public void setNestedScrollRightPriority(T view, String priorityName) { + if (view instanceof HippyNestedScrollComponent) { + HippyNestedScrollComponent.Priority priority = HippyNestedScrollHelper.priorityOf(priorityName); + ((HippyNestedScrollComponent) view).setNestedScrollPriority(HippyNestedScrollComponent.DIRECTION_RIGHT, priority); + } + } + + @HippyControllerProps(name = HippyNestedScrollComponent.PROP_BOTTOM_PRIORITY, defaultType = + HippyControllerProps.STRING) + public void setNestedScrollBottomPriority(T view, String priorityName) { + if (view instanceof HippyNestedScrollComponent) { + HippyNestedScrollComponent.Priority priority = HippyNestedScrollHelper.priorityOf(priorityName); + ((HippyNestedScrollComponent) view).setNestedScrollPriority(HippyNestedScrollComponent.DIRECTION_BOTTOM, priority); + } + } + @SuppressWarnings("EmptyMethod") @HippyControllerProps(name = NodeProps.CUSTOM_PROP) public void setCustomProp(T view, String methodName, Object props) { @@ -653,7 +752,9 @@ public void dispatchFunction(T view, String functionName, HippyArray var) { public void dispatchFunction(T view, String functionName, HippyArray params, Promise promise) { } + public void onBatchStart(T view){ + } public void onBatchComplete(T view) { } @@ -675,6 +776,7 @@ protected void addView(ViewGroup parentView, View view, int index) { if (realIndex > parentView.getChildCount()) { realIndex = parentView.getChildCount(); } + try { parentView.addView(view, realIndex); } catch (Exception e) { diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/NativeGestureDispatcher.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/NativeGestureDispatcher.java index 357622d3af4..73e08d05e19 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/NativeGestureDispatcher.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/NativeGestureDispatcher.java @@ -38,6 +38,10 @@ public class NativeGestureDispatcher implements NativeGestureProcessor.Callback private static final String KEY_TAG_ID = "id"; private static final String KEY_PAGE_X = "page_x"; private static final String KEY_PAGE_Y = "page_y"; + + private static final String KEY_WINDOW_X = "window_x"; + private static final String KEY_WINDOW_Y = "window_y"; + private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout(); private static final View.OnClickListener mOnClickListener = new View.OnClickListener() { @@ -208,7 +212,7 @@ public static void handlePressOut(HippyEngineContext context, int tagId) { } public static void handleTouchDown(HippyEngineContext context, int mTagId, float x, float y, - int viewId) { + int viewId) { int[] viewCoords = new int[2]; getLocationInWindow(context, viewId, viewCoords); HippyMap params = new HippyMap(); @@ -216,12 +220,27 @@ public static void handleTouchDown(HippyEngineContext context, int mTagId, float params.pushInt(KEY_TAG_ID, mTagId); params.pushDouble(KEY_PAGE_X, PixelUtil.px2dp(viewCoords[0] + x)); params.pushDouble(KEY_PAGE_Y, PixelUtil.px2dp(viewCoords[1] + y)); + context.getModuleManager().getJavaScriptModule(EventDispatcher.class) + .receiveNativeGesture(params); + } + + public static void handleTouchDown(HippyEngineContext context, int mTagId, MotionEvent event, + int viewId) { + int[] viewCoords = new int[2]; + getLocationInWindow(context, viewId, viewCoords); + HippyMap params = new HippyMap(); + params.pushString(KEY_EVENT_NAME, NodeProps.ON_TOUCH_DOWN); + params.pushInt(KEY_TAG_ID, mTagId); + params.pushDouble(KEY_WINDOW_X, PixelUtil.px2dp(event.getRawX())); + params.pushDouble(KEY_WINDOW_Y, PixelUtil.px2dp(event.getRawY())); + params.pushDouble(KEY_PAGE_X, PixelUtil.px2dp(viewCoords[0] + event.getX())); + params.pushDouble(KEY_PAGE_Y, PixelUtil.px2dp(viewCoords[1] + event.getY())); context.getModuleManager().getJavaScriptModule(EventDispatcher.class) .receiveNativeGesture(params); } public static void handleTouchMove(HippyEngineContext context, int mTagId, float x, float y, - int viewId) { + int viewId) { int[] viewCoords = new int[2]; getLocationInWindow(context, viewId, viewCoords); HippyMap params = new HippyMap(); @@ -229,12 +248,27 @@ public static void handleTouchMove(HippyEngineContext context, int mTagId, float params.pushInt(KEY_TAG_ID, mTagId); params.pushDouble(KEY_PAGE_X, PixelUtil.px2dp(viewCoords[0] + x)); params.pushDouble(KEY_PAGE_Y, PixelUtil.px2dp(viewCoords[1] + y)); + context.getModuleManager().getJavaScriptModule(EventDispatcher.class) + .receiveNativeGesture(params); + } + + public static void handleTouchMove(HippyEngineContext context, int mTagId, MotionEvent event, + int viewId) { + int[] viewCoords = new int[2]; + getLocationInWindow(context, viewId, viewCoords); + HippyMap params = new HippyMap(); + params.pushString(KEY_EVENT_NAME, NodeProps.ON_TOUCH_MOVE); + params.pushInt(KEY_TAG_ID, mTagId); + params.pushDouble(KEY_WINDOW_X, PixelUtil.px2dp(event.getRawX())); + params.pushDouble(KEY_WINDOW_Y, PixelUtil.px2dp(event.getRawY())); + params.pushDouble(KEY_PAGE_X, PixelUtil.px2dp(viewCoords[0] + event.getX())); + params.pushDouble(KEY_PAGE_Y, PixelUtil.px2dp(viewCoords[1] + event.getY())); context.getModuleManager().getJavaScriptModule(EventDispatcher.class) .receiveNativeGesture(params); } public static void handleTouchEnd(HippyEngineContext context, int mTagId, float x, float y, - int viewId) { + int viewId) { int[] viewCoords = new int[2]; getLocationInWindow(context, viewId, viewCoords); HippyMap params = new HippyMap(); @@ -242,12 +276,27 @@ public static void handleTouchEnd(HippyEngineContext context, int mTagId, float params.pushInt(KEY_TAG_ID, mTagId); params.pushDouble(KEY_PAGE_X, PixelUtil.px2dp(viewCoords[0] + x)); params.pushDouble(KEY_PAGE_Y, PixelUtil.px2dp(viewCoords[1] + y)); + context.getModuleManager().getJavaScriptModule(EventDispatcher.class) + .receiveNativeGesture(params); + } + + public static void handleTouchEnd(HippyEngineContext context, int mTagId, MotionEvent event, + int viewId) { + int[] viewCoords = new int[2]; + getLocationInWindow(context, viewId, viewCoords); + HippyMap params = new HippyMap(); + params.pushString(KEY_EVENT_NAME, NodeProps.ON_TOUCH_END); + params.pushInt(KEY_TAG_ID, mTagId); + params.pushDouble(KEY_WINDOW_X, PixelUtil.px2dp(event.getRawX())); + params.pushDouble(KEY_WINDOW_Y, PixelUtil.px2dp(event.getRawY())); + params.pushDouble(KEY_PAGE_X, PixelUtil.px2dp(viewCoords[0] + event.getX())); + params.pushDouble(KEY_PAGE_Y, PixelUtil.px2dp(viewCoords[1] + event.getY())); context.getModuleManager().getJavaScriptModule(EventDispatcher.class) .receiveNativeGesture(params); } public static void handleTouchCancel(HippyEngineContext context, int mTagId, float x, float y, - int viewId) { + int viewId) { int[] viewCoords = new int[2]; getLocationInWindow(context, viewId, viewCoords); HippyMap params = new HippyMap(); @@ -255,6 +304,21 @@ public static void handleTouchCancel(HippyEngineContext context, int mTagId, flo params.pushInt(KEY_TAG_ID, mTagId); params.pushDouble(KEY_PAGE_X, PixelUtil.px2dp(viewCoords[0] + x)); params.pushDouble(KEY_PAGE_Y, PixelUtil.px2dp(viewCoords[1] + y)); + context.getModuleManager().getJavaScriptModule(EventDispatcher.class) + .receiveNativeGesture(params); + } + + public static void handleTouchCancel(HippyEngineContext context, int mTagId, MotionEvent event, + int viewId) { + int[] viewCoords = new int[2]; + getLocationInWindow(context, viewId, viewCoords); + HippyMap params = new HippyMap(); + params.pushString(KEY_EVENT_NAME, NodeProps.ON_TOUCH_CANCEL); + params.pushInt(KEY_TAG_ID, mTagId); + params.pushDouble(KEY_WINDOW_X, PixelUtil.px2dp(event.getRawX())); + params.pushDouble(KEY_WINDOW_Y, PixelUtil.px2dp(event.getRawY())); + params.pushDouble(KEY_PAGE_X, PixelUtil.px2dp(viewCoords[0] + event.getX())); + params.pushDouble(KEY_PAGE_Y, PixelUtil.px2dp(viewCoords[1] + event.getY())); context.getModuleManager().getJavaScriptModule(EventDispatcher.class) .receiveNativeGesture(params); } @@ -328,16 +392,42 @@ public void handle(String type, float x, float y) { handlePressOut(mEngineContext, mTargetView.getId()); } else if (TextUtils.equals(type, NodeProps.ON_TOUCH_DOWN)) { NativeGestureDispatcher - .handleTouchDown(mEngineContext, mTargetView.getId(), x, y, mTargetView.getId()); + .handleTouchDown(mEngineContext, mTargetView.getId(), x, y, mTargetView.getId()); + } else if (TextUtils.equals(type, NodeProps.ON_TOUCH_MOVE)) { + NativeGestureDispatcher + .handleTouchMove(mEngineContext, mTargetView.getId(), x, y, mTargetView.getId()); + } else if (TextUtils.equals(type, NodeProps.ON_TOUCH_END)) { + NativeGestureDispatcher + .handleTouchEnd(mEngineContext, mTargetView.getId(), x, y, mTargetView.getId()); + } else if (TextUtils.equals(type, NodeProps.ON_TOUCH_CANCEL)) { + NativeGestureDispatcher + .handleTouchCancel(mEngineContext, mTargetView.getId(), x, y, mTargetView.getId()); + } + } + + @Override + public void handle(String type, MotionEvent event) { + if (mTargetView == null) { + LogUtils.e("NativeGestureDispatcher", "handle!!! but view is null!!!!"); + return; + } + + if (TextUtils.equals(type, NodeProps.ON_PRESS_IN)) { + handlePressIn(mEngineContext, mTargetView.getId()); + } else if (TextUtils.equals(type, NodeProps.ON_PRESS_OUT)) { + handlePressOut(mEngineContext, mTargetView.getId()); + } else if (TextUtils.equals(type, NodeProps.ON_TOUCH_DOWN)) { + NativeGestureDispatcher + .handleTouchDown(mEngineContext, mTargetView.getId(), event, mTargetView.getId()); } else if (TextUtils.equals(type, NodeProps.ON_TOUCH_MOVE)) { NativeGestureDispatcher - .handleTouchMove(mEngineContext, mTargetView.getId(), x, y, mTargetView.getId()); + .handleTouchMove(mEngineContext, mTargetView.getId(), event, mTargetView.getId()); } else if (TextUtils.equals(type, NodeProps.ON_TOUCH_END)) { NativeGestureDispatcher - .handleTouchEnd(mEngineContext, mTargetView.getId(), x, y, mTargetView.getId()); + .handleTouchEnd(mEngineContext, mTargetView.getId(), event, mTargetView.getId()); } else if (TextUtils.equals(type, NodeProps.ON_TOUCH_CANCEL)) { NativeGestureDispatcher - .handleTouchCancel(mEngineContext, mTargetView.getId(), x, y, mTargetView.getId()); + .handleTouchCancel(mEngineContext, mTargetView.getId(), event, mTargetView.getId()); } } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/NativeGestureProcessor.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/NativeGestureProcessor.java index 5bc130506a3..8308153b0a1 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/NativeGestureProcessor.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/NativeGestureProcessor.java @@ -70,7 +70,7 @@ public boolean onTouchEvent(MotionEvent event) { } if (mCallback.needHandle(NodeProps.ON_TOUCH_DOWN)) { - mCallback.handle(NodeProps.ON_TOUCH_DOWN, event.getX(), event.getY()); + mCallback.handle(NodeProps.ON_TOUCH_DOWN, event); handle = true; } @@ -89,7 +89,7 @@ public boolean onTouchEvent(MotionEvent event) { } case MotionEvent.ACTION_MOVE: { if (mCallback.needHandle(NodeProps.ON_TOUCH_MOVE)) { - mCallback.handle(NodeProps.ON_TOUCH_MOVE, event.getX(), event.getY()); + mCallback.handle(NodeProps.ON_TOUCH_MOVE, event); handle = true; } @@ -114,12 +114,12 @@ public boolean onTouchEvent(MotionEvent event) { } case MotionEvent.ACTION_UP: { if (mCallback.needHandle(NodeProps.ON_TOUCH_END)) { - mCallback.handle(NodeProps.ON_TOUCH_END, event.getX(), event.getY()); + mCallback.handle(NodeProps.ON_TOUCH_END, event); handle = true; } if (mNoPressIn && mCallback.needHandle(NodeProps.ON_PRESS_OUT)) { - mCallback.handle(NodeProps.ON_PRESS_OUT, event.getX(), event.getY()); + mCallback.handle(NodeProps.ON_PRESS_OUT, event); handle = true; } else if (!mNoPressIn && mCallback.needHandle(NodeProps.ON_PRESS_OUT)) { getGestureHandler().sendEmptyMessageDelayed(PRESS_OUT, TAP_TIMEOUT); @@ -131,12 +131,12 @@ public boolean onTouchEvent(MotionEvent event) { case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_OUTSIDE: { if (mCallback.needHandle(NodeProps.ON_TOUCH_CANCEL)) { - mCallback.handle(NodeProps.ON_TOUCH_CANCEL, event.getX(), event.getY()); + mCallback.handle(NodeProps.ON_TOUCH_CANCEL, event); handle = true; } if (mNoPressIn && mCallback.needHandle(NodeProps.ON_PRESS_OUT)) { - mCallback.handle(NodeProps.ON_PRESS_OUT, event.getX(), event.getY()); + mCallback.handle(NodeProps.ON_PRESS_OUT, event); handle = true; } else if (!mNoPressIn && mCallback.needHandle(NodeProps.ON_PRESS_OUT)) { if (getGestureHandler().hasMessages(PRESS_IN)) { @@ -156,6 +156,8 @@ public interface Callback { boolean needHandle(String type); + void handle(String type, MotionEvent event); + void handle(String type, float x, float y); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/PullFooterRenderNode.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/PullFooterRenderNode.java index c1becc5bfc3..d674abc6472 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/PullFooterRenderNode.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/PullFooterRenderNode.java @@ -28,6 +28,11 @@ public PullFooterRenderNode(int mId, HippyMap mPropsToUpdate, String className, super(mId, mPropsToUpdate, className, mRootView, componentManager, isLazyLoad); } + @Override + public boolean shouldSticky() { + return false; + } + /** * 通过类名的hashCode来定义type,计算出来是一个很大的值,几乎不会和前端类型重复 */ diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/PullHeaderRenderNode.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/PullHeaderRenderNode.java index 72d88557e5d..aeea0fcd229 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/PullHeaderRenderNode.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/PullHeaderRenderNode.java @@ -28,6 +28,11 @@ public PullHeaderRenderNode(int mId, HippyMap mPropsToUpdate, String className, super(mId, mPropsToUpdate, className, mRootView, componentManager, isLazyLoad); } + @Override + public boolean shouldSticky() { + return false; + } + /** * 通过类名的hashCode来定义type,计算出来是一个很大的值,几乎不会和前端类型重复 */ diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/RecyclerItemRenderNode.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/RecyclerItemRenderNode.java new file mode 100644 index 00000000000..e57ebc5c008 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/RecyclerItemRenderNode.java @@ -0,0 +1,47 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.mtt.hippy.uimanager; + +import android.view.View; +import com.tencent.mtt.hippy.HippyRootView; +import com.tencent.mtt.hippy.common.HippyMap; + +/** + * Created on 2021/4/25. + * Description + */ +public class RecyclerItemRenderNode extends ListItemRenderNode { + + public RecyclerItemRenderNode(int mId, HippyMap mPropsToUpdate, String className, HippyRootView mRootView, + ControllerManager componentManager, boolean isLazyLoad) { + super(mId, mPropsToUpdate, className, mRootView, componentManager, isLazyLoad); + } + + /** + * y值是前端传入的,前端没有复用的概念,所有y是整个list长度的y值,并不是recyclerView的排版的y。 + * 真正意义上面的y是排版到屏幕范围以内的y,也是子view相对于recyclerView的起始位置的y,也就是子view的top + * 系统的recyclerView在刷新list前,layoutManager会调用anchorInfo.assignFromView,取第一个view计算当前的 + * anchorInfo,如果整个地方把y值修改了,导致anchorInfo会取不对. + * 这里保证updateLayout不要改变已经挂在到RecyclerView的view的top + */ + @Override + public void updateLayout(int x, int y, int w, int h) { + View renderView = mComponentManager.mControllerRegistry.getView(mId); + y = renderView != null ? renderView.getTop() : 0; + super.updateLayout(x, y, w, h); + mY = y; + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/RenderManager.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/RenderManager.java index 080af324427..e98e1cca28e 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/RenderManager.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/RenderManager.java @@ -17,7 +17,6 @@ import android.text.TextUtils; import android.util.SparseArray; - import com.tencent.mtt.hippy.HippyAPIProvider; import com.tencent.mtt.hippy.HippyEngineContext; import com.tencent.mtt.hippy.HippyRootView; @@ -26,8 +25,8 @@ import com.tencent.mtt.hippy.dom.node.DomNode; import com.tencent.mtt.hippy.dom.node.NodeProps; import com.tencent.mtt.hippy.modules.Promise; +import com.tencent.mtt.hippy.runtime.builtins.JSObject; import com.tencent.mtt.hippy.utils.LogUtils; - import java.util.ArrayList; import java.util.List; @@ -104,10 +103,16 @@ void addNullUINodeIfNeeded(RenderNode renderNode) { public void updateLayout(int id, int x, int y, int w, int h) { LogUtils.d("RenderManager", "updateLayout ID " + id); - RenderNode uiNode = mNodes.get(id); - uiNode.updateLayout(x, y, w, h); - - addUpdateNodeIfNeeded(uiNode); + RenderNode node = mNodes.get(id); + if (node != null) { + node.updateLayout(x, y, w, h); + addUpdateNodeIfNeeded(node); + if (node.getParent() instanceof ScrollViewRenderNode) { + // ScrollView doesn't receive updateLayout when its content changes, + // so we specifically call addUpdateNodeIfNeeded() + addUpdateNodeIfNeeded(node.getParent()); + } + } } public void updateNode(int id, HippyMap map) { @@ -131,6 +136,7 @@ public void moveNode(ArrayList moveIds, int pId, int id) { } // mContext.getGlobalConfigs().getLogAdapter().log(TAG,"render moveNode pId:" + pId+" id: "+id+" moveids:"+moveIds.toString()); parentNode.move(arrayList, id); + addUpdateNodeIfNeeded(parentNode); addUpdateNodeIfNeeded(newParent); } @@ -146,7 +152,7 @@ public void deleteNode(int id) { RenderNode uiNode = mNodes.get(id); uiNode.setDelete(true); - if (uiNode.mParent != null && mControllerManager.hasView(id)) { + if (uiNode.mParent != null) { uiNode.mParent.addDeleteId(id, uiNode); addUpdateNodeIfNeeded(uiNode.mParent); } else if (TextUtils.equals(NodeProps.ROOT_NODE, uiNode.getClassName())) { @@ -186,6 +192,10 @@ public void batch() { LogUtils.d("RenderManager", "do batch size " + mUIUpdateNodes.size()); // mContext.getGlobalConfigs().getLogAdapter().log(TAG,"do batch size " + mShouldUpdateNodes.size()); + for (int i = 0; i < mUIUpdateNodes.size(); i++) { + RenderNode uiNode = mUIUpdateNodes.get(i); + uiNode.batchStart(); + } for (int i = 0; i < mUIUpdateNodes.size(); i++) { mUIUpdateNodes.get(i).createView(); } @@ -213,19 +223,18 @@ public void batch() { } private void deleteSelfFromParent(RenderNode uiNode) { - - LogUtils.d("RenderManager", "delete RenderNode " + uiNode.mId + " class " + uiNode.mClassName); - // mContext.getGlobalConfigs().getLogAdapter().log(TAG,"delete RenderNode " + uiNode.mId + " class " + uiNode.mClassName); - if (uiNode.mParent != null) { - uiNode.mParent.removeChild(uiNode); + if (uiNode == null) { + return; } - - mNodes.remove(uiNode.mId); - + LogUtils.d("RenderManager", "delete RenderNode " + uiNode.mId + " class " + uiNode.mClassName); int childCount = uiNode.getChildCount(); for (int i = 0; i < childCount; i++) { deleteSelfFromParent(uiNode.getChildAt(0)); } + if (uiNode.mParent != null) { + uiNode.mParent.removeChild(uiNode); + } + mNodes.remove(uiNode.mId); } public DomNode createStyleNode(String className, boolean isVirtual, int id, int rootId) { @@ -248,12 +257,18 @@ public void replaceID(int oldId, int newId) { } - public void measureInWindow(int id, Promise promise) { + public void measureInWindow(int id, JSObject options, Promise promise) { RenderNode renderNode = mNodes.get(id); if (renderNode == null) { - promise.reject("this view is null"); + if (options.get(RenderNode.KEY_COMPATIBLE) == Boolean.TRUE) { + promise.resolve("this view is null"); + } else { + JSObject result = new JSObject(); + result.set(RenderNode.KEY_ERR_MSG, "this node is null"); + promise.resolve(result); + } } else { - renderNode.measureInWindow(promise); + renderNode.measureInWindow(options, promise); addNullUINodeIfNeeded(renderNode); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/RenderNode.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/RenderNode.java index 9e59bf7a8bd..8f8e73e0732 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/RenderNode.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/RenderNode.java @@ -16,6 +16,7 @@ package com.tencent.mtt.hippy.uimanager; import android.text.TextUtils; +import android.util.Pair; import android.util.SparseArray; import android.view.View; import com.tencent.mtt.hippy.HippyRootView; @@ -23,13 +24,20 @@ import com.tencent.mtt.hippy.common.HippyMap; import com.tencent.mtt.hippy.dom.node.NodeProps; import com.tencent.mtt.hippy.modules.Promise; +import com.tencent.mtt.hippy.runtime.builtins.JSObject; import com.tencent.mtt.hippy.utils.LogUtils; - -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Set; @SuppressWarnings({"deprecation", "unused"}) public class RenderNode { + public static final String KEY_COMPATIBLE = "compatible"; + public static final String KEY_REL_TO_CONTAINER = "relToContainer"; + public static final String KEY_ERR_MSG = "errMsg"; final int mId; int mX; int mY; @@ -46,7 +54,7 @@ public class RenderNode { SparseArray mDeletedIdIndexMap; - List mMeasureInWindows = null; + List> mMeasureInWindows = null; Object mTextExtra; Object mTextExtraUpdate; @@ -277,7 +285,6 @@ private boolean shouldUpdateView() { return mComponentManager.hasView(mId); } - protected void addChildToPendingList(RenderNode renderNode) { mChildPendingList.add(renderNode); } @@ -344,8 +351,13 @@ public int compare(RenderNode o1, RenderNode o2) { } if (mMeasureInWindows != null && mMeasureInWindows.size() > 0) { for (int i = 0; i < mMeasureInWindows.size(); i++) { - Promise promise = mMeasureInWindows.get(i); - mComponentManager.measureInWindow(mId, promise); + Pair pair = mMeasureInWindows.get(i); + if (pair.first.get(KEY_COMPATIBLE) == Boolean.TRUE) { + mComponentManager.measureInWindow(mId, pair.second); + } else { + boolean relToContainer = pair.first.get(KEY_REL_TO_CONTAINER) == Boolean.TRUE; + mComponentManager.getBoundingClientRect(mId, mRootView, relToContainer, pair.second); + } } mMeasureInWindows.clear(); mMeasureInWindows = null; @@ -369,11 +381,11 @@ public void updateLayout(int x, int y, int w, int h) { mHasUpdateLayout = true; } - public void measureInWindow(Promise promise) { + public void measureInWindow(JSObject options, Promise promise) { if (mMeasureInWindows == null) { mMeasureInWindows = new ArrayList<>(); } - mMeasureInWindows.add(promise); + mMeasureInWindows.add(new Pair<>(options, promise)); } @@ -411,6 +423,11 @@ public boolean isDelete() { return mIsDelete; } + public void batchStart() { + if (!mIsLazyLoad && !mIsDelete) { + mComponentManager.onBatchStart(mClassName, mId); + } + } public void batchComplete() { if (!mIsLazyLoad && !mIsDelete) { mComponentManager.onBatchComplete(mClassName, mId); diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/ScrollViewRenderNode.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/ScrollViewRenderNode.java new file mode 100644 index 00000000000..af165dcd482 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/ScrollViewRenderNode.java @@ -0,0 +1,28 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.uimanager; + +import com.tencent.mtt.hippy.HippyRootView; +import com.tencent.mtt.hippy.common.HippyMap; + +public class ScrollViewRenderNode extends RenderNode { + + public ScrollViewRenderNode(int mId, HippyMap mPropsToUpdate, String className, + HippyRootView mRootView, ControllerManager componentManager, boolean isLazyLoad) { + super(mId, mPropsToUpdate, className, mRootView, componentManager, isLazyLoad); + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/BuglyUtils.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/BuglyUtils.java new file mode 100644 index 00000000000..9997a8ef5fc --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/BuglyUtils.java @@ -0,0 +1,45 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.utils; + +import android.content.Context; +import android.content.SharedPreferences; +import androidx.annotation.Nullable; +import com.tencent.mtt.hippy.BuildConfig; + +public class BuglyUtils { + + private static final String BUGLY_KEY = "BuglySdkInfos"; + private static final String SDK_APP_ID = "8aa644f958"; + private static boolean sHasCommitted = false; + + public static void registerSdkAppIdIfNeeded(@Nullable Context context) { + if (!BuildConfig.ENABLE_BUGLY_REPORT || sHasCommitted || context == null) { + return; + } + Context appContext = context.getApplicationContext(); + SharedPreferences settings = appContext.getSharedPreferences(BUGLY_KEY, Context.MODE_PRIVATE); + String version = settings.getString(SDK_APP_ID, null); + if (!BuildConfig.LIBRARY_VERSION.equals(version)) { + SharedPreferences.Editor editor = settings.edit(); + editor.putString(SDK_APP_ID, BuildConfig.LIBRARY_VERSION); + sHasCommitted = editor.commit(); + } else { + sHasCommitted = true; + } + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/ContextHolder.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/ContextHolder.java index c2b7701489e..f871e6e1a31 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/ContextHolder.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/ContextHolder.java @@ -15,19 +15,44 @@ */ package com.tencent.mtt.hippy.utils; +import android.app.ActivityManager; +import android.app.ActivityManager.RunningAppProcessInfo; import android.content.Context; +import java.util.List; public class ContextHolder { - private static Context appContext; + private static Context sAppContext; - public static void initAppContext(Context context) { - if (context != null && appContext == null) { - appContext = context.getApplicationContext(); + public static void initAppContext(Context context) { + if (context != null && sAppContext == null) { + sAppContext = context.getApplicationContext(); + } } - } - public static Context getAppContext() { - return appContext; - } + public static Context getAppContext() { + return sAppContext; + } + + public static boolean isAppOnBackground() { + if (sAppContext == null) { + return true; + } + ActivityManager activityManager = (ActivityManager) sAppContext + .getSystemService(Context.ACTIVITY_SERVICE); + List appProcesses = activityManager.getRunningAppProcesses(); + if (appProcesses == null) { + return true; + } + int myPid = android.os.Process.myPid(); + for (RunningAppProcessInfo appProcess : appProcesses) { + if (appProcess.pid == myPid) { + if (appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { + return false; + } + return true; + } + } + return true; + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/DimensionsUtil.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/DimensionsUtil.java index 9ede14aab49..2d6e833004e 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/DimensionsUtil.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/DimensionsUtil.java @@ -19,14 +19,17 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; +import android.graphics.Insets; import android.os.Build; import android.provider.Settings; +import android.view.WindowInsets; import androidx.annotation.NonNull; import android.text.TextUtils; import android.util.DisplayMetrics; import android.view.Display; import android.view.WindowManager; +import android.view.WindowMetrics; import com.tencent.mtt.hippy.common.HippyMap; import java.lang.reflect.Field; @@ -120,6 +123,54 @@ public static int getNavigationBarHeight(Context context) { return result; } + public static int getStatusBarHeight() { + return getStatusBarHeight(ContextHolder.getAppContext()); + } + + public static int getStatusBarHeight(Context context) { + assert context != null; + if (STATUS_BAR_HEIGHT > 0) { + return STATUS_BAR_HEIGHT; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + WindowMetrics windowMetrics = wm.getCurrentWindowMetrics(); + WindowInsets windowInsets = windowMetrics.getWindowInsets(); + Insets insets = windowInsets + .getInsetsIgnoringVisibility(WindowInsets.Type.statusBars()); + STATUS_BAR_HEIGHT = insets.top; + } else { + Class c; + Object obj; + Field field; + int x; + try { + c = Class.forName("com.android.internal.R$dimen"); + obj = c.newInstance(); + field = c.getField("status_bar_height"); + //noinspection ConstantConditions + x = Integer.parseInt(field.get(obj).toString()); + STATUS_BAR_HEIGHT = context.getResources().getDimensionPixelSize(x); + } catch (Exception e) { + STATUS_BAR_HEIGHT = -1; + e.printStackTrace(); + } + + if (STATUS_BAR_HEIGHT < 1) { + try { + int statebarH_id = context.getResources() + .getIdentifier("statebar_height", "dimen", context.getPackageName()); + STATUS_BAR_HEIGHT = Math.round(context.getResources().getDimension(statebarH_id)); + } catch (NotFoundException e) { + LogUtils.d("DimensionsUtil", "getDimensions: " + e.getMessage()); + } + } + } + + return STATUS_BAR_HEIGHT; + } + public static HippyMap getDimensions(int windowWidth, int windowHeight, Context context, boolean shouldUseScreenDisplay) { if (context == null) { @@ -152,34 +203,7 @@ public static HippyMap getDimensions(int windowWidth, int windowHeight, Context // construct param HippyMap dimensionMap = new HippyMap(); - if (STATUS_BAR_HEIGHT < 0) { - Class c; - Object obj; - Field field; - int x; - try { - c = Class.forName("com.android.internal.R$dimen"); - obj = c.newInstance(); - field = c.getField("status_bar_height"); - //noinspection ConstantConditions - x = Integer.parseInt(field.get(obj).toString()); - STATUS_BAR_HEIGHT = context.getResources().getDimensionPixelSize(x); - } catch (Exception e) { - STATUS_BAR_HEIGHT = -1; - e.printStackTrace(); - } - - if (STATUS_BAR_HEIGHT < 1) { - try { - int statebarH_id = context.getResources() - .getIdentifier("statebar_height", "dimen", context.getPackageName()); - STATUS_BAR_HEIGHT = Math.round(context.getResources().getDimension(statebarH_id)); - } catch (NotFoundException e) { - LogUtils.d("DimensionsUtil", "getDimensions: " + e.getMessage()); - } - } - } - + getStatusBarHeight(context); int navigationBarHeight = getNavigationBarHeight(context); HippyMap windowDisplayMetricsMap = new HippyMap(); diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/FileUtils.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/FileUtils.java index 19de12437bf..63224e3aeac 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/FileUtils.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/FileUtils.java @@ -71,24 +71,35 @@ public static byte[] readFileToByteArray(String filePath) { if (!file.exists()) { return null; } else { + FileInputStream fileReader = null; + ByteArrayOutputStream byteArrayOutputStream = null; try { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - FileInputStream fileReader = new FileInputStream(file); - try { - byte[] buffer = new byte[4096]; - int len; - while ((len = fileReader.read(buffer, 0, buffer.length)) != -1) { - byteArrayOutputStream.write(buffer, 0, len); - } - } catch (Throwable e) { - LogUtils.d("FileUtils", "readFileToByteArray: " + e.getMessage()); + byteArrayOutputStream = new ByteArrayOutputStream(); + fileReader = new FileInputStream(file); + byte[] buffer = new byte[4096]; + int len; + while ((len = fileReader.read(buffer, 0, buffer.length)) != -1) { + byteArrayOutputStream.write(buffer, 0, len); } - - fileReader.close(); data = byteArrayOutputStream.toByteArray(); - byteArrayOutputStream.close(); } catch (Exception e) { e.printStackTrace(); + } finally { + if (fileReader != null) { + try { + fileReader.close(); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + if (byteArrayOutputStream != null) { + try { + byteArrayOutputStream.close(); + } catch (Throwable e) { + e.printStackTrace(); + } + } } } return data; diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/FocusFinder.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/FocusFinder.java new file mode 100644 index 00000000000..c5665f02fba --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/FocusFinder.java @@ -0,0 +1,803 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.utils; + +import android.graphics.Rect; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; + +/** + * 自定义FocusFinder,处理tv特殊的寻焦逻辑 + */ +public class FocusFinder { + + private static final ThreadLocal tlFocusFinder = + new ThreadLocal() { + @Override + protected FocusFinder initialValue() { + return new FocusFinder(); + } + }; + + /** + * Get the focus finder for this thread. + */ + public static FocusFinder getInstance() { + return tlFocusFinder.get(); + } + + final Rect mFocusedRect = new Rect(); + final Rect mOtherRect = new Rect(); + final Rect mBestCandidateRect = new Rect(); + private final FocusFinder.FocusSorter mFocusSorter = new FocusFinder.FocusSorter(); + + private final ArrayList mTempList = new ArrayList(); + + // enforce thread local access + FocusFinder() { + } + + /** + * Find the next view to take focus in root's descendants, starting from the view + * that currently is focused. + * + * @param root Contains focused. Cannot be null. + * @param focused Has focus now. + * @param direction Direction to look. + * @return The next focusable view, or null if none exists. + */ + public final View findNextFocus(ViewGroup root, View focused, int direction) { + return findNextFocus(root, focused, null, direction); + } + + private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) { + View next = null; + ViewGroup effectiveRoot = root; + if (focused != null) { + next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction); + } + if (next != null) { + return next; + } + ArrayList focusables = mTempList; + try { + focusables.clear(); + effectiveRoot.addFocusables(focusables, direction); + if (!focusables.isEmpty()) { + next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables); + } + } finally { + focusables.clear(); + } + return next; + } + + private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, + int direction, ArrayList focusables) { + if (focused != null) { + if (focusedRect == null) { + focusedRect = mFocusedRect; + } + // fill in interesting rect from focused + focused.getFocusedRect(focusedRect); + root.offsetDescendantRectToMyCoords(focused, focusedRect); + } else { + if (focusedRect == null) { + focusedRect = mFocusedRect; + // make up a rect at top left or bottom right of root + makeUpRectOfRoot(root, focusedRect, direction); + } + } + + return getView(root, focused, focusedRect, direction, focusables); + } + + private View getView(ViewGroup root, View focused, Rect focusedRect, int direction, ArrayList focusables) { + if (isRelativeDirection(direction)) { + return findNextFocusInRelativeDirection(focusables, root, focused, focusedRect, + direction); + } else if (isAbsoluteDirection(direction)) { + return findNextFocusInAbsoluteDirection(focusables, root, focused, + focusedRect, direction); + } + throw new IllegalArgumentException("Unknown direction: " + direction); + } + + private boolean isAbsoluteDirection(int direction) { + return direction == View.FOCUS_UP || direction == View.FOCUS_DOWN || direction == View.FOCUS_LEFT + || direction == View.FOCUS_RIGHT; + } + + private boolean isRelativeDirection(int direction) { + return direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD; + } + + private void makeUpRectOfRoot(ViewGroup root, Rect focusedRect, int direction) { + if (direction == View.FOCUS_RIGHT || direction == View.FOCUS_DOWN) { + setFocusTopLeft(root, focusedRect); + } else if (direction == View.FOCUS_LEFT || direction == View.FOCUS_UP) { + setFocusBottomRight(root, focusedRect); + } + } + + /** + * Find the next view to take focus in root's descendants, searching from + * a particular rectangle in root's coordinates. + * + * @param root Contains focusedRect. Cannot be null. + * @param focusedRect The starting point of the search. + * @param direction Direction to look. + * @return The next focusable view, or null if none exists. + */ + public View findNextFocusFromRect(ViewGroup root, Rect focusedRect, int direction) { + mFocusedRect.set(focusedRect); + return findNextFocus(root, null, mFocusedRect, direction); + } + + private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) { + // check for user specified next focus + View userSetNextFocus = findUserSetNextFocus(root, focused, direction); + int findTimes = 0; + while (userSetNextFocus != null) { + if (isVisible(userSetNextFocus)) { + return userSetNextFocus; + } + + if (userSetNextFocus == focused) { + return focused; + } + + if (findTimes >= 10) { + break; + } + userSetNextFocus = findUserSetNextFocus(root, userSetNextFocus, direction); + findTimes++; + } + return null; + } + + private View findUserSetNextFocus(ViewGroup root, View focused, int direction) { + int id = View.NO_ID; + switch (direction) { + case View.FOCUS_LEFT: + id = focused.getNextFocusLeftId(); + break; + case View.FOCUS_RIGHT: + id = focused.getNextFocusRightId(); + break; + case View.FOCUS_UP: + id = focused.getNextFocusUpId(); + break; + case View.FOCUS_DOWN: + id = focused.getNextFocusDownId(); + break; + case View.FOCUS_FORWARD: + id = focused.getNextFocusForwardId(); + if (id == View.NO_ID) { + id = focused.getNextFocusRightId(); + if (id == View.NO_ID) { + id = focused.getNextFocusDownId(); + } + } + break; + default: + break; + } + + if (id != View.NO_ID) { + return root.findViewById(id); + } + return null; + } + + private boolean isVisible(View view) { + View root = view.getRootView(); + + boolean ret = true; + View v = view; + while (v != null && v != root) { + if (v.getVisibility() != View.VISIBLE) { + ret = false; + break; + } + + ViewParent parent = v.getParent(); + if (parent instanceof ViewGroup) { + v = (View) parent; + } else { + ret = false; + break; + } + } + + return ret; + } + + private View findNextFocusInRelativeDirection(ArrayList focusables, ViewGroup root, + View focused, Rect focusedRect, int direction) { + + final int count = focusables.size(); + switch (direction) { + case View.FOCUS_FORWARD: + return getNextFocusable(focused, focusables, count); + case View.FOCUS_BACKWARD: + return getPreviousFocusable(focused, focusables, count); + default: + break; + } + return focusables.get(count - 1); + } + + private void setFocusBottomRight(ViewGroup root, Rect focusedRect) { + final int rootBottom = root.getScrollY() + root.getHeight(); + final int rootRight = root.getScrollX() + root.getWidth(); + focusedRect.set(rootRight, rootBottom, rootRight, rootBottom); + } + + private void setFocusTopLeft(ViewGroup root, Rect focusedRect) { + final int rootTop = root.getScrollY(); + final int rootLeft = root.getScrollX(); + focusedRect.set(rootLeft, rootTop, rootLeft, rootTop); + } + + View findNextFocusInAbsoluteDirection(ArrayList focusables, ViewGroup root, View focused, + Rect focusedRect, int direction) { + // initialize the best candidate to something impossible + // (so the first plausible view will become the best choice) + mBestCandidateRect.set(focusedRect); + if (direction == View.FOCUS_LEFT) { + mBestCandidateRect.offset(focusedRect.width() + 1, 0); + } else if (direction == View.FOCUS_RIGHT) { + mBestCandidateRect.offset(-(focusedRect.width() + 1), 0); + } else if (direction == View.FOCUS_UP) { + mBestCandidateRect.offset(0, focusedRect.height() + 1); + } else if (direction == View.FOCUS_DOWN) { + mBestCandidateRect.offset(0, -(focusedRect.height() + 1)); + } + + View closest = null; + + float scale = getFocusScale(focused, direction); + + int numFocusables = focusables.size(); + for (int i = 0; i < numFocusables; i++) { + View focusable = focusables.get(i); + + // only interested in other non-root views + if (focusable == focused || focusable == root) { + continue; + } + + // get focus bounds of other view in same coordinate system + focusable.getFocusedRect(mOtherRect); + root.offsetDescendantRectToMyCoords(focusable, mOtherRect); + + if (isBetterCandidate(scale, direction, focusedRect, mOtherRect, mBestCandidateRect)) { + mBestCandidateRect.set(mOtherRect); + closest = focusable; + } + } + return closest; + } + + private static float getFocusScale(View focused, int direction) { + return 0.5f; + } + + private static View getNextFocusable(View focused, ArrayList focusables, int count) { + if (focused != null) { + int position = focusables.lastIndexOf(focused); + if (position >= 0 && position + 1 < count) { + return focusables.get(position + 1); + } + } + if (!focusables.isEmpty()) { + return focusables.get(0); + } + return null; + } + + private static View getPreviousFocusable(View focused, ArrayList focusables, int count) { + if (focused != null) { + int position = focusables.indexOf(focused); + if (position > 0) { + return focusables.get(position - 1); + } + } + if (!focusables.isEmpty()) { + return focusables.get(count - 1); + } + return null; + } + + private static View getNextKeyboardNavigationCluster( + View root, + View currentCluster, + List clusters, + int count) { + if (currentCluster == null) { + // The current cluster is the default one. + // The next cluster after the default one is the first one. + // Note that the caller guarantees that 'clusters' is not empty. + return clusters.get(0); + } + + final int position = clusters.lastIndexOf(currentCluster); + if (position >= 0 && position + 1 < count) { + // Return the next non-default cluster if we can find it. + return clusters.get(position + 1); + } + + // The current cluster is the last one. The next one is the default one, i.e. the + // root. + return root; + } + + private static View getPreviousKeyboardNavigationCluster( + View root, + View currentCluster, + List clusters, + int count) { + if (currentCluster == null) { + // The current cluster is the default one. + // The previous cluster before the default one is the last one. + // Note that the caller guarantees that 'clusters' is not empty. + return clusters.get(count - 1); + } + + final int position = clusters.indexOf(currentCluster); + if (position > 0) { + // Return the previous non-default cluster if we can find it. + return clusters.get(position - 1); + } + + // The current cluster is the first one. The previous one is the default one, i.e. + // the root. + return root; + } + + /** + * Is rect1 a better candidate than rect2 for a focus search in a particular + * direction from a source rect? This is the core routine that determines + * the order of focus searching. + * + * @param direction the direction (up, down, left, right) + * @param source The source we are searching from + * @param rect1 The candidate rectangle + * @param rect2 The current best candidate. + * @return Whether the candidate is the new best. + */ + boolean isBetterCandidate(float scale, int direction, Rect source, Rect rect1, Rect rect2) { + + // to be a better candidate, need to at least be a candidate in the first + // place :) + if (!isCandidate(source, rect1, direction)) { + return false; + } + + // we know that rect1 is a candidate.. if rect2 is not a candidate, + // rect1 is better + if (!isCandidate(source, rect2, direction)) { + return true; + } + + // if rect1 is better by beam, it wins + if (beamBeats(direction, source, rect1, rect2)) { + return true; + } + + // if rect2 is better, then rect1 cant' be :) + if (beamBeats(direction, source, rect2, rect1)) { + return false; + } + + if (middle(direction, source, rect2, rect1)) { + return true; + } + + if (middle(direction, source, rect1, rect2)) { + return false; + } + + // otherwise, do fudge-tastic comparison of the major and minor axis + return (getWeightedDistanceFor( + majorAxisDistance(direction, source, rect1), + minorAxisDistance(scale, direction, source, rect1)) + < getWeightedDistanceFor( + majorAxisDistance(direction, source, rect2), + minorAxisDistance(scale, direction, source, rect2))); + } + + /** + * 判断rect2 是否在source和rect1中间 + * + * @param direction + * @param source + * @param rect1 + * @param rect2 + * @return + */ + boolean middle(int direction, Rect source, Rect rect1, Rect rect2) { + switch (direction) { + case View.FOCUS_LEFT: + return rect2.right < source.centerX() && rect1.centerX() < rect2.left; + case View.FOCUS_RIGHT: + return rect2.left > source.centerX() && rect1.centerX() > rect2.right; + + case View.FOCUS_UP: + return rect2.bottom < source.centerY() && rect1.centerY() < rect2.top; + case View.FOCUS_DOWN: + return rect2.top > source.centerY() && rect1.centerY() > rect2.bottom; + default: + break; + } + throw new IllegalArgumentException("direction must be one of " + + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); + } + + /** + * One rectangle may be another candidate than another by virtue of being + * exclusively in the beam of the source rect. + * + * @return Whether rect1 is a better candidate than rect2 by virtue of it being in src's + * beam + */ + boolean beamBeats(int direction, Rect source, Rect rect1, Rect rect2) { + final boolean rect1InSrcBeam = beamsOverlap(direction, source, rect1); + final boolean rect2InSrcBeam = beamsOverlap(direction, source, rect2); + + // if rect1 isn't exclusively in the src beam, it doesn't win + if (rect2InSrcBeam || !rect1InSrcBeam) { + return false; + } + + // we know rect1 is in the beam, and rect2 is not + + // if rect1 is to the direction of, and rect2 is not, rect1 wins. + // for example, for direction left, if rect1 is to the left of the source + // and rect2 is below, then we always prefer the in beam rect1, since rect2 + // could be reached by going down. + if (!isToDirectionOf(direction, source, rect2)) { + return true; + } + + // for horizontal directions, being exclusively in beam always wins + if ((direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT)) { + return true; + } + + // for vertical directions, beams only beat up to a point: + // now, as long as rect2 isn't completely closer, rect1 wins + // e.g for direction down, completely closer means for rect2's top + // edge to be closer to the source's top edge than rect1's bottom edge. + return (majorAxisDistance(direction, source, rect1) + < majorAxisDistanceToFarEdge(direction, source, rect2)); + } + + /** + * Fudge-factor opportunity: how to calculate distance given major and minor + * axis distances. Warning: this fudge factor is finely tuned, be sure to + * run all focus tests if you dare tweak it. + */ + int getWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance) { + return 26 * majorAxisDistance * majorAxisDistance + + minorAxisDistance * minorAxisDistance; + } + + /** + * Is destRect a candidate for the next focus given the direction? This + * checks whether the dest is at least partially to the direction of (e.g left of) + * from source. + *

+ * Includes an edge case for an empty rect (which is used in some cases when + * searching from a point on the screen). + */ + boolean isCandidate(Rect srcRect, Rect destRect, int direction) { + if (direction == View.FOCUS_LEFT) { + return isCandidateLeft(srcRect, destRect); + } else if (direction == View.FOCUS_RIGHT) { + return isCandidateRight(srcRect, destRect); + } else if (direction == View.FOCUS_UP) { + return isCandidateUp(srcRect, destRect); + } else if (direction == View.FOCUS_DOWN) { + return isCandidateDown(srcRect, destRect); + } + throw new IllegalArgumentException("direction must be one of " + + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); + } + + private boolean isCandidateDown(Rect srcRect, Rect destRect) { + return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top) + && srcRect.bottom < destRect.bottom; + } + + private boolean isCandidateUp(Rect srcRect, Rect destRect) { + return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom) + && srcRect.top > destRect.top; + } + + private boolean isCandidateRight(Rect srcRect, Rect destRect) { + return (srcRect.left < destRect.left || srcRect.right <= destRect.left) + && srcRect.right < destRect.right; + } + + private boolean isCandidateLeft(Rect srcRect, Rect destRect) { + return (srcRect.right > destRect.right || srcRect.left >= destRect.right) + && srcRect.left > destRect.left; + } + + /** + * Do the "beams" w.r.t the given direction's axis of rect1 and rect2 overlap? + * + * @param direction the direction (up, down, left, right) + * @param rect1 The first rectangle + * @param rect2 The second rectangle + * @return whether the beams overlap + */ + boolean beamsOverlap(int direction, Rect rect1, Rect rect2) { + switch (direction) { + case View.FOCUS_LEFT: + case View.FOCUS_RIGHT: + return (rect2.bottom >= rect1.top) && (rect2.top <= rect1.bottom); + case View.FOCUS_UP: + case View.FOCUS_DOWN: + return (rect2.right >= rect1.left) && (rect2.left <= rect1.right); + default: + break; + } + throw new IllegalArgumentException("direction must be one of " + + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); + } + + /** + * e.g for left, is 'to left of' + */ + boolean isToDirectionOf(int direction, Rect src, Rect dest) { + switch (direction) { + case View.FOCUS_LEFT: + return src.left >= dest.right; + case View.FOCUS_RIGHT: + return src.right <= dest.left; + case View.FOCUS_UP: + return src.top >= dest.bottom; + case View.FOCUS_DOWN: + return src.bottom <= dest.top; + default: + break; + } + throw new IllegalArgumentException("direction must be one of " + + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); + } + + /** + * @return The distance from the edge furthest in the given direction + * of source to the edge nearest in the given direction of dest. If the + * dest is not in the direction from source, return 0. + */ + static int majorAxisDistance(int direction, Rect source, Rect dest) { + return Math.max(0, majorAxisDistanceRaw(direction, source, dest)); + } + + static int majorAxisDistanceRaw(int direction, Rect source, Rect dest) { + switch (direction) { + case View.FOCUS_LEFT: + return source.left - dest.right; + case View.FOCUS_RIGHT: + return dest.left - source.right; + case View.FOCUS_UP: + return source.top - dest.bottom; + case View.FOCUS_DOWN: + return dest.top - source.bottom; + default: + break; + } + throw new IllegalArgumentException("direction must be one of " + + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); + } + + /** + * @return The distance along the major axis w.r.t the direction from the + * edge of source to the far edge of dest. If the + * dest is not in the direction from source, return 1 (to break ties with + * {@link #majorAxisDistance}). + */ + static int majorAxisDistanceToFarEdge(int direction, Rect source, Rect dest) { + return Math.max(1, majorAxisDistanceToFarEdgeRaw(direction, source, dest)); + } + + static int majorAxisDistanceToFarEdgeRaw(int direction, Rect source, Rect dest) { + switch (direction) { + case View.FOCUS_LEFT: + return source.left - dest.left; + case View.FOCUS_RIGHT: + return dest.right - source.right; + case View.FOCUS_UP: + return source.top - dest.top; + case View.FOCUS_DOWN: + return dest.bottom - source.bottom; + default: + break; + } + throw new IllegalArgumentException("direction must be one of " + + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); + } + + /** + * Find the distance on the minor axis w.r.t the direction to the nearest + * edge of the destination rectangle. + * + * @param direction the direction (up, down, left, right) + * @param source The source rect. + * @param dest The destination rect. + * @return The distance. + */ + static int minorAxisDistance(float scale, int direction, Rect source, Rect dest) { + if (direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT) { + // the distance between the center verticals + return Math.abs(((source.top + (int) (source.height() * scale)) - ((dest.top + dest.height() / 2)))); + } else if (direction == View.FOCUS_UP || direction == View.FOCUS_DOWN) { + // the distance between the center horizontals + return Math.abs(((source.left + (int) (source.width() * scale)) - ((dest.left + dest.width() / 2)))); + } + throw new IllegalArgumentException( + "direction must be one of {FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); + } + + /** + * Is destRect a candidate for the next touch given the direction? + */ + private boolean isTouchCandidate(int x, int y, Rect destRect, int direction) { + if (direction == View.FOCUS_LEFT) { + return isTouchCandidateLeft(x, y, destRect); + } else if (direction == View.FOCUS_RIGHT) { + return isTouchCandidateRight(x, y, destRect); + } else if (direction == View.FOCUS_UP) { + return isTouchCandidateUp(x, y, destRect); + } else if (direction == View.FOCUS_DOWN) { + return isTouchCandidateDown(x, y, destRect); + } + throw new IllegalArgumentException("direction must be one of " + + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); + } + + private boolean isTouchCandidateDown(int x, int y, Rect destRect) { + return destRect.top >= y && destRect.left <= x && x <= destRect.right; + } + + private boolean isTouchCandidateUp(int x, int y, Rect destRect) { + return destRect.top <= y && destRect.left <= x && x <= destRect.right; + } + + private boolean isTouchCandidateRight(int x, int y, Rect destRect) { + return destRect.left >= x && destRect.top <= y && y <= destRect.bottom; + } + + private boolean isTouchCandidateLeft(int x, int y, Rect destRect) { + return destRect.left <= x && destRect.top <= y && y <= destRect.bottom; + } + + private static final boolean isValidId(final int id) { + return id != 0 && id != View.NO_ID; + } + + static final class FocusSorter { + + private ArrayList mRectPool = new ArrayList<>(); + private int mLastPoolRect; + private int mRtlMult; + private HashMap mRectByView = null; + + private Comparator mTopsComparator = new Comparator() { + @Override + public int compare(View first, View second) { + if (first == second) { + return 0; + } + + Rect firstRect = mRectByView.get(first); + Rect secondRect = mRectByView.get(second); + + int result = firstRect.top - secondRect.top; + if (result == 0) { + return firstRect.bottom - secondRect.bottom; + } + return result; + } + }; + + private Comparator mSidesComparator = new Comparator() { + @Override + public int compare(View first, View second) { + if (first == second) { + return 0; + } + + Rect firstRect = mRectByView.get(first); + Rect secondRect = mRectByView.get(second); + + int result = firstRect.left - secondRect.left; + if (result == 0) { + return firstRect.right - secondRect.right; + } + return mRtlMult * result; + } + }; + + public void sort(View[] views, int start, int end, ViewGroup root, boolean isRtl) { + int count = end - start; + if (count < 2) { + return; + } + if (mRectByView == null) { + mRectByView = new HashMap<>(); + } + mRtlMult = isRtl ? -1 : 1; + for (int i = mRectPool.size(); i < count; ++i) { + mRectPool.add(new Rect()); + } + for (int i = start; i < end; ++i) { + Rect next = mRectPool.get(mLastPoolRect++); + views[i].getDrawingRect(next); + root.offsetDescendantRectToMyCoords(views[i], next); + mRectByView.put(views[i], next); + } + + // Sort top-to-bottom + Arrays.sort(views, start, count, mTopsComparator); + // Sweep top-to-bottom to identify rows + int sweepBottom = mRectByView.get(views[start]).bottom; + int rowStart = start; + int sweepIdx = start + 1; + for (; sweepIdx < end; ++sweepIdx) { + Rect currRect = mRectByView.get(views[sweepIdx]); + if (currRect.top >= sweepBottom) { + // Next view is on a new row, sort the row we've just finished left-to-right. + if ((sweepIdx - rowStart) > 1) { + Arrays.sort(views, rowStart, sweepIdx, mSidesComparator); + } + sweepBottom = currRect.bottom; + rowStart = sweepIdx; + } else { + // Next view vertically overlaps, we need to extend our "row height" + sweepBottom = Math.max(sweepBottom, currRect.bottom); + } + } + // Sort whatever's left (final row) left-to-right + if ((sweepIdx - rowStart) > 1) { + Arrays.sort(views, rowStart, sweepIdx, mSidesComparator); + } + + mLastPoolRect = 0; + mRectByView.clear(); + } + } + + /** + * Public for testing. + * + * @hide + */ + public static void sort(View[] views, int start, int end, ViewGroup root, boolean isRtl) { + getInstance().mFocusSorter.sort(views, start, end, root, isRtl); + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/PixelUtil.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/PixelUtil.java index f8992a7da98..4d082b888da 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/PixelUtil.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/PixelUtil.java @@ -15,33 +15,42 @@ */ package com.tencent.mtt.hippy.utils; +import android.content.Context; import android.util.DisplayMetrics; import android.util.TypedValue; +import android.view.Display; +import android.view.WindowManager; @SuppressWarnings({"unused"}) public class PixelUtil { - - static DisplayMetrics sMetrics = null; - + private static DisplayMetrics sDisplayMetrics = null; private static DisplayMetrics getMetrics() { - if (sMetrics == null) { - sMetrics = ContextHolder.getAppContext().getResources().getDisplayMetrics(); + if (sDisplayMetrics == null) { + sDisplayMetrics = ContextHolder.getAppContext().getResources().getDisplayMetrics(); } - return sMetrics; + return sDisplayMetrics; } + /** Set display metrics, call by host app */ + @SuppressWarnings("unused") + public static void setDisplayMetrics(DisplayMetrics metrics) { + sDisplayMetrics = metrics; + } + /** Convert from dp to px impl */ public static float dp2px(float value) { return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, getMetrics()); } + /** Convert from dp to px */ public static float dp2px(double value) { return dp2px((float) value); } + /** Convert from px to dp */ public static float px2dp(float value) { - return value / getMetrics().density; + return value / getDensity(); } public static float sp2px(float value) { diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/StrictFocusFinder.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/StrictFocusFinder.java new file mode 100644 index 00000000000..a21d698e075 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/StrictFocusFinder.java @@ -0,0 +1,69 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.utils; + +import android.graphics.Rect; +import android.view.View; + +/** + * 使用严格模式搜索焦点 + *

+ * 1. 水平查找焦点时,要求竖直方向必须有重叠部分 + */ +public class StrictFocusFinder extends FocusFinder { + + private static final ThreadLocal tlFocusFinder = + new ThreadLocal() { + @Override + protected FocusFinder initialValue() { + return new StrictFocusFinder(); + } + }; + + /** + * Get the focus finder for this thread. + */ + public static FocusFinder getInstance() { + return tlFocusFinder.get(); + } + + @Override + boolean isBetterCandidate(float scale, int direction, Rect source, Rect rect1, Rect rect2) { + if (isNonOverlapped(source, rect1, direction)) { + return false; + } + return super.isBetterCandidate(scale, direction, source, rect1, rect2); + } + + /** + * 判断是否目标区域和src是否没有重叠区域 + */ + boolean isNonOverlapped(Rect srcRect, Rect destRect, int direction) { + switch (direction) { + case View.FOCUS_LEFT: + case View.FOCUS_RIGHT: + return srcRect.top > destRect.bottom || srcRect.bottom < destRect.top; + case View.FOCUS_UP: + case View.FOCUS_DOWN: + return false; + default: + break; + } + throw new IllegalArgumentException("direction must be one of " + + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/TimeMonitor.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/TimeMonitor.java index 15e333285f6..963e3401ff3 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/TimeMonitor.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/utils/TimeMonitor.java @@ -13,78 +13,132 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.tencent.mtt.hippy.utils; import android.os.SystemClock; import android.text.TextUtils; import com.tencent.mtt.hippy.adapter.monitor.HippyEngineMonitorEvent; - +import com.tencent.mtt.hippy.adapter.monitor.HippyEngineMonitorPoint; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; public class TimeMonitor { - long mStartTime; - int mTotalTime; - final boolean mEnable; - HippyEngineMonitorEvent mCurrentEvent; - List mEvents; + private static final long SYS_TIME_DIFF = System.currentTimeMillis() - SystemClock.elapsedRealtime(); + private final boolean mEnable; + private final ConcurrentHashMap mStandardPoints = new ConcurrentHashMap<>(); + private final ConcurrentHashMap mCustomPoints = new ConcurrentHashMap<>(); + private long mStartTime; + private int mTotalTime; + private HippyEngineMonitorEvent mCurrentEvent; + private List mEvents; + private TimeMonitor mParent; - public TimeMonitor(boolean enable) { - mEnable = enable; - } + public TimeMonitor(boolean enable) { + mEnable = enable; + } - public void startEvent(String event) { - if (!mEnable) { - return; + public void setParent(TimeMonitor parent) { + mParent = parent; } - if (mCurrentEvent != null) { - mCurrentEvent.endTime = System.currentTimeMillis(); - mEvents.add(mCurrentEvent); - LogUtils.d("hippy", "hippy endEvent: " + mCurrentEvent.eventName); + + public void startEvent(String event) { + if (!mEnable) { + return; + } + if (mCurrentEvent != null) { + mCurrentEvent.endTime = currentTimeMillis(); + mEvents.add(mCurrentEvent); + LogUtils.d("hippy", "hippy endEvent: " + mCurrentEvent.eventName); + } + + if (TextUtils.isEmpty(event)) { + return; + } + + mCurrentEvent = new HippyEngineMonitorEvent(); + mCurrentEvent.eventName = event; + mCurrentEvent.startTime = currentTimeMillis(); + LogUtils.d("hippy", "hippy startEvent: " + event); } - if (TextUtils.isEmpty(event)) { - return; + public void begine() { + if (!mEnable) { + return; + } + mStartTime = currentTimeMillis(); + mCurrentEvent = null; + if (mEvents == null) { + mEvents = Collections.synchronizedList(new ArrayList()); + } + mEvents.clear(); + mTotalTime = 0; } - mCurrentEvent = new HippyEngineMonitorEvent(); - mCurrentEvent.eventName = event; - mCurrentEvent.startTime = System.currentTimeMillis(); - LogUtils.d("hippy", "hippy startEvent: " + event); - } + public void end() { + if (!mEnable) { + return; + } + if (mCurrentEvent != null) { + mCurrentEvent.endTime = currentTimeMillis(); + mEvents.add(mCurrentEvent); + } - public void begine() { - if (!mEnable) { - return; + mTotalTime = (int) (currentTimeMillis() - mStartTime); } - mStartTime = SystemClock.elapsedRealtime(); - mCurrentEvent = null; - if (mEvents == null) { - mEvents = Collections.synchronizedList(new ArrayList()); + + public int getTotalTime() { + return mTotalTime; } - mEvents.clear(); - mTotalTime = 0; - } - public void end() { - if (!mEnable) { - return; + public List getEvents() { + return mEvents; } - if (mCurrentEvent != null) { - mCurrentEvent.endTime = System.currentTimeMillis(); - mEvents.add(mCurrentEvent); + + public void addPoint(HippyEngineMonitorPoint eventName) { + addPoint(eventName, currentTimeMillis()); } - mTotalTime = (int) (SystemClock.elapsedRealtime() - mStartTime); - } + public void addPoint(HippyEngineMonitorPoint eventName, long timeMillis) { + mStandardPoints.put(eventName, timeMillis); + } - public int getTotalTime() { - return mTotalTime; - } + public void addCustomPoint(String eventName) { + addCustomPoint(eventName, currentTimeMillis()); + } + + public void addCustomPoint(String eventName, long timeMillis) { + mCustomPoints.put(eventName, timeMillis); + } + + public Map getAllPoints() { + Map result; + if (mParent != null) { + // collect parent + result = mParent.getAllPoints(); + } else { + result = new HashMap<>(mStandardPoints.size() + mCustomPoints.size()); + } + // collect standard + for (Map.Entry entry : mStandardPoints.entrySet()) { + result.put(entry.getKey().value(), entry.getValue()); + } + // collect custom + result.putAll(mCustomPoints); + return result; + } + + public void clearAllPoints() { + mStandardPoints.clear(); + mCustomPoints.clear(); + } - public List getEvents() { - return mEvents; - } + /* private */ long currentTimeMillis() { + return SystemClock.elapsedRealtime() + SYS_TIME_DIFF; + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/v8/V8.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/v8/V8.java new file mode 100644 index 00000000000..50b9403bcff --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/v8/V8.java @@ -0,0 +1,102 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.v8; + +import androidx.annotation.NonNull; + +import com.tencent.mtt.hippy.common.Callback; +import com.tencent.mtt.hippy.v8.memory.V8HeapCodeStatistics; +import com.tencent.mtt.hippy.v8.memory.V8HeapSpaceStatistics; +import com.tencent.mtt.hippy.v8.memory.V8HeapStatistics; +import com.tencent.mtt.hippy.v8.memory.V8Memory; + +import java.util.ArrayList; + +public class V8 implements V8Memory { + + public interface NearHeapLimitCallback { + /** + * This callback is invoked when the heap size is close to the heap limit and + * V8 is likely to abort with out-of-memory error. + * The callback can extend the heap limit by returning a value that is greater + * than the currentHeapLimit. The initial heap limit is the limit that was + * set after heap setup. + */ + long callback(long currentHeapLimit, long initialHeapLimit); + } + + private final long mV8RuntimeId; + + public V8(long mV8RuntimeId) { + this.mV8RuntimeId = mV8RuntimeId; + } + + // The method must be called in the js thread + @Override + public boolean getHeapStatistics(@NonNull Callback callback) throws NoSuchMethodException { + return getHeapStatistics(mV8RuntimeId, callback); + } + + // The method must be called in the js thread + @Override + public boolean getHeapCodeStatistics(@NonNull Callback callback) throws NoSuchMethodException { + return getHeapCodeStatistics(mV8RuntimeId, callback); + } + + // The method must be called in the js thread + @Override + public boolean getHeapSpaceStatistics(@NonNull Callback> callback) throws NoSuchMethodException { + return getHeapSpaceStatistics(mV8RuntimeId, callback); + } + + // The method must be called in the js thread + @Override + public boolean writeHeapSnapshot(@NonNull String filePath, @NonNull Callback callback) throws NoSuchMethodException { + return writeHeapSnapshot(mV8RuntimeId, filePath, callback); + } + + // The method must be called in the js thread + public void addNearHeapLimitCallback(NearHeapLimitCallback callback) { + addNearHeapLimitCallback(mV8RuntimeId, callback); + } + + // The method must be called in the js thread + public void printCurrentStackTrace(Callback callback) { + printCurrentStackTrace(mV8RuntimeId, callback); + } + + // the method can be called from any thread + public void requestInterrupt(Callback callback) { + requestInterrupt(mV8RuntimeId, callback); + } + + // [memory] + private native boolean getHeapStatistics(long runtimeId, Callback callback) throws NoSuchMethodException; + + private native boolean getHeapCodeStatistics(long runtimeId, Callback callback) throws NoSuchMethodException; + + private native boolean getHeapSpaceStatistics(long runtimeId, Callback> callback) throws NoSuchMethodException; + + private native boolean writeHeapSnapshot(long runtimeId, String filePath, Callback callback) throws NoSuchMethodException; + + private native void addNearHeapLimitCallback(long runtimeId, NearHeapLimitCallback callback); + + private native void printCurrentStackTrace(long runtimeId, Callback callback); + + private native void requestInterrupt(long runtimeId, Callback callback); + +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/v8/memory/V8HeapCodeStatistics.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/v8/memory/V8HeapCodeStatistics.java new file mode 100644 index 00000000000..280d5be4aa1 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/v8/memory/V8HeapCodeStatistics.java @@ -0,0 +1,31 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.v8.memory; + +public class V8HeapCodeStatistics { + public long codeAndMetadataSize; + public long bytecodeAndMetadataSize; + public long externalScriptSourceSize; + + public V8HeapCodeStatistics(long codeAndMetadataSize, + long bytecodeAndMetadataSize, + long externalScriptSourceSize) { + this.codeAndMetadataSize = codeAndMetadataSize; + this.bytecodeAndMetadataSize = bytecodeAndMetadataSize; + this.externalScriptSourceSize = externalScriptSourceSize; + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/v8/memory/V8HeapSpaceStatistics.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/v8/memory/V8HeapSpaceStatistics.java new file mode 100644 index 00000000000..480000ccfdd --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/v8/memory/V8HeapSpaceStatistics.java @@ -0,0 +1,37 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.v8.memory; + +public class V8HeapSpaceStatistics { + public String spaceName; + public long spaceSize; + public long spaceUsedSize; + public long spaceAvailableSize; + public long physicalSpaceSize; + + public V8HeapSpaceStatistics(String spaceName, + long spaceSize, + long spaceUsedSize, + long spaceAvailableSize, + long physicalSpaceSize) { + this.spaceName = spaceName; + this.spaceSize = spaceSize; + this.spaceUsedSize = spaceUsedSize; + this.spaceAvailableSize = spaceAvailableSize; + this.physicalSpaceSize = physicalSpaceSize; + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/v8/memory/V8HeapStatistics.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/v8/memory/V8HeapStatistics.java new file mode 100644 index 00000000000..7b099577648 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/v8/memory/V8HeapStatistics.java @@ -0,0 +1,61 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.v8.memory; + +public class V8HeapStatistics { + public long totalHeapSize; + public long totalHeapSizeExecutable; + public long totalPhysicalSize; + public long totalAvailableSize; + public long totalGlobalHandlesSize; + public long usedGlobalHandlesSize; + public long usedHeapSize; + public long heapSizeLimit; + public long mallocedMemory; + public long externalMemory; + public long peakMallocedMemory; + public long numberOfNativeContexts; + public long numberOfDetachedContexts; + + public V8HeapStatistics(long totalHeapSize, + long totalHeapSizeExecutable, + long totalPhysicalSize, + long totalAvailableSize, + long totalGlobalHandlesSize, + long usedGlobalHandlesSize, + long usedHeapSize, + long heapSizeLimit, + long mallocedMemory, + long externalMemory, + long peakMallocedMemory, + long numberOfNativeContexts, + long numberOfDetachedContexts) { + this.totalHeapSize = totalHeapSize; + this.totalHeapSizeExecutable = totalHeapSizeExecutable; + this.totalPhysicalSize = totalPhysicalSize; + this.totalAvailableSize = totalAvailableSize; + this.totalGlobalHandlesSize = totalGlobalHandlesSize; + this.usedGlobalHandlesSize = usedGlobalHandlesSize; + this.usedHeapSize = usedHeapSize; + this.heapSizeLimit = heapSizeLimit; + this.mallocedMemory = mallocedMemory; + this.externalMemory = externalMemory; + this.peakMallocedMemory = peakMallocedMemory; + this.numberOfNativeContexts = numberOfNativeContexts; + this.numberOfDetachedContexts = numberOfDetachedContexts; + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/v8/memory/V8Memory.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/v8/memory/V8Memory.java new file mode 100644 index 00000000000..de0066fd640 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/v8/memory/V8Memory.java @@ -0,0 +1,30 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.v8.memory; + +import androidx.annotation.NonNull; + +import com.tencent.mtt.hippy.common.Callback; + +import java.util.ArrayList; + +public interface V8Memory { + public abstract boolean getHeapStatistics(@NonNull Callback callback) throws Exception; + public abstract boolean getHeapCodeStatistics(@NonNull Callback callback) throws Exception; + public abstract boolean getHeapSpaceStatistics(@NonNull Callback> callback) throws Exception; + public abstract boolean writeHeapSnapshot(@NonNull String filePath, @NonNull Callback callback) throws Exception; +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/common/HippyNestedScrollComponent.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/common/HippyNestedScrollComponent.java new file mode 100644 index 00000000000..95530100bd3 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/common/HippyNestedScrollComponent.java @@ -0,0 +1,69 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.common; + +import androidx.core.view.NestedScrollingChild; +import androidx.core.view.NestedScrollingChild2; +import androidx.core.view.NestedScrollingParent; +import androidx.core.view.NestedScrollingParent2; + +public interface HippyNestedScrollComponent { + + String PROP_PRIORITY = "nestedScrollPriority"; + String PROP_LEFT_PRIORITY = "nestedScrollLeftPriority"; + String PROP_TOP_PRIORITY = "nestedScrollTopPriority"; + String PROP_RIGHT_PRIORITY = "nestedScrollRightPriority"; + String PROP_BOTTOM_PRIORITY = "nestedScrollBottomPriority"; + + String PRIORITY_PARENT = "parent"; + String PRIORITY_SELF = "self"; + String PRIORITY_NONE = "none"; + + int DIRECTION_INVALID = -1; + int DIRECTION_ALL = 0; + int DIRECTION_LEFT = 1; + int DIRECTION_TOP = 2; + int DIRECTION_RIGHT = 3; + int DIRECTION_BOTTOM = 4; + + void setNestedScrollPriority(int direction, Priority priority); + + Priority getNestedScrollPriority(int direction); + + enum Priority { + NOT_SET, + PARENT, + SELF, + NONE, + } + + /** + * Nested scroll interface declaration + */ + interface HippyNestedScrollTarget extends HippyNestedScrollComponent, NestedScrollingParent, + NestedScrollingChild { + + } + + /** + * Added non-touch scrolling type than {@link HippyNestedScrollTarget} + */ + interface HippyNestedScrollTarget2 extends HippyNestedScrollTarget, NestedScrollingParent2, + NestedScrollingChild2 { + + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/common/HippyNestedScrollHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/common/HippyNestedScrollHelper.java new file mode 100644 index 00000000000..bd7d372668b --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/common/HippyNestedScrollHelper.java @@ -0,0 +1,76 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.common; + +import static com.tencent.mtt.hippy.views.common.HippyNestedScrollComponent.DIRECTION_BOTTOM; +import static com.tencent.mtt.hippy.views.common.HippyNestedScrollComponent.DIRECTION_LEFT; +import static com.tencent.mtt.hippy.views.common.HippyNestedScrollComponent.DIRECTION_RIGHT; +import static com.tencent.mtt.hippy.views.common.HippyNestedScrollComponent.DIRECTION_TOP; +import static com.tencent.mtt.hippy.views.common.HippyNestedScrollComponent.PRIORITY_NONE; +import static com.tencent.mtt.hippy.views.common.HippyNestedScrollComponent.PRIORITY_PARENT; +import static com.tencent.mtt.hippy.views.common.HippyNestedScrollComponent.PRIORITY_SELF; +import static com.tencent.mtt.hippy.views.common.HippyNestedScrollComponent.Priority; + +import android.view.View; +import com.tencent.mtt.hippy.annotation.HippyControllerProps; +import com.tencent.mtt.hippy.views.common.HippyNestedScrollComponent.HippyNestedScrollTarget; + +public class HippyNestedScrollHelper { + + public static Priority priorityOf(String name) { + switch (name) { + case PRIORITY_SELF: + return Priority.SELF; + case PRIORITY_PARENT: + return Priority.PARENT; + case PRIORITY_NONE: + return Priority.NONE; + case HippyControllerProps.DEFAULT: + return Priority.NOT_SET; + default: + throw new RuntimeException("Invalid priority: " + name); + } + } + + public static Priority priorityOfX(View target, int dx) { + // not scrolling, priority is NONE + if (dx == 0) { + return Priority.NONE; + } + // non-HippyNestedScrollTarget View, priority is SELF + if (!(target instanceof HippyNestedScrollTarget)) { + return Priority.SELF; + } + // get priority from target by direction + return ((HippyNestedScrollTarget) target).getNestedScrollPriority( + dx > 0 ? DIRECTION_LEFT : DIRECTION_RIGHT); + } + + public static Priority priorityOfY(View target, int dy) { + // not scrolling, priority is NONE + if (dy == 0) { + return Priority.NONE; + } + // non-HippyNestedScrollTarget View, priority is SELF + if (!(target instanceof HippyNestedScrollTarget)) { + return Priority.SELF; + } + // get priority from target by direction + return ((HippyNestedScrollTarget) target).getNestedScrollPriority( + dy > 0 ? DIRECTION_TOP : DIRECTION_BOTTOM); + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyListUtils.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyListUtils.java new file mode 100644 index 00000000000..51e64ff46e9 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyListUtils.java @@ -0,0 +1,96 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.hippylist; + +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.LayoutManager; +import com.tencent.mtt.hippy.HippyEngineContext; +import com.tencent.mtt.hippy.uimanager.ListItemRenderNode; +import com.tencent.mtt.hippy.uimanager.ListViewRenderNode; +import com.tencent.mtt.hippy.uimanager.RenderNode; + +/** + * HippyList一些公用的方法 + */ +public class HippyListUtils { + + private HippyListUtils() { + + } + + /** + * 是否垂直方向排版 + */ + public static boolean isVerticalLayout(RecyclerView recyclerView) { + LayoutManager layoutManager = recyclerView.getLayoutManager(); + if (layoutManager != null) { + return layoutManager.canScrollVertically(); + } + return false; + } + + /** + * 是否水平方向排版 + */ + public static boolean isHorizontalLayout(RecyclerView recyclerView) { + LayoutManager layoutManager = recyclerView.getLayoutManager(); + if (layoutManager != null) { + return layoutManager.canScrollHorizontally(); + } + return false; + } + + public static boolean isLinearLayout(RecyclerView recyclerView) { + return recyclerView.getLayoutManager() instanceof LinearLayoutManager; + } + + /** + * 凡是ListItemRenderNode的createView都是在ListView的onCreateView的时候创建 + * 这里将父节点下面的所有ListItemRenderNode设置懒加载,避免listItemView提前在这里创建 + * 否则会导致HippyListView无法createItemView + */ + public static void setListItemNodeLazy(RenderNode parentNode) { + for (int i = 0; i < parentNode.getChildCount(); i++) { + RenderNode childNode = parentNode.getChildAt(i); + if (childNode instanceof ListItemRenderNode) { + childNode.setLazy(true); + } else { + setListItemNodeLazy(childNode); + } + } + } + + /** + * 解决mFixedContentIndex不生效的问题,场景是viewPagerItem被摧毁了,再次滑动回来的场景。 + * ViewPagerItem的Node调用了updateViewRecursive,如果下面有HippyListView的节点,HippyListView的节点会触发 + * HippyListView的排版,但是此时的排版不会走bachComplete,导致HippyListView的setListData不会调用 + * 这样就无法实现mFixedContentIndex的生效,所以这里需要补一下setListData + */ + public static void updateListView(HippyEngineContext hippyContext, RenderNode parentNode) { + for (int i = 0; i < parentNode.getChildCount(); i++) { + RenderNode childNode = parentNode.getChildAt(i); + if (childNode instanceof ListViewRenderNode) { + hippyContext.getRenderManager().getControllerManager() + .onBatchComplete(childNode.getClassName(), childNode.getId()); + break; + } else { + updateListView(hippyContext, childNode); + } + } + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecycleViewFocusHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecycleViewFocusHelper.java new file mode 100644 index 00000000000..ce4d3511c79 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecycleViewFocusHelper.java @@ -0,0 +1,363 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.hippylist; + +import android.graphics.Rect; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import androidx.recyclerview.widget.RecyclerView.LayoutManager; +import androidx.recyclerview.widget.RecyclerView.ViewHolder; +import com.tencent.mtt.hippy.utils.LogUtils; +import com.tencent.mtt.hippy.utils.StrictFocusFinder; +import com.tencent.mtt.hippy.views.list.HippyListItemView; +import com.tencent.mtt.hippy.views.refresh.HippyPullFooterView; +import com.tencent.mtt.hippy.views.refresh.HippyPullHeaderView; + +public class HippyRecycleViewFocusHelper { + + private static final String TAG = "HippyRecycleView"; + private View mFocusedView; + private int mKeyCode = KeyEvent.KEYCODE_DPAD_CENTER; + private HippyRecyclerView mRecyclerView; + private Rect mFocusRect = new Rect(); + + public HippyRecycleViewFocusHelper(HippyRecyclerView recyclerView) { + this.mRecyclerView = recyclerView; + } + + public void setListData() { + mFocusedView = null; + } + + public void setKeyCode(int keyCode) { + mKeyCode = keyCode; + } + + public void requestChildFocus(View child, View focused) { + if (mRecyclerView.getLayoutManager().canScrollVertically()) { + scrollToFocusChildVertical(focused); + } else { + scrollToFocusChildHorizontal(focused); + } + mFocusedView = focused; + } + + private void scrollToFocusChildVertical(View focused) { + View parent = getParentRecycleItem(focused); + if (parent != null) { + + int focusPos = mRecyclerView.getLayoutManager().getPosition(mRecyclerView.getFocusedChild()); + int offset = getOffset(parent, focusPos); + + LogUtils.d(TAG, "requestChildFocus offset=" + offset); + // 校验滚动位置是否合适 + if (mKeyCode == KeyEvent.KEYCODE_DPAD_DOWN) { + // 如果向下滑,并且最后一个Layout在界面上,能向下滑动的最大距离为最后一个Layout到下边界的距离 + // 将最后一个Layout对齐到下边界,可以做反向距离纠正 + offset = getOffsetKeyDown(offset); + } else if (mKeyCode == KeyEvent.KEYCODE_DPAD_UP) { + // 如果向上滑,并且第一个Layout在界面上,能向上滑动的最大距离为第一个Layout到上边界的距离 + // 将第一个Layout对齐到上边界,可以做反向距离纠正 + offset = getOffsetKeyUp(offset); + } + + if (isNoFocusDoNothing()) { + //需求1-初次进入页面首屏不滚动 + } else if (isSameItemDoNothing(focused)) { + //需求2-同一个item内部不滚动 + } else { + mRecyclerView.smoothScrollBy(0, offset); + } + } + } + + public void scrollToFocusChildHorizontal(View focused) { + focused.getDrawingRect(mFocusRect); + mRecyclerView.offsetDescendantRectToMyCoords(focused, mFocusRect); + int scrollX = computeScrollDelta(mFocusRect); + if (scrollX != 0) { + mRecyclerView.scrollBy(scrollX, 0); + } + } + + protected int computeScrollDelta(Rect rect) { + if (mRecyclerView.getChildCount() == 0) { + return 0; + } + int width = mRecyclerView.getWidth(); + int screenLeft = mRecyclerView.getScrollX(); + int screenRight = screenLeft + width; + int fadingEdge = mRecyclerView.getHorizontalFadingEdgeLength(); + if (rect.left > 0) { + screenLeft += fadingEdge; + } + if (rect.right < mRecyclerView.getChildAt(0).getWidth()) { + screenRight -= fadingEdge; + } + + int scrollXDelta = 0; + if (rect.right > screenRight && rect.left > screenLeft) { + if (rect.width() > width) { + scrollXDelta += (rect.left - screenLeft); + } else { + scrollXDelta += (rect.right - screenRight); + } + int right = mRecyclerView.getChildAt(0).getRight(); + int distanceToRight = right - screenRight; + scrollXDelta = Math.min(scrollXDelta, distanceToRight); + + } else if (rect.left < screenLeft && rect.right < screenRight) { + if (rect.width() > width) { + scrollXDelta -= (screenRight - rect.right); + } else { + scrollXDelta -= (screenLeft - rect.left); + } + scrollXDelta = Math.max(scrollXDelta, -mRecyclerView.getScrollX()); + } + return scrollXDelta; + } + + private boolean isSameItemDoNothing(View focused) { + return mFocusedView != null && isViewOfSameRecycleItem(mFocusedView, focused); + } + + private boolean isNoFocusDoNothing() { + return mFocusedView == null && getFirstFocusHeightBefore() < mRecyclerView.getHeight(); + } + + private int getFirstFocusHeightBefore() { + View focusItem = mRecyclerView.getFocusedChild(); + int focusIndex = mRecyclerView.getLayoutManager().getPosition(focusItem); + if (focusItem != null) { + View focusItemReal = getRealFocusedChild(focusItem); + return mRecyclerView.getTotalHeightBefore(focusIndex) + focusItemReal.getHeight(); + } + + return mRecyclerView.getTotalHeightBefore(focusIndex); + } + + private View getRealFocusedChild(View parent) { + if (parent instanceof ViewGroup) { + View focusView = ((ViewGroup) parent).getFocusedChild(); + if (focusView == null) { + return parent; + } + return getRealFocusedChild(focusView); + } + return parent; + } + + private View getParentRecycleItem(View focused) { + if (focused instanceof HippyPullHeaderView || focused instanceof HippyPullFooterView) { + return (View) focused.getParent(); + } + + if (focused instanceof HippyListItemView) { + return focused; + } + + final ViewParent theParent = focused.getParent(); + if (theParent == null) { + return null; + } + return getParentRecycleItem((View) theParent); + } + + private int getOffsetKeyUp(int offset) { + if (mRecyclerView.getAdapter().getItemCount() == mRecyclerView.getChildCount()) { + View view = mRecyclerView.getChildAt(0); + int maxTop = view.getBottom() - view.getHeight(); + if (offset > 0) { + offset = 0; + } else if (offset < maxTop) { + offset = maxTop; + } + LogUtils.d(TAG, "requestChildFocus offset=" + offset + ",max_top=" + maxTop); + } else if (offset > 0) { + offset = 0; + } + return offset; + } + + private int getOffsetKeyDown(int offset) { + if (mRecyclerView.getAdapter().getItemCount() == mRecyclerView.getChildCount()) { + View view = mRecyclerView.getChildAt(mRecyclerView.getChildCount() - 1); + int maxBottom = view.getTop() + view.getHeight() - mRecyclerView.getHeight(); + if (offset < 0) { + offset = 0; + } else if (offset > maxBottom) { + offset = maxBottom; + } + LogUtils + .d(TAG, "requestChildFocus offset=" + offset + ",max_bottom=" + maxBottom); + } else if (offset < 0) { + offset = 0; + } + return offset; + } + + private int getOffset(View parent, int focusPos) { + int offset = 0; + if (isBottomEdge(focusPos)) { + offset = parent.getTop() + parent.getHeight() - mRecyclerView.getHeight(); + } else if (isTopEdge(focusPos)) { + offset = parent.getBottom() - parent.getHeight(); + } else { + offset = parent.getTop() + parent.getHeight() / 2 - mRecyclerView.getHeight() / 2; + } + return offset; + } + + private boolean isViewOfSameRecycleItem(View view, View view1) { + if (view == view1) { + return true; + } + ViewHolder viewHolder = mRecyclerView.getChildViewHolder(view); + ViewHolder viewHolder1 = mRecyclerView.getChildViewHolder(view1); + if (mRecyclerView.isTheSameRenderNode((HippyRecyclerViewHolder) viewHolder, + (HippyRecyclerViewHolder) viewHolder1)) { + return true; + } + + return false; + } + + public boolean isTopEdge(int childPosition) { + LayoutManager layoutManager = mRecyclerView.getLayoutManager(); + if (layoutManager.canScrollVertically()) { + return childPosition == 0; + } + return false; + } + + public boolean isBottomEdge(int childPosition) { + LayoutManager layoutManager = mRecyclerView.getLayoutManager(); + if (layoutManager.canScrollVertically()) { + return childPosition == mRecyclerView.getLayoutManager().getItemCount() - 1; + } + return false; + } + + public int getFirstVisiblePosition() { + if (mRecyclerView.getChildCount() == 0) { + return 0; + } else { + return getChildAdapterPosition(mRecyclerView.getChildAt(0)); + } + } + + public int getChildAdapterPosition(View view) { + LayoutManager layoutManager = mRecyclerView.getLayoutManager(); + for (int i = 0; i < layoutManager.getChildCount(); i++) { + if (layoutManager.findViewByPosition(i) == view) { + return i; + } + } + + return -1; + } + + public View focusSearch(View focused, int direction) { + View nextFocus = StrictFocusFinder.getInstance().findNextFocus(mRecyclerView, focused, direction); + if (nextFocus == null && mRecyclerView.getParent() != null) { + return mRecyclerView.getParent().focusSearch(focused, direction); + } + return nextFocus; + } + + public int getChildDrawingOrder(int childCount, int i) { + View view = mRecyclerView.getFocusedChild(); + if (null != view) { + int position = getChildAdapterPosition(view) - getFirstVisiblePosition(); + if (position < 0) { + return i; + } else { + if (i == childCount - 1) { + if (position > i) { + position = i; + } + return position; + } + if (i == position) { + return childCount - 1; + } + } + } + return i; + } + + public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) { + final int parentLeft = mRecyclerView.getPaddingLeft(); + final int parentRight = mRecyclerView.getWidth() - mRecyclerView.getPaddingRight(); + + final int parentTop = mRecyclerView.getPaddingTop(); + final int parentBottom = mRecyclerView.getHeight() - mRecyclerView.getPaddingBottom(); + + final int childLeft = child.getLeft() + rect.left; + final int childTop = child.getTop() + rect.top; + + final int childRight = childLeft + rect.width(); + final int childBottom = childTop + rect.height(); + + final int offScreenLeft = Math.min(0, childLeft - parentLeft); + final int offScreenRight = Math.max(0, childRight - parentRight); + + final int offScreenTop = Math.min(0, childTop - parentTop); + final int offScreenBottom = Math.max(0, childBottom - parentBottom); + + final boolean canScrollHorizontal = mRecyclerView.getLayoutManager().canScrollHorizontally(); + final boolean canScrollVertical = mRecyclerView.getLayoutManager().canScrollVertically(); + + // Favor the "start" layout direction over the end when bringing one side or the other + // of a large rect into view. If we decide to bring in end because start is already + // visible, limit the scroll such that start won't go out of bounds. + final int dx; + if (canScrollHorizontal) { + if (/*ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL*/false) { + dx = offScreenRight != 0 ? offScreenRight + : Math.max(offScreenLeft, childRight - parentRight); + } else { + dx = offScreenLeft != 0 ? offScreenLeft + : Math.min(childLeft - parentLeft, offScreenRight); + } + } else { + dx = 0; + } + + // Favor bringing the top into view over the bottom. If top is already visible and + // we should scroll to make bottom visible, make sure top does not go out of bounds. + final int dy; + if (canScrollVertical) { + dy = offScreenTop != 0 ? offScreenTop : Math.min(childTop - parentTop, offScreenBottom); + } else { + dy = 0; + } + + if (dx != 0 || dy != 0) { + if (immediate) { + mRecyclerView.scrollBy(dx, dy); + } else { + mRecyclerView.smoothScrollBy(dx, dy); + } + mRecyclerView.postInvalidate(); + return true; + } + return false; + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerListAdapter.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerListAdapter.java index d4beb560fd0..5d4202c7a75 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerListAdapter.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerListAdapter.java @@ -20,384 +20,450 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.HippyItemTypeHelper; -import androidx.recyclerview.widget.IItemLayoutParams; +import androidx.recyclerview.widget.ItemLayoutParams; import androidx.recyclerview.widget.RecyclerView.Adapter; import androidx.recyclerview.widget.RecyclerView.LayoutParams; import android.view.View; import android.view.ViewGroup; -import android.widget.FrameLayout; import com.tencent.mtt.hippy.HippyEngineContext; import com.tencent.mtt.hippy.uimanager.DiffUtils; import com.tencent.mtt.hippy.uimanager.DiffUtils.PatchType; import com.tencent.mtt.hippy.uimanager.ListItemRenderNode; +import com.tencent.mtt.hippy.uimanager.PullFooterRenderNode; import com.tencent.mtt.hippy.uimanager.PullHeaderRenderNode; import com.tencent.mtt.hippy.uimanager.RenderNode; import com.tencent.mtt.hippy.views.list.IRecycleItemTypeChange; +import com.tencent.mtt.hippy.views.refresh.HippyPullFooterView; import com.tencent.mtt.hippy.views.refresh.HippyPullHeaderView; -import com.tencent.mtt.nxeasy.recyclerview.helper.skikcy.IStickyItemsProvider; +import com.tencent.mtt.hippy.views.hippylist.recyclerview.helper.skikcy.IStickyItemsProvider; import java.util.ArrayList; -public class HippyRecyclerListAdapter extends - Adapter - implements IRecycleItemTypeChange, IStickyItemsProvider, IItemLayoutParams { - - protected final HippyEngineContext hpContext; - protected final HRCV hippyRecyclerView; - protected final HippyItemTypeHelper hippyItemTypeHelper; - protected int positionToCreateHolder; - protected PullFooterEventHelper footerEventHelper; - protected PullHeaderEventHelper headerEventHelper; - protected PreloadHelper preloadHelper; - - public HippyRecyclerListAdapter(HRCV hippyRecyclerView, HippyEngineContext hpContext) { - this.hpContext = hpContext; - this.hippyRecyclerView = hippyRecyclerView; - hippyItemTypeHelper = new HippyItemTypeHelper(hippyRecyclerView); - preloadHelper = new PreloadHelper(hippyRecyclerView); - } - - /** - * 对于吸顶到RenderNode需要特殊处理 吸顶的View需要包一层ViewGroup,吸顶的时候,从ViewGroup把RenderNode的View取出来挂载到顶部 - * 当RenderNode的View已经挂载到Header位置上面,如果重新触发创建ViewHolder,renderView会创建失败, - * 此时就只返回一个空到renderViewContainer上去,等viewHolder需要显示到时候,再把header上面的View还原到这个 ViewHolder上面。 - */ - @NonNull - @Override - public HippyRecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - ListItemRenderNode renderNode = getChildNodeByAdapterPosition(positionToCreateHolder); - boolean isViewExist = renderNode.isViewExist(); - boolean needsDelete = renderNode.needDeleteExistRenderView(); - View renderView = createRenderView(renderNode); - if (isPullHeader(positionToCreateHolder)) { - ((HippyPullHeaderView) renderView).setParentView(hippyRecyclerView); - initPullHeadEventHelper((PullHeaderRenderNode) renderNode, renderView); - return new HippyRecyclerViewHolder(headerEventHelper.getView(), renderNode); - } else if (isStickyPosition(positionToCreateHolder)) { - return new HippyRecyclerViewHolder(getStickyContainer(parent, renderView), renderNode); - } else { - if (renderView == null) { - throw new IllegalArgumentException("createRenderView error!" - + ",isDelete:" + renderNode.isDelete() - + ",isViewExist:" + isViewExist - + ",needsDelete:" + needsDelete - + ",className:" + renderNode.getClassName() - + ",isLazy :" + renderNode.isIsLazyLoad() - + ",itemCount :" + getItemCount() - + ",getNodeCount:" + getRenderNodeCount() - + ",notifyCount:" + hippyRecyclerView.renderNodeCount - + "curPos:" + positionToCreateHolder - + ",rootView:" + renderNode.hasRootView() - + ",parentNode:" + (renderNode.getParent() != null) - + ",offset:" + hippyRecyclerView.computeVerticalScrollOffset() - + ",range:" + hippyRecyclerView.computeVerticalScrollRange() - + ",extent:" + hippyRecyclerView.computeVerticalScrollExtent() - + ",viewType:" + viewType - + ",id:" + renderNode.getId() - + ",attachedIds:" + getAttachedIds() - + ",nodeOffset:" + hippyRecyclerView.getNodePositionHelper().getNodeOffset() - + ",view:" + hippyRecyclerView - ); - } - return new HippyRecyclerViewHolder(renderView, renderNode); - } - } - - String getAttachedIds() { - StringBuilder attachedIds = new StringBuilder(); - int childCount = hippyRecyclerView.getChildCount(); - for (int i = 0; i < childCount; ++i) { - View attachedView = hippyRecyclerView.getChildAt(i); - attachedIds.append("|p_" + hippyRecyclerView.getChildAdapterPosition(attachedView)); - attachedIds.append("_i_" + attachedView.getId()); - } - return attachedIds.toString(); - } - - - /** - * deleteExistRenderView 是异常情况,正常情况应该不会走这个逻辑, 本来在{@link HippyRecyclerView#onViewAbound(HippyRecyclerViewHolder)} - * 的地方应该已经调用了deleteExistRenderView,deleteExistRenderView这里不应该可能走到 如果是吸顶的View,shouldSticky为true的情况下,是本身就存在的,不应该去调用deleteExistRenderView - * - * @param renderNode - * @return - */ - protected View createRenderView(ListItemRenderNode renderNode) { - if (renderNode.needDeleteExistRenderView() && !renderNode.shouldSticky()) { - deleteExistRenderView(renderNode); - } - renderNode.setLazy(false); - View view = renderNode.createViewRecursive(); - renderNode.updateViewRecursive(); - return view; - } - - public void deleteExistRenderView(ListItemRenderNode renderNode) { - renderNode.setLazy(true); - RenderNode parentNode = getParentNode(); - if (parentNode != null) { - hpContext.getRenderManager().getControllerManager() - .deleteChild(parentNode.getId(), renderNode.getId()); - } - renderNode.setRecycleItemTypeChangeListener(null); - } - - private FrameLayout getStickyContainer(ViewGroup parent, View renderView) { - FrameLayout container = new FrameLayout(parent.getContext()); - if (renderView != null) { - container.addView(renderView, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); - } - return container; - } - - private void initPullHeadEventHelper(PullHeaderRenderNode renderNode, View renderView) { - if (headerEventHelper == null) { - headerEventHelper = new PullHeaderEventHelper(hippyRecyclerView, renderNode); - } - headerEventHelper.setRenderNodeView(renderView); - } - - @Override - public String toString() { - return "HippyRecyclerAdapter: itemCount:" + getItemCount(); - } - - /** - * 绑定数据 对于全新的viewHolder,isCreated 为true,调用updateViewRecursive进行物理树的创建,以及数据的绑定 - * 对于非全新创建的viewHolder,进行view树的diff,然后在把数据绑定到view树上面 - * - * @param hippyRecyclerViewHolder position当前的viewHolder - * @param position 绑定数据的节点位置 - */ - @Override - public void onBindViewHolder(HippyRecyclerViewHolder hippyRecyclerViewHolder, int position) { - setLayoutParams(hippyRecyclerViewHolder.itemView, position); - RenderNode oldNode = hippyRecyclerViewHolder.bindNode; - ListItemRenderNode newNode = getChildNodeByAdapterPosition(position); - oldNode.setLazy(true); - newNode.setLazy(false); - if (oldNode != newNode) { - //step 1: diff - ArrayList patchTypes = DiffUtils.diff(oldNode, newNode); - //step:2 delete unUseful views - DiffUtils.deleteViews(hpContext.getRenderManager().getControllerManager(), patchTypes); - //step:3 replace id - DiffUtils.replaceIds(hpContext.getRenderManager().getControllerManager(), patchTypes); - //step:4 create view is do not reUse - DiffUtils.createView(patchTypes); - //step:5 patch the dif result - DiffUtils.doPatch(hpContext.getRenderManager().getControllerManager(), patchTypes); - } - newNode.setRecycleItemTypeChangeListener(this); - hippyRecyclerViewHolder.bindNode = newNode; - enablePullFooter(position, hippyRecyclerViewHolder.itemView); - } - - /** - * 检测最后一个item是否是footer,如果是,需要对这个itemView设置监控,footer显示就通知前端加载下一页 - */ - private void enablePullFooter(int position, View itemView) { - if (position == getItemCount() - 1) { - ListItemRenderNode renderNode = getChildNodeByAdapterPosition(position); - if (renderNode.isPullFooter()) { - if (footerEventHelper == null) { - footerEventHelper = new PullFooterEventHelper(hippyRecyclerView); +/** + * Created on 2020/12/22. + * Description RecyclerView的子View直接是前端的RenderNode节点,没有之前包装的那层RecyclerViewItem。 + * 对于特殊的renderNode,比如header和sticky的节点,我们进行了不同的处理。 + */ +public class HippyRecyclerListAdapter extends Adapter + implements IRecycleItemTypeChange, IStickyItemsProvider, ItemLayoutParams { + + private static final int STICK_ITEM_VIEW_TYPE_BASE = -100000; + protected final HippyEngineContext hpContext; + protected final HRCV hippyRecyclerView; + protected final HippyItemTypeHelper hippyItemTypeHelper; + protected int positionToCreateHolder; + protected PullFooterRefreshHelper footerRefreshHelper; + protected PullHeaderRefreshHelper headerRefreshHelper; + + public HippyRecyclerListAdapter(HRCV hippyRecyclerView, HippyEngineContext hpContext) { + this.hpContext = hpContext; + this.hippyRecyclerView = hippyRecyclerView; + hippyItemTypeHelper = new HippyItemTypeHelper(hippyRecyclerView); + } + + /** + * 对于吸顶到RenderNode需要特殊处理 + * 吸顶的View需要包一层ViewGroup,吸顶的时候,从ViewGroup把RenderNode的View取出来挂载到顶部 + * 当RenderNode的View已经挂载到Header位置上面,如果重新触发创建ViewHolder,renderView会创建失败, + * 此时就只返回一个空到renderViewContainer上去,等viewHolder需要显示到时候,再把header上面的View还原到这个 + * ViewHolder上面。 + */ + @NonNull + @Override + public HippyRecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + ListItemRenderNode renderNode = getChildNodeByAdapterPosition(positionToCreateHolder); + boolean isViewExist = renderNode.isViewExist(); + boolean needsDelete = renderNode.needDeleteExistRenderView(); + View renderView = createRenderView(renderNode); + if (isPullHeader(positionToCreateHolder)) { + ((HippyPullHeaderView) renderView).setRecyclerView(hippyRecyclerView); + initHeaderRefreshHelper(renderView, renderNode); + return new HippyRecyclerViewHolder(headerRefreshHelper.getView(), renderNode); + } else if (renderView instanceof HippyPullFooterView) { + ((HippyPullFooterView) renderView).setRecyclerView(hippyRecyclerView); + initFooterRefreshHelper(renderView, renderNode); + return new HippyRecyclerViewHolder(footerRefreshHelper.getView(), renderNode); + } else if (isStickyPosition(positionToCreateHolder)) { + View stickyView = hippyRecyclerView.getStickyContainer(parent.getContext(), renderView); + return new HippyRecyclerViewHolder(stickyView, renderNode); + } else { + if (renderView == null) { + throw new IllegalArgumentException("createRenderView error!" + + ",isDelete:" + renderNode.isDelete() + + ",isViewExist:" + isViewExist + + ",needsDelete:" + needsDelete + + ",className:" + renderNode.getClassName() + + ",isLazy :" + renderNode.isIsLazyLoad() + + ",itemCount :" + getItemCount() + + ",getNodeCount:" + getRenderNodeCount() + + ",notifyCount:" + hippyRecyclerView.renderNodeCount + + "curPos:" + positionToCreateHolder + + ",rootView:" + renderNode.hasRootView() + + ",parentNode:" + (renderNode.getParent() != null) + + ",offset:" + hippyRecyclerView.computeVerticalScrollOffset() + + ",range:" + hippyRecyclerView.computeVerticalScrollRange() + + ",extent:" + hippyRecyclerView.computeVerticalScrollExtent() + + ",viewType:" + viewType + + ",id:" + renderNode.getId() + + ",attachedIds:" + getAttachedIds() + + ",nodeOffset:" + hippyRecyclerView.getNodePositionHelper().getNodeOffset() + + ",view:" + hippyRecyclerView + ); + } + return new HippyRecyclerViewHolder(renderView, renderNode); + } + } + + String getAttachedIds() { + StringBuilder attachedIds = new StringBuilder(); + int childCount = hippyRecyclerView.getChildCount(); + for (int i = 0; i < childCount; ++i) { + View attachedView = hippyRecyclerView.getChildAt(i); + attachedIds.append("|p_" + hippyRecyclerView.getChildAdapterPosition(attachedView)); + attachedIds.append("_i_" + attachedView.getId()); + } + return attachedIds.toString(); + } + + + /** + * deleteExistRenderView 是异常情况,正常情况应该不会走这个逻辑, + * 本来在{@link HippyRecyclerView#onViewAbound(HippyRecyclerViewHolder)} + * 的地方应该已经调用了deleteExistRenderView,deleteExistRenderView这里不应该可能走到 + * 如果是吸顶的View,shouldSticky为true的情况下,是本身就存在的,不应该去调用deleteExistRenderView + * + * @param renderNode + * @return + */ + protected View createRenderView(ListItemRenderNode renderNode) { + if (renderNode.needDeleteExistRenderView() && !renderNode.shouldSticky()) { + deleteExistRenderView(renderNode); + } + renderNode.setLazy(false); + View view = renderNode.createViewRecursive(); + renderNode.updateViewRecursive(); + return view; + } + + public void deleteExistRenderView(ListItemRenderNode renderNode) { + renderNode.setLazy(true); + RenderNode parentNode = getParentNode(); + if (parentNode != null) { + hpContext.getRenderManager().getControllerManager() + .deleteChild(parentNode.getId(), renderNode.getId()); + } else { + hpContext.getRenderManager().getControllerManager().removeViewFromRegistry(renderNode.getId()); + } + renderNode.setRecycleItemTypeChangeListener(null); + } + + @Override + public String toString() { + return "HippyRecyclerAdapter: itemCount:" + getItemCount(); + } + + /** + * 绑定数据 对于全新的viewHolder,isCreated 为true,调用updateViewRecursive进行物理树的创建,以及数据的绑定 + * 对于非全新创建的viewHolder,进行view树的diff,然后在把数据绑定到view树上面 + * + * @param hippyRecyclerViewHolder position当前的viewHolder + * @param position 绑定数据的节点位置 + */ + @Override + public void onBindViewHolder(HippyRecyclerViewHolder hippyRecyclerViewHolder, int position) { + setLayoutParams(hippyRecyclerViewHolder.itemView, position); + RenderNode oldNode = hippyRecyclerViewHolder.bindNode; + ListItemRenderNode newNode = getChildNodeByAdapterPosition(position); + oldNode.setLazy(true); + newNode.setLazy(false); + if (oldNode != newNode) { + //step 1: diff + ArrayList patchTypes = DiffUtils.diff(oldNode, newNode); + //step:2 delete unUseful views + DiffUtils.deleteViews(hpContext.getRenderManager().getControllerManager(), patchTypes); + //step:3 replace id + DiffUtils.replaceIds(hpContext.getRenderManager().getControllerManager(), patchTypes); + //step:4 create view is do not reUse + DiffUtils.createView(patchTypes); + //step:5 patch the dif result + DiffUtils.doPatch(hpContext.getRenderManager().getControllerManager(), patchTypes); + } + newNode.setRecycleItemTypeChangeListener(this); + hippyRecyclerViewHolder.bindNode = newNode; + } + + public void onFooterRefreshCompleted() { + if (footerRefreshHelper != null) { + footerRefreshHelper.onRefreshCompleted(); + } + } + + public void onFooterDestroy() { + if (footerRefreshHelper != null) { + footerRefreshHelper.onDestroy(); + footerRefreshHelper = null; + } + } + + public void onHeaderRefreshCompleted() { + if (headerRefreshHelper != null) { + headerRefreshHelper.onRefreshCompleted(); + } + } + + public void onHeaderDestroy() { + if (headerRefreshHelper != null) { + headerRefreshHelper.onDestroy(); + headerRefreshHelper = null; + } + } + + public void enableHeaderRefresh() { + if (headerRefreshHelper != null) { + headerRefreshHelper.enableRefresh(); + } + } + + public void onLayoutOrientationChanged() { + if (headerRefreshHelper != null) { + headerRefreshHelper.onLayoutOrientationChanged(); + } + if (footerRefreshHelper != null) { + footerRefreshHelper.onLayoutOrientationChanged(); + } + hippyRecyclerView.enableOverPullIfNeeded(); + } + + private void initHeaderRefreshHelper(View itemView, RenderNode node) { + if (headerRefreshHelper == null) { + headerRefreshHelper = new PullHeaderRefreshHelper(hippyRecyclerView, node); + } + headerRefreshHelper.setItemView(itemView); + } + + /** + * 检测最后一个item是否是footer,如果是,需要对这个itemView设置监控,footer显示就通知前端加载下一页 + */ + private void initFooterRefreshHelper(View itemView, RenderNode node) { + if (footerRefreshHelper == null) { + footerRefreshHelper = new PullFooterRefreshHelper(hippyRecyclerView, node); + } + footerRefreshHelper.setItemView(itemView); + } + + /** + * 设置View的LayoutParams排版属性,宽高由render节点提供 + * 对于LinearLayout的排版,竖向排版,宽度强行顶满,横向排版,高度强行顶满 + */ + protected void setLayoutParams(View itemView, int position) { + LayoutParams childLp = getLayoutParams(itemView); + RenderNode childNode = getChildNodeByAdapterPosition(position); + if (childNode instanceof PullFooterRenderNode || childNode instanceof PullHeaderRenderNode) { + return; + } + if (HippyListUtils.isLinearLayout(hippyRecyclerView)) { + boolean isVertical = HippyListUtils.isVerticalLayout(hippyRecyclerView); + childLp.height = isVertical ? childNode.getHeight() : MATCH_PARENT; + childLp.width = isVertical ? MATCH_PARENT : childNode.getWidth(); + } else { + childLp.height = childNode.getHeight(); + childLp.width = childNode.getWidth(); + } + itemView.setLayoutParams(childLp); + } + + + protected LayoutParams getLayoutParams(View itemView) { + ViewGroup.LayoutParams params = itemView.getLayoutParams(); + LayoutParams childLp = null; + if (params instanceof LayoutParams) { + childLp = (LayoutParams) params; } - footerEventHelper.enableFooter(itemView); - } else { - if (footerEventHelper != null) { - footerEventHelper.disableFooter(); + if (childLp == null) { + childLp = new LayoutParams(MATCH_PARENT, 0); } - } - } - } - - /** - * 设置View的LayoutParams排版属性,宽高由render节点提供 - */ - protected void setLayoutParams(View itemView, int position) { - LayoutParams childLp = getLayoutParams(itemView); - ListItemRenderNode childNode = getChildNodeByAdapterPosition(position); - childLp.height = getRenderNodeHeight(position); - childLp.width = childNode.getWidth(); - itemView.setLayoutParams(childLp); - } - - - protected LayoutParams getLayoutParams(View itemView) { - ViewGroup.LayoutParams params = itemView.getLayoutParams(); - LayoutParams childLp = null; - if (params instanceof LayoutParams) { - childLp = (LayoutParams) params; - } - if (childLp == null) { - childLp = new LayoutParams(MATCH_PARENT, 0); - } - return childLp; - } - - @Override - public int getItemViewType(int position) { - //在调用onCreateViewHolder之前,必然会调用getItemViewType,所以这里把position记下来 - //用在onCreateViewHolder的时候来创建View,不然onCreateViewHolder是无法创建RenderNode到View的 - setPositionToCreate(position); - return getChildNodeByAdapterPosition(position).getItemViewType(); - } - - protected void setPositionToCreate(int position) { - positionToCreateHolder = position; - } - - /** - * 获取子节点,理论上面是不会返回空的,否则就是某个流程出了问题 - * - * @param position adapter实际的item位置 - */ - public ListItemRenderNode getChildNodeByAdapterPosition(int position) { - return getChildNode(hippyRecyclerView.getNodePositionHelper().getRenderNodePosition(position)); - } - - /** - * 获取前端的renderNode的子节点 - * - * @param position 前端的子节点的位置 - */ - public ListItemRenderNode getChildNode(int position) { - RenderNode parentNode = getParentNode(); - if (parentNode != null && position < parentNode.getChildCount() && position >= 0) { - return (ListItemRenderNode) parentNode.getChildAt(position); - } - return null; - } - - /** - * listItemView的数量 - */ - @Override - public int getItemCount() { - return getRenderNodeCount(); - } - - /** - * 返回前端的list的内容Item数目 - * - * @return - */ - public int getRenderNodeCount() { - RenderNode listNode = getParentNode(); - if (listNode != null) { - return listNode.getChildCount(); - } - return 0; - } - - /** - * 前端展示的内容的高度 - * - * @return - */ - public int getRenderNodeTotalHeight() { - int renderCount = getRenderNodeCount(); - int renderNodeTotalHeight = 0; - for (int i = 0; i < renderCount; i++) { - renderNodeTotalHeight += getRenderNodeHeight(i); - } - return renderNodeTotalHeight; - } - - public int getItemHeight(int position) { - return getRenderNodeHeight(position); - } - - public int getRenderNodeHeight(int position) { - ListItemRenderNode childNode = getChildNode(position); - if (childNode != null) { - if (childNode.isPullHeader()) { - if (headerEventHelper != null) { - return headerEventHelper.getVisibleHeight(); + return childLp; + } + + @Override + public int getItemViewType(int position) { + //在调用onCreateViewHolder之前,必然会调用getItemViewType,所以这里把position记下来 + //用在onCreateViewHolder的时候来创建View,不然onCreateViewHolder是无法创建RenderNode到View的 + setPositionToCreate(position); + ListItemRenderNode node = getChildNodeByAdapterPosition(position); + if (node == null) { + return 0; + } + if (node.shouldSticky()) { + return STICK_ITEM_VIEW_TYPE_BASE - position; + } + return node.getItemViewType(); + } + + protected void setPositionToCreate(int position) { + positionToCreateHolder = position; + } + + /** + * 获取子节点,理论上面是不会返回空的,否则就是某个流程出了问题 + * + * @param position adapter实际的item位置 + */ + public ListItemRenderNode getChildNodeByAdapterPosition(int position) { + return getChildNode(hippyRecyclerView.getNodePositionHelper().getRenderNodePosition(position)); + } + + /** + * 获取前端的renderNode的子节点 + * + * @param position 前端的子节点的位置 + */ + public ListItemRenderNode getChildNode(int position) { + RenderNode parentNode = getParentNode(); + if (parentNode != null && position < parentNode.getChildCount() && position >= 0) { + return (ListItemRenderNode) parentNode.getChildAt(position); + } + return null; + } + + /** + * listItemView的数量 + */ + @Override + public int getItemCount() { + return getRenderNodeCount(); + } + + /** + * 返回前端的list的内容Item数目 + * + * @return + */ + public int getRenderNodeCount() { + RenderNode listNode = getParentNode(); + if (listNode != null) { + return listNode.getChildCount(); } + return 0; + } + + /** + * 前端展示的内容的高度 + * + * @return + */ + public int getRenderNodeTotalHeight() { + int renderCount = getRenderNodeCount(); + int renderNodeTotalHeight = 0; + for (int i = 0; i < renderCount; i++) { + renderNodeTotalHeight += getRenderNodeHeight(i); + } + return renderNodeTotalHeight; + } + public int getItemHeight(int position) { + Integer itemHeight = getRenderNodeHeight(position); + if (itemHeight != null) { + return itemHeight; + } + return 0; + } + + public int getRenderNodeHeight(int position) { + ListItemRenderNode childNode = getChildNode(position); + if (childNode != null) { + if (childNode.isPullHeader()) { + if (headerRefreshHelper != null) { + return headerRefreshHelper.getVisibleHeight(); + } + + return 0; + } + if (childNode.isPullFooter()) { + if (footerRefreshHelper != null) { + return footerRefreshHelper.getVisibleHeight(); + } + + return 0; + } + return childNode.getHeight(); + } return 0; - } - return childNode.getHeight(); - } - return 0; - } - - public int getItemWidth(int position) { - Integer renderNodeWidth = getRenderNodeWidth(position); - if (renderNodeWidth != null) { - return renderNodeWidth; - } - return 0; - } - - public int getRenderNodeWidth(int position) { - ListItemRenderNode childNode = getChildNode(position); - if (childNode != null) { - return childNode.getWidth(); - } - return 0; - } - - protected RenderNode getParentNode() { - return hpContext.getRenderManager().getRenderNode(getHippyListViewId()); - } - - private int getHippyListViewId() { - return ((View) hippyRecyclerView.getParent()).getId(); - } - - @Override - public void onRecycleItemTypeChanged(int oldType, int newType, ListItemRenderNode listItemNode) { - hippyItemTypeHelper.updateItemType(oldType, newType, listItemNode); - } - - @Override - public long getItemId(int position) { - return getChildNodeByAdapterPosition(position).getId(); - } - - /** - * 该position对于的renderNode是否是吸顶的属性 - */ - @Override - public boolean isStickyPosition(int position) { - if (position >= 0 && position < getItemCount()) { - return getChildNodeByAdapterPosition(position).shouldSticky(); - } - return false; - } - - /** - * 该position对于的renderNode是否是Header属性,值判断第一个节点 - */ - private boolean isPullHeader(int position) { - if (position == 0) { - return getChildNodeByAdapterPosition(0).isPullHeader(); - } - return false; - } - - /** - * 获取下拉刷新的事件辅助器 - */ - public PullHeaderEventHelper getHeaderEventHelper() { - return headerEventHelper; - } - - public PreloadHelper getPreloadHelper() { - return preloadHelper; - } - - public void setPreloadItemNumber(int preloadItemNumber) { - preloadHelper.setPreloadItemNumber(preloadItemNumber); - } - - @Override - public void getItemLayoutParams(int position, LayoutParams lp) { - if (lp == null) { - return; - } - lp.height = getRenderNodeHeight(position); - } + } + + public int getItemWidth(int position) { + Integer renderNodeWidth = getRenderNodeWidth(position); + if (renderNodeWidth != null) { + return renderNodeWidth; + } + return 0; + } + + public int getRenderNodeWidth(int position) { + ListItemRenderNode childNode = getChildNode(position); + if (childNode != null) { + if (childNode.isPullHeader()) { + if (headerRefreshHelper != null) { + return headerRefreshHelper.getVisibleWidth(); + } + + return 0; + } + if (childNode.isPullFooter()) { + if (footerRefreshHelper != null) { + return footerRefreshHelper.getVisibleWidth(); + } + + return 0; + } + return childNode.getWidth(); + } + return 0; + } + + protected RenderNode getParentNode() { + return hpContext.getRenderManager().getRenderNode(getHippyListViewId()); + } + + private int getHippyListViewId() { + return ((View) hippyRecyclerView.getParent()).getId(); + } + + @Override + public void onRecycleItemTypeChanged(int oldType, int newType, ListItemRenderNode listItemNode) { + hippyItemTypeHelper.updateItemType(oldType, newType, listItemNode); + } + + @Override + public long getItemId(int position) { + return getChildNodeByAdapterPosition(position).getId(); + } + + /** + * 该position对于的renderNode是否是吸顶的属性 + */ + @Override + public boolean isStickyPosition(int position) { + if (position >= 0 && position < getItemCount()) { + return getChildNodeByAdapterPosition(position).shouldSticky(); + } + return false; + } + + /** + * 该position对于的renderNode是否是Header属性,值判断第一个节点 + */ + private boolean isPullHeader(int position) { + if (position == 0) { + return getChildNodeByAdapterPosition(0).isPullHeader(); + } + return false; + } + + @Override + public void getItemLayoutParams(int position, LayoutParams lp) { + if (lp == null) { + return; + } + lp.height = getItemHeight(position); + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerView.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerView.java index 8ad707ffd50..0c7d13c1da6 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerView.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerView.java @@ -16,360 +16,895 @@ package com.tencent.mtt.hippy.views.hippylist; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; + import android.content.Context; import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.view.ViewCompat; import androidx.recyclerview.widget.HippyRecyclerViewBase; import androidx.recyclerview.widget.IHippyViewAboundListener; import androidx.recyclerview.widget.LinearLayoutManager; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; import com.tencent.mtt.hippy.HippyEngineContext; import com.tencent.mtt.hippy.utils.LogUtils; import com.tencent.mtt.hippy.utils.PixelUtil; -import com.tencent.mtt.nxeasy.recyclerview.helper.skikcy.IHeaderAttachListener; -import com.tencent.mtt.nxeasy.recyclerview.helper.skikcy.IHeaderHost; -import com.tencent.mtt.nxeasy.recyclerview.helper.skikcy.StickyHeaderHelper; - +import com.tencent.mtt.hippy.views.common.HippyNestedScrollComponent.HippyNestedScrollTarget2; +import com.tencent.mtt.hippy.views.common.HippyNestedScrollHelper; +import com.tencent.mtt.hippy.views.hippylist.recyclerview.helper.skikcy.IHeaderAttachListener; +import com.tencent.mtt.hippy.views.hippylist.recyclerview.helper.skikcy.IHeaderHost; +import com.tencent.mtt.hippy.views.hippylist.recyclerview.helper.skikcy.StickyHeaderHelper; +import java.util.ArrayList; + +/** + * Created on 2020/12/22. Description + */ public class HippyRecyclerView extends HippyRecyclerViewBase - implements IHeaderAttachListener, IHippyViewAboundListener { - - protected HippyEngineContext hippyEngineContext; - protected ADP listAdapter; - protected boolean isEnableScroll = true; //使能ListView的滚动功能 - protected StickyHeaderHelper stickyHeaderHelper; //支持吸顶 - protected IHeaderHost headerHost; //用于pullHeader下拉刷新 - protected LayoutManager layoutManager; - protected RecyclerViewEventHelper recyclerViewEventHelper;//事件集合 - private NodePositionHelper nodePositionHelper; - protected int renderNodeCount = 0; - - public HippyRecyclerView(Context context) { - super(context); - } - - public HippyRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - } - - public HippyRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - public ADP getAdapter() { - return listAdapter; - } - - @Override - public void setAdapter(@Nullable Adapter adapter) { - listAdapter = (ADP) adapter; - super.setAdapter(adapter); - } - - public NodePositionHelper getNodePositionHelper() { - if (nodePositionHelper == null) { - nodePositionHelper = new NodePositionHelper(); - } - return nodePositionHelper; - } - - public void setOrientation(LinearLayoutManager layoutManager) { - this.layoutManager = layoutManager; - } - - public void setHeaderHost(IHeaderHost headerHost) { - this.headerHost = headerHost; - } - - public void setHippyEngineContext(HippyEngineContext hippyEngineContext) { - this.hippyEngineContext = hippyEngineContext; - } - - public void initRecyclerView() { - setAdapter(new HippyRecyclerListAdapter(this, this.hippyEngineContext)); - intEventHelper(); - } - - - @Override - public boolean onTouchEvent(MotionEvent e) { - if (!isEnableScroll) { - return false; - } - return super.onTouchEvent(e); - } - - /** - * 刷新数据 - */ - public void setListData() { - LogUtils.d("HippyRecyclerView", "itemCount =" + listAdapter.getItemCount()); - listAdapter.notifyDataSetChanged(); - //notifyDataSetChanged 本身是可以触发requestLayout的,但是Hippy框架下 HippyRootView 已经把 - //onLayout方法重载写成空方法,requestLayout不会回调孩子节点的onLayout,这里需要自己发起dispatchLayout - renderNodeCount = getAdapter().getRenderNodeCount(); - dispatchLayout(); - } - - /** - * 内容偏移,返回recyclerView顶部被滑出去的内容 1、找到顶部第一个View前面的逻辑内容高度 2、加上第一个View被遮住的区域 - */ - public int getContentOffsetY() { - return computeVerticalScrollOffset(); - } - - /** - * 内容偏移,返回recyclerView被滑出去的内容 1、找到顶部第一个View前面的逻辑内容宽度 2、加上第一个View被遮住的区域 - */ - public int getContentOffsetX() { - int firstChildPosition = getFirstChildPosition(); - int totalWidthBeforePosition = getTotalWithBefore(firstChildPosition); - int firstChildOffset = - listAdapter.getItemWidth(firstChildPosition) - getVisibleWidth(getChildAt(0)); - return totalWidthBeforePosition + firstChildOffset; - } - - /** - * 获取一个View的可视高度,并非view本身的height,有可能部分是被滑出到屏幕外部 - */ - protected int getVisibleHeight(View firstChildView) { - return getViewVisibleRect(firstChildView).height(); - } - - /** - * 获取一个View的可视高度,并非view本身的height,有可能部分是被滑出到屏幕外部 - */ - protected int getVisibleWidth(View firstChildView) { - return getViewVisibleRect(firstChildView).width(); - } - - /** - * 获取view在父亲中的可视区域 - */ - private Rect getViewVisibleRect(View view) { - Rect rect = new Rect(); - if (view != null) { - view.getLocalVisibleRect(rect); - } - return rect; - } - - /** - * 获取position 前面的内容高度,不包含position自身的高度 - */ - public int getTotalHeightBefore(int position) { - int totalHeightBefore = 0; - for (int i = 0; i < position; i++) { - totalHeightBefore += listAdapter.getItemHeight(i); - } - return totalHeightBefore; - } - - /** - * 获取renderNodePosition前面的内容高度,不包含renderNodePosition自身的高度 - */ - public int getRenderNodeHeightBefore(int renderNodePosition) { - int renderNodeTotalHeight = 0; - for (int i = 0; i < renderNodePosition; i++) { - renderNodeTotalHeight += listAdapter.getRenderNodeHeight(i); - } - return renderNodeTotalHeight; - } - - - /** - * 获取position 前面的内容高度,不包含position自身的高度 - */ - public int getTotalWithBefore(int position) { - int totalWidthBefore = 0; - for (int i = 0; i < position; i++) { - totalWidthBefore += listAdapter.getItemWidth(i); - } - return totalWidthBefore; - } - - public RecyclerViewEventHelper getRecyclerViewEventHelper() { - return intEventHelper(); - } - - private RecyclerViewEventHelper intEventHelper() { - if (recyclerViewEventHelper == null) { - recyclerViewEventHelper = createEventHelper(); - } - return recyclerViewEventHelper; - } - - protected RecyclerViewEventHelper createEventHelper() { - return new RecyclerViewEventHelper(this); - } - - /** - * 设置recyclerView可以滚动 - */ - public void setScrollEnable(boolean enable) { - isEnableScroll = enable; - } - - public int getNodePositionInAdapter(int position) { - return position; - } - - public void scrollToIndex(int xIndex, int yPosition, boolean animated, int duration) { - int positionInAdapter = getNodePositionInAdapter(yPosition); - if (animated) { - doSmoothScrollY(duration, getTotalHeightBefore(positionInAdapter) - getContentOffsetY()); - postDispatchLayout(); - } else { - scrollToPositionWithOffset(positionInAdapter, 0); - //不能调用postDispatchLayout,需要立即调研dispatchLayout,否则滚动位置不对 - dispatchLayout(); - } - } - - /** - * @param xOffset 暂不支持 - * @param yOffset yOffset>0 内容向上移动,yOffset<0, 内容向下移动 - * @param animated 是否有动画 - * @param duration 动画的时间 - */ - public void scrollToContentOffset(double xOffset, double yOffset, boolean animated, - int duration) { - if (!canScrollToContentOffset()) { - return; - } - int yOffsetInPixel = (int) PixelUtil.dp2px(yOffset); - int deltaY = yOffsetInPixel - getContentOffsetY(); - //增加异常保护 - if (animated) { - doSmoothScrollY(duration, deltaY); - } else { - scrollBy(0, deltaY); - } - } - - /** - * renderNodeCount是在setListData的时候更新,必须调用setListData后,确保renderNodeCount和 - * getAdapter().getRenderNodeCount()的值相等,才能进行滚动,否则scrollBy会出现IndexOutOfBoundsException - * 的问题,主要原因就是RecyclerView的内部状态没有通过setListData进行刷新,还是老的数据 - * RenderManager的batch方法应该把dispatchUIFunction放到batchComplete后面,但是这样改动太大 - * - * @return - */ - private boolean canScrollToContentOffset() { - return renderNodeCount == getAdapter().getRenderNodeCount(); - } - - private void doSmoothScrollY(int duration, int scrollToYPos) { - if (duration != 0) { - if (scrollToYPos != 0 && !didStructureChange()) { - smoothScrollBy(0, scrollToYPos, null, duration); - postDispatchLayout(); - } - } else { - smoothScrollBy(0, scrollToYPos); - postDispatchLayout(); + implements IHeaderAttachListener, IHippyViewAboundListener, HippyNestedScrollTarget2 { + + private static int DEFAULT_ITEM_VIEW_CACHE_SIZE = 8; + protected HippyEngineContext hippyEngineContext; + protected ADP listAdapter; + protected boolean isEnableScroll = true; //使能ListView的滚动功能 + protected StickyHeaderHelper stickyHeaderHelper; //支持吸顶 + protected IHeaderHost headerHost; //用于pullHeader下拉刷新 + protected LayoutManager layoutManager; + protected RecyclerViewEventHelper recyclerViewEventHelper;//事件集合 + protected int renderNodeCount = 0; + private NodePositionHelper nodePositionHelper; + private ViewStickEventHelper viewStickEventHelper; + private boolean stickEventEnable; + private int mInitialContentOffset; + private boolean isTvPlatform = false; + private HippyRecycleViewFocusHelper mFocusHelper = null; + private final int[] mScrollConsumedPair = new int[2]; + private final Priority[] mNestedScrollPriority = {Priority.SELF, Priority.NOT_SET, + Priority.NOT_SET, Priority.NOT_SET, Priority.NOT_SET}; + private int mNestedScrollAxesTouch; + private int mNestedScrollAxesNonTouch; + private ArrayList mFocusableViews; + + public HippyRecyclerView(Context context) { + super(context); + } + + public HippyRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public HippyRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void init() { + super.init(); + // enable nested scrolling + setNestedScrollingEnabled(true); + } + + public void onDestroy() { + if (stickyHeaderHelper != null) { + stickyHeaderHelper.detachSticky(); + } + } + + public ADP getAdapter() { + return listAdapter; + } + + @Override + public void setAdapter(@Nullable Adapter adapter) { + listAdapter = (ADP) adapter; + super.setAdapter(adapter); + } + + public NodePositionHelper getNodePositionHelper() { + if (nodePositionHelper == null) { + nodePositionHelper = new NodePositionHelper(); + } + return nodePositionHelper; + } + + public void setOrientation(LinearLayoutManager layoutManager) { + this.layoutManager = layoutManager; + } + + public void setHeaderHost(IHeaderHost headerHost) { + this.headerHost = headerHost; + } + + public void setHippyEngineContext(HippyEngineContext hippyEngineContext) { + this.hippyEngineContext = hippyEngineContext; + } + + public void initRecyclerView() { + isTvPlatform = hippyEngineContext.isRunningOnTVPlatform(); + setAdapter(new HippyRecyclerListAdapter(this, this.hippyEngineContext)); + intEventHelper(); + setItemViewCacheSize(DEFAULT_ITEM_VIEW_CACHE_SIZE); + if (isTvPlatform) { + mFocusHelper = new HippyRecycleViewFocusHelper(this); + setFocusableInTouchMode(true); + } + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (!isEnableScroll || mNestedScrollAxesTouch != SCROLL_AXIS_NONE) { + // We want to prevent the same direction intercepts only, so we can't use + // `requestDisallowInterceptTouchEvent` for this purpose. + // `mNestedScrollAxesTouch != SCROLL_AXIS_NONE` means has nested scroll child, no + // need to intercept + return false; + } + return super.onInterceptTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent e) { + if (!isEnableScroll) { + return false; + } + return super.onTouchEvent(e); + } + + public void setInitialContentOffset(int initialContentOffset) { + mInitialContentOffset = initialContentOffset; + } + + private int getFirstVisiblePositionByOffset(int offset) { + int position = 0; + int distanceToPosition = 0; + int itemCount = getAdapter().getItemCount(); + boolean vertical = HippyListUtils.isVerticalLayout(this); + for (int i = 0; i < itemCount; i++) { + distanceToPosition += + vertical ? listAdapter.getItemHeight(i) : listAdapter.getItemWidth(i); + if (distanceToPosition > offset) { + position = i; + break; + } + } + return position; } - } - private void postDispatchLayout() { - post(new Runnable() { - @Override - public void run() { + private void scrollToInitContentOffset() { + int position = getFirstVisiblePositionByOffset(mInitialContentOffset); + int positionOffset = -(mInitialContentOffset - getTotalHeightBefore(position)); + scrollToPositionWithOffset(position, positionOffset); + mInitialContentOffset = 0; + } + + /** + * 刷新数据 + */ + public void setListData() { + LogUtils.d("HippyRecyclerView", "itemCount =" + listAdapter.getItemCount()); + listAdapter.notifyDataSetChanged(); + + if (isTvPlatform) { + mFocusHelper.setListData(); + } + + renderNodeCount = listAdapter.getRenderNodeCount(); + if (renderNodeCount > 0 && mInitialContentOffset > 0) { + scrollToInitContentOffset(); + } + //notifyDataSetChanged 本身是可以触发requestLayout的,但是Hippy框架下 HippyRootView 已经把 + //onLayout方法重载写成空方法,requestLayout不会回调孩子节点的onLayout,这里需要自己发起dispatchLayout dispatchLayout(); - } - }); - } - - public void scrollToTop() { - LayoutManager layoutManager = getLayoutManager(); - if (layoutManager.canScrollHorizontally()) { - smoothScrollBy(-getContentOffsetX(), 0); - } else { - smoothScrollBy(0, -getContentOffsetY()); - } - postDispatchLayout(); - } - - /** - * @param enable true :支持Item 上滑吸顶功能 - */ - public void setRowShouldSticky(boolean enable) { - if (enable) { - if (stickyHeaderHelper == null) { - stickyHeaderHelper = new StickyHeaderHelper(this, listAdapter, this, headerHost); - addOnScrollListener(stickyHeaderHelper); - } - } else { - if (stickyHeaderHelper != null) { - removeOnScrollListener(stickyHeaderHelper); - } - } - } - - /** - * 同步删除RenderNode对应注册的View,deleteChild是递归删除RenderNode创建的所有的view - */ - @Override - public void onViewAbound(HippyRecyclerViewHolder viewHolder) { - if (viewHolder.bindNode != null && !viewHolder.bindNode.isDelete()) { - getAdapter().deleteExistRenderView(viewHolder.bindNode); - } - } - - /** - * 当header被摘下来,需要对header进行还原或者回收对处理 遍历所有都ViewHolder,看看有没有收纳这个headerView都ViewHolder - * 如果没有,需要把aboundHeader进行回收,并同步删除render节点对应都view - * - * @param aboundHeader HeaderView对应的Holder - * @param currentHeaderView headerView的实体内容 - */ - @Override - public void onHeaderDetached(ViewHolder aboundHeader, View currentHeaderView) { - boolean findHostViewHolder = false; - for (int i = 0; i < getChildCountWithCaches(); i++) { - ViewHolder viewHolder = getChildViewHolder(getChildAtWithCaches(i)); - if (isTheSameRenderNode((HippyRecyclerViewHolder) aboundHeader, - (HippyRecyclerViewHolder) viewHolder)) { - findHostViewHolder = true; - fillContentView(currentHeaderView, viewHolder); - break; - } - } - //当header无处安放,抛弃view都同时,需要同步给Hippy进行View都删除,不然后续无法创建对应都View - if (!findHostViewHolder) { - onViewAbound((HippyRecyclerViewHolder) aboundHeader); - } - } - - private boolean fillContentView(View currentHeaderView, ViewHolder viewHolder) { - if (viewHolder != null && viewHolder.itemView instanceof ViewGroup) { - ViewGroup itemView = (ViewGroup) viewHolder.itemView; - if (itemView.getChildCount() <= 0) { - itemView.addView(currentHeaderView); - } - } - return false; - } - - private boolean isTheSameRenderNode(HippyRecyclerViewHolder aboundHeader, - HippyRecyclerViewHolder viewHolder) { - if (viewHolder.bindNode != null && aboundHeader.bindNode != null) { - return viewHolder.bindNode.getId() == aboundHeader.bindNode.getId(); - } - return false; - } - - public void setNodePositionHelper(NodePositionHelper nodePositionHelper) { - this.nodePositionHelper = nodePositionHelper; - } - - @Override - public String toString() { - return this.getClass().getSimpleName() + "{renderNodeCount:" + renderNodeCount + ",state:" - + getStateInfo() - + "}"; - } + } + + /** + * Vertical offset of the content, might be different than the + * {@link #computeVerticalScrollOffset()} if pullHeader exist. + */ + public int getContentOffsetY() { + if (HippyListUtils.isVerticalLayout(this)) { + int offset = computeVerticalScrollOffset(); + if (listAdapter.headerRefreshHelper != null) { + offset -= listAdapter.headerRefreshHelper.getVisibleSize(); + } + return offset; + } + return 0; + } + + /** + * Horizontal offset of the content, might be different than the + * {@link #computeHorizontalScrollOffset()} if pullHeader exist. + */ + public int getContentOffsetX() { + if (HippyListUtils.isHorizontalLayout(this)) { + int offset = computeHorizontalScrollOffset(); + if (listAdapter.headerRefreshHelper != null) { + offset -= listAdapter.headerRefreshHelper.getVisibleSize(); + } + return offset; + } + return 0; + } + + /** + * 获取一个View的可视高度,并非view本身的height,有可能部分是被滑出到屏幕外部 + */ + protected int getVisibleHeight(View firstChildView) { + return getViewVisibleRect(firstChildView).height(); + } + + /** + * 获取一个View的可视高度,并非view本身的height,有可能部分是被滑出到屏幕外部 + */ + protected int getVisibleWidth(View firstChildView) { + return getViewVisibleRect(firstChildView).width(); + } + + /** + * 获取view在父亲中的可视区域 + */ + private Rect getViewVisibleRect(View view) { + Rect rect = new Rect(); + if (view != null) { + view.getLocalVisibleRect(rect); + } + return rect; + } + + /** + * 获取position 前面的内容高度,不包含position自身的高度 对于竖向排版,取ItemHeight求和,对于横向排版,取ItemWidth求和 + */ + public int getTotalHeightBefore(int position) { + int totalHeightBefore = 0; + boolean vertical = HippyListUtils.isVerticalLayout(this); + for (int i = 0; i < position; i++) { + totalHeightBefore += + vertical ? listAdapter.getItemHeight(i) : listAdapter.getItemWidth(i); + } + return totalHeightBefore; + } + + /** + * 获取renderNodePosition前面的内容高度,不包含renderNodePosition自身的高度 对于竖向排版,取RenderNodeHeight求和,对于横向排版,取RenderNodeWidth求和 + */ + public int getRenderNodeHeightBefore(int renderNodePosition) { + int renderNodeTotalHeight = 0; + boolean vertical = HippyListUtils.isVerticalLayout(this); + for (int i = 0; i < renderNodePosition; i++) { + renderNodeTotalHeight += vertical ? listAdapter.getRenderNodeHeight(i) + : listAdapter.getRenderNodeWidth(i); + } + return renderNodeTotalHeight; + } + + + /** + * 获取position 前面的内容高度,不包含position自身的高度 + */ + public int getTotalWithBefore(int position) { + int totalWidthBefore = 0; + for (int i = 0; i < position; i++) { + totalWidthBefore += listAdapter.getItemWidth(i); + } + return totalWidthBefore; + } + + public RecyclerViewEventHelper getRecyclerViewEventHelper() { + return intEventHelper(); + } + + private RecyclerViewEventHelper intEventHelper() { + if (recyclerViewEventHelper == null) { + recyclerViewEventHelper = createEventHelper(); + } + return recyclerViewEventHelper; + } + + protected RecyclerViewEventHelper createEventHelper() { + return new RecyclerViewEventHelper(this); + } + + /** + * 设置recyclerView可以滚动 + */ + public void setScrollEnable(boolean enable) { + isEnableScroll = enable; + } + + public int getNodePositionInAdapter(int position) { + if (listAdapter.headerRefreshHelper != null) { + // pullHeader exist and occupy position #0, skip it + return position + 1; + } + return position; + } + + public void scrollToIndex(int xIndex, int yIndex, boolean animated, int duration) { + boolean isHorizontal = HippyListUtils.isHorizontalLayout(this); + int positionInAdapter = getNodePositionInAdapter(isHorizontal ? xIndex : yIndex); + if (animated) { + int deltaX = 0; + int deltaY = 0; + if (isHorizontal) { + deltaX = getTotalHeightBefore(positionInAdapter) - computeHorizontalScrollOffset(); + } else { + deltaY = getTotalHeightBefore(positionInAdapter) - computeVerticalScrollOffset(); + } + doSmoothScrollBy(deltaX, deltaY, duration); + postDispatchLayout(); + } else { + scrollToPositionWithOffset(positionInAdapter, 0); + //不能调用postDispatchLayout,需要立即调研dispatchLayout,否则滚动位置不对 + dispatchLayout(); + } + } + + /** + * @param xOffset 暂不支持 + * @param yOffset yOffset>0 内容向上移动,yOffset<0, 内容向下移动 + * @param animated 是否有动画 + * @param duration 动画的时间 + */ + public void scrollToContentOffset(double xOffset, double yOffset, boolean animated, + int duration) { + if (!canScrollToContentOffset()) { + return; + } + int xOffsetInPixel = (int) PixelUtil.dp2px(xOffset); + int deltaX = xOffsetInPixel - getContentOffsetX(); + int yOffsetInPixel = (int) PixelUtil.dp2px(yOffset); + int deltaY = yOffsetInPixel - getContentOffsetY(); + //增加异常保护 + if (animated) { + doSmoothScrollBy(deltaX, deltaY, duration); + } else { + scrollBy(deltaX, deltaY); + } + } + + /** + * renderNodeCount是在setListData的时候更新,必须调用setListData后,确保renderNodeCount和 + * getAdapter().getRenderNodeCount()的值相等,才能进行滚动,否则scrollBy会出现IndexOutOfBoundsException + * 的问题,主要原因就是RecyclerView的内部状态没有通过setListData进行刷新,还是老的数据, 这个比现的场景来自于QQ浏览器的搜索tab的切换。 + * RenderManager的batch方法应该把dispatchUIFunction放到batchComplete后面,但是这样改动太大 + * + * @return + */ + private boolean canScrollToContentOffset() { + return renderNodeCount == getAdapter().getRenderNodeCount(); + } + + private void doSmoothScrollBy(int dx, int dy, int duration) { + if (dx == 0 && dy == 0) { + return; + } + if (duration != 0) { + if (!didStructureChange()) { + smoothScrollBy(dx, dy, duration); + postDispatchLayout(); + } + } else { + smoothScrollBy(dx, dy); + postDispatchLayout(); + } + } + + private void postDispatchLayout() { + post(new Runnable() { + @Override + public void run() { + dispatchLayout(); + } + }); + } + + public void scrollToTop() { + if (HippyListUtils.isHorizontalLayout(this)) { + smoothScrollBy(-getContentOffsetX(), 0); + } else { + smoothScrollBy(0, -getContentOffsetY()); + } + postDispatchLayout(); + } + + /** + * @param enable true :支持Item 上滑吸顶功能 + */ + public void setRowShouldSticky(boolean enable) { + if (enable) { + if (stickyHeaderHelper == null) { + stickyHeaderHelper = new StickyHeaderHelper(this, listAdapter, this, headerHost); + addOnScrollListener(stickyHeaderHelper); + } + } else { + if (stickyHeaderHelper != null) { + removeOnScrollListener(stickyHeaderHelper); + } + } + } + + /** + * 同步删除RenderNode对应注册的View,deleteChild是递归删除RenderNode创建的所有的view + */ + @Override + public void onViewAbound(HippyRecyclerViewHolder viewHolder) { + if (viewHolder.bindNode != null && !viewHolder.bindNode.isDelete()) { + getAdapter().deleteExistRenderView(viewHolder.bindNode); + } + } + + /** + * 当header被摘下来,需要对header进行还原或者回收对处理 遍历所有都ViewHolder,看看有没有收纳这个headerView都ViewHolder + * 如果没有,需要把aboundHeader进行回收,并同步删除render节点对应都view + * + * @param aboundHeader HeaderView对应的Holder + * @param currentHeaderView headerView的实体内容 + */ + @Override + public void onHeaderDetached(ViewHolder aboundHeader, View currentHeaderView) { + boolean findHostViewHolder = false; + for (int i = 0; i < getChildCountWithCaches(); i++) { + ViewHolder viewHolder = getChildViewHolder(getChildAtWithCaches(i)); + if (isTheSameRenderNode((HippyRecyclerViewHolder) aboundHeader, + (HippyRecyclerViewHolder) viewHolder)) { + findHostViewHolder = true; + fillContentView(currentHeaderView, viewHolder); + break; + } + } + //当header无处安放,抛弃view都同时,需要同步给Hippy进行View都删除,不然后续无法创建对应都View + if (!findHostViewHolder) { + onViewAbound((HippyRecyclerViewHolder) aboundHeader); + } + } + + private boolean fillContentView(View currentHeaderView, ViewHolder viewHolder) { + if (viewHolder != null && viewHolder.itemView instanceof ViewGroup) { + ViewGroup itemView = (ViewGroup) viewHolder.itemView; + if (itemView.getChildCount() <= 0) { + itemView.addView(currentHeaderView, generateStickyLayoutParams()); + } + } + return false; + } + + public boolean isTheSameRenderNode(HippyRecyclerViewHolder aboundHeader, + HippyRecyclerViewHolder viewHolder) { + if (viewHolder.bindNode != null && aboundHeader.bindNode != null) { + return viewHolder.bindNode.getId() == aboundHeader.bindNode.getId(); + } + return false; + } + + public void setNodePositionHelper(NodePositionHelper nodePositionHelper) { + this.nodePositionHelper = nodePositionHelper; + } + + public void enableStickEvent(boolean enable) { + stickEventEnable = enable; + } + + public void onAfterUpdateProps() { + if (stickEventEnable) { + ensureViewStickEventHelper(); + } else { + destoryViewStickEventHelper(); + } + } + + private void ensureViewStickEventHelper() { + if (viewStickEventHelper == null) { + viewStickEventHelper = new ViewStickEventHelper((View) this.getParent()); + } + if (stickyHeaderHelper != null) { + stickyHeaderHelper.setStickViewListener(viewStickEventHelper); + } + } + + private void destoryViewStickEventHelper() { + if (stickyHeaderHelper != null) { + stickyHeaderHelper.setStickViewListener(null); + } + viewStickEventHelper = null; + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + if (isTvPlatform) { + mFocusHelper.setKeyCode(event.getKeyCode()); + } + } + return super.dispatchKeyEvent(event); + } + + @Override + public void requestChildFocus(View child, View focused) { + super.requestChildFocus(child, focused); + if (isTvPlatform) { + mFocusHelper.requestChildFocus(child, focused); + } + } + + @Override + public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) { + if (isTvPlatform) { + return mFocusHelper.requestChildRectangleOnScreen(child, rect, immediate); + } + return super.requestChildRectangleOnScreen(child, rect, immediate); + } + + @Override + protected int getChildDrawingOrder(int childCount, int i) { + if (isTvPlatform) { + return mFocusHelper.getChildDrawingOrder(childCount, i); + } + return super.getChildDrawingOrder(childCount, i); + } + + @Override + public View focusSearch(View focused, int direction) { + if (isTvPlatform) { + return mFocusHelper.focusSearch(focused, direction); + } + View result = super.focusSearch(focused, direction); + // {@link RecyclerView#focusSearch} may return not focusable view, + // cause IllegalStateException, so we verify again. + if (result == null || !verifyFocusable(result, direction)) { + return null; + } + return result; + } + + private boolean verifyFocusable(@NonNull View view, int direction) { + boolean inTouchMode = isInTouchMode(); + // first check whether self is able to take focus + if (inTouchMode ? view.isFocusableInTouchMode() : view.isFocusable()) { + return true; + } + // then check whether has focusable descendants + if (!(view instanceof ViewGroup)) { + return false; + } + if (mFocusableViews == null) { + mFocusableViews = new ArrayList<>(); + } + view.addFocusables(mFocusableViews, direction, inTouchMode ? FOCUSABLES_TOUCH_MODE : FOCUSABLES_ALL); + boolean result = !mFocusableViews.isEmpty(); + // clear up the temp array + mFocusableViews.clear(); + return result; + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + "{renderNodeCount:" + renderNodeCount + ",state:" + + getStateInfo() + + "}"; + } + + /*package*/ ViewGroup getStickyContainer(Context context, View renderView) { + FrameLayout container = new FrameLayout(context); + if (renderView != null) { + container.addView(renderView, generateStickyLayoutParams()); + } + return container; + } + + /*package*/ ViewGroup.LayoutParams generateStickyLayoutParams() { + return new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT); + } + + + @Override + public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { + mScrollConsumedPair[0] = 0; + mScrollConsumedPair[1] = 0; + boolean result = super.dispatchNestedPreScroll(dx, dy, mScrollConsumedPair, offsetInWindow); + if (result) { + dx -= mScrollConsumedPair[0]; + dy -= mScrollConsumedPair[1]; + if (consumed != null) { + consumed[0] += mScrollConsumedPair[0]; + consumed[1] += mScrollConsumedPair[1]; + } + } + return handlePullRefresh(dx, dy, consumed) || result; + } + + @Override + public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow, + int type) { + mScrollConsumedPair[0] = 0; + mScrollConsumedPair[1] = 0; + boolean result = super.dispatchNestedPreScroll(dx, dy, mScrollConsumedPair, offsetInWindow, type); + if (result) { + dx -= mScrollConsumedPair[0]; + dy -= mScrollConsumedPair[1]; + if (consumed != null) { + consumed[0] += mScrollConsumedPair[0]; + consumed[1] += mScrollConsumedPair[1]; + } + } + return type == ViewCompat.TYPE_TOUCH && handlePullRefresh(dx, dy, consumed) || result; + } + + @Override + public void stopNestedScroll() { + super.stopNestedScroll(); + endPullRefresh(); + } + + @Override + public void stopNestedScroll(int type) { + super.stopNestedScroll(type); + if (type == ViewCompat.TYPE_TOUCH) { + endPullRefresh(); + } + } + + protected boolean handlePullRefresh(int dx, int dy, @Nullable int[] consumed) { + if (listAdapter == null || + (listAdapter.headerRefreshHelper == null && listAdapter.footerRefreshHelper == null)) { + return false; + } + boolean isHorizontal = HippyListUtils.isHorizontalLayout(this); + int diff = isHorizontal ? dx : dy; + if (diff == 0) { + return false; + } + if (listAdapter.headerRefreshHelper != null) { + int myConsumed = listAdapter.headerRefreshHelper.handleDrag(diff); + if (myConsumed != 0) { + if (consumed != null) { + consumed[isHorizontal ? 0 : 1] += myConsumed; + } + return true; + } + } + if (listAdapter.footerRefreshHelper != null) { + int myConsumed = listAdapter.footerRefreshHelper.handleDrag(diff); + if (myConsumed != 0) { + if (consumed != null) { + consumed[isHorizontal ? 0 : 1] += myConsumed; + } + return true; + } + } + return false; + } + + protected void endPullRefresh() { + if (listAdapter == null) { + return; + } + if (listAdapter.headerRefreshHelper != null) { + listAdapter.headerRefreshHelper.endDrag(); + } + if (listAdapter.footerRefreshHelper != null) { + listAdapter.footerRefreshHelper.endDrag(); + } + } + + protected boolean isPullRefreshShowing() { + if (listAdapter == null) { + return false; + } + return listAdapter.headerRefreshHelper != null + && listAdapter.headerRefreshHelper.getVisibleSize() > 0 + || listAdapter.footerRefreshHelper != null + && listAdapter.footerRefreshHelper.getVisibleSize() > 0; + } + + @Override + public void setNestedScrollPriority(int direction, Priority priority) { + mNestedScrollPriority[direction] = priority; + } + + @Override + public Priority getNestedScrollPriority(int direction) { + Priority result = mNestedScrollPriority[direction]; + if (result == Priority.NOT_SET) { + result = mNestedScrollPriority[DIRECTION_ALL]; + } + return result; + } + + private int computeHorizontallyScrollDistance(int dx) { + if (dx < 0) { + return Math.max(dx, -computeHorizontalScrollOffset()); + } + if (dx > 0) { + int avail = computeHorizontalScrollRange() - computeHorizontalScrollExtent() + - computeHorizontalScrollOffset() - 1; + return Math.min(dx, avail); + } + return 0; + } + + private int computeVerticallyScrollDistance(int dy) { + if (dy < 0) { + return Math.max(dy, -computeVerticalScrollOffset()); + } + if (dy > 0) { + int avail = computeVerticalScrollRange() - computeVerticalScrollExtent() + - computeVerticalScrollOffset() - 1; + return Math.min(dy, avail); + } + return 0; + } + + @Override + public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes) { + return onStartNestedScroll(child, target, axes, ViewCompat.TYPE_TOUCH); + } + + @Override + public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes, + int type) { + if (!isEnableScroll) { + return false; + } + // Determine whether to respond to the nested scrolling event of the child + LayoutManager manager = getLayoutManager(); + if (manager == null) { + return false; + } + int myAxes = SCROLL_AXIS_NONE; + if (manager.canScrollVertically() && (axes & SCROLL_AXIS_VERTICAL) != 0) { + myAxes |= SCROLL_AXIS_VERTICAL; + } + if (manager.canScrollHorizontally() && (axes & SCROLL_AXIS_HORIZONTAL) != 0) { + myAxes |= SCROLL_AXIS_HORIZONTAL; + } + if (myAxes != SCROLL_AXIS_NONE) { + if (type == ViewCompat.TYPE_TOUCH) { + mNestedScrollAxesTouch = myAxes; + } else { + mNestedScrollAxesNonTouch = myAxes; + } + return true; + } + return false; + } + + @Override + public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes) { + onNestedScrollAccepted(child, target, axes, ViewCompat.TYPE_TOUCH); + } + + @Override + public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes, + int type) { + startNestedScroll( + type == ViewCompat.TYPE_TOUCH ? mNestedScrollAxesTouch : mNestedScrollAxesNonTouch, + type); + } + + @Override + public void onStopNestedScroll(@NonNull View child) { + onStopNestedScroll(child, ViewCompat.TYPE_TOUCH); + } + + @Override + public void onStopNestedScroll(@NonNull View target, int type) { + if (type == ViewCompat.TYPE_TOUCH) { + mNestedScrollAxesTouch = SCROLL_AXIS_NONE; + } else { + mNestedScrollAxesNonTouch = SCROLL_AXIS_NONE; + } + stopNestedScroll(type); + } + + @Override + public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, + int dxUnconsumed, int dyUnconsumed) { + onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, + ViewCompat.TYPE_TOUCH); + } + + @Override + public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, + int dxUnconsumed, int dyUnconsumed, int type) { + // Step 1: process pull refresh + if (type == ViewCompat.TYPE_TOUCH) { + if (HippyNestedScrollHelper.priorityOfX(target, dxUnconsumed) == Priority.SELF + || HippyNestedScrollHelper.priorityOfY(target, dyUnconsumed) == Priority.SELF) { + mScrollConsumedPair[0] = 0; + mScrollConsumedPair[1] = 0; + if (handlePullRefresh(dxUnconsumed, dyUnconsumed, mScrollConsumedPair)) { + dxConsumed += mScrollConsumedPair[0]; + dyConsumed += mScrollConsumedPair[1]; + dxUnconsumed -= mScrollConsumedPair[0]; + dyUnconsumed -= mScrollConsumedPair[1]; + } + } + } else if (isPullRefreshShowing()) { + // don't respond non-touch scroll, prevent header/footer scroll to wrong position + return; + } + // Step 2: process the current View + int myDx = HippyNestedScrollHelper.priorityOfX(target, dxUnconsumed) == Priority.SELF + ? computeHorizontallyScrollDistance(dxUnconsumed) : 0; + int myDy = HippyNestedScrollHelper.priorityOfY(target, dyUnconsumed) == Priority.SELF + ? computeVerticallyScrollDistance(dyUnconsumed) : 0; + if (myDx != 0 || myDy != 0) { + scrollBy(myDx, myDy); + dxConsumed += myDx; + dyConsumed += myDy; + dxUnconsumed -= myDx; + dyUnconsumed -= myDy; + } + // Step 3: dispatch to the parent for processing + int parentDx = HippyNestedScrollHelper.priorityOfX(this, dxUnconsumed) == Priority.NONE ? 0 + : dxUnconsumed; + int parentDy = HippyNestedScrollHelper.priorityOfY(this, dyUnconsumed) == Priority.NONE ? 0 + : dyUnconsumed; + if (parentDx != 0 || parentDy != 0) { + dispatchNestedScroll(dxConsumed, dyConsumed, parentDx, parentDy, null, type); + } + } + + @Override + public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed) { + onNestedPreScroll(target, dx, dy, consumed, ViewCompat.TYPE_TOUCH); + } + + @Override + public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, + int type) { + // Step 1: Dispatch to the parent for processing + int parentDx = HippyNestedScrollHelper.priorityOfX(this, dx) == Priority.NONE ? 0 : dx; + int parentDy = HippyNestedScrollHelper.priorityOfY(this, dy) == Priority.NONE ? 0 : dy; + if (parentDx != 0 || parentDy != 0) { + mScrollConsumedPair[0] = 0; + mScrollConsumedPair[1] = 0; + // must use super to prevent duplicate handlePullRefresh + super.dispatchNestedPreScroll(parentDx, parentDy, mScrollConsumedPair, null, type); + consumed[0] += mScrollConsumedPair[0]; + consumed[1] += mScrollConsumedPair[1]; + dx -= mScrollConsumedPair[0]; + dy -= mScrollConsumedPair[1]; + } + // Step 2: process pull refresh + if (HippyNestedScrollHelper.priorityOfX(target, dx) == Priority.PARENT + || HippyNestedScrollHelper.priorityOfY(target, dy) == Priority.PARENT) { + if (type == ViewCompat.TYPE_TOUCH) { + mScrollConsumedPair[0] = 0; + mScrollConsumedPair[1] = 0; + if (handlePullRefresh(dx, dy, mScrollConsumedPair)) { + consumed[0] += mScrollConsumedPair[0]; + consumed[1] += mScrollConsumedPair[1]; + dx -= mScrollConsumedPair[0]; + dy -= mScrollConsumedPair[1]; + } + } else if (isPullRefreshShowing()) { + // don't respond non-touch scroll, prevent header/footer scroll to wrong position + consumed[0] += dx; + consumed[1] += dy; + return; + } + } + // Step 3: process the current View + int myDx = HippyNestedScrollHelper.priorityOfX(target, dx) == Priority.PARENT + ? computeHorizontallyScrollDistance(dx) : 0; + int myDy = HippyNestedScrollHelper.priorityOfY(target, dy) == Priority.PARENT + ? computeVerticallyScrollDistance(dy) : 0; + if (myDx != 0 || myDy != 0) { + consumed[0] += myDx; + consumed[1] += myDy; + scrollBy(myDx, myDy); + } + } + + @Override + public int getNestedScrollAxes() { + return mNestedScrollAxesTouch | mNestedScrollAxesNonTouch; + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewController.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewController.java index 6dd6dbcb4ec..85c89e98071 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewController.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewController.java @@ -16,172 +16,266 @@ package com.tencent.mtt.hippy.views.hippylist; +import static com.tencent.mtt.hippy.dom.node.NodeProps.OVER_PULL; + import android.content.Context; -import androidx.recyclerview.widget.EasyLinearLayoutManager; +import android.view.ViewGroup; +import androidx.recyclerview.widget.HippyLinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; import android.view.View; +import androidx.recyclerview.widget.RecyclerView.LayoutManager; import com.tencent.mtt.hippy.HippyInstanceContext; import com.tencent.mtt.hippy.HippyRootView; import com.tencent.mtt.hippy.annotation.HippyController; import com.tencent.mtt.hippy.annotation.HippyControllerProps; import com.tencent.mtt.hippy.common.HippyArray; import com.tencent.mtt.hippy.common.HippyMap; +import com.tencent.mtt.hippy.dom.node.NodeProps; import com.tencent.mtt.hippy.uimanager.ControllerManager; import com.tencent.mtt.hippy.uimanager.HippyViewController; import com.tencent.mtt.hippy.uimanager.ListViewRenderNode; import com.tencent.mtt.hippy.uimanager.RenderNode; +import com.tencent.mtt.hippy.utils.PixelUtil; + +/** + * Created on 2020/12/22. + */ + +@HippyController(name = HippyRecyclerViewController.CLASS_NAME, names = {HippyRecyclerViewController.EXTRA_CLASS_NAME}) +public class HippyRecyclerViewController extends HippyViewController { + + public static final String CLASS_NAME = "ListView"; + public static final String EXTRA_CLASS_NAME = "RecyclerView"; + public static final String SCROLL_TO_INDEX = "scrollToIndex"; + public static final String SCROLL_TO_CONTENT_OFFSET = "scrollToContentOffset"; + public static final String SCROLL_TO_TOP = "scrollToTop"; + public static final String HORIZONTAL = "horizontal"; -@HippyController(name = HippyRecyclerViewController.CLASS_NAME) -public class HippyRecyclerViewController extends - HippyViewController { - - public static final String CLASS_NAME = "RecyclerView"; - public static final String SCROLL_TO_INDEX = "scrollToIndex"; - public static final String SCROLL_TO_CONTENT_OFFSET = "scrollToContentOffset"; - public static final String SCROLL_TO_TOP = "scrollToTop"; - public static final String COLLAPSE_PULL_HEADER = "collapsePullHeader"; - public static final String EXPAND_PULL_HEADER = "expandPullHeader"; - - public HippyRecyclerViewController() { - - } - - @Override - public int getChildCount(HRW viewGroup) { - return viewGroup.getChildCountWithCaches(); - } - - @Override - public View getChildAt(HRW viewGroup, int index) { - return viewGroup.getChildAtWithCaches(index); - } - - @Override - public void onBatchComplete(HRW view) { - super.onBatchComplete(view); - view.setListData(); - } - - @Override - protected View createViewImpl(Context context) { - return createViewImpl(context, null); - } - - @Override - protected View createViewImpl(Context context, HippyMap iniProps) { - return new HippyRecyclerViewWrapper(context, - initDefault(context, iniProps, new HippyRecyclerView(context))); - } - - public static HippyRecyclerView initDefault(Context context, HippyMap iniProps, - HippyRecyclerView recyclerView) { - LinearLayoutManager layoutManager = new EasyLinearLayoutManager(context); - recyclerView.setItemAnimator(null); - if (iniProps != null && iniProps.containsKey("horizontal")) { - layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); - } - recyclerView.setLayoutManager(layoutManager); - recyclerView.setHippyEngineContext(((HippyInstanceContext) context).getEngineContext()); - recyclerView.initRecyclerView(); - return recyclerView; - } - - @Override - public RenderNode createRenderNode(int id, HippyMap props, String className, - HippyRootView hippyRootView, - ControllerManager controllerManager, - boolean lazy) { - return new ListViewRenderNode(id, props, className, hippyRootView, controllerManager, lazy); - } - - @HippyControllerProps(name = "rowShouldSticky") - public void setRowShouldSticky(HRW view, boolean enable) { - view.setRowShouldSticky(enable); - } - - @HippyControllerProps(name = "onScrollBeginDrag", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) - public void setScrollBeginDragEventEnable(HRW view, boolean flag) { - view.getRecyclerViewEventHelper().setScrollBeginDragEventEnable(flag); - } - - @HippyControllerProps(name = "onScrollEndDrag", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) - public void setScrollEndDragEventEnable(HRW view, boolean flag) { - view.getRecyclerViewEventHelper().setScrollEndDragEventEnable(flag); - } - - @HippyControllerProps(name = "onMomentumScrollBegin", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) - public void setMomentumScrollBeginEventEnable(HRW view, boolean flag) { - view.getRecyclerViewEventHelper().setMomentumScrollBeginEventEnable(flag); - } - - @HippyControllerProps(name = "onMomentumScrollEnd", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) - public void setMomentumScrollEndEventEnable(HRW view, boolean flag) { - view.getRecyclerViewEventHelper().setMomentumScrollEndEventEnable(flag); - } - - @HippyControllerProps(name = "onScrollEnable", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) - public void setOnScrollEventEnable(HRW view, boolean flag) { - view.getRecyclerViewEventHelper().setOnScrollEventEnable(flag); - } - - @HippyControllerProps(name = "exposureEventEnabled", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) - public void setExposureEventEnable(HRW view, boolean flag) { - view.getRecyclerViewEventHelper().setExposureEventEnable(flag); - } - - @HippyControllerProps(name = "scrollEnabled", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = true) - public void setScrollEnable(HRW view, boolean flag) { - view.setScrollEnable(flag); - } - - @HippyControllerProps(name = "scrollEventThrottle", defaultType = HippyControllerProps.NUMBER, defaultNumber = 30.0D) - public void setscrollEventThrottle(HRW view, int scrollEventThrottle) { - view.getRecyclerViewEventHelper().setScrollEventThrottle(scrollEventThrottle); - } - - @HippyControllerProps(name = "preloadItemNumber") - public void setPreloadItemNumber(HRW view, int preloadItemNumber) { - getAdapter(view).setPreloadItemNumber(preloadItemNumber); - } - - @Override - public void dispatchFunction(HRW view, String functionName, HippyArray dataArray) { - super.dispatchFunction(view, functionName, dataArray); - switch (functionName) { - case SCROLL_TO_INDEX: { - // list滑动到某个item - int xIndex = dataArray.getInt(0); - int yIndex = dataArray.getInt(1); - boolean animated = dataArray.getBoolean(2); - int duration = dataArray.getInt(3); //1.2.7 增加滚动时间 ms,animated==true时生效 - view.scrollToIndex(xIndex, yIndex, animated, duration); - break; - } - case SCROLL_TO_CONTENT_OFFSET: { - // list滑动到某个距离 - double xOffset = dataArray.getDouble(0); - double yOffset = dataArray.getDouble(1); - boolean animated = dataArray.getBoolean(2); - int duration = dataArray.getInt(3); //1.2.7 增加滚动时间 ms,animated==true时生效 - view.scrollToContentOffset(xOffset, yOffset, animated, duration); - break; - } - case SCROLL_TO_TOP: { - view.scrollToTop(); - break; - } - case COLLAPSE_PULL_HEADER: { - getAdapter(view).getHeaderEventHelper().onHeaderRefreshFinish(); - break; - } - case EXPAND_PULL_HEADER: { - getAdapter(view).getHeaderEventHelper().onHeaderRefresh(); - break; - } - } - } - - private HippyRecyclerListAdapter getAdapter(HRW view) { - return view.getRecyclerView().getAdapter(); - } + public HippyRecyclerViewController() { + + } + + @Override + public int getChildCount(HRW viewGroup) { + return viewGroup.getChildCountWithCaches(); + } + + @Override + public View getChildAt(HRW viewGroup, int index) { + return viewGroup.getChildAtWithCaches(index); + } + + @Override + public void onViewDestroy(HRW viewGroup) { + ((HRW) viewGroup).getRecyclerView().onDestroy(); + } + + /** + * view 被Hippy的RenderNode 删除了,这样会导致View的child完全是空的,这个view是不能再被recyclerView复用了 + * 否则如果被复用,在adapter的onBindViewHolder的时候,view的实际子view和renderNode的数据不匹配,diff会出现异常 + * 导致item白条,显示不出来,所以被删除的view,需要把viewHolder.setIsRecyclable(false),刷新list后,这个view就 + * 不会进入缓存。 + */ + @Override + protected void deleteChild(ViewGroup parentView, View childView) { + super.deleteChild(parentView, childView); + ((HRW) parentView).getRecyclerView().disableRecycle(childView); + } + + @Override + public void onBatchStart(HRW view) { + super.onBatchStart(view); + view.onBatchStart(); + } + + @Override + public void onBatchComplete(HRW view) { + super.onBatchComplete(view); + view.onBatchComplete(); + view.setListData(); + } + + @Override + protected View createViewImpl(Context context) { + return createViewImpl(context, null); + } + + @Override + protected View createViewImpl(Context context, HippyMap iniProps) { + HippyRecyclerView hippyRecyclerView = initDefault(context, iniProps, new HippyRecyclerView(context)); + boolean overPull = iniProps != null && iniProps.getBoolean(OVER_PULL); + hippyRecyclerView.setEnableOverPull(HippyListUtils.isVerticalLayout(hippyRecyclerView) && overPull); + return new HippyRecyclerViewWrapper(context, hippyRecyclerView); + } + + public static HippyRecyclerView initDefault(Context context, HippyMap iniProps, HippyRecyclerView recyclerView) { + LinearLayoutManager layoutManager = new HippyLinearLayoutManager(context); + recyclerView.setItemAnimator(null); + boolean enableScrollEvent = false; + if (iniProps != null) { + if (iniProps.containsKey(HORIZONTAL)) { + layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); + } + enableScrollEvent = iniProps.getBoolean("onScroll"); + } + recyclerView.setLayoutManager(layoutManager); + recyclerView.setHippyEngineContext(((HippyInstanceContext) context).getEngineContext()); + recyclerView.initRecyclerView(); + recyclerView.getRecyclerViewEventHelper().setOnScrollEventEnable(enableScrollEvent); + return recyclerView; + } + + @Override + public RenderNode createRenderNode(int id, HippyMap props, String className, HippyRootView hippyRootView, + ControllerManager controllerManager, + boolean lazy) { + return new ListViewRenderNode(id, props, className, hippyRootView, controllerManager, lazy); + } + + @HippyControllerProps(name = "horizontal", defaultType = HippyControllerProps.BOOLEAN) + public void setHorizontalEnable(final HRW viewWrapper, boolean flag) { + LayoutManager layoutManager = viewWrapper.getRecyclerView().getLayoutManager(); + if (!(layoutManager instanceof LinearLayoutManager)) { + return; + } + int orientation = ((LinearLayoutManager) layoutManager).getOrientation(); + if (flag) { + if (orientation != LinearLayoutManager.HORIZONTAL) { + ((LinearLayoutManager) layoutManager).setOrientation( + LinearLayoutManager.HORIZONTAL); + viewWrapper.getRecyclerView().getAdapter().onLayoutOrientationChanged(); + } + } else { + if (orientation == LinearLayoutManager.HORIZONTAL) { + ((LinearLayoutManager) layoutManager).setOrientation( + LinearLayoutManager.VERTICAL); + viewWrapper.getRecyclerView().getAdapter().onLayoutOrientationChanged(); + } + } + } + + @HippyControllerProps(name = "rowShouldSticky") + public void setRowShouldSticky(HRW view, boolean enable) { + view.setRowShouldSticky(enable); + } + + @HippyControllerProps(name = "onScrollBeginDrag", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) + public void setScrollBeginDragEventEnable(HRW view, boolean flag) { + view.getRecyclerViewEventHelper().setScrollBeginDragEventEnable(flag); + } + + @HippyControllerProps(name = "onScrollEndDrag", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) + public void setScrollEndDragEventEnable(HRW view, boolean flag) { + view.getRecyclerViewEventHelper().setScrollEndDragEventEnable(flag); + } + + @HippyControllerProps(name = "onMomentumScrollBegin", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) + public void setMomentumScrollBeginEventEnable(HRW view, boolean flag) { + view.getRecyclerViewEventHelper().setMomentumScrollBeginEventEnable(flag); + } + + @HippyControllerProps(name = "onMomentumScrollEnd", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) + public void setMomentumScrollEndEventEnable(HRW view, boolean flag) { + view.getRecyclerViewEventHelper().setMomentumScrollEndEventEnable(flag); + } + + @HippyControllerProps(name = "onScrollEnable", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) + public void setOnScrollEventEnable(HRW view, boolean flag) { + view.getRecyclerViewEventHelper().setOnScrollEventEnable(flag); + } + + @HippyControllerProps(name = "exposureEventEnabled", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) + public void setExposureEventEnable(HRW view, boolean flag) { + view.getRecyclerViewEventHelper().setExposureEventEnable(flag); + } + + @HippyControllerProps(name = "scrollEnabled", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = true) + public void setScrollEnable(HRW view, boolean flag) { + view.setScrollEnable(flag); + } + + @HippyControllerProps(name = "scrollEventThrottle", defaultType = HippyControllerProps.NUMBER, defaultNumber = 30.0D) + public void setscrollEventThrottle(HRW view, int scrollEventThrottle) { + view.getRecyclerViewEventHelper().setScrollEventThrottle(scrollEventThrottle); + } + + @HippyControllerProps(name = "preloadItemNumber") + public void setPreloadItemNumber(HRW view, int preloadItemNumber) { + view.getRecyclerViewEventHelper().setPreloadItemNumber(preloadItemNumber); + } + + @HippyControllerProps(name = "suspendViewListener", defaultType = HippyControllerProps.NUMBER, defaultNumber = 0) + public void setSuspendViewListener(final HRW viewWrapper, int open) { + viewWrapper.getRecyclerView().enableStickEvent(open == 1); + } + + @HippyControllerProps(name = "overScrollEnabled", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) + public void setOverScrollEnable(HRW viewWrapper, boolean flag) { + HippyRecyclerView recyclerView = viewWrapper.getRecyclerView(); + if (recyclerView != null) { + if (flag) { + recyclerView.setOverScrollMode(View.OVER_SCROLL_ALWAYS); + } else { + recyclerView.setOverScrollMode(View.OVER_SCROLL_NEVER); + } + } + setBounces(viewWrapper, flag); + } + + @HippyControllerProps(name = OVER_PULL, defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = true) + public void setBounces(HRW viewWrapper, boolean flag) { + HippyRecyclerView recyclerView = viewWrapper.getRecyclerView(); + if (recyclerView != null) { + recyclerView.setEnableOverPull(flag); + } + } + + @HippyControllerProps(name = "initialContentOffset", defaultType = HippyControllerProps.NUMBER, defaultNumber = 0) + public void setInitialContentOffset(HRW viewWrapper, int offset) { + viewWrapper.getRecyclerView().setInitialContentOffset((int) PixelUtil.dp2px(offset)); + } + + @HippyControllerProps(name = "itemViewCacheSize", defaultType = HippyControllerProps.NUMBER, defaultNumber = 0) + public void setItemViewCacheSize(HRW viewWrapper, int size) { + viewWrapper.getRecyclerView().setItemViewCacheSize(Math.max(size, 2)); + } + + @Override + public void onAfterUpdateProps(HRW viewWrapper) { + super.onAfterUpdateProps(viewWrapper); + viewWrapper.getRecyclerView().onAfterUpdateProps(); + } + + @Override + public void dispatchFunction(HRW view, String functionName, HippyArray dataArray) { + super.dispatchFunction(view, functionName, dataArray); + switch (functionName) { + case SCROLL_TO_INDEX: { + // list滑动到某个item + int xIndex = dataArray.getInt(0); + int yIndex = dataArray.getInt(1); + boolean animated = dataArray.getBoolean(2); + int duration = dataArray.getInt(3); //1.2.7 增加滚动时间 ms,animated==true时生效 + view.scrollToIndex(xIndex, yIndex, animated, duration); + break; + } + case SCROLL_TO_CONTENT_OFFSET: { + // list滑动到某个距离 + double xOffset = dataArray.getDouble(0); + double yOffset = dataArray.getDouble(1); + boolean animated = dataArray.getBoolean(2); + int duration = dataArray.getInt(3); //1.2.7 增加滚动时间 ms,animated==true时生效 + view.scrollToContentOffset(xOffset, yOffset, animated, duration); + break; + } + case SCROLL_TO_TOP: { + view.scrollToTop(); + break; + } + } + } + + private HippyRecyclerListAdapter getAdapter(HRW view) { + return view.getRecyclerView().getAdapter(); + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewHolder.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewHolder.java index 3334e5f29b9..903f818698d 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewHolder.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewHolder.java @@ -21,19 +21,22 @@ import android.view.View; import com.tencent.mtt.hippy.uimanager.ListItemRenderNode; +/** + * Created on 2020/12/22. Description + */ public class HippyRecyclerViewHolder extends ViewHolder { - public ListItemRenderNode bindNode; + public ListItemRenderNode bindNode; - public HippyRecyclerViewHolder(@NonNull View itemView, ListItemRenderNode renderNode) { - super(itemView); - bindNode = renderNode; - } + public HippyRecyclerViewHolder(@NonNull View itemView, ListItemRenderNode renderNode) { + super(itemView); + bindNode = renderNode; + } - public boolean isRenderDeleted() { - if (bindNode != null) { - return bindNode.isDelete(); + public boolean isRenderDeleted() { + if (bindNode != null) { + return bindNode.isDelete(); + } + return false; } - return false; - } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewWrapper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewWrapper.java index e8daa7d6202..376b3252b9f 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewWrapper.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewWrapper.java @@ -18,117 +18,141 @@ import android.content.Context; import android.os.Build.VERSION_CODES; +import android.view.View; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; +import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.recyclerview.widget.HippyRecyclerExtension; import androidx.recyclerview.widget.HippyRecyclerPool; -import android.view.View; -import android.view.ViewTreeObserver.OnGlobalLayoutListener; -import android.widget.FrameLayout; import com.tencent.mtt.hippy.HippyEngineContext; import com.tencent.mtt.hippy.HippyInstanceContext; import com.tencent.mtt.hippy.uimanager.HippyViewBase; import com.tencent.mtt.hippy.uimanager.NativeGestureDispatcher; -import com.tencent.mtt.nxeasy.recyclerview.helper.skikcy.IHeaderHost; - -public class HippyRecyclerViewWrapper extends FrameLayout implements - HippyViewBase, - IHeaderHost { - - protected final HippyEngineContext hpContext; - protected HRCV recyclerView; - private NativeGestureDispatcher nativeGestureDispatcher; - - public HippyRecyclerViewWrapper(@NonNull Context context, HRCV recyclerView) { - super(context); - this.recyclerView = recyclerView; - addView(recyclerView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - hpContext = ((HippyInstanceContext) context).getEngineContext(); - HippyRecyclerExtension cacheExtension = new HippyRecyclerExtension(recyclerView, hpContext, - recyclerView.getNodePositionHelper()); - recyclerView.setViewCacheExtension(cacheExtension); - recyclerView.setHeaderHost(this); - HippyRecyclerPool pool = new HippyRecyclerPool(hpContext, this, cacheExtension, - recyclerView.getNodePositionHelper()); - pool.setViewAboundListener(recyclerView); - recyclerView.setRecycledViewPool(pool); - - } - - @Override - public int computeVerticalScrollOffset() { - return recyclerView.computeVerticalScrollOffset(); - } - - @Override - public NativeGestureDispatcher getGestureDispatcher() { - return nativeGestureDispatcher; - } - - @Override - public void setGestureDispatcher(NativeGestureDispatcher dispatcher) { - nativeGestureDispatcher = dispatcher; - } - - public int getChildCountWithCaches() { - return recyclerView.getChildCountWithCaches(); - } - - public View getChildAtWithCaches(int index) { - return recyclerView.getChildAtWithCaches(index); - } - - public void setListData() { - recyclerView.setListData(); - } - - public RecyclerViewEventHelper getRecyclerViewEventHelper() { - return recyclerView.getRecyclerViewEventHelper(); - } - - public void setScrollEnable(boolean flag) { - recyclerView.setScrollEnable(flag); - } - - public void scrollToIndex(int xIndex, int yIndex, boolean animated, int duration) { - recyclerView.scrollToIndex(xIndex, yIndex, animated, duration); - } - - public void scrollToContentOffset(double xOffset, double yOffset, boolean animated, - int duration) { - recyclerView.scrollToContentOffset(xOffset, yOffset, animated, duration); - } - - public void scrollToTop() { - recyclerView.scrollToTop(); - } - - public void setRowShouldSticky(boolean enable) { - recyclerView.setRowShouldSticky(enable); - } - - public HRCV getRecyclerView() { - return recyclerView; - } - - /** - * 将HeaderView放到RecyclerView到父亲View上面 - */ - @Override - public void attachHeader(View headerView, LayoutParams layoutParams) { - addView(headerView, layoutParams); - layout(getLeft(), getTop(), getRight(), getBottom()); - getViewTreeObserver().dispatchOnGlobalLayout(); - } - - @Override - public void addOnLayoutListener(OnGlobalLayoutListener listener) { - getViewTreeObserver().addOnGlobalLayoutListener(listener); - } - - @RequiresApi(api = VERSION_CODES.JELLY_BEAN) - @Override - public void removeOnLayoutListener(OnGlobalLayoutListener listener) { - getViewTreeObserver().removeOnGlobalLayoutListener(listener); - } +import com.tencent.mtt.hippy.views.common.HippyNestedScrollComponent; +import com.tencent.mtt.hippy.views.hippylist.recyclerview.helper.skikcy.IHeaderHost; + +/** + * Description + * 这里搞一个RecyclerViewWrapper + * 其实是一个普通的FrameLayout,并不是RecyclerView,主要为吸顶的Header功能考虑, + * 系统RecyclerView做吸顶功能最简单的实现的是在RecyclerView的父亲覆盖一个View, + * 这样不会影响RecyclerView的Layout的排版,否则就需要重写LayoutManager,重新layoutManager也是后面要考虑的。 + */ +public class HippyRecyclerViewWrapper extends FrameLayout implements HippyViewBase, + IHeaderHost, HippyNestedScrollComponent { + + protected final HippyEngineContext hpContext; + protected HRCV recyclerView; + private NativeGestureDispatcher nativeGestureDispatcher; + + public HippyRecyclerViewWrapper(@NonNull Context context, HRCV recyclerView) { + super(context); + this.recyclerView = recyclerView; + addView(recyclerView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + hpContext = ((HippyInstanceContext) context).getEngineContext(); + HippyRecyclerExtension cacheExtension = new HippyRecyclerExtension(recyclerView, hpContext, + recyclerView.getNodePositionHelper()); + recyclerView.setViewCacheExtension(cacheExtension); + recyclerView.setHeaderHost(this); + HippyRecyclerPool pool = new HippyRecyclerPool(hpContext, this, cacheExtension, + recyclerView.getNodePositionHelper()); + pool.setViewAboundListener(recyclerView); + recyclerView.setRecycledViewPool(pool); + + } + + @Override + public int computeVerticalScrollOffset() { + return recyclerView.computeVerticalScrollOffset(); + } + + @Override + public NativeGestureDispatcher getGestureDispatcher() { + return nativeGestureDispatcher; + } + + @Override + public void setGestureDispatcher(NativeGestureDispatcher dispatcher) { + nativeGestureDispatcher = dispatcher; + } + + public int getChildCountWithCaches() { + return recyclerView.getChildCountWithCaches(); + } + + public View getChildAtWithCaches(int index) { + return recyclerView.getChildAtWithCaches(index); + } + + public void setListData() { + recyclerView.setListData(); + } + + public RecyclerViewEventHelper getRecyclerViewEventHelper() { + return recyclerView.getRecyclerViewEventHelper(); + } + + public void setScrollEnable(boolean flag) { + recyclerView.setScrollEnable(flag); + } + + public void scrollToIndex(int xIndex, int yIndex, boolean animated, int duration) { + recyclerView.scrollToIndex(xIndex, yIndex, animated, duration); + } + + public void scrollToContentOffset(double xOffset, double yOffset, boolean animated, int duration) { + recyclerView.scrollToContentOffset(xOffset, yOffset, animated, duration); + } + + public void scrollToTop() { + recyclerView.scrollToTop(); + } + + public void setRowShouldSticky(boolean enable) { + recyclerView.setRowShouldSticky(enable); + } + + public HRCV getRecyclerView() { + return recyclerView; + } + + /** + * 将HeaderView放到RecyclerView到父亲View上面 + */ + @Override + public void attachHeader(View headerView, LayoutParams layoutParams) { + addView(headerView, layoutParams); + layout(getLeft(), getTop(), getRight(), getBottom()); + getViewTreeObserver().dispatchOnGlobalLayout(); + } + + @Override + public void addOnLayoutListener(OnGlobalLayoutListener listener) { + getViewTreeObserver().addOnGlobalLayoutListener(listener); + } + + @RequiresApi(api = VERSION_CODES.JELLY_BEAN) + @Override + public void removeOnLayoutListener(OnGlobalLayoutListener listener) { + getViewTreeObserver().removeOnGlobalLayoutListener(listener); + } + + public void onBatchStart() { + recyclerView.onBatchStart(); + } + + public void onBatchComplete() { + recyclerView.onBatchComplete(); + } + + @Override + public void setNestedScrollPriority(int direction, Priority priority) { + recyclerView.setNestedScrollPriority(direction, priority); + } + + @Override + public Priority getNestedScrollPriority(int direction) { + return recyclerView.getNestedScrollPriority(direction); + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/NodePositionHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/NodePositionHelper.java index b31f17fe124..0e1d687dfd4 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/NodePositionHelper.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/NodePositionHelper.java @@ -16,40 +16,47 @@ package com.tencent.mtt.hippy.views.hippylist; +/** + * Created on 2021/2/3. + * Description + * 由于我们在 HippyRecyclerListAdapter 可能加一些native得Header或者footer, + * 这样HippyRecyclerListAdapter里面得position和前端的列表位置就无法对应了, + * 比如adapter的位置是1,对应前端列表的位置是0,这样就不能用1的位置去获取renderNode的节点,需要矫正位置。 + */ public class NodePositionHelper { - private int nodeOffset = 0; - - public NodePositionHelper() { - - } - - /** - * 如果前面加了NativeHeader,nodeOffset就加1 - */ - public void increaseOffset() { - this.nodeOffset++; - } - - /** - * @return 当前render节点的偏移 - */ - public int getNodeOffset() { - return nodeOffset; - } - - /** - * @param adapterPosition 是节点在adapter的位置,adapter上面可能不都是renderNode - * @return 返回adapterPosition对应前端的列表的node节点位置,减去前面的NativeHeader的位置 - */ - public int getRenderNodePosition(int adapterPosition) { - return adapterPosition - nodeOffset; - } - - /** - * 如果去掉nativeHeader,就减1 - */ - public void decreaseOffset() { - nodeOffset--; - } + private int nodeOffset = 0; + + public NodePositionHelper() { + + } + + /** + * 如果前面加了NativeHeader,nodeOffset就加1 + */ + public void increaseOffset() { + this.nodeOffset++; + } + + /** + * @return 当前render节点的偏移 + */ + public int getNodeOffset() { + return nodeOffset; + } + + /** + * @param adapterPosition 是节点在adapter的位置,adapter上面可能不都是renderNode + * @return 返回adapterPosition对应前端的列表的node节点位置,减去前面的NativeHeader的位置 + */ + public int getRenderNodePosition(int adapterPosition) { + return adapterPosition - nodeOffset; + } + + /** + * 如果去掉nativeHeader,就减1 + */ + public void decreaseOffset() { + nodeOffset--; + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PreloadHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PreloadHelper.java deleted file mode 100644 index 4a6740231e7..00000000000 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PreloadHelper.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.tencent.mtt.hippy.views.hippylist; - -import static com.tencent.mtt.hippy.views.hippylist.PullFooterEventHelper.EVENT_ON_END_REACHED; - -import androidx.recyclerview.widget.RecyclerView; -import android.view.View; -import com.tencent.mtt.hippy.uimanager.HippyViewEvent; - -public class PreloadHelper extends RecyclerView.OnScrollListener { - - protected HippyRecyclerView hippyRecyclerView; - protected int preloadItemNumber; - protected boolean isPreloading; - - public PreloadHelper(HippyRecyclerView hippyRecyclerView) { - this.hippyRecyclerView = hippyRecyclerView; - } - - @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - int itemCount = recyclerView.getAdapter().getItemCount(); - //频控,记录上次预加载的总条目数,相同就不再次触发预加载 - if (isPreloading) { - return; - } - if (hippyRecyclerView.getAdapter().getRenderNodeCount() > 0) { - View lastChild = recyclerView.getChildAt(recyclerView.getChildCount() - 1); - int lastPosition = recyclerView.getChildAdapterPosition(lastChild); - if (lastPosition + preloadItemNumber >= itemCount) { - isPreloading = true; - sendReachEndEvent(recyclerView); - } - } - } - - public void sendReachEndEvent(RecyclerView recyclerView) { - new HippyViewEvent(EVENT_ON_END_REACHED).send((View) recyclerView.getParent(), null); - } - - /** - * @param preloadItemNumber 提前多少条Item,通知前端加载下一页数据 - */ - public void setPreloadItemNumber(int preloadItemNumber) { - this.preloadItemNumber = preloadItemNumber; - hippyRecyclerView.removeOnScrollListener(this); - if (preloadItemNumber > 0) { - hippyRecyclerView.addOnScrollListener(this); - } - } - - public void reset() { - isPreloading = false; - } -} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullFooterEventHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullFooterEventHelper.java deleted file mode 100644 index db8bfcf68a8..00000000000 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullFooterEventHelper.java +++ /dev/null @@ -1,61 +0,0 @@ -/* Tencent is pleased to support the open source community by making Hippy available. - * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.tencent.mtt.hippy.views.hippylist; - -import android.view.View; -import com.tencent.mtt.hippy.uimanager.HippyViewEvent; -import com.tencent.mtt.nxeasy.recyclerview.helper.footer.FooterExposureHelper; -import com.tencent.mtt.nxeasy.recyclerview.helper.footer.IFooterLoadMoreListener; - -class PullFooterEventHelper implements IFooterLoadMoreListener { - - public static final String EVENT_ON_END_REACHED = "onLoadMore"; - private final HippyRecyclerView recyclerView; - private FooterExposureHelper footerExposureHelper; - private HippyViewEvent onEndReachedEvent; - - PullFooterEventHelper(HippyRecyclerView recyclerView) { - this.recyclerView = recyclerView; - } - - public void enableFooter(View itemView) { - disableFooter(); - footerExposureHelper = new FooterExposureHelper(); - footerExposureHelper.setFooterListener(this); - footerExposureHelper.setExposureView(itemView); - recyclerView.addOnScrollListener(footerExposureHelper); - } - - public void disableFooter() { - if (footerExposureHelper != null) { - recyclerView.removeOnScrollListener(footerExposureHelper); - footerExposureHelper = null; - } - } - - protected HippyViewEvent getOnEndReachedEvent() { - if (onEndReachedEvent == null) { - onEndReachedEvent = new HippyViewEvent(EVENT_ON_END_REACHED); - } - return onEndReachedEvent; - } - - @Override - public void onFooterLoadMore() { - getOnEndReachedEvent().send((View) recyclerView.getParent(), null); - } -} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullFooterRefreshHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullFooterRefreshHelper.java new file mode 100644 index 00000000000..b008796f615 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullFooterRefreshHelper.java @@ -0,0 +1,115 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.hippylist; + +import androidx.annotation.NonNull; +import com.tencent.mtt.hippy.common.HippyMap; +import com.tencent.mtt.hippy.uimanager.HippyViewEvent; +import com.tencent.mtt.hippy.uimanager.RenderNode; +import com.tencent.mtt.hippy.utils.PixelUtil; + +class PullFooterRefreshHelper extends PullRefreshHelper { + + public static final String EVENT_TYPE_FOOTER_RELEASED = "onFooterReleased"; + public static final String EVENT_TYPE_FOOTER_PULLING = "onFooterPulling"; + + PullFooterRefreshHelper(@NonNull HippyRecyclerView recyclerView, + @NonNull RenderNode renderNode) { + super(recyclerView, renderNode); + } + + @Override + protected int handleDrag(int distance) { + int consumed = 0; + int size = 0; + switch (mRefreshStatus) { + case PULL_STATUS_FOLDED: + if (distance > 0) { // down towards + // make sure edge reached, aka distance - getOffsetFromEnd() > 0 + consumed = Math.max(0, distance - getOffsetFromEnd()); + if (consumed != 0) { + mRefreshStatus = PullRefreshStatus.PULL_STATUS_DRAGGING; + size = getVisibleSize() + Math.round(consumed / PULL_RATIO); + setVisibleSize(size); + } + } + break; + case PULL_STATUS_DRAGGING: + case PULL_STATUS_REFRESHING: + if (distance > 0) { // down towards + // make sure edge reached, aka distance - getOffsetFromEnd() > 0 + consumed = Math.max(0, distance - getOffsetFromEnd()); + } else { + // make sure consume no more than the opposite of footer size (converted by + // PULL_RATIO) + consumed = Math.max(-Math.round(getVisibleSize() * PULL_RATIO), distance); + } + if (consumed != 0) { + size = getVisibleSize() + Math.round(consumed / PULL_RATIO); + setVisibleSize(size); + } + break; + default: + break; + } + if (consumed != 0) { + endAnimation(); + sendPullingEvent(size); + } + // reduce value of changed size, let the RecyclerView scroll to the correct position + return consumed - Math.round(consumed / PULL_RATIO); + } + + @Override + protected void sendReleasedEvent() { + new HippyViewEvent(EVENT_TYPE_FOOTER_RELEASED).send(mItemView, null); + } + + @Override + protected void sendPullingEvent(int offset) { + HippyMap params = new HippyMap(); + params.pushDouble("contentOffset", PixelUtil.px2dp(offset)); + new HippyViewEvent(EVENT_TYPE_FOOTER_PULLING).send(mItemView, params); + } + + /** + * scrollable distance from list end, value greater than or equal to 0 + */ + protected int getOffsetFromEnd() { + final HippyRecyclerView v = mRecyclerView; + return isVertical() + ? v.computeVerticalScrollRange() - v.computeVerticalScrollExtent() + - v.computeVerticalScrollOffset() + : v.computeHorizontalScrollRange() - v.computeHorizontalScrollExtent() + - v.computeHorizontalScrollOffset(); + } + + @Override + public void enableRefresh() { + mRefreshStatus = PullRefreshStatus.PULL_STATUS_REFRESHING; + int nodeSize = isVertical() ? mRenderNode.getHeight() : mRenderNode.getWidth(); + endAnimation(); + int visibleSize = getVisibleSize(); + if (visibleSize < nodeSize) { + setVisibleSize(nodeSize); + } + HippyRecyclerListAdapter adapter = mRecyclerView.getAdapter(); + if (adapter != null) { + mRecyclerView.smoothScrollToPosition(adapter.getItemCount()); + } + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullHeaderEventHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullHeaderEventHelper.java deleted file mode 100644 index d2bdf6ae17e..00000000000 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullHeaderEventHelper.java +++ /dev/null @@ -1,131 +0,0 @@ -/* Tencent is pleased to support the open source community by making Hippy available. - * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.tencent.mtt.hippy.views.hippylist; - -import android.view.Gravity; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.LinearLayout.LayoutParams; -import com.tencent.mtt.hippy.common.HippyMap; -import com.tencent.mtt.hippy.uimanager.HippyViewEvent; -import com.tencent.mtt.hippy.uimanager.PullHeaderRenderNode; -import com.tencent.mtt.hippy.utils.PixelUtil; -import com.tencent.mtt.nxeasy.recyclerview.helper.header.HeaderRefreshHelper; -import com.tencent.mtt.nxeasy.recyclerview.helper.header.IHeaderRefreshListener; -import com.tencent.mtt.nxeasy.recyclerview.helper.header.IHeaderRefreshView; -import com.tencent.mtt.nxeasy.recyclerview.helper.header.ILayoutRequester; - -public class PullHeaderEventHelper implements IHeaderRefreshListener, IHeaderRefreshView, - ILayoutRequester { - - public static final String EVENT_TYPE_HEADER_PULLING = "onHeaderPulling"; - public static final String EVENT_TYPE_HEADER_RELEASED = "onHeaderReleased"; - private final PullHeaderRenderNode renderNode; - private HippyRecyclerView recyclerView; - private View renderNodeView; - private LinearLayout headerContainer; - private LayoutParams contentLayoutParams; - private HeaderRefreshHelper headerRefreshHelper; - - PullHeaderEventHelper(HippyRecyclerView recyclerView, PullHeaderRenderNode renderNode) { - this.recyclerView = recyclerView; - this.renderNode = renderNode; - headerContainer = new LinearLayout(recyclerView.getContext()); - headerRefreshHelper = new HeaderRefreshHelper(); - headerRefreshHelper.setHeaderRefreshView(this); - headerRefreshHelper.setHeaderRefreshListener(this); - headerRefreshHelper.setLayoutRequester(this); - recyclerView.setOnTouchListener(headerRefreshHelper); - } - - public void setRenderNodeView(View renderNodeView) { - if (this.renderNodeView != renderNodeView) { - this.renderNodeView = renderNodeView; - headerContainer.removeAllViews(); - contentLayoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, renderNode.getHeight()); - contentLayoutParams.gravity = Gravity.BOTTOM; - headerContainer.addView(renderNodeView, contentLayoutParams); - } - } - - - public View getView() { - return headerContainer; - } - - @Override - public void onStartDrag() { - - } - - @Override - public void onHeaderHeightChanged(int sumOffset) { - HippyMap params = new HippyMap(); - params.pushDouble("contentOffset", PixelUtil.px2dp(sumOffset)); - sendPullHeaderEvent(EVENT_TYPE_HEADER_PULLING, params); - } - - @Override - public void onRefreshing() { - - } - - @Override - public void onFolded() { - - } - - /** - * 松手后,触发的刷新回调,需要通知Hippy前端业务进行数据的刷新操作 - */ - @Override - public void onHeaderRefreshing(int refreshWay) { - sendPullHeaderEvent(EVENT_TYPE_HEADER_RELEASED, new HippyMap()); - } - - @Override - public int getContentHeight() { - return renderNode.getHeight(); - } - - protected void sendPullHeaderEvent(String eventName, HippyMap param) { - new HippyViewEvent(eventName).send(renderNodeView, param); - } - - /** - * Hippy前端业务通知数据已经刷新完毕,这里通知给headerRefreshHelper,进行header的收起功能 - */ - public void onHeaderRefreshFinish() { - headerRefreshHelper.onRefreshDone(); - } - - /** - * Hippy前端业务调用主动刷新功能,这款需要通知headerRefreshHelper进行自动下拉刷新 - */ - public void onHeaderRefresh() { - headerRefreshHelper.triggerRefresh(); - } - - public int getVisibleHeight() { - return headerRefreshHelper.getVisibleHeight(); - } - - @Override - public void requestLayout() { - recyclerView.dispatchLayout(); - } -} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullHeaderRefreshHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullHeaderRefreshHelper.java new file mode 100644 index 00000000000..51f38966378 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullHeaderRefreshHelper.java @@ -0,0 +1,113 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.hippylist; + +import com.tencent.mtt.hippy.common.HippyMap; +import com.tencent.mtt.hippy.uimanager.HippyViewEvent; +import com.tencent.mtt.hippy.uimanager.RenderNode; +import com.tencent.mtt.hippy.utils.PixelUtil; + +public class PullHeaderRefreshHelper extends PullRefreshHelper { + + public static final String EVENT_TYPE_HEADER_PULLING = "onHeaderPulling"; + public static final String EVENT_TYPE_HEADER_RELEASED = "onHeaderReleased"; + + PullHeaderRefreshHelper(HippyRecyclerView recyclerView, RenderNode renderNode) { + super(recyclerView, renderNode); + } + + @Override + protected int handleDrag(int distance) { + int consumed = 0; + int size = 0; + switch (mRefreshStatus) { + case PULL_STATUS_FOLDED: + if (distance < 0) { // down towards + // make sure edge reached, aka distance + getOffset() < 0 + consumed = Math.min(0, distance + getOffset()); + if (consumed != 0) { + mRefreshStatus = PullRefreshStatus.PULL_STATUS_DRAGGING; + size = getVisibleSize() - Math.round(consumed / PULL_RATIO); + setVisibleSize(size); + } + } + break; + case PULL_STATUS_DRAGGING: + case PULL_STATUS_REFRESHING: + if (distance < 0) { // down towards + // make sure edge reached, aka distance + getOffset() < 0 + consumed = Math.min(0, distance + getOffset()); + } else { // up towards + // make sure consume no more than header size (converted by PULL_RATIO) + consumed = Math.min(Math.round(getVisibleSize() * PULL_RATIO), distance); + } + if (consumed != 0) { + size = getVisibleSize() - Math.round(consumed / PULL_RATIO); + setVisibleSize(size); + } + break; + default: + break; + } + if (consumed != 0) { + endAnimation(); + sendPullingEvent(size); + // when header not visible, reduce value of changed size to scroll the header out + if (mRecyclerView.getFirstChildPosition() > 0) { + consumed -= Math.round(consumed / PULL_RATIO); + } + } + return consumed; + } + + @Override + protected void sendReleasedEvent() { + new HippyViewEvent(EVENT_TYPE_HEADER_RELEASED).send(mItemView, null); + } + + @Override + protected void sendPullingEvent(int offset) { + HippyMap params = new HippyMap(); + params.pushDouble("contentOffset", PixelUtil.px2dp(offset)); + new HippyViewEvent(EVENT_TYPE_HEADER_PULLING).send(mItemView, params); + } + + @Override + public void enableRefresh() { + mRefreshStatus = PullRefreshStatus.PULL_STATUS_REFRESHING; + int nodeSize = isVertical() ? mRenderNode.getHeight() : mRenderNode.getWidth(); + if (mRecyclerView.getFirstChildPosition() > 0) { + endAnimation(); + setVisibleSize(nodeSize); + mRecyclerView.smoothScrollToPosition(0); + } else { + int visibleSize = getVisibleSize(); + if (visibleSize < nodeSize) { + smoothResizeTo(visibleSize, nodeSize, DURATION); + } + } + } + + /** + * scrollable distance from list start, value greater than or equal to 0 + */ + protected int getOffset() { + return isVertical() ? mRecyclerView.computeVerticalScrollOffset() + : mRecyclerView.computeHorizontalScrollOffset(); + } + +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullRefreshHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullRefreshHelper.java new file mode 100644 index 00000000000..fe6da49cc91 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullRefreshHelper.java @@ -0,0 +1,237 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.hippylist; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.LayoutManager; +import com.tencent.mtt.hippy.uimanager.RenderNode; +import com.tencent.mtt.hippy.views.hippylist.recyclerview.helper.AnimatorListenerBase; +import com.tencent.mtt.hippy.views.refresh.HippyPullFooterView; +import com.tencent.mtt.hippy.views.refresh.HippyPullHeaderView; + +public abstract class PullRefreshHelper { + + public enum PullRefreshStatus { + PULL_STATUS_FOLDED, + PULL_STATUS_DRAGGING, + PULL_STATUS_REFRESHING + } + + public static final int DURATION = 200; + public static final float PULL_RATIO = 2.4f; + protected final HippyRecyclerView mRecyclerView; + protected final LinearLayout mContainer; + protected final RenderNode mRenderNode; + @Nullable + protected View mItemView; + @Nullable + protected ValueAnimator mAnimator; + protected PullRefreshStatus mRefreshStatus = PullRefreshStatus.PULL_STATUS_FOLDED; + + PullRefreshHelper(@NonNull HippyRecyclerView recyclerView, @NonNull RenderNode footerNode) { + mRecyclerView = recyclerView; + mRenderNode = footerNode; + mContainer = new LinearLayout(recyclerView.getContext()); + } + + protected abstract int handleDrag(int distance); + + protected void endDrag() { + if (mRefreshStatus == PullRefreshStatus.PULL_STATUS_FOLDED) { + return; + } + int nodeSize = getNodeSize(); + int visibleSize = getVisibleSize(); + if (visibleSize >= nodeSize) { // fully showing + if (mRefreshStatus == PullRefreshStatus.PULL_STATUS_DRAGGING) { + mRefreshStatus = PullRefreshStatus.PULL_STATUS_REFRESHING; + sendReleasedEvent(); + } + smoothResizeTo(getVisibleSize(), nodeSize, DURATION); + } else { // only partially showing + if (mRefreshStatus == PullRefreshStatus.PULL_STATUS_DRAGGING) { + mRefreshStatus = PullRefreshStatus.PULL_STATUS_FOLDED; + } + if (visibleSize > 0) { + smoothResizeTo(getVisibleSize(), 0, DURATION); + } + } + } + + protected int getNodeSize() { + return isVertical() ? mRenderNode.getHeight() : mRenderNode.getWidth(); + } + + protected abstract void sendReleasedEvent(); + + protected abstract void sendPullingEvent(int offset); + + protected void sendCompatScrollEvent() { + mRecyclerView.getRecyclerViewEventHelper().checkSendOnScrollEvent(); + } + + public void onDestroy() { + mItemView = null; + mContainer.removeAllViews(); + endAnimation(); + } + + public void onRefreshCompleted() { + if (mRefreshStatus == PullRefreshStatus.PULL_STATUS_REFRESHING) { + mRefreshStatus = PullRefreshStatus.PULL_STATUS_FOLDED; + smoothResizeTo(getVisibleSize(), 0, DURATION); + } + } + + public abstract void enableRefresh(); + + public View getView() { + return mContainer; + } + + protected void endAnimation() { + if (mAnimator != null) { + mAnimator.removeAllListeners(); + mAnimator.removeAllUpdateListeners(); + mAnimator.end(); + mAnimator = null; + } + } + + public void onLayoutOrientationChanged() { + if (mItemView == null || mContainer == null) { + return; + } + boolean isVertical = isVertical(); + if (isVertical) { + mContainer.setOrientation(LinearLayout.HORIZONTAL); + } else { + mContainer.setOrientation(LinearLayout.VERTICAL); + } + ViewGroup.LayoutParams lpChild = mItemView.getLayoutParams(); + if (lpChild instanceof LinearLayout.LayoutParams) { + lpChild.width = mRenderNode.getWidth(); + lpChild.height = mRenderNode.getHeight(); + if (mItemView instanceof HippyPullHeaderView) { + ((LinearLayout.LayoutParams) lpChild).gravity = isVertical ? Gravity.BOTTOM : Gravity.RIGHT; + } else if (mItemView instanceof HippyPullFooterView) { + ((LinearLayout.LayoutParams) lpChild).gravity = isVertical ? Gravity.TOP : Gravity.LEFT; + } + } + ViewGroup.LayoutParams lpContainer = mContainer.getLayoutParams(); + if (lpContainer != null) { + lpContainer.width = isVertical ? MATCH_PARENT : 0; + lpContainer.height = isVertical ? 0 : MATCH_PARENT; + } + } + + public void setItemView(View itemView) { + boolean isVertical = isVertical(); + mItemView = itemView; + mContainer.removeAllViews(); + if (isVertical) { + mContainer.setOrientation(LinearLayout.HORIZONTAL); + } else { + mContainer.setOrientation(LinearLayout.VERTICAL); + } + LinearLayout.LayoutParams lpChild = new LinearLayout.LayoutParams(mRenderNode.getWidth(), mRenderNode.getHeight()); + if (itemView instanceof HippyPullHeaderView) { + lpChild.gravity = isVertical ? Gravity.BOTTOM : Gravity.RIGHT; + } else if (itemView instanceof HippyPullFooterView) { + lpChild.gravity = isVertical ? Gravity.TOP : Gravity.LEFT; + } + mContainer.addView(itemView, lpChild); + int width = isVertical ? MATCH_PARENT : 0; + int height = isVertical ? 0 : MATCH_PARENT; + RecyclerView.LayoutParams lpContainer = new RecyclerView.LayoutParams(width, height); + mContainer.setLayoutParams(lpContainer); + } + + public int getVisibleHeight() { + ViewGroup.LayoutParams layoutParams = mContainer.getLayoutParams(); + if (layoutParams == null) { + return 0; + } + return layoutParams.height; + } + + public int getVisibleWidth() { + ViewGroup.LayoutParams layoutParams = mContainer.getLayoutParams(); + if (layoutParams == null) { + return 0; + } + return layoutParams.width; + } + + public int getVisibleSize() { + ViewGroup.LayoutParams layoutParams = mContainer.getLayoutParams(); + if (layoutParams == null) { + return 0; + } + return isVertical() ? layoutParams.height : layoutParams.width; + } + + protected void setVisibleSize(int size) { + ViewGroup.LayoutParams layoutParams = mContainer.getLayoutParams(); + if (isVertical()) { + layoutParams.height = Math.max(size, 0); + } else { + layoutParams.width = Math.max(size, 0); + } + mContainer.setLayoutParams(layoutParams); + mRecyclerView.dispatchLayout(); + sendCompatScrollEvent(); + } + + protected void smoothResizeTo(int fromValue, int toValue, int duration) { + endAnimation(); + mAnimator = ValueAnimator.ofInt(fromValue, toValue); + mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + setVisibleSize((int) animation.getAnimatedValue()); + } + }); + mAnimator.addListener(new AnimatorListenerBase() { + @Override + public void onAnimationEnd(Animator animation) { + + } + }); + mAnimator.setDuration(duration).start(); + } + + protected boolean isVertical() { + int orientation = RecyclerView.VERTICAL; + LayoutManager layoutManager = mRecyclerView.getLayoutManager(); + if (layoutManager instanceof LinearLayoutManager) { + orientation = ((LinearLayoutManager) layoutManager).getOrientation(); + } + return orientation == RecyclerView.VERTICAL; + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/RecyclerViewEventHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/RecyclerViewEventHelper.java index b9f6652e15e..445e822f480 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/RecyclerViewEventHelper.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/RecyclerViewEventHelper.java @@ -22,10 +22,13 @@ import android.graphics.Rect; import androidx.annotation.NonNull; -import androidx.recyclerview.widget.OverPullHelper; -import androidx.recyclerview.widget.OverPullListener; +import androidx.recyclerview.widget.HippyOverPullHelper; +import androidx.recyclerview.widget.HippyOverPullListener; +import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView.OnScrollListener; + +import android.os.SystemClock; import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.View.OnLayoutChangeListener; @@ -39,356 +42,392 @@ import com.tencent.mtt.hippy.views.list.HippyListItemView; import com.tencent.mtt.hippy.views.scroll.HippyScrollViewEventHelper; +/** + * Created on 2020/12/24. Description + * 各种事件的通知,通知前端view的曝光事件,用于前端的统计上报 + */ public class RecyclerViewEventHelper extends OnScrollListener implements OnLayoutChangeListener, - OnAttachStateChangeListener, OverPullListener { - - public static final String EVENT_ON_END_REACHED = "onEndReached"; - public static final String EVENT_ON_TOP_REACHED = "onTopReached"; - public static final String INITIAL_LIST_READY = "initialListReady"; - protected final HippyRecyclerView hippyRecyclerView; - private boolean scrollBeginDragEventEnable; - private boolean scrollEndDragEventEnable; - private HippyViewEvent onScrollDragEndedEvent; - private boolean momentumScrollBeginEventEnable; - private boolean momentumScrollEndEventEnable; - private HippyViewEvent onScrollFlingStartedEvent; - private HippyViewEvent onScrollFlingEndedEvent; - private int currentState; - protected boolean onScrollEventEnable = true; - private HippyViewEvent onScrollEvent; - private long lastScrollEventTimeStamp; - private int scrollEventThrottle; - private boolean exposureEventEnable; - private HippyViewEvent onScrollDragStartedEvent; - - //initialListReady event - private boolean isInitialListReadyNotified = false; - private ViewTreeObserver viewTreeObserver; - private OnPreDrawListener preDrawListener; - - - public RecyclerViewEventHelper(HippyRecyclerView recyclerView) { - this.hippyRecyclerView = recyclerView; - hippyRecyclerView.addOnScrollListener(this); - hippyRecyclerView.addOnAttachStateChangeListener(this); - hippyRecyclerView.addOnLayoutChangeListener(this); - preDrawListener = new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - notifyInitialListReady(); - return true; - } - }; - } + OnAttachStateChangeListener, HippyOverPullListener { + + public static final String INITIAL_LIST_READY = "initialListReady"; + protected final HippyRecyclerView hippyRecyclerView; + private boolean scrollBeginDragEventEnable; + private boolean scrollEndDragEventEnable; + private HippyViewEvent onScrollDragEndedEvent; + private boolean momentumScrollBeginEventEnable; + private boolean momentumScrollEndEventEnable; + private HippyViewEvent onScrollFlingStartedEvent; + private HippyViewEvent onScrollFlingEndedEvent; + private int currentState; + protected boolean onScrollEventEnable = true; + private HippyViewEvent onScrollEvent; + private long lastScrollEventTimeStamp; + private int scrollEventThrottle; + private boolean mHasUnsentScrollEvent; + private boolean exposureEventEnable; + private HippyViewEvent onScrollDragStartedEvent; + + //initialListReady event + private boolean isInitialListReadyNotified = false; + private ViewTreeObserver viewTreeObserver; + private OnPreDrawListener preDrawListener; + private boolean isLastTimeReachEnd; + private int preloadItemNumber; + + public RecyclerViewEventHelper(HippyRecyclerView recyclerView) { + this.hippyRecyclerView = recyclerView; + hippyRecyclerView.addOnScrollListener(this); + hippyRecyclerView.addOnAttachStateChangeListener(this); + hippyRecyclerView.addOnLayoutChangeListener(this); + preDrawListener = new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + notifyInitialListReady(); + return true; + } + }; + } - void notifyInitialListReady() { - if (canNotifyInit()) { - isInitialListReadyNotified = true; - viewTreeObserver.removeOnPreDrawListener(preDrawListener); - hippyRecyclerView.post(new Runnable() { - @Override - public void run() { - new HippyViewEvent(INITIAL_LIST_READY).send(getParentView(), null); + void notifyInitialListReady() { + if (canNotifyInit()) { + isInitialListReadyNotified = true; + viewTreeObserver.removeOnPreDrawListener(preDrawListener); + hippyRecyclerView.post(new Runnable() { + @Override + public void run() { + new HippyViewEvent(INITIAL_LIST_READY).send(getParentView(), null); + } + }); } - }); } - } - - protected View getParentView() { - return (View) hippyRecyclerView.getParent(); - } - - /** - * 是否满足initialListReady的通知条件,需要有真实view上屏才进行通知 - */ - private boolean canNotifyInit() { - return !isInitialListReadyNotified && hippyRecyclerView.getAdapter().getItemCount() > 0 - && hippyRecyclerView.getChildCount() > 0 - && viewTreeObserver.isAlive(); - } - - public void setScrollBeginDragEventEnable(boolean enable) { - scrollBeginDragEventEnable = enable; - } - public void setScrollEndDragEventEnable(boolean enable) { - scrollEndDragEventEnable = enable; - } + protected View getParentView() { + return (View) hippyRecyclerView.getParent(); + } - public void setMomentumScrollBeginEventEnable(boolean enable) { - momentumScrollBeginEventEnable = enable; - } + /** + * 是否满足initialListReady的通知条件,需要有真实view上屏才进行通知 + */ + private boolean canNotifyInit() { + return !isInitialListReadyNotified && hippyRecyclerView.getAdapter().getItemCount() > 0 + && hippyRecyclerView.getChildCount() > 0 + && viewTreeObserver.isAlive(); + } - public void setMomentumScrollEndEventEnable(boolean enable) { - momentumScrollEndEventEnable = enable; - } + public void setScrollBeginDragEventEnable(boolean enable) { + scrollBeginDragEventEnable = enable; + } - public void setOnScrollEventEnable(boolean enable) { - onScrollEventEnable = enable; - } + public void setScrollEndDragEventEnable(boolean enable) { + scrollEndDragEventEnable = enable; + } - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, - int oldTop, int oldRight, - int oldBottom) { - checkSendExposureEvent(); - } + public void setMomentumScrollBeginEventEnable(boolean enable) { + momentumScrollBeginEventEnable = enable; + } - protected HippyViewEvent getOnScrollDragStartedEvent() { - if (onScrollDragStartedEvent == null) { - onScrollDragStartedEvent = new HippyViewEvent( - HippyScrollViewEventHelper.EVENT_TYPE_BEGIN_DRAG); + public void setMomentumScrollEndEventEnable(boolean enable) { + momentumScrollEndEventEnable = enable; } - return onScrollDragStartedEvent; - } - // scroll - protected HippyViewEvent getOnScrollEvent() { - if (onScrollEvent == null) { - onScrollEvent = new HippyViewEvent(HippyScrollViewEventHelper.EVENT_TYPE_SCROLL); + public void setOnScrollEventEnable(boolean enable) { + onScrollEventEnable = enable; } - return onScrollEvent; - } - // start fling - protected HippyViewEvent getOnScrollFlingStartedEvent() { - if (onScrollFlingStartedEvent == null) { - onScrollFlingStartedEvent = new HippyViewEvent( - HippyScrollViewEventHelper.EVENT_TYPE_MOMENTUM_BEGIN); + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, + int oldBottom) { + checkSendExposureEvent(); } - return onScrollFlingStartedEvent; - } - // end drag event - protected HippyViewEvent getOnScrollDragEndedEvent() { - if (onScrollDragEndedEvent == null) { - onScrollDragEndedEvent = new HippyViewEvent(HippyScrollViewEventHelper.EVENT_TYPE_END_DRAG); + protected HippyViewEvent getOnScrollDragStartedEvent() { + if (onScrollDragStartedEvent == null) { + onScrollDragStartedEvent = new HippyViewEvent(HippyScrollViewEventHelper.EVENT_TYPE_BEGIN_DRAG); + } + return onScrollDragStartedEvent; } - return onScrollDragEndedEvent; - } - @Override - public void onScrollStateChanged(RecyclerView recyclerView, int newState) { - int oldState = currentState; - currentState = newState; - sendDragEvent(newState); - sendDragEndEvent(oldState, currentState); - sendFlingEvent(newState); - sendFlingEndEvent(oldState, currentState); - } + // scroll + protected HippyViewEvent getOnScrollEvent() { + if (onScrollEvent == null) { + onScrollEvent = new HippyViewEvent(HippyScrollViewEventHelper.EVENT_TYPE_SCROLL); + } + return onScrollEvent; + } - @Override - public void onScrolled(@NonNull final RecyclerView recyclerView, int dx, int dy) { - if (dx != 0 || dy != 0) { - checkSendOnScrollEvent(); + // start fling + protected HippyViewEvent getOnScrollFlingStartedEvent() { + if (onScrollFlingStartedEvent == null) { + onScrollFlingStartedEvent = new HippyViewEvent(HippyScrollViewEventHelper.EVENT_TYPE_MOMENTUM_BEGIN); + } + return onScrollFlingStartedEvent; } - checkSendExposureEvent(); + // end drag event + protected HippyViewEvent getOnScrollDragEndedEvent() { + if (onScrollDragEndedEvent == null) { + onScrollDragEndedEvent = new HippyViewEvent(HippyScrollViewEventHelper.EVENT_TYPE_END_DRAG); + } + return onScrollDragEndedEvent; + } - if (!recyclerView.canScrollVertically(1)) { - sendOnEndReachedEvent(); + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + int oldState = currentState; + currentState = newState; + if (mHasUnsentScrollEvent) { + sendOnScrollEvent(); + } + sendDragEvent(newState); + sendDragEndEvent(oldState, currentState); + sendFlingEvent(newState); + sendFlingEndEvent(oldState, currentState); } - if (!recyclerView.canScrollVertically(-1)) { - sendOnTopReachedEvent(); + @Override + public void onScrolled(@NonNull final RecyclerView recyclerView, int dx, int dy) { + if (scrollHappened(dx, dy)) { + checkSendOnScrollEvent(); + } + checkSendExposureEvent(); + if (scrollHappened(dx, dy)) { + checkSendReachEndEvent(); + } } - } - protected void sendOnEndReachedEvent() { - new HippyViewEvent(EVENT_ON_END_REACHED).send(getParentView(), null); - } + protected boolean scrollHappened(int dx, int dy) { + return dx != 0 || dy != 0; + } - protected void sendOnTopReachedEvent() { - new HippyViewEvent(EVENT_ON_TOP_REACHED).send(getParentView(), null); - } + /** + * 检查是否已经触底,发生onEndReached事件给前端 + * 如果上次是没有到底,这次滑动底了,需要发事件通知,如果上一次已经是到底了,这次到底不会发事件 + */ + private void checkSendReachEndEvent() { + boolean isThisTimeReachEnd; + if (HippyListUtils.isHorizontalLayout(hippyRecyclerView)) { + isThisTimeReachEnd = isHorizontalReachEnd(); + } else { + isThisTimeReachEnd = isVerticalReachEnd(); + } + if (!isLastTimeReachEnd && isThisTimeReachEnd) { + sendOnReachedEvent(); + } + isLastTimeReachEnd = isThisTimeReachEnd; + } - private void checkSendOnScrollEvent() { - if (onScrollEventEnable) { - long currTime = System.currentTimeMillis(); - if (currTime - lastScrollEventTimeStamp >= scrollEventThrottle) { - lastScrollEventTimeStamp = currTime; - sendOnScrollEvent(); + /** + * 竖向滑动,内容已经到达最下边 + */ + private boolean isVerticalReachEnd() { + RecyclerView.LayoutManager manager; + if (preloadItemNumber > 0 && (manager = hippyRecyclerView.getLayoutManager()) instanceof LinearLayoutManager) { + return ((LinearLayoutManager) manager).findLastVisibleItemPosition() >= manager.getItemCount() - preloadItemNumber; } + return !hippyRecyclerView.canScrollVertically(1); } - } - public void sendOnScrollEvent() { - getOnScrollEvent().send(getParentView(), generateScrollEvent()); - } - - private void observePreDraw() { - if (!isInitialListReadyNotified && viewTreeObserver == null) { - viewTreeObserver = hippyRecyclerView.getViewTreeObserver(); - viewTreeObserver.addOnPreDrawListener(preDrawListener); + /** + * 水平滑动,内容已经到达最右边 + */ + private boolean isHorizontalReachEnd() { + RecyclerView.LayoutManager manager; + if (preloadItemNumber > 0 && (manager = hippyRecyclerView.getLayoutManager()) instanceof LinearLayoutManager) { + return ((LinearLayoutManager) manager).findLastVisibleItemPosition() >= manager.getItemCount() - preloadItemNumber; + } + return !hippyRecyclerView.canScrollHorizontally(1); } - } - protected void sendFlingEvent(int newState) { - if (momentumScrollBeginEventEnable && newState == SCROLL_STATE_SETTLING) { - getOnScrollFlingStartedEvent().send(getParentView(), generateScrollEvent()); + protected void sendOnReachedEvent() { + new HippyViewEvent(HippyScrollViewEventHelper.EVENT_ON_END_REACHED).send(getParentView(), null); } - } - protected void sendDragEndEvent(int oldState, int newState) { - if (scrollEndDragEventEnable && isReleaseDrag(oldState, newState) && !hippyRecyclerView - .isOverPulling()) { - getOnScrollDragEndedEvent().send(getParentView(), generateScrollEvent()); + protected void checkSendOnScrollEvent() { + if (onScrollEventEnable) { + long currTime = SystemClock.elapsedRealtime(); + if (currTime - lastScrollEventTimeStamp >= scrollEventThrottle) { + lastScrollEventTimeStamp = currTime; + sendOnScrollEvent(); + } else { + mHasUnsentScrollEvent = true; + } + } } - } - private boolean isReleaseDrag(int oldState, int newState) { - return (oldState == SCROLL_STATE_DRAGGING && - (newState == SCROLL_STATE_IDLE || newState == SCROLL_STATE_SETTLING)); - } - - protected void sendFlingEndEvent(int oldState, int newState) { - if (momentumScrollEndEventEnable && oldState == SCROLL_STATE_SETTLING - && newState != SCROLL_STATE_SETTLING) { - getOnScrollFlingEndedEvent().send(getParentView(), generateScrollEvent()); + public void sendOnScrollEvent() { + mHasUnsentScrollEvent = false; + getOnScrollEvent().send(getParentView(), generateScrollEvent()); } - } - protected void sendDragEvent(int newState) { - if (scrollBeginDragEventEnable && newState == RecyclerView.SCROLL_STATE_DRAGGING) { - getOnScrollDragStartedEvent().send(getParentView(), generateScrollEvent()); + private void observePreDraw() { + if (!isInitialListReadyNotified) { + if (viewTreeObserver == null) { + viewTreeObserver = hippyRecyclerView.getViewTreeObserver(); + } + viewTreeObserver.addOnPreDrawListener(preDrawListener); + } } - } - // end fling - protected HippyViewEvent getOnScrollFlingEndedEvent() { - if (onScrollFlingEndedEvent == null) { - onScrollFlingEndedEvent = new HippyViewEvent( - HippyScrollViewEventHelper.EVENT_TYPE_MOMENTUM_END); + protected void sendFlingEvent(int newState) { + if (momentumScrollBeginEventEnable && newState == SCROLL_STATE_SETTLING) { + getOnScrollFlingStartedEvent().send(getParentView(), generateScrollEvent()); + } } - return onScrollFlingEndedEvent; - } - public void setScrollEventThrottle(int scrollEventThrottle) { - this.scrollEventThrottle = scrollEventThrottle; - } + protected void sendDragEndEvent(int oldState, int newState) { + if (scrollEndDragEventEnable && isReleaseDrag(oldState, newState) && !hippyRecyclerView.isOverPulling()) { + getOnScrollDragEndedEvent().send(getParentView(), generateScrollEvent()); + } + } - public final HippyMap generateScrollEvent() { - HippyMap contentOffset = new HippyMap(); + private boolean isReleaseDrag(int oldState, int newState) { + return (oldState == SCROLL_STATE_DRAGGING && + (newState == SCROLL_STATE_IDLE || newState == SCROLL_STATE_SETTLING)); + } - float offsetX = hippyRecyclerView.getContentOffsetX(); - float offsetY = hippyRecyclerView.getContentOffsetY(); + protected void sendFlingEndEvent(int oldState, int newState) { + if (momentumScrollEndEventEnable && oldState == SCROLL_STATE_SETTLING && newState != SCROLL_STATE_SETTLING) { + getOnScrollFlingEndedEvent().send(getParentView(), generateScrollEvent()); + } + } - if (offsetX != 0) { - offsetX = PixelUtil.px2dp(offsetX); + protected void sendDragEvent(int newState) { + if (scrollBeginDragEventEnable && newState == RecyclerView.SCROLL_STATE_DRAGGING) { + getOnScrollDragStartedEvent().send(getParentView(), generateScrollEvent()); + } } - if (offsetY != 0) { - offsetY = PixelUtil.px2dp(offsetY); + // end fling + protected HippyViewEvent getOnScrollFlingEndedEvent() { + if (onScrollFlingEndedEvent == null) { + onScrollFlingEndedEvent = new HippyViewEvent(HippyScrollViewEventHelper.EVENT_TYPE_MOMENTUM_END); + } + return onScrollFlingEndedEvent; } - contentOffset.pushDouble("x", offsetX); - contentOffset.pushDouble("y", offsetY); - HippyMap event = new HippyMap(); - event.pushMap("contentOffset", contentOffset); - return event; - } + public void setScrollEventThrottle(int scrollEventThrottle) { + this.scrollEventThrottle = scrollEventThrottle; + } - public void setExposureEventEnable(boolean enable) { - exposureEventEnable = enable; - } + public final HippyMap generateScrollEvent() { + HippyMap contentOffset = new HippyMap(); + contentOffset.pushDouble("x", PixelUtil.px2dp(hippyRecyclerView.getContentOffsetX())); + contentOffset.pushDouble("y", PixelUtil.px2dp(hippyRecyclerView.getContentOffsetY())); + HippyMap event = new HippyMap(); + event.pushMap("contentOffset", contentOffset); + return event; + } - /** - * 可视面积小于10%,任务view当前已经不在可视区域 - */ - private boolean isViewVisible(View view) { - if (view == null) { - return false; - } - Rect rect = new Rect(); - boolean visibility = view.getGlobalVisibleRect(rect); - if (!visibility) { - return false; - } else { - float visibleArea = rect.height() * rect.width(); //可见区域的面积 - float viewArea = view.getMeasuredWidth() * view.getMeasuredHeight();//当前view的总面积 - return visibleArea > viewArea * 0.1f; + public void setExposureEventEnable(boolean enable) { + exposureEventEnable = enable; } - } - protected void checkExposureView(View view) { - if (view instanceof HippyListItemView) { - HippyListItemView itemView = (HippyListItemView) view; - if (isViewVisible(view)) { - if (itemView.getExposureState() != HippyListItemView.EXPOSURE_STATE_APPEAR) { - sendExposureEvent(view, HippyListItemView.EXPOSURE_EVENT_APPEAR); - itemView.setExposureState(HippyListItemView.EXPOSURE_STATE_APPEAR); + /** + * 可视面积小于10%,任务view当前已经不在可视区域 + */ + private boolean isViewVisible(View view) { + if (view == null) { + return false; } - } else { - if (itemView.getExposureState() != HippyListItemView.EXPOSURE_STATE_DISAPPEAR) { - sendExposureEvent(view, HippyListItemView.EXPOSURE_EVENT_DISAPPEAR); - itemView.setExposureState(HippyListItemView.EXPOSURE_STATE_DISAPPEAR); + Rect rect = new Rect(); + boolean visibility = view.getGlobalVisibleRect(rect); + if (!visibility) { + return false; + } else { + float visibleArea = rect.height() * rect.width(); //可见区域的面积 + float viewArea = view.getMeasuredWidth() * view.getMeasuredHeight();//当前view的总面积 + return visibleArea > viewArea * 0.1f; } - } } - } - protected void sendExposureEvent(View view, String eventName) { - if (eventName.equals(HippyListItemView.EXPOSURE_EVENT_APPEAR) || eventName - .equals(HippyListItemView.EXPOSURE_EVENT_DISAPPEAR)) { - new HippyViewEvent(eventName).send(view, null); + protected void checkExposureView(View view) { + if (view instanceof HippyListItemView) { + HippyListItemView itemView = (HippyListItemView) view; + if (isViewVisible(view)) { + if (itemView.getExposureState() != HippyListItemView.EXPOSURE_STATE_APPEAR) { + sendExposureEvent(view, HippyListItemView.EXPOSURE_EVENT_APPEAR); + itemView.setExposureState(HippyListItemView.EXPOSURE_STATE_APPEAR); + } + } else { + if (itemView.getExposureState() != HippyListItemView.EXPOSURE_STATE_DISAPPEAR) { + sendExposureEvent(view, HippyListItemView.EXPOSURE_EVENT_DISAPPEAR); + itemView.setExposureState(HippyListItemView.EXPOSURE_STATE_DISAPPEAR); + } + } + } } - } - private void checkSendExposureEvent() { - if (!exposureEventEnable) { - return; - } - int childCount = hippyRecyclerView.getChildCount(); - for (int i = 0; i < childCount; i++) { - checkExposureView(findHippyListItemView((ViewGroup) hippyRecyclerView.getChildAt(i))); + protected void sendExposureEvent(View view, String eventName) { + if (eventName.equals(HippyListItemView.EXPOSURE_EVENT_APPEAR) || eventName + .equals(HippyListItemView.EXPOSURE_EVENT_DISAPPEAR)) { + new HippyViewEvent(eventName).send(view, null); + } } - } - /** - * 由于挂载到RecyclerView到子View可能不是HippyListItemView,比如对于stickyItem,我们会包一层 - * ViewGroup,所以这里拿HippyListItemView,需要做两层的判断 - */ - private View findHippyListItemView(ViewGroup viewGroup) { - if (viewGroup instanceof HippyListItemView) { - return viewGroup; - } - if (viewGroup.getChildCount() > 0) { - View child = viewGroup.getChildAt(0); - if (child instanceof HippyListItemView) { - return child; - } + protected void checkSendExposureEvent() { + if (!exposureEventEnable) { + return; + } + int childCount = hippyRecyclerView.getChildCount(); + for (int i = 0; i < childCount; i++) { + checkExposureView(findHippyListItemView((ViewGroup) hippyRecyclerView.getChildAt(i))); + } } - return null; - } - - @Override - public void onViewAttachedToWindow(View v) { - observePreDraw(); - } - @Override - public void onViewDetachedFromWindow(View v) { + /** + * 由于挂载到RecyclerView到子View可能不是HippyListItemView,比如对于stickyItem,我们会包一层 + * ViewGroup,所以这里拿HippyListItemView,需要做两层的判断 + */ + private View findHippyListItemView(ViewGroup viewGroup) { + if (viewGroup instanceof HippyListItemView) { + return viewGroup; + } + if (viewGroup.getChildCount() > 0) { + View child = viewGroup.getChildAt(0); + if (child instanceof HippyListItemView) { + return child; + } + } + return null; + } - } + @Override + public void onViewAttachedToWindow(View v) { + observePreDraw(); + } - @Override - public void onOverPullStateChanged(int oldState, int newState, int offset) { - LogUtils.d("QBRecyclerViewEventHelper", "oldState:" + oldState + ",newState:" + newState); - if (oldState == OverPullHelper.OVER_PULL_NONE && isOverPulling(newState)) { - sendOnEndReachedEvent(); - getOnScrollDragStartedEvent().send(getParentView(), generateScrollEvent()); + @Override + public void onViewDetachedFromWindow(View v) { + if (!isInitialListReadyNotified && viewTreeObserver != null) { + viewTreeObserver.removeOnPreDrawListener(preDrawListener); + } } - if (isOverPulling(oldState) && isOverPulling(newState)) { - sendOnScrollEvent(); + + @Override + public void onOverPullStateChanged(int oldState, int newState, int offset) { + LogUtils.d("QBRecyclerViewEventHelper", "oldState:" + oldState + ",newState:" + newState); + if (oldState == HippyOverPullHelper.OVER_PULL_NONE && (isOverPulling(newState) + || newState == HippyOverPullHelper.OVER_PULL_NORMAL)) { + getOnScrollDragStartedEvent().send(getParentView(), generateScrollEvent()); + } + if (isOverPulling(oldState) && isOverPulling(newState)) { + sendOnScrollEvent(); + } + if (newState == HippyOverPullHelper.OVER_PULL_SETTLING && oldState != HippyOverPullHelper.OVER_PULL_SETTLING) { + getOnScrollDragEndedEvent().send(getParentView(), generateScrollEvent()); + } } - if (newState == OverPullHelper.OVER_PULL_SETTLING - && oldState != OverPullHelper.OVER_PULL_SETTLING) { - getOnScrollDragEndedEvent().send(getParentView(), generateScrollEvent()); + + private boolean isOverPulling(int newState) { + return newState == HippyOverPullHelper.OVER_PULL_DOWN_ING || newState == HippyOverPullHelper.OVER_PULL_UP_ING; } - } - private boolean isOverPulling(int newState) { - return newState == OverPullHelper.OVER_PULL_DOWN_ING - || newState == OverPullHelper.OVER_PULL_UP_ING; + /** + * @param preloadItemNumber 提前多少条Item,通知前端加载下一页数据 + */ + public void setPreloadItemNumber(int preloadItemNumber) { + this.preloadItemNumber = preloadItemNumber; + checkSendReachEndEvent(); } + } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/ViewStickEventHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/ViewStickEventHelper.java new file mode 100644 index 00000000000..75a00fa4499 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/ViewStickEventHelper.java @@ -0,0 +1,38 @@ +package com.tencent.mtt.hippy.views.hippylist; + +import android.view.View; +import com.tencent.mtt.hippy.common.HippyMap; +import com.tencent.mtt.hippy.uimanager.HippyViewEvent; +import com.tencent.mtt.hippy.views.hippylist.recyclerview.helper.skikcy.StickViewListener; + +/** + * Created on 2021/8/24. + * Description + * 吸顶事件分发器 + */ +public class ViewStickEventHelper implements StickViewListener { + + public static final String ON_VIEW_SUSPEND_LISTENER = "onViewSuspendListener"; + public static final String IS_SHOW = "isShow"; + private View view; + + public ViewStickEventHelper(View view) { + this.view = view; + } + + @Override + public void onStickAttached(int stickyPosition) { + notifyStickEvent(true); + } + + @Override + public void onStickDetached(int stickyPosition) { + notifyStickEvent(false); + } + + private void notifyStickEvent(boolean isStickViewShown) { + HippyMap map = new HippyMap(); + map.pushBoolean(IS_SHOW, isStickViewShown); + new HippyViewEvent(ON_VIEW_SUSPEND_LISTENER).send(view, map); + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/AnimatorListenerBase.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/AnimatorListenerBase.java similarity index 94% rename from android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/AnimatorListenerBase.java rename to android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/AnimatorListenerBase.java index 8c748aa22aa..ff587187892 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/AnimatorListenerBase.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/AnimatorListenerBase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.tencent.mtt.nxeasy.recyclerview.helper; +package com.tencent.mtt.hippy.views.hippylist.recyclerview.helper; import android.animation.Animator; import android.animation.Animator.AnimatorListener; diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/IHeaderAttachListener.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/IHeaderAttachListener.java new file mode 100644 index 00000000000..175a7ecdc43 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/IHeaderAttachListener.java @@ -0,0 +1,35 @@ +/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. + * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.mtt.hippy.views.hippylist.recyclerview.helper.skikcy; + +import android.view.View; +import androidx.recyclerview.widget.RecyclerView.ViewHolder; + +/** + * Created by on 2021/1/12. + * Description + */ +public interface IHeaderAttachListener { + + /** + * header被摘下来,需要对header进行还原或者回收对处理 + * + * @param aboundHeader HeaderView对应的Holder + * @param currentHeaderView headerView的实体内容 + */ + void onHeaderDetached(ViewHolder aboundHeader, View currentHeaderView); + +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/IHeaderHost.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/IHeaderHost.java new file mode 100644 index 00000000000..1e59e16417b --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/IHeaderHost.java @@ -0,0 +1,34 @@ +/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. + * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.mtt.hippy.views.hippylist.recyclerview.helper.skikcy; + +import android.view.View; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; +import android.widget.FrameLayout.LayoutParams; + +/** + * Created by on 2021/1/12. + * Description + * HeaderView的宿主,用于HeaderView的挂载,监听挂载的回调 + */ +public interface IHeaderHost { + + void attachHeader(View headerView, LayoutParams layoutParams); + + void addOnLayoutListener(OnGlobalLayoutListener listener); + + void removeOnLayoutListener(OnGlobalLayoutListener listener); +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IHeaderViewFactory.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/IHeaderViewFactory.java similarity index 82% rename from android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IHeaderViewFactory.java rename to android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/IHeaderViewFactory.java index ddbd982a78e..e319be04bc3 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IHeaderViewFactory.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/IHeaderViewFactory.java @@ -14,11 +14,15 @@ * limitations under the License. */ -package com.tencent.mtt.nxeasy.recyclerview.helper.skikcy; +package com.tencent.mtt.hippy.views.hippylist.recyclerview.helper.skikcy; import androidx.recyclerview.widget.RecyclerView.ViewHolder; +/** + * Created by on 2021/1/6. + * Description + */ public interface IHeaderViewFactory { - ViewHolder getHeaderForPosition(int position); + ViewHolder getHeaderForPosition(int position); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IStickyAbleItem.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/IStickyAbleItem.java similarity index 83% rename from android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IStickyAbleItem.java rename to android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/IStickyAbleItem.java index c90952b0716..cb13deff31d 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IStickyAbleItem.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/IStickyAbleItem.java @@ -14,9 +14,13 @@ * limitations under the License. */ -package com.tencent.mtt.nxeasy.recyclerview.helper.skikcy; +package com.tencent.mtt.hippy.views.hippylist.recyclerview.helper.skikcy; +/** + * Created by on 2020/12/29. + * Description + */ public interface IStickyAbleItem { - boolean isStickyItem(); + boolean isStickyItem(); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IStickyItemsProvider.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/IStickyItemsProvider.java similarity index 82% rename from android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IStickyItemsProvider.java rename to android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/IStickyItemsProvider.java index 89076f338fe..9048a6b1dcc 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IStickyItemsProvider.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/IStickyItemsProvider.java @@ -14,9 +14,13 @@ * limitations under the License. */ -package com.tencent.mtt.nxeasy.recyclerview.helper.skikcy; +package com.tencent.mtt.hippy.views.hippylist.recyclerview.helper.skikcy; +/** + * Created by on 2020/12/29. + * Description + */ public interface IStickyItemsProvider { - boolean isStickyPosition(int position); + boolean isStickyPosition(int position); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/StickViewListener.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/StickViewListener.java new file mode 100644 index 00000000000..bab6a4d6f39 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/StickViewListener.java @@ -0,0 +1,12 @@ +package com.tencent.mtt.hippy.views.hippylist.recyclerview.helper.skikcy; + +/** + * Created by on 2021/8/24. + * Description + */ +public interface StickViewListener { + + void onStickAttached(int stickyPosition); + + void onStickDetached(int stickyPosition); +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/StickyHeaderHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/StickyHeaderHelper.java new file mode 100644 index 00000000000..0e8ff17a8a4 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/StickyHeaderHelper.java @@ -0,0 +1,281 @@ +/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. + * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.hippylist.recyclerview.helper.skikcy; + +import static androidx.recyclerview.widget.RecyclerView.HORIZONTAL; +import static androidx.recyclerview.widget.RecyclerView.VERTICAL; + +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.widget.FrameLayout.LayoutParams; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerViewBase; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.OnScrollListener; +import androidx.recyclerview.widget.RecyclerView.ViewHolder; + +/** + * Created on 2020/12/29. Description 一个RecyclerView的StickyHeader控制器, + * 通过recyclerView的onScroll事件,1、从列表中选择吸顶的View,2、挂载到顶部,3、设置吸顶View的OffSet + * 吸顶的headerView是从recyclerView里面的Item的子View抠出来挂载的,需要吸顶属性的ItemView包装一层ViewGroup + */ +public class StickyHeaderHelper extends OnScrollListener implements + ViewTreeObserver.OnGlobalLayoutListener { + + private static final int INVALID_POSITION = -1; + private final com.tencent.mtt.hippy.views.hippylist.recyclerview.helper.skikcy.IHeaderAttachListener headerAttachListener; + private RecyclerViewBase recyclerView; + private com.tencent.mtt.hippy.views.hippylist.recyclerview.helper.skikcy.IStickyItemsProvider stickyItemsProvider; + private StickyViewFactory stickyViewFactory; + private ViewHolder headerOrgViewHolder; + private boolean orgViewHolderCanRecyclable = false; + private View currentHeaderView; + private int orientation; + private int currentStickPos = -1; + private com.tencent.mtt.hippy.views.hippylist.recyclerview.helper.skikcy.IHeaderHost headerHost; + private com.tencent.mtt.hippy.views.hippylist.recyclerview.helper.skikcy.StickViewListener stickViewListener; + private boolean isUpdateStickyHolderWhenLayout; + + public StickyHeaderHelper(final RecyclerViewBase recyclerView, + IStickyItemsProvider stickyItemsProvider, + IHeaderAttachListener headerAttachListener, IHeaderHost headerHost) { + this.recyclerView = recyclerView; + this.headerAttachListener = headerAttachListener; + this.stickyItemsProvider = stickyItemsProvider; + stickyViewFactory = new StickyViewFactory(recyclerView); + this.headerHost = headerHost; + orientation = recyclerView.getLayoutManager().canScrollVertically() ? VERTICAL : HORIZONTAL; + } + + /** + * 1、寻找stickyHeader 2、挂载stickyHeader 3、设置stickyHeader的偏移 + */ + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + int newStickyPosition = getStickyItemPosition(); + if (currentStickPos != newStickyPosition) { + detachSticky(); + attachSticky(newStickyPosition); + } + offsetSticky(); + } + + public void setStickViewListener(StickViewListener stickViewListener) { + this.stickViewListener = stickViewListener; + } + + public void setUpdateStickyViewWhenLayout(boolean bindStickyHolderWhenLayout) { + isUpdateStickyHolderWhenLayout = bindStickyHolderWhenLayout; + } + + /** + * 如果当前stickHolder和新的stickyHolder 不一样,那么把当前的stickyHolder删除掉,并还原HeaderView的Translation + */ + public void detachSticky() { + if (headerOrgViewHolder != null) { + removeViewFromParent(this.currentHeaderView); + currentHeaderView.setTranslationY(0); + currentHeaderView.setTranslationX(0); + returnHeaderBackToList(); + headerHost.removeOnLayoutListener(this); + notifyStickDetached(); + } + currentStickPos = -1; + headerOrgViewHolder = null; + } + + private void notifyStickDetached() { + if (stickViewListener != null) { + stickViewListener.onStickDetached(currentStickPos); + } + } + + /** + * 还原Header到List中去 1、ViewHolder正好是之前的ViewHolder,直接将headerView返回给headerOrgContainer + * 2、position相同,但是ViewHolder已经是不同了,出现在header的Item滑动到屏幕外,又滑回来,重新创建了一个ViewHolder + * 3、对于被顶出去的headView,是无法还原到list中的,需要把headView进行回收处理,如果不回收,Hippy场景无法重新创建View + */ + private void returnHeaderBackToList() { + headerOrgViewHolder.setIsRecyclable(orgViewHolderCanRecyclable); + if (headerAttachListener != null) { + headerAttachListener.onHeaderDetached(headerOrgViewHolder, currentHeaderView); + } else { + ViewHolder viewHolderToReturn = recyclerView + .findViewHolderForAdapterPosition(headerOrgViewHolder.getAdapterPosition()); + if (viewHolderToReturn != null && viewHolderToReturn.itemView instanceof ViewGroup) { + ViewGroup itemView = (ViewGroup) viewHolderToReturn.itemView; + //已经有孩子了,就不要加了,这个可能是新创建的ViewHolder已经有了内容 + if (itemView.getChildCount() <= 0) { + itemView.addView(this.currentHeaderView); + } + } + } + } + + /** + * 将stickyItemPosition对应的View挂载到RecyclerView的父亲上面 + */ + private void attachSticky(int newStickyPosition) { + if (newStickyPosition != INVALID_POSITION) { + headerOrgViewHolder = stickyViewFactory.getHeaderForPosition(newStickyPosition); + currentStickPos = newStickyPosition; + Log.d("returnHeader", "attachSticky:" + headerOrgViewHolder); + currentHeaderView = ((ViewGroup) headerOrgViewHolder.itemView).getChildAt(0); + removeViewFromParent(currentHeaderView); + //内容被取走了,不能被回收,避免view滑出屏幕,回收再利用,此时已经不能再被别人用了 + orgViewHolderCanRecyclable = headerOrgViewHolder.isRecyclable(); + headerOrgViewHolder.setIsRecyclable(false); + currentHeaderView.setVisibility(View.INVISIBLE); + LayoutParams layoutParams = new LayoutParams( + LayoutParams.MATCH_PARENT, 0); + ViewGroup.LayoutParams lp = headerOrgViewHolder.itemView.getLayoutParams(); + layoutParams.height = lp != null ? lp.height : LayoutParams.WRAP_CONTENT; + headerHost.addOnLayoutListener(this); + headerHost.attachHeader(currentHeaderView, layoutParams); + notifyStickAttached(newStickyPosition); + } + } + + private void notifyStickAttached(int stickyPosition) { + if (stickViewListener != null) { + stickViewListener.onStickAttached(stickyPosition); + } + } + + /** + * 设置吸顶的View的偏移 在下一个吸顶view和当前吸顶的view交汇的时候,需要把当前吸顶view往上面移动,慢慢会把当前的吸顶view顶出屏幕 + */ + private void offsetSticky() { + if (headerOrgViewHolder != null) { + float offset = getOffset(findNextSticky(currentStickPos)); + if (orientation == VERTICAL) { + currentHeaderView.setTranslationY(offset); + } else { + currentHeaderView.setTranslationX(offset); + } + } + } + + /** + * 找到屏幕中,下一个即将吸顶的view,主要用于计算当前吸顶的HeaderView的Offset, 下一个即将吸顶的View会慢慢把当前正在吸顶的HeaderView慢慢顶出屏幕外 + */ + private View findNextSticky(int currentStickyPos) { + for (int i = 0; i < recyclerView.getChildCount(); i++) { + View nextStickyView = recyclerView.getChildAt(i); + int nextStickyPos = recyclerView.getChildLayoutPosition(nextStickyView); + if (nextStickyPos > currentStickyPos && stickyItemsProvider + .isStickyPosition(nextStickyPos)) { + return nextStickyView; + } + } + return null; + } + + /** + * 当nextStickyView和当前stickyView重叠的时候,是应该把当前的view移出屏幕外 支持水平排版和垂直排版 + */ + private float getOffset(View nextStickyView) { + float offset = 0; + View stickView = this.currentHeaderView; + if (stickView != null && nextStickyView != null) { + if (orientation == VERTICAL) { + if (nextStickyView.getY() < stickView.getHeight()) { + offset = nextStickyView.getY() - stickView.getHeight(); + } + } else { + if (stickView.isShown()) { + offset = -stickView.getWidth(); + } else if (nextStickyView.getX() < stickView.getWidth()) { + offset = stickView.getX() - stickView.getWidth(); + } + } + } + return offset; + } + + /** + * 高度确定后,设置HeaderView为可见状态,并且重新刷新offset的正确位置,解决下拉header上屏的闪烁问题 + */ + @Override + public void onGlobalLayout() { + if (currentHeaderView != null) { + currentHeaderView.setVisibility(View.VISIBLE); + offsetSticky(); + if (isUpdateStickyHolderWhenLayout) { + updateStickHolder(); + } + } + } + + /** + * 找到距离顶部最近的一个stickyItem的位置 + * + * @return INVALID_POSITION,没有找到stickyItem + */ + public int getStickyItemPosition() { + if (recyclerView.getChildCount() <= 0) { + return INVALID_POSITION; + } + int positionToSticky = INVALID_POSITION; + int startPosition = recyclerView.getFirstChildPosition(); + View firstView = recyclerView.getChildAt(0); + if (firstView.getY() >= 0) { + startPosition--;//当前view已经完全露出,需要往从前面一个开始寻找 + } + for (int i = startPosition; i >= 0; i--) { + if (stickyItemsProvider.isStickyPosition(i)) { + positionToSticky = i; + break; + } + } + if (positionToSticky != INVALID_POSITION) { + if (positionToSticky != recyclerView.getFirstChildPosition()) { + //positionToSticky已经被滑出屏幕,此时positionToSticky可以直接返回 + return positionToSticky; + } else { + //stickyItem和第一个孩子位置一样,如果完全和吸顶位置重合,不需要进行吸顶 + positionToSticky = + !headerAwayFromEdge(firstView) ? INVALID_POSITION : positionToSticky; + } + } + return positionToSticky; + } + + /** + * headerToCopy == null表示,headerToCopy 已经完全移除屏幕外 headerToCopy != null ,getY()<0 部分移动屏幕外 + * + * @param headerToCopy 即将被选中吸顶的view + */ + private boolean headerAwayFromEdge(View headerToCopy) { + return headerToCopy != null && (orientation == VERTICAL ? headerToCopy.getY() < 0 + : headerToCopy.getX() < 0); + } + + private void removeViewFromParent(View view) { + if (view.getParent() instanceof ViewGroup) { + ((ViewGroup) view.getParent()).removeView(view); + } + } + + private void updateStickHolder() { + if (headerOrgViewHolder != null) { + recyclerView.getAdapter().onBindViewHolder(headerOrgViewHolder, currentStickPos); + } + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/StickyViewFactory.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/StickyViewFactory.java new file mode 100644 index 00000000000..012e04fb38f --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/recyclerview/helper/skikcy/StickyViewFactory.java @@ -0,0 +1,49 @@ +/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. + * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.hippylist.recyclerview.helper.skikcy; + +import androidx.recyclerview.widget.RecyclerViewBase; +import androidx.recyclerview.widget.RecyclerView.ViewHolder; + +public final class StickyViewFactory implements IHeaderViewFactory { + + private final RecyclerViewBase recyclerView; + + public StickyViewFactory(RecyclerViewBase recyclerView) { + this.recyclerView = recyclerView; + } + + /** + * 根据position的位置,获取到一个实体到ViewHolder + * 1、先在已经上屏的view中,找到一个ViewHolder + * 2、第一步没有找到,就重新通过recyclerView去获取一个,这种获取的viewHolder可能是cache里面的,也可能 + * 是新创建的,不用再次进行bindViewHolder的操作 + * + * @param position 指定要获取到ViewHolder到位置 + * @return 返回对应到ViewHolder,不会返回Null + */ + public ViewHolder getHeaderForPosition(int position) { + if (position < 0) { + return null; + } + ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(position); + if (viewHolder == null) { + viewHolder = recyclerView.getViewHolderForPosition(position); + } + return viewHolder; + } +} \ No newline at end of file diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPager.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPager.java new file mode 100644 index 00000000000..5d25a70c46e --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPager.java @@ -0,0 +1,469 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.hippypager; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; +import android.view.MotionEvent; +import android.view.View; +import android.widget.Scroller; +import androidx.annotation.NonNull; +import androidx.viewpager.widget.ViewPager; +import com.tencent.mtt.hippy.modules.Promise; +import com.tencent.mtt.hippy.uimanager.HippyViewBase; +import com.tencent.mtt.hippy.uimanager.NativeGestureDispatcher; +import com.tencent.mtt.hippy.utils.LogUtils; +import com.tencent.mtt.hippy.views.hippypager.transform.VerticalPageTransformer; +import com.tencent.mtt.hippy.views.viewpager.HippyViewPagerItem; +import com.tencent.mtt.supportui.views.ScrollChecker; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + + +public class HippyPager extends ViewPager implements HippyViewBase { + + private static final String TAG = "HippyViewPager"; + private final Handler handler = new Handler(Looper.getMainLooper()); + private NativeGestureDispatcher gestureDispatcher; + private boolean scrollEnabled = true; + private boolean firstUpdateChild = true; + private Promise callBackPromise; + private boolean isVertical = false; + private Scroller scroller; + private boolean ignoreCheck; + private boolean dataUpdated = false; + private boolean isFirstLayoutSucceed = false; + private PageSelectNotifier pageSelectNotifier = new PageSelectNotifier(); + private Runnable measureAndLayout = () -> { + if (readyToLayout()) { + measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY)); + layout(getLeft(), getTop(), getRight(), getBottom()); + } + }; + + + public HippyPager(Context context, boolean isVertical) { + super(context); + this.isVertical = isVertical; + init(); + } + + public HippyPager(Context context) { + super(context); + init(); + } + + private void init() { + addOnPageChangeListener(new HippyPagerPageChangeListener(this)); + setAdapter(createAdapter()); + initViewPager(); + initScroller(); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + isFirstLayoutSucceed = readyToLayout(); + } + + /** + * onLayout有效排版需要两个条件,两个条件都满足才算首次有效排版 + * 1、是dataUpdated 数据已经上屏 + * 2、有windowToken,表示viewPager排版上屏,已经有宽高 + */ + private boolean readyToLayout() { + return dataUpdated && getWindowToken() != null; + } + + @Override + public void addOnPageChangeListener(@NonNull OnPageChangeListener listener) { + super.addOnPageChangeListener(listener); + pageSelectNotifier.addOnPageChangeListener(listener); + } + + @Override + public void removeOnPageChangeListener(@NonNull OnPageChangeListener listener) { + super.removeOnPageChangeListener(listener); + pageSelectNotifier.removeOnPageChangeListener(listener); + } + + @Override + public void clearOnPageChangeListeners() { + super.clearOnPageChangeListeners(); + pageSelectNotifier.clearOnPageChangeListeners(); + } + + public int getCurrentPage() { + return getCurrentItem(); + } + + + protected void initViewPager() { + if (isVertical) { + setPageTransformer(true, new VerticalPageTransformer()); + // The easiest way to get rid of the overscroll drawing that happens on the left and right + setOverScrollMode(OVER_SCROLL_NEVER); + } + } + + public int getPageCount() { + return getAdapter() == null ? 0 : getAdapter().getCount(); + } + + public Object getCurrentItemView() { + if (getAdapter() != null) { + return getAdapter().getCurrentItemObj(); + } + return null; + } + + public void setCallBackPromise(Promise promise) { + callBackPromise = promise; + } + + public Promise getCallBackPromise() { + return callBackPromise; + } + + protected HippyPagerAdapter createAdapter() { + return new HippyPagerAdapter(this); + } + + public void setInitialPageIndex(final int index) { + LogUtils.d(TAG, HippyPager.this.getClass().getName() + " " + "setInitialPageIndex=" + index); + setCurrentItem(index);//可能不会生效,因为当前viewPager可能没有初始化数据 + setDefaultItem(index); + } + + public void setChildCountAndUpdate(final int childCount) { + LogUtils.d(TAG, "doUpdateInternal: " + hashCode() + ", childCount=" + childCount); + dataUpdated = true; + getAdapter().setChildSize(childCount); + getAdapter().notifyDataSetChanged(); + triggerRequestLayout(); + if (firstUpdateChild) { + //首次更新,ViewPager不会触发onPageSelected的分发通知,这里需要手动补一个首次事件 + pageSelectNotifier.notifyPageSelected(getCurrentItem()); + firstUpdateChild = false; + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + //当viewPager重新挂着当时候,调用super.onAttachedToWindow 把mFirstLayout 设置为true + //这样滑动viewPager,放手后,不会触发自动滚动,原因是mFirstLayout为true,setCurrentItemInternal, + //走了requestLayout,这样会没有动画 + setFirstLayout(false); + //onAttach可能在dataUpdated后面执行,onAttach的时候,需要check一下,是否需要再次触发triggerRequestLayout + //如果这里不check一次triggerRequestLayout,那么前面setChildCountAndUpdate的那一次triggerRequestLayout + //会因为viewPager没有上屏,宽高为0,而变得没有任何的作用,这样会出现白屏的的问题。 + if (!isFirstLayoutSucceed) { + triggerRequestLayout(); + } + } + + public void addViewToAdapter(HippyViewPagerItem view, int position) { + HippyPagerAdapter adapter = getAdapter(); + if (adapter != null) { + adapter.addView(view, position); + } + } + + protected int getAdapterViewSize() { + HippyPagerAdapter adapter = getAdapter(); + if (adapter != null) { + return adapter.getItemViewSize(); + } + return 0; + } + + protected void removeViewFromAdapter(HippyViewPagerItem view) { + HippyPagerAdapter adapter = getAdapter(); + if (adapter != null) { + adapter.removeView(view); + } + } + + public View getViewFromAdapter(int currentItem) { + HippyPagerAdapter adapter = getAdapter(); + if (adapter != null) { + return adapter.getViewAt(currentItem); + } + return null; + } + + @Override + public HippyPagerAdapter getAdapter() { + return (HippyPagerAdapter) super.getAdapter(); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + resetIgnoreCheck(ev); + if (!scrollEnabled) { + return false; + } + if (isVertical) { + boolean intercepted = super.onInterceptTouchEvent(swapXY(ev)); + swapXY(ev); // return touch coordinates to original reference frame for any child views + return intercepted; + } + return super.onInterceptTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + resetIgnoreCheck(ev); + if (!scrollEnabled) { + return false; + } + if (isVertical) { + return super.onTouchEvent(swapXY(ev)); + } + return super.onTouchEvent(ev); + } + + private void resetIgnoreCheck(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN || ev.getAction() == MotionEvent.ACTION_UP + || ev.getAction() == MotionEvent.ACTION_CANCEL) { + ignoreCheck = false; + } + } + + public void switchToPage(int item, boolean animated) { + if (getAdapter() == null || getAdapter().getCount() == 0) { + //1、setChildCountAndUpdate 还没有调用前,ViewPager的Adapter还没有初始化,通过反射,修改viewPager的默认Item + //当调用了setChildCountAndUpdate后,会不发一次onPageSelected给到前端 + setDefaultItem(item); + } else { + if (getCurrentItem() != item) { + //2、执行正常的切换Item的操作,会正常回调onPageSelected事件给到前端 + stopAnimationAndScrollToFinal(); + setCurrentItem(item, animated); + } else if (!firstUpdateChild) { + //3、本身当前已经在item的这个位置,就不需要调用setCurrentItem,但是前端还是要求通知一次事件onPageSelected + pageSelectNotifier.notifyPageSelected(item); + } + } + } + + + @Override + public boolean canScrollHorizontally(int direction) { + if (!scrollEnabled) { + return false; + } + return super.canScrollHorizontally(direction); + } + + @Override + public boolean canScrollVertically(int direction) { + if (!scrollEnabled) { + return false; + } + return super.canScrollVertically(direction); + } + + /** + * 如果仍然在滑动中,重置一下状态,abortAnimation ,getScrollX 会处于mFinalX的状态,直接scrollTo到mFinalX + */ + private void stopAnimationAndScrollToFinal() { + if (!scroller.isFinished()) { + invokeSetScrollingCacheEnabled(false); + if (scroller != null) { + scroller.abortAnimation(); + int oldX = getScrollX(); + int oldY = getScrollY(); + int x = scroller.getCurrX(); + int y = scroller.getCurrY(); + if (oldX != x || oldY != y) { + scrollTo(x, y); + } + } + invokeSetScrollState(SCROLL_STATE_IDLE); + } + } + + public void setScrollEnabled(boolean scrollEnabled) { + this.scrollEnabled = scrollEnabled; + } + + @Override + public NativeGestureDispatcher getGestureDispatcher() { + return gestureDispatcher; + } + + @Override + public void setGestureDispatcher(NativeGestureDispatcher nativeGestureDispatcher) { + gestureDispatcher = nativeGestureDispatcher; + } + + public void triggerRequestLayout() { + //对象构造的时候,就会调用到这里,必须判空 + if (handler != null) { + handler.removeCallbacks(measureAndLayout); + handler.post(measureAndLayout); + } + } + + public void setOverflow(String overflow) { + //robinsli Android 支持 overflow: visible,超出容器之外的属性节点也可以正常显示 + if (!TextUtils.isEmpty(overflow)) { + switch (overflow) { + case "visible": + setClipChildren(false); //可以超出父亲区域 + break; + case "hidden": { + setClipChildren(true); //默认值是false + break; + } + } + } + invalidate(); + } + + public void onOverScrollSuccess() { + invokeSetIsUnableToDrag(false); + ignoreCheck = true; + } + + + /** + * viewpPager的孩子已经滚动到底了,已经不能继续滚动了,会触发通过onOverScroll事件,告诉 + * viewPager的进行继续,需要执行onOverScrollSuccess,让viewPager开始滚动 + * 会让mIsUnableToDrag设置为false,ignoreCheck 表示不在进行孩子的判断,有些孩子没有正确实现canScroll, + * 用ignoreCheck的值来忽略孩子的滚动,这是一种兼容老代码的逻辑,按道理来说,ignoreCheck应该不需要 + */ + public boolean onOverScroll(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, + int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) { + if (isVertical) { + if (((scrollY == 0 && deltaY < 0) || (scrollY == scrollRangeY && deltaY > 0))) { + onOverScrollSuccess(); + } + } else { + if (((scrollX == 0 && deltaX < 0) || (scrollX == scrollRangeX && deltaX > 0))) { + onOverScrollSuccess(); + } + } + return true; + } + + /** + * ViewPager 在滚动的时候,他的树下的孩子节点可能也会滚动,这里需要check一下孩子是否可以滚动 + * ScrollChecker.canScroll 是QQ浏览器里面非标准的做法 + */ + @Override + protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { + if (ignoreCheck) { + return false; + } + return ScrollChecker.canScroll(v, checkV, isVertical, dx, x, y) || super.canScroll(v, checkV, dx, x, y); + } + + private MotionEvent swapXY(MotionEvent ev) { + float width = getWidth(); + float height = getHeight(); + float newX = (ev.getY() / height) * width; + float newY = (ev.getX() / width) * height; + ev.setLocation(newX, newY); + return ev; + } + + @Override + public void requestLayout() { + super.requestLayout(); + triggerRequestLayout(); + } + + /** + * hook 方法,不建议调用,这里只是为了兼容,目的是为了触发一次firstLayout恢复状态 + */ + private void setFirstLayout(boolean isFirstLayout) { + try { + Field field = ViewPager.class.getDeclaredField("mFirstLayout"); + field.setAccessible(true); + field.set(this, isFirstLayout); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + /** + * 也是Hack方法,设置初始化index + * + * @param position + */ + private void setDefaultItem(int position) { + try { + Field field = ViewPager.class.getDeclaredField("mCurItem"); + field.setAccessible(true); + field.setInt(this, position); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void initScroller() { + try { + Field velocityTrackerField = ViewPager.class.getDeclaredField("mScroller"); + velocityTrackerField.setAccessible(true); + scroller = (Scroller) velocityTrackerField.get(this); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + private void invokeSetIsUnableToDrag(boolean enabled) { + try { + Field field = ViewPager.class.getDeclaredField("mIsUnableToDrag"); + field.setAccessible(true); + field.set(this, enabled); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + private void invokeSetScrollingCacheEnabled(boolean enabled) { + try { + Method method = ViewPager.class.getDeclaredMethod("setScrollingCacheEnabled", Boolean.class); + method.setAccessible(true); + method.invoke(this, enabled); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void invokeSetScrollState(int state) { + try { + Method method = ViewPager.class.getDeclaredMethod("setScrollState", Integer.class); + method.setAccessible(true); + method.invoke(this, state); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPagerAdapter.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPagerAdapter.java new file mode 100644 index 00000000000..a04d6bd1001 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPagerAdapter.java @@ -0,0 +1,146 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.hippypager; + +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.viewpager.widget.PagerAdapter; +import com.tencent.mtt.hippy.views.viewpager.HippyViewPagerItem; +import java.util.ArrayList; +import java.util.List; + +/** + * Created on 2021/7/23. + */ + +public class HippyPagerAdapter extends PagerAdapter { + + protected final List views = new ArrayList<>(); + protected final HippyPager viewPager; + private int childSize = 0; + private Object currentItemObj = null; + + public HippyPagerAdapter(HippyPager viewPager) { + this.viewPager = viewPager; + } + + public void setChildSize(int size) { + childSize = size; + } + + public Object getCurrentItemObj() { + return currentItemObj; + } + + + protected void addView(HippyViewPagerItem view, int position) { + if (view != null && position >= 0) { + if (position >= views.size()) { + views.add(view); + } else { + views.add(position, view); + } + } + } + + protected void removeView(View view) { + int size = views.size(); + int index = -1; + for (int i = 0; i < size; i++) { + View curr = getViewAt(i); + if (curr == view) { + index = i; + break; + } + } + if (index >= 0) { + views.remove(index); + } + } + + protected View getViewAt(int index) { + if (index < 0 || index >= views.size()) { + return null; + } + return views.get(index); + } + + protected int getItemViewSize() { + return views.size(); + } + + @Override + public int getCount() { + return childSize; + } + + @Override + public int getItemPosition(Object object) { + if (views.isEmpty()) { + return POSITION_NONE; + } + int index = views.indexOf(object); + if (index < 0) { + return POSITION_NONE; + } + return index; + } + + @NonNull + @Override + public Object instantiateItem(ViewGroup container, int position) { + View pageItemView = getPageItemView(position); + if (pageItemView.getParent() == null) { + container.addView(pageItemView, new HippyPager.LayoutParams()); + viewPager.triggerRequestLayout(); + } + return pageItemView; + } + + protected View getPageItemView(int position) { + View pageItemView = null; + if (position >= 0 && position < views.size()) { + pageItemView = views.get(position); + } + if (pageItemView == null) { + throw new NullPointerException("Can not instantiateItem,position:" + position + ",size:" + views.size()); + } + return pageItemView; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + if (object instanceof View) { + View view = (View) object; + view.layout(0, 0, 0, 0); + container.removeView(view); + } + } + + @Override + public void setPrimaryItem(@NonNull ViewGroup container, int position, + @NonNull Object object) { + super.setPrimaryItem(container, position, object); + currentItemObj = object; + } + + @Override + public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { + return view == object; + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPagerController.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPagerController.java new file mode 100644 index 00000000000..315eef4dbee --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPagerController.java @@ -0,0 +1,210 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.hippypager; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import com.tencent.mtt.hippy.annotation.HippyController; +import com.tencent.mtt.hippy.annotation.HippyControllerProps; +import com.tencent.mtt.hippy.common.HippyArray; +import com.tencent.mtt.hippy.common.HippyMap; +import com.tencent.mtt.hippy.dom.node.NodeProps; +import com.tencent.mtt.hippy.modules.Promise; +import com.tencent.mtt.hippy.uimanager.HippyViewController; +import com.tencent.mtt.hippy.utils.LogUtils; +import com.tencent.mtt.hippy.utils.PixelUtil; +import com.tencent.mtt.hippy.views.viewpager.HippyViewPagerItem; + + +/** + * Created on 2021/7/23. + */ +@HippyController(name = HippyPagerController.CLASS_NAME) +public class HippyPagerController extends HippyViewController { + + public static final String CLASS_NAME = "ViewPager"; + + private static final String TAG = "HippyViewPagerController"; + + private static final String FUNC_SET_PAGE = "setPage"; + private static final String FUNC_SET_PAGE_WITHOUT_ANIM = "setPageWithoutAnimation"; + + private static final String FUNC_SET_INDEX = "setIndex"; + private static final String FUNC_NEXT_PAGE = "next"; + private static final String FUNC_PREV_PAGE = "prev"; + + @Override + protected View createViewImpl(Context context) { + return new HippyPager(context); + } + + @Override + protected View createViewImpl(Context context, HippyMap iniProps) { + boolean isVertical = false; + if (iniProps != null) { + if ((iniProps.containsKey("direction") && iniProps.getString("direction").equals("vertical")) + || iniProps.containsKey("vertical")) { + isVertical = true; + } + } + + return new HippyPager(context, isVertical); + } + + @Override + public View getChildAt(HippyPager hippyViewPager, int i) { + return hippyViewPager.getViewFromAdapter(i); + } + + @Override + public int getChildCount(HippyPager hippyViewPager) { + return hippyViewPager.getAdapter().getCount(); + } + + @Override + protected void addView(ViewGroup parentView, View view, int index) { + LogUtils.d(TAG, "addView: " + parentView.hashCode() + ", index=" + index); + if (parentView instanceof HippyPager && view instanceof HippyViewPagerItem) { + HippyPager hippyViewPager = (HippyPager) parentView; + hippyViewPager.addViewToAdapter((HippyViewPagerItem) view, index); + } else { + LogUtils.e(TAG, "add view got invalid params"); + } + } + + @Override + protected void deleteChild(ViewGroup parentView, View childView) { + LogUtils.d(TAG, "deleteChild: " + parentView.hashCode()); + if (parentView instanceof HippyPager && childView instanceof HippyViewPagerItem) { + ((HippyPager) parentView).removeViewFromAdapter((HippyViewPagerItem) childView); + } else { + LogUtils.e(TAG, "delete view got invalid params"); + } + } + + @Override + protected void onManageChildComplete(HippyPager viewPager) { + viewPager.setChildCountAndUpdate(viewPager.getAdapter().getItemViewSize()); + } + + @HippyControllerProps(name = "initialPage", defaultNumber = 0, defaultType = HippyControllerProps.NUMBER) + public void setInitialPage(HippyPager parent, int initialPage) { + parent.setInitialPageIndex(initialPage); + } + + @HippyControllerProps(name = "scrollEnabled", defaultBoolean = true, defaultType = HippyControllerProps.BOOLEAN) + public void setScrollEnabled(HippyPager viewPager, boolean value) { + viewPager.setScrollEnabled(value); + } + + @HippyControllerProps(name = "pageMargin", defaultNumber = 0, defaultType = HippyControllerProps.NUMBER) + public void setPageMargin(HippyPager pager, float margin) { + pager.setPageMargin((int) PixelUtil.dp2px(margin)); + } + + @HippyControllerProps(name = NodeProps.OVERFLOW, defaultType = HippyControllerProps.STRING, defaultString = "visible") + public void setOverflow(HippyPager pager, String overflow) { + pager.setOverflow(overflow); + } + + @HippyControllerProps(name = "offscreenPageLimit", defaultNumber = 0, defaultType = HippyControllerProps.NUMBER) + public void setOffscreenPageLimit(HippyPager pager, int limit) { + pager.setOffscreenPageLimit(limit); + } + @Override + public void dispatchFunction(HippyPager view, String functionName, HippyArray var) { + if (view == null) { + return; + } + + int curr = view.getCurrentItem(); + + switch (functionName) { + case FUNC_SET_PAGE: + if (var != null) { + Object selected = var.get(0); + if (selected instanceof Integer) { + view.switchToPage((int) selected, true); + } + } + break; + case FUNC_SET_PAGE_WITHOUT_ANIM: + if (var != null) { + Object selected = var.get(0); + if (selected instanceof Integer) { + view.switchToPage((int) selected, false); + } + } + break; + case FUNC_SET_INDEX: + if (var != null && var.size() > 0) { + HippyMap paramsMap = var.getMap(0); + if (paramsMap != null && paramsMap.size() > 0 && paramsMap.containsKey("index")) { + int index = paramsMap.getInt("index"); + boolean animated = !paramsMap.containsKey("animated") || paramsMap.getBoolean("animated"); + view.switchToPage(index, animated); + } + } + break; + case FUNC_NEXT_PAGE: + int total = view.getAdapter().getCount(); + if (curr < total - 1) { + view.switchToPage(curr + 1, true); + } + break; + case FUNC_PREV_PAGE: + if (curr > 0) { + view.switchToPage(curr - 1, true); + } + break; + default: + break; + } + } + + @Override + public void dispatchFunction(HippyPager view, String functionName, HippyArray params, Promise promise) { + if (view == null) { + return; + } + + switch (functionName) { + case FUNC_SET_INDEX: + if (params != null && params.size() > 0) { + HippyMap paramsMap = params.getMap(0); + if (paramsMap != null && paramsMap.size() > 0 && paramsMap.containsKey("index")) { + int index = paramsMap.getInt("index"); + view.setCallBackPromise(promise); + boolean animated = !paramsMap.containsKey("animated") || paramsMap.getBoolean("animated"); + view.switchToPage(index, animated); + return; + } + } + + if (promise != null) { + String msg = "invalid parameter!"; + HippyMap resultMap = new HippyMap(); + resultMap.pushString("msg", msg); + promise.resolve(resultMap); + } + break; + default: + break; + } + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPagerPageChangeListener.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPagerPageChangeListener.java new file mode 100644 index 00000000000..109d06f5afe --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPagerPageChangeListener.java @@ -0,0 +1,117 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.hippypager; + +import android.view.View; +import androidx.viewpager.widget.ViewPager; +import com.tencent.mtt.hippy.common.HippyMap; +import com.tencent.mtt.hippy.modules.Promise; +import com.tencent.mtt.hippy.utils.LogUtils; +import com.tencent.mtt.hippy.views.viewpager.event.HippyPageItemExposureEvent; +import com.tencent.mtt.hippy.views.viewpager.event.HippyPageScrollEvent; +import com.tencent.mtt.hippy.views.viewpager.event.HippyPageScrollStateChangedEvent; +import com.tencent.mtt.hippy.views.viewpager.event.HippyPageSelectedEvent; + +/** + * Created on 2021/7/23. + */ + +public class HippyPagerPageChangeListener implements ViewPager.OnPageChangeListener { + + public static final String IDLE = "idle"; + public static final String DRAGGING = "dragging"; + public static final String SETTLING = "settling"; + private HippyPageScrollEvent pageScrollEmitter; + private HippyPageScrollStateChangedEvent pageScrollStateChangeEmitter; + private HippyPageSelectedEvent pageSelectedEmitter; + private int lastPageIndex; + private int currPageIndex; + private HippyPager hippyPager; + + public HippyPagerPageChangeListener(HippyPager pager) { + hippyPager = pager; + pageScrollEmitter = new HippyPageScrollEvent(pager); + pageScrollStateChangeEmitter = new HippyPageScrollStateChangedEvent(pager); + pageSelectedEmitter = new HippyPageSelectedEvent(pager); + lastPageIndex = 0; + currPageIndex = 0; + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + pageScrollEmitter.send(position, positionOffset); + } + + @Override + public void onPageSelected(int position) { + currPageIndex = position; + pageSelectedEmitter.send(position); + if (hippyPager != null) { + View currView = hippyPager.getViewFromAdapter(currPageIndex); + HippyPageItemExposureEvent eventWillAppear = new HippyPageItemExposureEvent( + HippyPageItemExposureEvent.EVENT_PAGER_ITEM_WILL_APPEAR); + eventWillAppear.send(currView, currPageIndex); + View lastView = hippyPager.getViewFromAdapter(lastPageIndex); + HippyPageItemExposureEvent eventWillDisAppear = new HippyPageItemExposureEvent( + HippyPageItemExposureEvent.EVENT_PAGER_ITEM_WILL_DISAPPEAR); + eventWillDisAppear.send(lastView, lastPageIndex); + } + } + + private void onScrollStateChangeToIdle() { + if (hippyPager != null && currPageIndex != lastPageIndex) { + Promise promise = hippyPager.getCallBackPromise(); + if (promise != null) { + String msg = "on set index successful!"; + HippyMap resultMap = new HippyMap(); + resultMap.pushString("msg", msg); + promise.resolve(resultMap); + hippyPager.setCallBackPromise(null); + } + View currView = hippyPager.getViewFromAdapter(currPageIndex); + HippyPageItemExposureEvent eventWillAppear = new HippyPageItemExposureEvent( + HippyPageItemExposureEvent.EVENT_PAGER_ITEM_DID_APPEAR); + eventWillAppear.send(currView, currPageIndex); + View lastView = hippyPager.getViewFromAdapter(lastPageIndex); + HippyPageItemExposureEvent eventWillDisAppear = new HippyPageItemExposureEvent( + HippyPageItemExposureEvent.EVENT_PAGER_ITEM_DID_DISAPPEAR); + eventWillDisAppear.send(lastView, lastPageIndex); + lastPageIndex = currPageIndex; + } + } + + @Override + public void onPageScrollStateChanged(int newState) { + LogUtils.i("HippyPagerStateChanged", "onPageScrollStateChanged newState=" + newState); + String pageScrollState; + switch (newState) { + case ViewPager.SCROLL_STATE_IDLE: + pageScrollState = IDLE; + onScrollStateChangeToIdle(); + break; + case ViewPager.SCROLL_STATE_DRAGGING: + pageScrollState = DRAGGING; + break; + case ViewPager.SCROLL_STATE_SETTLING: + pageScrollState = SETTLING; + break; + default: + throw new IllegalStateException("Unsupported pageScrollState"); + } + pageScrollStateChangeEmitter.send(pageScrollState); + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/PageSelectNotifier.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/PageSelectNotifier.java new file mode 100644 index 00000000000..3a4ca9345e2 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/PageSelectNotifier.java @@ -0,0 +1,47 @@ +package com.tencent.mtt.hippy.views.hippypager; + +import androidx.annotation.NonNull; +import androidx.viewpager.widget.ViewPager.OnPageChangeListener; +import java.util.ArrayList; +import java.util.List; + +/** + * ViewPager目前的onPageSelected事件通知不满足Hippy的需求,比如首次通知,相同page的通知,需要补充通知一次 + * 这个和ViewPager的通知是互斥的,ViewPager如果有通知,这里是不会再通知的 + */ +class PageSelectNotifier { + + private List onPageChangeListeners; + + void notifyPageSelected(int position) { + if (onPageChangeListeners != null) { + for (int i = 0, z = onPageChangeListeners.size(); i < z; i++) { + OnPageChangeListener listener = onPageChangeListeners.get(i); + if (listener != null) { + listener.onPageSelected(position); + } + } + } + } + + void addOnPageChangeListener(@NonNull OnPageChangeListener listener) { + if (onPageChangeListeners == null) { + onPageChangeListeners = new ArrayList<>(); + } + if (!onPageChangeListeners.contains(listener)) { + onPageChangeListeners.add(listener); + } + } + + void removeOnPageChangeListener(@NonNull OnPageChangeListener listener) { + if (onPageChangeListeners != null) { + onPageChangeListeners.remove(listener); + } + } + + public void clearOnPageChangeListeners() { + if (onPageChangeListeners != null) { + onPageChangeListeners.clear(); + } + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/transform/VerticalPageTransformer.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/transform/VerticalPageTransformer.java new file mode 100644 index 00000000000..6c352fae7ba --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/transform/VerticalPageTransformer.java @@ -0,0 +1,36 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.hippypager.transform; + +import android.view.View; +import androidx.viewpager.widget.ViewPager; + +/** + * Created on 2021/7/23. + */ +public class VerticalPageTransformer implements ViewPager.PageTransformer { + + @Override + public void transformPage(View view, float position) { + if (position >= -1 && position <= 1) { + view.setTranslationX(view.getWidth() * -position); + float yPosition = position * view.getHeight(); + view.setTranslationY(yPosition); + } + } + +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/image/HippyImageView.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/image/HippyImageView.java index c3706eff781..d8149b72139 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/image/HippyImageView.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/image/HippyImageView.java @@ -20,7 +20,9 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Movie; +import android.graphics.Path; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.text.TextUtils; @@ -44,6 +46,7 @@ import com.tencent.mtt.hippy.views.common.CommonBorder; import com.tencent.mtt.hippy.views.list.HippyRecycler; import com.tencent.mtt.supportui.adapters.image.IDrawableTarget; +import com.tencent.mtt.supportui.utils.CommonTool; import com.tencent.mtt.supportui.views.asyncimage.AsyncImageView; import com.tencent.mtt.supportui.views.asyncimage.BackgroundDrawable; import com.tencent.mtt.supportui.views.asyncimage.ContentDrawable; @@ -62,9 +65,6 @@ public class HippyImageView extends AsyncImageView implements CommonBorder, Hipp public static final String IMAGE_VIEW_OBJ = "viewobj"; private HippyMap initProps = new HippyMap(); - private boolean mHasSetTempBackgroundColor = false; - private boolean mUserHasSetBackgroudnColor = false; - private int mUserSetBackgroundColor = Color.TRANSPARENT; /** * 播放GIF动画的关键类 @@ -74,8 +74,9 @@ public class HippyImageView extends AsyncImageView implements CommonBorder, Hipp private int mGifStartY = 0; private float mGifScaleX = 1; private float mGifScaleY = 1; + private Path mGifPath; private boolean mGifMatrixComputed = false; - private int mGifProgress = 0; + private long mGifProgress = 0; private long mGifLastPlayTime = -1; @Override @@ -92,6 +93,7 @@ public void resetProps() { mImageType = null; setBackgroundDrawable(null); Arrays.fill(mShouldSendImageEvent, false); + mGifMatrixComputed = false; } @Override @@ -155,10 +157,11 @@ protected void setImageEventEnable(int index, boolean enable) { @Override protected void resetContent() { - super.resetContent(); mGifMovie = null; + mGifMatrixComputed = false; mGifProgress = 0; mGifLastPlayTime = -1; + super.resetContent(); } @Override @@ -239,68 +242,13 @@ public void onRequestFail(Throwable throwable, String source) { } } - public void setBackgroundColor(int backgroundColor) { - mUserHasSetBackgroudnColor = true; - mUserSetBackgroundColor = backgroundColor; - super.setBackgroundColor(backgroundColor); - } - - @Override - protected void onFetchImage(String url) { - if (mContentDrawable instanceof ContentDrawable && - ((ContentDrawable) mContentDrawable).getSourceType() == SOURCE_TYPE_DEFAULT_SRC) { - return; - } - - Drawable oldBGDrawable = getBackground(); - resetContent(); - - if (url != null && (UrlUtils.isWebUrl(url) || UrlUtils.isFileUrl(url))) { - int defaultBackgroundColor = Color.LTGRAY; - if (mUserHasSetBackgroudnColor) { - defaultBackgroundColor = mUserSetBackgroundColor; - } - - if (oldBGDrawable instanceof CommonBackgroundDrawable) { - ((CommonBackgroundDrawable) oldBGDrawable).setBackgroundColor(defaultBackgroundColor); - setCustomBackgroundDrawable((CommonBackgroundDrawable) oldBGDrawable); - } else if (oldBGDrawable instanceof LayerDrawable) { - LayerDrawable layerDrawable = (LayerDrawable) oldBGDrawable; - int numberOfLayers = layerDrawable.getNumberOfLayers(); - - if (numberOfLayers > 0) { - Drawable bgDrawable = layerDrawable.getDrawable(0); - if (bgDrawable instanceof CommonBackgroundDrawable) { - ((CommonBackgroundDrawable) bgDrawable).setBackgroundColor(defaultBackgroundColor); - setCustomBackgroundDrawable((CommonBackgroundDrawable) bgDrawable); - } - } - } - super.setBackgroundColor(defaultBackgroundColor); - mHasSetTempBackgroundColor = true; - } - } - - @Override - protected void afterSetContent(String url) { - restoreBackgroundColorAfterSetContent(); - } - - @Override - protected void restoreBackgroundColorAfterSetContent() { - if (mBGDrawable != null && mHasSetTempBackgroundColor) { - int defaultBackgroundColor = Color.TRANSPARENT; - mBGDrawable.setBackgroundColor(defaultBackgroundColor); - mHasSetTempBackgroundColor = false; - } - } - @Override protected void updateContentDrawableProperty(int sourceType) { super.updateContentDrawableProperty(sourceType); if (mContentDrawable instanceof HippyContentDrawable && sourceType == SOURCE_TYPE_SRC) { ((HippyContentDrawable) mContentDrawable).setNinePatchCoordinate(mNinePatchRect); } + mGifMatrixComputed = false; } @Override @@ -344,7 +292,21 @@ protected void handleGetImageStart() { protected void handleGetImageSuccess() { // send onLoad event if (mShouldSendImageEvent[ImageEvent.ONLOAD.ordinal()]) { - getOnLoadEvent().send(this, null); + HippyMap map = new HippyMap(); + if (mSourceDrawable != null) { + if (mSourceDrawable instanceof HippyDrawable) { + HippyDrawable hippyTarget = (HippyDrawable) mSourceDrawable; + map.pushInt("width", hippyTarget.getWidth()); + map.pushInt("height", hippyTarget.getHeight()); + map.pushString("url", mUrl != null ? mUrl : ""); + } else if (mSourceDrawable.getBitmap() != null) { + Bitmap bitmap = mSourceDrawable.getBitmap(); + map.pushInt("width", bitmap.getWidth()); + map.pushInt("height", bitmap.getHeight()); + map.pushString("url", mUrl != null ? mUrl : ""); + } + } + getOnLoadEvent().send(this, map); } // send onLoadEnd event if (mShouldSendImageEvent[ImageEvent.ONLOAD_END.ordinal()]) { @@ -367,7 +329,11 @@ protected void handleGetImageSuccess() { protected void handleGetImageFail(Throwable throwable) { // send onError event if (mShouldSendImageEvent[ImageEvent.ONERROR.ordinal()]) { - getOnErrorEvent().send(this, null); + HippyMap map = new HippyMap(); + map.pushString("error", String.valueOf(throwable)); + map.pushInt("errorCode", -1); + map.pushString("errorURL", mUrl != null ? mUrl : ""); + getOnErrorEvent().send(this, map); } // send onLoadEnd event if (mShouldSendImageEvent[ImageEvent.ONLOAD_END.ordinal()]) { @@ -429,10 +395,38 @@ private void computeMatrixParams() { mGifStartX = (int) ((getWidth() / mGifScaleX - mGifMovie.width()) / 2f); mGifStartY = (int) ((getHeight() / mGifScaleY - mGifMovie.height()) / 2f); } + if (mGifPath == null) { + mGifPath = new Path(); + } else { + mGifPath.rewind(); + } + if (mBGDrawable != null) { + float[] borderWidthArray = mBGDrawable.getBorderWidthArray(); + float fullBorderWidth = borderWidthArray == null ? 0 : borderWidthArray[0]; + RectF bounds = new RectF(fullBorderWidth, fullBorderWidth, getWidth() - fullBorderWidth, + getHeight() - fullBorderWidth); + float[] borderRadiusArray = mBGDrawable.getBorderRadiusArray(); + if (CommonTool.hasPositiveItem(borderRadiusArray)) { + float fullRadius = borderRadiusArray[0]; + float topLeftRadius = calculateBorderRadius(borderRadiusArray[1], fullRadius, fullBorderWidth); + float topRightRadius = calculateBorderRadius(borderRadiusArray[2], fullRadius, fullBorderWidth); + float bottomRightRadius = calculateBorderRadius(borderRadiusArray[3], fullRadius, fullBorderWidth); + float bottomLeftRadius = calculateBorderRadius(borderRadiusArray[4], fullRadius, fullBorderWidth); + float[] radii = new float[] { topLeftRadius, topLeftRadius, topRightRadius, topRightRadius, + bottomRightRadius, bottomRightRadius, bottomLeftRadius, bottomLeftRadius }; + mGifPath.addRoundRect(bounds, radii, Path.Direction.CW); + } else { + mGifPath.addRect(bounds, Path.Direction.CW); + } + } mGifMatrixComputed = true; } } + private static float calculateBorderRadius(float value, float fullValue, float inset) { + return Math.max(0, (value != 0 ? value : fullValue) - inset * .5f); + } + @Override protected void handleImageRequest(IDrawableTarget target, int sourceType, Object requestInfo) { if (target != null && !TextUtils.isEmpty(target.getImageType())) { @@ -441,6 +435,7 @@ protected void handleImageRequest(IDrawableTarget target, int sourceType, Object if (target instanceof HippyDrawable && ((HippyDrawable)target).isAnimated()) { mGifMovie = ((HippyDrawable) target).getGIF(); + mGifMatrixComputed = false; setLayerType(View.LAYER_TYPE_SOFTWARE, null); } @@ -465,6 +460,43 @@ protected void handleImageRequest(IDrawableTarget target, int sourceType, Object } } + @Override + protected boolean hasImage(IDrawableTarget resultDrawable) { + return (resultDrawable != null && resultDrawable.getBitmap() != null) + || (resultDrawable != null && resultDrawable.getDrawable() != null) + || (resultDrawable instanceof HippyDrawable && ((HippyDrawable) resultDrawable).getGIF() != null); + } + + @Override + public void setBorderRadius(float radius, int position) { + super.setBorderRadius(radius, position); + mGifMatrixComputed = false; + } + + @Override + public void setBorderWidth(float width, int position) { + super.setBorderWidth(width, position); + mGifMatrixComputed = false; + } + + @Override + public void setBorderColor(int color, int position) { + super.setBorderColor(color, position); + mGifMatrixComputed = false; + } + + @Override + public void setBorderStyle(int borderStyle) { + super.setBorderStyle(borderStyle); + mGifMatrixComputed = false; + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + mGifMatrixComputed = false; + } + protected boolean drawGIF(Canvas canvas) { if (mGifMovie == null) { return false; @@ -479,7 +511,7 @@ protected boolean drawGIF(Canvas canvas) { if (!isGifPaused) { if (mGifLastPlayTime != -1) { - mGifProgress += now - mGifLastPlayTime; + mGifProgress += (now - mGifLastPlayTime); if (mGifProgress > duration) { mGifProgress = 0; @@ -489,8 +521,12 @@ protected boolean drawGIF(Canvas canvas) { } computeMatrixParams(); - mGifMovie.setTime(mGifProgress); + int progress = mGifProgress > Integer.MAX_VALUE ? 0 : (int) mGifProgress; + mGifMovie.setTime(progress); canvas.save(); // 保存变换矩阵 + if (!mGifPath.isEmpty()) { + canvas.clipPath(mGifPath); + } canvas.scale(mGifScaleX, mGifScaleY); mGifMovie.draw(canvas, mGifStartX, mGifStartY); canvas.restore(); // 恢复变换矩阵 diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/list/HippyListAdapter.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/list/HippyListAdapter.java index 45d13c9001b..ebaff27174c 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/list/HippyListAdapter.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/list/HippyListAdapter.java @@ -73,10 +73,10 @@ public ContentHolder onCreateContentViewWithPos(ViewGroup parent, int position, View view = contentViewRenderNode.createViewRecursive(); contentHolder.mContentView = view; if (view instanceof HippyPullHeaderView) { - ((HippyPullHeaderView) view).setParentView(mParentRecyclerView); + ((HippyPullHeaderView) view).setRecyclerView(mParentRecyclerView); } if (view instanceof HippyPullFooterView) { - ((HippyPullFooterView) view).setParentView(mParentRecyclerView); + ((HippyPullFooterView) view).setRecyclerView(mParentRecyclerView); } contentHolder.mBindNode = contentViewRenderNode; contentHolder.isCreated = true; diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/list/HippyListView.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/list/HippyListView.java index b888001e64f..7e62210acb5 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/list/HippyListView.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/list/HippyListView.java @@ -15,6 +15,9 @@ */ package com.tencent.mtt.hippy.views.list; +import android.graphics.Rect; +import android.os.SystemClock; +import android.view.KeyEvent; import android.view.ViewConfiguration; import com.tencent.mtt.hippy.HippyEngineContext; import com.tencent.mtt.hippy.HippyInstanceContext; @@ -85,6 +88,7 @@ public class HippyListView extends RecyclerView implements HippyViewBase { protected int mLastOffsetX = Integer.MIN_VALUE; protected int mLastOffsetY = Integer.MIN_VALUE; protected long mLastScrollEventTimeStamp = -1; + protected boolean mHasUnsentScrollEvent; private boolean mHasRemovePreDraw = false; private ViewTreeObserver.OnPreDrawListener mPreDrawListener = null; @@ -97,12 +101,20 @@ public class HippyListView extends RecyclerView implements HippyViewBase { private OnScrollFlingEndedEvent mOnScrollFlingEndedEvent; private OnScrollEvent mOnScrollEvent; + private boolean isTvPlatform; + private HippyListViewFocusHelper mFocusHelper = null; + private void init(Context context, int orientation) { mHippyContext = ((HippyInstanceContext) context).getEngineContext(); this.setLayoutManager(new LinearLayoutManager(context, orientation, false)); setRepeatableSuspensionMode(false); mListAdapter = createAdapter(this, mHippyContext); setAdapter(mListAdapter); + isTvPlatform = mHippyContext.isRunningOnTVPlatform(); + if (isTvPlatform) { + mFocusHelper = new HippyListViewFocusHelper(this); + setFocusableInTouchMode(true); + } final ViewConfiguration configuration = ViewConfiguration.get(context); touchSlop = configuration.getScaledTouchSlop(); @@ -173,12 +185,17 @@ public boolean onInterceptTouchEvent(MotionEvent motionEvent) { public void setListData() { LogUtils.d("hippylistview", "setListData"); mListAdapter.notifyDataSetChanged(); + if (isTvPlatform) { + mFocusHelper.setListData(); + } dispatchLayout(); + if (mExposureEventEnable) { + dispatchExposureEvent(); + } if (!hasCompleteFirstBatch && getChildCount() > 0) { if (initialContentOffset > 0) { scrollToInitContentOffset(); } - hasCompleteFirstBatch = true; } } @@ -488,20 +505,25 @@ protected void onScrollFlingEnded() { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); - if (changed && mExposureEventEnable) { - dispatchExposureEvent(); - } } @Override public void onScrolled(int x, int y) { super.onScrolled(x, y); - sendOnScrollEvent(); + checkSendOnScrollEvent(); if (mExposureEventEnable) { dispatchExposureEvent(); } } + @Override + public void onScrollStateChanged(int oldState, int newState) { + super.onScrollStateChanged(oldState, newState); + if (mHasUnsentScrollEvent) { + sendOnScrollEvent(); + } + } + protected void sendExposureEvent(View view, String eventName, HippyMap props) { if (props.containsKey(eventName)) { new HippyViewEvent(eventName).send(view, null); @@ -574,6 +596,10 @@ protected void checkExposureView(View view, int visibleStart, int visibleEnd, in private void dispatchExposureEvent() { if (mLayout instanceof BaseLayoutManager) { BaseLayoutManager.OrientationHelper layoutHelper = ((BaseLayoutManager) mLayout).mOrientationHelper; + if (layoutHelper == null) { + return; + } + int count = getChildCount(); int fixOffset = (mLayout.canScrollHorizontally()) ? mState.mCustomHeaderWidth : mState.mCustomHeaderHeight; @@ -680,18 +706,23 @@ public void run() { } } - protected void sendOnScrollEvent() { + protected void checkSendOnScrollEvent() { if (mScrollEventEnable) { - long currTime = System.currentTimeMillis(); - if (currTime - mLastScrollEventTimeStamp < mScrollEventThrottle) { - return; + long currTime = SystemClock.elapsedRealtime(); + if (currTime - mLastScrollEventTimeStamp >= mScrollEventThrottle) { + mLastScrollEventTimeStamp = currTime; + sendOnScrollEvent(); + } else { + mHasUnsentScrollEvent = true; } - - mLastScrollEventTimeStamp = currTime; - getOnScrollEvent().send(this, generateScrollEvent()); } } + protected void sendOnScrollEvent() { + mHasUnsentScrollEvent = false; + getOnScrollEvent().send(this, generateScrollEvent()); + } + // start drag event protected OnScrollDragStartedEvent getOnScrollDragStartedEvent() { if (mOnScrollDragStartedEvent == null) { @@ -812,4 +843,47 @@ protected void sendPullFooterEvent(String eventName, HippyMap param) { event.send(footerView, param); } } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + if (isTvPlatform) { + mFocusHelper.setKeyCode(event.getKeyCode()); + } + } + return super.dispatchKeyEvent(event); + } + + @Override + public void requestChildFocus(View child, View focused) { + super.requestChildFocus(child, focused); + if (!isTvPlatform) { + return; + } + mFocusHelper.requestChildFocus(child, focused); + } + + @Override + public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) { + if (!isTvPlatform) { + return super.requestChildRectangleOnScreen(child, rect, immediate); + } + return mFocusHelper.requestChildRectangleOnScreen(child, rect, immediate); + } + + @Override + protected int getChildDrawingOrder(int childCount, int i) { + if (!isTvPlatform) { + return super.getChildDrawingOrder(childCount, i); + } + return mFocusHelper.getChildDrawingOrder(childCount, i); + } + + @Override + public View focusSearch(View focused, int direction) { + if (!isTvPlatform) { + return super.focusSearch(focused, direction); + } + return mFocusHelper.focusSearch(focused, direction); + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/list/HippyListViewController.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/list/HippyListViewController.java index c7797ccddcb..daa1d6d5cd4 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/list/HippyListViewController.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/list/HippyListViewController.java @@ -88,11 +88,17 @@ protected View createViewImpl(Context context) { @Override protected View createViewImpl(Context context, HippyMap iniProps) { - if (iniProps != null && iniProps.containsKey("horizontal")) { - return new HippyListView(context, BaseLayoutManager.HORIZONTAL); - } else { - return new HippyListView(context, BaseLayoutManager.VERTICAL); + boolean enableScrollEvent = false; + int orientation = BaseLayoutManager.VERTICAL; + if (iniProps != null) { + if (iniProps.getBoolean("horizontal")) { + orientation = BaseLayoutManager.HORIZONTAL; + } + enableScrollEvent = iniProps.getBoolean("onScroll"); } + HippyListView listView = new HippyListView(context, orientation); + listView.setOnScrollEventEnable(enableScrollEvent); + return listView; } @Override @@ -127,11 +133,6 @@ public void setMomentumScrollEndEventEnable(HippyListView view, boolean flag) { view.setMomentumScrollEndEventEnable(flag); } - @HippyControllerProps(name = "onScrollEnable", defaultType = HippyControllerProps.BOOLEAN) - public void setOnScrollEventEnable(HippyListView view, boolean flag) { - view.setOnScrollEventEnable(flag); - } - @HippyControllerProps(name = "exposureEventEnabled", defaultType = HippyControllerProps.BOOLEAN) public void setExposureEventEnable(HippyListView view, boolean flag) { view.setExposureEventEnable(flag); diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/list/HippyListViewFocusHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/list/HippyListViewFocusHelper.java new file mode 100644 index 00000000000..dc6e49edc73 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/list/HippyListViewFocusHelper.java @@ -0,0 +1,323 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.list; + +import android.graphics.Rect; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import com.tencent.mtt.hippy.utils.LogUtils; +import com.tencent.mtt.hippy.utils.StrictFocusFinder; +import com.tencent.mtt.supportui.views.recyclerview.LinearLayoutManager; +import com.tencent.mtt.supportui.views.recyclerview.RecyclerViewBase.LayoutManager; +import com.tencent.mtt.supportui.views.recyclerview.RecyclerViewItem; + +public class HippyListViewFocusHelper { + + private View mFocusedView; + private int mKeyCode = KeyEvent.KEYCODE_DPAD_CENTER; + private HippyListView mListView; + + public HippyListViewFocusHelper(HippyListView listView) { + this.mListView = listView; + } + + public void setListData() { + mFocusedView = null; + } + + public void setKeyCode(int keyCode) { + mKeyCode = keyCode; + } + + public boolean isScrollToFix(int yIndex, boolean fix) { + return fix && mListView.getHeightBefore(yIndex) - mListView.getOffsetY() < mListView + .getHeight(); + } + + public void requestChildFocus(View child, View focused) { + View parent = getParentRecycleItem(focused); + if (parent != null) { + + int focusPos = mListView.getLayoutManager().getPosition(mListView.getFocusedChild()); + int offset = getOffset(parent, focusPos); + + LogUtils.d("HippyListView", "requestChildFocus offset=" + offset); + // 校验滚动位置是否合适 + if (mKeyCode == KeyEvent.KEYCODE_DPAD_DOWN) { + // 如果向下滑,并且最后一个Layout在界面上,能向下滑动的最大距离为最后一个Layout到下边界的距离 + // 将最后一个Layout对齐到下边界,可以做反向距离纠正 + offset = getOffsetKeyDown(offset); + } else if (mKeyCode == KeyEvent.KEYCODE_DPAD_UP) { + // 如果向上滑,并且第一个Layout在界面上,能向上滑动的最大距离为第一个Layout到上边界的距离 + // 将第一个Layout对齐到上边界,可以做反向距离纠正 + offset = getOffsetKeyUp(offset); + } + + if (isNoFocusDoNothing()) { + //需求1-初次进入页面首屏不滚动 + } else if (isSameItemDoNothing(focused)) { + //需求2-同一个item内部不滚动 + } else { + mListView.smoothScrollBy(0, offset); + } + } + + mFocusedView = focused; + } + + private boolean isSameItemDoNothing(View focused) { + return mFocusedView != null && isViewOfSameRecycleItem(mFocusedView, focused); + } + + private boolean isNoFocusDoNothing() { + return mFocusedView == null && getFirstFocusHeightBefore() < mListView.getHeight(); + } + + private int getFirstFocusHeightBefore() { + View focusItem = mListView.getFocusedChild(); + int focusIndex = mListView.getLayoutManager().getPosition(focusItem); + if (focusItem != null) { + View focusItemReal = getRealFocusedChild(focusItem); + return mListView.getHeightBefore(focusIndex) + focusItemReal.getHeight(); + } + + return mListView.getHeightBefore(focusIndex); + } + + private View getRealFocusedChild(View parent) { + if (parent instanceof ViewGroup) { + View focusView = ((ViewGroup) parent).getFocusedChild(); + if (focusView == null) { + return parent; + } + return getRealFocusedChild(focusView); + } + return parent; + } + + private View getParentRecycleItem(View focused) { + if (focused instanceof RecyclerViewItem) { + return focused; + } + + final ViewParent theParent = focused.getParent(); + if (theParent == null) { + return null; + } + return getParentRecycleItem((View) theParent); + } + + private int getOffsetKeyUp(int offset) { + if (mListView.getAdapter().getItemCount() == mListView.getChildCount()) { + View view = mListView.getChildAt(0); + int maxTop = view.getBottom() - view.getHeight(); + if (offset > 0) { + offset = 0; + } else if (offset < maxTop) { + offset = maxTop; + } + LogUtils.d("HippyListView", "requestChildFocus offset=" + offset + ",max_top=" + maxTop); + } else if (offset > 0) { + offset = 0; + } + return offset; + } + + private int getOffsetKeyDown(int offset) { + if (mListView.getAdapter().getItemCount() == mListView.getChildCount()) { + View view = mListView.getChildAt(mListView.getChildCount() - 1); + int maxBottom = view.getTop() + view.getHeight() - mListView.getHeight(); + if (offset < 0) { + offset = 0; + } else if (offset > maxBottom) { + offset = maxBottom; + } + LogUtils + .d("HippyListView", "requestChildFocus offset=" + offset + ",max_bottom=" + maxBottom); + } else if (offset < 0) { + offset = 0; + } + return offset; + } + + private int getOffset(View parent, int focusPos) { + int offset = 0; + if (isBottomEdge(focusPos)) { + offset = parent.getTop() + parent.getHeight() - mListView.getHeight(); + } else if (isTopEdge(focusPos)) { + offset = parent.getBottom() - parent.getHeight(); + } else { + offset = parent.getTop() + parent.getHeight() / 2 - mListView.getHeight() / 2; + } + return offset; + } + + private boolean isViewOfSameRecycleItem(View view, View view1) { + if (view == view1) { + return true; + } + + if (getParentRecycleItem(view) == getParentRecycleItem(view1)) { + return true; + } + + return false; + } + + private boolean isVertical() { + LayoutManager manager = mListView.getLayoutManager(); + if (manager != null) { + LinearLayoutManager layout = (LinearLayoutManager) mListView.getLayoutManager(); + return layout.getOrientation() == LinearLayoutManager.VERTICAL; + } + return false; + } + + public boolean isTopEdge(int childPosition) { + LayoutManager layoutManager = mListView.getLayoutManager(); + if (layoutManager instanceof LinearLayoutManager) { + if (isVertical()) { + return childPosition == 0; + } else { + return true; + } + } + + return false; + } + + public boolean isBottomEdge(int childPosition) { + LayoutManager layoutManager = mListView.getLayoutManager(); + if (layoutManager instanceof LinearLayoutManager) { + if (isVertical()) { + return childPosition == mListView.getLayoutManager().getItemCount() - 1; + } else { + return true; + } + } + return false; + } + + public int getFirstVisiblePosition() { + if (mListView.getChildCount() == 0) { + return 0; + } else { + return getChildAdapterPosition(mListView.getChildAt(0)); + } + } + + public int getChildAdapterPosition(View view) { + LinearLayoutManager layoutManager = (LinearLayoutManager) mListView.getLayoutManager(); + for (int i = 0; i < layoutManager.getChildCount(); i++) { + if (layoutManager.findViewByPosition(i) == view) { + return i; + } + } + + return -1; + } + + public View focusSearch(View focused, int direction) { + View nextFocus = StrictFocusFinder.getInstance().findNextFocus(mListView, focused, direction); + if (nextFocus == null && mListView.getParent() != null) { + return mListView.getParent().focusSearch(focused, direction); + } + return nextFocus; + } + + public int getChildDrawingOrder(int childCount, int i) { + View view = mListView.getFocusedChild(); + if (null != view) { + int position = getChildAdapterPosition(view) - getFirstVisiblePosition(); + if (position < 0) { + return i; + } else { + if (i == childCount - 1) { + if (position > i) { + position = i; + } + return position; + } + if (i == position) { + return childCount - 1; + } + } + } + return i; + } + + public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) { + final int parentLeft = mListView.getPaddingLeft(); + final int parentRight = mListView.getWidth() - mListView.getPaddingRight(); + + final int parentTop = mListView.getPaddingTop(); + final int parentBottom = mListView.getHeight() - mListView.getPaddingBottom(); + + final int childLeft = child.getLeft() + rect.left; + final int childTop = child.getTop() + rect.top; + + final int childRight = childLeft + rect.width(); + final int childBottom = childTop + rect.height(); + + final int offScreenLeft = Math.min(0, childLeft - parentLeft); + final int offScreenRight = Math.max(0, childRight - parentRight); + + final int offScreenTop = Math.min(0, childTop - parentTop); + final int offScreenBottom = Math.max(0, childBottom - parentBottom); + + final boolean canScrollHorizontal = mListView.getLayoutManager().canScrollHorizontally(); + final boolean canScrollVertical = mListView.getLayoutManager().canScrollVertically(); + + // Favor the "start" layout direction over the end when bringing one side or the other + // of a large rect into view. If we decide to bring in end because start is already + // visible, limit the scroll such that start won't go out of bounds. + final int dx; + if (canScrollHorizontal) { + if (/*ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL*/false) { + dx = offScreenRight != 0 ? offScreenRight + : Math.max(offScreenLeft, childRight - parentRight); + } else { + dx = offScreenLeft != 0 ? offScreenLeft + : Math.min(childLeft - parentLeft, offScreenRight); + } + } else { + dx = 0; + } + + // Favor bringing the top into view over the bottom. If top is already visible and + // we should scroll to make bottom visible, make sure top does not go out of bounds. + final int dy; + if (canScrollVertical) { + dy = offScreenTop != 0 ? offScreenTop : Math.min(childTop - parentTop, offScreenBottom); + } else { + dy = 0; + } + + if (dx != 0 || dy != 0) { + if (immediate) { + mListView.scrollBy(dx, dy); + } else { + mListView.smoothScrollBy(dx, dy); + } + mListView.postInvalidate(); + return true; + } + return false; + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/list/HippyRecyclerItemViewController.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/list/HippyRecyclerItemViewController.java new file mode 100644 index 00000000000..17f43b2aa83 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/list/HippyRecyclerItemViewController.java @@ -0,0 +1,49 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.list; + +import android.content.Context; +import android.view.View; +import com.tencent.mtt.hippy.HippyRootView; +import com.tencent.mtt.hippy.annotation.HippyController; +import com.tencent.mtt.hippy.common.HippyMap; +import com.tencent.mtt.hippy.uimanager.ControllerManager; +import com.tencent.mtt.hippy.uimanager.HippyViewController; +import com.tencent.mtt.hippy.uimanager.ListItemRenderNode; +import com.tencent.mtt.hippy.uimanager.RecyclerItemRenderNode; +import com.tencent.mtt.hippy.uimanager.RenderNode; + +/** + * Created by leonardgong on 2017/12/7 0007. + */ + +@HippyController(name = HippyRecyclerItemViewController.CLASS_NAME, isLazyLoad = true) +public class HippyRecyclerItemViewController extends HippyViewController { + + public static final String CLASS_NAME = "ListViewItem"; + + @Override + protected View createViewImpl(Context context) { + return new HippyListItemView(context); + } + + @Override + public RenderNode createRenderNode(int id, HippyMap props, String className, HippyRootView hippyRootView, + ControllerManager controllerManager, boolean lazy) { + return new RecyclerItemRenderNode(id, props, className, hippyRootView, controllerManager, lazy); + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/modal/HippyModalHostView.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/modal/HippyModalHostView.java index c56218eac25..d735ca4eed9 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/modal/HippyModalHostView.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/modal/HippyModalHostView.java @@ -15,14 +15,10 @@ */ package com.tencent.mtt.hippy.views.modal; -import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; import android.app.Activity; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; -import android.content.res.Resources; -import android.content.res.Resources.NotFoundException; import android.graphics.Canvas; import android.graphics.Color; import android.os.Build; @@ -39,11 +35,12 @@ import com.tencent.mtt.hippy.HippyEngineContext; import com.tencent.mtt.hippy.HippyInstanceContext; import com.tencent.mtt.hippy.HippyInstanceLifecycleEventListener; +import com.tencent.mtt.hippy.R; import com.tencent.mtt.hippy.utils.ContextHolder; +import com.tencent.mtt.hippy.utils.DimensionsUtil; import com.tencent.mtt.hippy.utils.LogUtils; import com.tencent.mtt.hippy.views.view.HippyViewGroup; -import java.lang.reflect.Field; import java.util.ArrayList; @SuppressWarnings({"unused"}) @@ -210,56 +207,6 @@ public Dialog getDialog() { return mDialog; } - static int mStatusBarHeight = -1; - static boolean hasCheckStatusBarHeight = false; - - public int getStatusBarHeightFixed() { - if (mStatusBarHeight == -1) { - mStatusBarHeight = getStatusBarHeightFromSystem(); - - hasCheckStatusBarHeight = true; - } - return mStatusBarHeight; - } - - private static int statusBarHeight = -1; - - @SuppressWarnings("ConstantConditions") - public static int getStatusBarHeightFromSystem() { - if (statusBarHeight > 0) { - return statusBarHeight; - } - - Class c; - Object obj; - Field field; - int x; - try { - c = Class.forName("com.android.internal.R$dimen"); - obj = c.newInstance(); - field = c.getField("status_bar_height"); - x = Integer.parseInt(field.get(obj).toString()); - statusBarHeight = ContextHolder.getAppContext().getResources().getDimensionPixelSize(x); - } catch (Exception e1) { - statusBarHeight = -1; - e1.printStackTrace(); - } - - if (statusBarHeight < 1) { - try { - int statebarH_id = ContextHolder.getAppContext().getResources() - .getIdentifier("statebar_height", "dimen", - ContextHolder.getAppContext().getPackageName()); - statusBarHeight = Math - .round(ContextHolder.getAppContext().getResources().getDimension(statebarH_id)); - } catch (NotFoundException e) { - LogUtils.d("HippyModalHostView", "getStatusBarHeightFromSystem: " + e.getMessage()); - statusBarHeight = 0; - } - } - return statusBarHeight; - } - public void setDialogBar(boolean isDarkIcon) { try { Window window = mDialog.getWindow(); @@ -327,33 +274,23 @@ protected void showOrUpdate() { } assert mDialog != null; + switch (mAniType) { + case STYLE_THEME_ANIMATED_FADE_DIALOG: + mDialog.getWindow().setWindowAnimations(R.style.modal_style_theme_animated_fade); + break; + case STYLE_THEME_ANIMATED_SLIDE_DIALOG: + mDialog.getWindow().setWindowAnimations(R.style.modal_style_theme_animated_slide); + break; + case STYLE_THEME_ANIMATED_SLIDE_FADE_DIALOG: + mDialog.getWindow().setWindowAnimations(R.style.modal_style_theme_animated_slide_fade); + break; + default: + } + mDialog.setOnShowListener(new DialogInterface.OnShowListener() { @Override public void onShow(DialogInterface dialogInterface) { mOnShowListener.onShow(dialogInterface); - ObjectAnimator alphaAnimation = null; - switch (mAniType) { - case STYLE_THEME_ANIMATED_FADE_DIALOG: - alphaAnimation = ObjectAnimator.ofFloat(mContentView, "alpha", 0.0f, 1.0f); - break; - case STYLE_THEME_ANIMATED_SLIDE_DIALOG: - alphaAnimation = ObjectAnimator.ofFloat(mContentView, "translationY", 0); - break; - case STYLE_THEME_ANIMATED_SLIDE_FADE_DIALOG: - PropertyValuesHolder fadeValuesHolder = PropertyValuesHolder - .ofFloat("alpha", 0.0f, 1.0f); - PropertyValuesHolder slideValuesHolder = PropertyValuesHolder - .ofFloat("translationY", 0); - alphaAnimation = ObjectAnimator - .ofPropertyValuesHolder(mContentView, fadeValuesHolder, slideValuesHolder); - break; - default: - } - - if (alphaAnimation != null) { - alphaAnimation.setDuration(200); - alphaAnimation.start(); - } } }); mDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @@ -385,25 +322,6 @@ public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { mDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); mDialog.show(); - - int nScreenHeight = getScreenHeight(); - switch (mAniType) { - case STYLE_THEME_ANIMATED_FADE_DIALOG: - mContentView.setAlpha(0); - break; - case STYLE_THEME_ANIMATED_SLIDE_DIALOG: - if (nScreenHeight != -1) { - mContentView.setTranslationY(nScreenHeight); - } - break; - case STYLE_THEME_ANIMATED_SLIDE_FADE_DIALOG: - mContentView.setAlpha(0); - if (nScreenHeight != -1) { - mContentView.setTranslationY(nScreenHeight); - } - break; - default: - } } private int getScreenHeight() { @@ -424,29 +342,14 @@ private int getScreenHeight() { @SuppressWarnings("SameReturnValue") protected int getThemeResId() { - return 0; + return android.R.style.Theme_Translucent_NoTitleBar; } protected Dialog createDialog(Context context) { int themeResId = getThemeResId(); - if (context != null) { - Resources res = context.getResources(); - themeResId = res.getIdentifier("HippyFullScreenDialog", "style", context.getPackageName()); - } assert context != null; - Dialog dialog = new Dialog(context, themeResId); - if (themeResId == 0) { - Window window = dialog.getWindow(); - if (window != null) { - window.requestFeature(Window.FEATURE_NO_TITLE); - window.setBackgroundDrawableResource(android.R.color.transparent); - window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams - .MATCH_PARENT); - } - } - - return dialog; + return new Dialog(context, themeResId); } protected View createContentView(View hostView) { @@ -454,11 +357,12 @@ protected View createContentView(View hostView) { @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); - if (mEnterImmersionStatusBar && mStatusBarHeight != -1 + int statusBarHeight = DimensionsUtil.getStatusBarHeight(); + if (mEnterImmersionStatusBar && statusBarHeight != -1 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { canvas.save(); - canvas.clipRect(0, 0, getMeasuredWidth(), mStatusBarHeight); + canvas.clipRect(0, 0, getMeasuredWidth(), statusBarHeight); canvas.drawColor(0x40000000); canvas.restore(); } @@ -467,7 +371,7 @@ protected void dispatchDraw(Canvas canvas) { if (mEnterImmersionStatusBar && Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); - params.topMargin = -1 * getStatusBarHeightFixed(); + params.topMargin = -1 * DimensionsUtil.getStatusBarHeight(); frameLayout.addView(hostView, params); } else { frameLayout.addView(hostView); diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/FooterUtil.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/FooterUtil.java index 37d42f361c2..4c1b847d6dc 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/FooterUtil.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/FooterUtil.java @@ -34,8 +34,8 @@ public static boolean isFooterView(View view) { public static void sendFooterReleasedEvent(HippyPullFooterView footerView) { IFooterContainer footerContainer = null; - if (footerView.getParentView() instanceof IFooterContainer) { - footerContainer = (IFooterContainer) footerView.getParentView(); + if (footerView.getRecyclerView() instanceof IFooterContainer) { + footerContainer = (IFooterContainer) footerView.getRecyclerView(); } int curState = footerContainer != null ? footerContainer.getFooterState() : HippyListView.REFRESH_STATE_IDLE; @@ -54,7 +54,7 @@ public static void sendFooterReleasedEvent(HippyPullFooterView footerView) { public static void checkFooterBinding(RecyclerViewBase list, View view) { if (view instanceof HippyPullFooterView) { - ((HippyPullFooterView) view).setParentView(list); + ((HippyPullFooterView) view).setRecyclerView(list); } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/HippyPullFooterView.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/HippyPullFooterView.java index 84d272b8097..60e4682ec51 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/HippyPullFooterView.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/HippyPullFooterView.java @@ -18,30 +18,39 @@ import android.content.Context; import android.view.View; +import androidx.annotation.NonNull; +import com.tencent.mtt.hippy.views.hippylist.HippyRecyclerView; import com.tencent.mtt.hippy.views.view.HippyViewGroup; public class HippyPullFooterView extends HippyViewGroup { - private View mParentView; - private boolean mStickEnabled = false; + private View mRecyclerView; + private boolean mStickEnabled = false; - public HippyPullFooterView(Context context) { - super(context); - } + public HippyPullFooterView(Context context) { + super(context); + } - public void setParentView(View parentView) { - mParentView = parentView; - } + public void setRecyclerView(@NonNull View recyclerView) { + mRecyclerView = recyclerView; + } - public View getParentView() { - return mParentView; - } + public View getRecyclerView() { + return mRecyclerView; + } - public void setStickEnabled(boolean enabled) { - mStickEnabled = enabled; - } + public void setStickEnabled(boolean enabled) { + mStickEnabled = enabled; + } - public boolean getStickEnabled() { - return mStickEnabled; - } + public boolean getStickEnabled() { + return mStickEnabled; + } + + void onDestroy() { + if (mRecyclerView instanceof HippyRecyclerView + && ((HippyRecyclerView) mRecyclerView).getAdapter() != null){ + ((HippyRecyclerView) mRecyclerView).getAdapter().onFooterDestroy(); + } + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/HippyPullFooterViewController.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/HippyPullFooterViewController.java index 3b585251045..7f72980bc43 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/HippyPullFooterViewController.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/HippyPullFooterViewController.java @@ -27,6 +27,7 @@ import com.tencent.mtt.hippy.uimanager.HippyViewController; import com.tencent.mtt.hippy.uimanager.PullFooterRenderNode; import com.tencent.mtt.hippy.uimanager.RenderNode; +import com.tencent.mtt.hippy.views.hippylist.HippyRecyclerView; import com.tencent.mtt.hippy.views.list.HippyListView; @SuppressWarnings({"deprecation", "unused"}) @@ -51,18 +52,22 @@ public void setStickEnabled(HippyPullFooterView view, boolean flag) { view.setStickEnabled(flag); } + @Override + public void onViewDestroy(HippyPullFooterView pullFooterView) { + pullFooterView.onDestroy(); + } + @Override public void dispatchFunction(HippyPullFooterView view, String functionName, HippyArray dataArray) { super.dispatchFunction(view, functionName, dataArray); - View parent = view.getParentView(); - switch (functionName) { - case "collapsePullFooter": { - if (parent instanceof HippyListView) { - ((HippyListView) parent).onFooterRefreshFinish(); - } else if (parent instanceof IFooterContainer) { - ((IFooterContainer) parent).onFooterRefreshFinish(); - } - break; + View recyclerView = view.getRecyclerView(); + if (functionName.equals("collapsePullFooter")) { + if (recyclerView instanceof HippyRecyclerView) { + ((HippyRecyclerView) recyclerView).getAdapter().onFooterRefreshCompleted(); + } else if (recyclerView instanceof HippyListView) { + ((HippyListView) recyclerView).onFooterRefreshFinish(); + } else if (recyclerView instanceof IFooterContainer) { + ((IFooterContainer) recyclerView).onFooterRefreshFinish(); } } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/HippyPullHeaderView.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/HippyPullHeaderView.java index 969835b7338..481cf5e4569 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/HippyPullHeaderView.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/HippyPullHeaderView.java @@ -18,22 +18,29 @@ import android.content.Context; import android.view.View; +import com.tencent.mtt.hippy.views.hippylist.HippyRecyclerView; import com.tencent.mtt.hippy.views.view.HippyViewGroup; public class HippyPullHeaderView extends HippyViewGroup { - private View mParentView; + private View mRecyclerView; - public HippyPullHeaderView(Context context) { - super(context); - } + public HippyPullHeaderView(Context context) { + super(context); + } - public void setParentView(View parentView) { - mParentView = parentView; - } + public void setRecyclerView(View recyclerView) { + mRecyclerView = recyclerView; + } - public View getParentView() { - return mParentView; - } + public View getRecyclerView() { + return mRecyclerView; + } + void onDestroy() { + if (mRecyclerView instanceof HippyRecyclerView + && ((HippyRecyclerView) mRecyclerView).getAdapter() != null) { + ((HippyRecyclerView) mRecyclerView).getAdapter().onHeaderDestroy(); + } + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/HippyPullHeaderViewController.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/HippyPullHeaderViewController.java index 73a0389e8f0..7f6aa7ce7f1 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/HippyPullHeaderViewController.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/HippyPullHeaderViewController.java @@ -28,77 +28,120 @@ import com.tencent.mtt.hippy.uimanager.PullHeaderRenderNode; import com.tencent.mtt.hippy.uimanager.RenderNode; import com.tencent.mtt.hippy.utils.LogUtils; +import com.tencent.mtt.hippy.views.hippylist.HippyRecyclerListAdapter; import com.tencent.mtt.hippy.views.hippylist.HippyRecyclerView; -import com.tencent.mtt.hippy.views.hippylist.PullHeaderEventHelper; +import com.tencent.mtt.hippy.views.hippylist.PullHeaderRefreshHelper; import com.tencent.mtt.hippy.views.list.HippyListView; @SuppressWarnings({"deprecation", "unused"}) @HippyController(name = HippyPullHeaderViewController.CLASS_NAME, isLazyLoad = true) public class HippyPullHeaderViewController extends HippyViewController { - public static final String CLASS_NAME = "PullHeaderView"; - public static final String COLLAPSE_PULL_HEADER = "collapsePullHeader"; - public static final String EXPAND_PULL_HEADER = "expandPullHeader"; + private static final String TAG = "HippyPullHeaderViewController"; + public static final String CLASS_NAME = "PullHeaderView"; + public static final String COLLAPSE_PULL_HEADER = "collapsePullHeader"; + public static final String EXPAND_PULL_HEADER = "expandPullHeader"; + public static final String COLLAPSE_PULL_HEADER_WITH_OPTIONS = "collapsePullHeaderWithOptions"; - @Override - protected View createViewImpl(Context context) { - return new HippyPullHeaderView(context); - } + @Override + protected View createViewImpl(Context context) { + return new HippyPullHeaderView(context); + } - @Override - public RenderNode createRenderNode(int id, HippyMap props, String className, - HippyRootView hippyRootView, - ControllerManager controllerManager, boolean lazy) { - return new PullHeaderRenderNode(id, props, className, hippyRootView, controllerManager, lazy); - } + @Override + public RenderNode createRenderNode(int id, HippyMap props, String className, + HippyRootView hippyRootView, + ControllerManager controllerManager, boolean isLazyLoad) { + return new PullHeaderRenderNode(id, props, className, hippyRootView, controllerManager, + isLazyLoad); + } - private void execListViewFunction(HippyListView listView, String functionName) { - switch (functionName) { - case COLLAPSE_PULL_HEADER: { - listView.onHeaderRefreshFinish(); - break; - } - case EXPAND_PULL_HEADER: { - listView.onHeaderRefresh(); - break; - } - default: { - LogUtils - .d("HippyPullHeaderViewController", "execListViewFunction: unknown function name!!"); - } + @Override + public void onViewDestroy(HippyPullHeaderView pullHeaderView) { + pullHeaderView.onDestroy(); } - } - private void execRecyclerViewFunction(HippyRecyclerView recyclerView, String functionName) { - PullHeaderEventHelper headerEventHelper = recyclerView.getAdapter() - .getHeaderEventHelper(); - if (headerEventHelper != null) { - switch (functionName) { - case COLLAPSE_PULL_HEADER: { - headerEventHelper.onHeaderRefreshFinish(); - break; - } - case EXPAND_PULL_HEADER: { - headerEventHelper.onHeaderRefresh(); - break; + private void execListViewFunction(final HippyListView listView, String functionName, HippyArray dataArray) { + switch (functionName) { + case COLLAPSE_PULL_HEADER: { + listView.onHeaderRefreshFinish(); + break; + } + case EXPAND_PULL_HEADER: { + listView.onHeaderRefresh(); + break; + } + case COLLAPSE_PULL_HEADER_WITH_OPTIONS: { + HippyMap valueMap = dataArray.getMap(0); + if (valueMap == null) { + return; + } + final int time = valueMap.getInt("time"); + if (time > 0) { + listView.postDelayed(new Runnable() { + @Override + public void run() { + listView.onHeaderRefreshFinish(); + } + }, time); + } else { + listView.onHeaderRefreshFinish(); + } + break; + } + default: { + LogUtils.w(TAG, "Unknown function name: " + functionName); + } } - default: { - LogUtils.d("HippyPullHeaderViewController", - "execRecyclerViewFunction: unknown function name!!"); + } + + private void execRecyclerViewFunction(HippyRecyclerView recyclerView, String functionName, HippyArray dataArray) { + switch (functionName) { + case COLLAPSE_PULL_HEADER: { + recyclerView.getAdapter().onHeaderRefreshCompleted(); + break; + } + case EXPAND_PULL_HEADER: { + recyclerView.getAdapter().enableHeaderRefresh(); + break; + } + case COLLAPSE_PULL_HEADER_WITH_OPTIONS: { + HippyMap valueMap = dataArray.getMap(0); + if (valueMap == null) { + return; + } + final int time = valueMap.getInt("time"); + final HippyRecyclerListAdapter adapter = recyclerView.getAdapter(); + if (adapter == null) { + return; + } + if (time > 0) { + recyclerView.postDelayed(new Runnable() { + @Override + public void run() { + adapter.onHeaderRefreshCompleted(); + } + }, time); + } else { + adapter.onHeaderRefreshCompleted(); + } + break; + } + default: { + LogUtils.w(TAG, "Unknown function name: " + functionName); + } } - } } - } - @Override - public void dispatchFunction(HippyPullHeaderView view, String functionName, - HippyArray dataArray) { - super.dispatchFunction(view, functionName, dataArray); - View parent = view.getParentView(); - if (parent instanceof HippyListView) { - execListViewFunction((HippyListView)parent, functionName); - } else if (parent instanceof HippyRecyclerView) { - execRecyclerViewFunction((HippyRecyclerView)parent, functionName); + @Override + public void dispatchFunction(HippyPullHeaderView view, String functionName, + HippyArray dataArray) { + super.dispatchFunction(view, functionName, dataArray); + View parent = view.getRecyclerView(); + if (parent instanceof HippyListView) { + execListViewFunction((HippyListView) parent, functionName, dataArray); + } else if (parent instanceof HippyRecyclerView) { + execRecyclerViewFunction((HippyRecyclerView) parent, functionName, dataArray); + } } - } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/RefreshWrapper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/RefreshWrapper.java index ed4d30a36ba..efc12e3201f 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/RefreshWrapper.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/RefreshWrapper.java @@ -25,6 +25,7 @@ import com.tencent.mtt.hippy.common.HippyMap; import com.tencent.mtt.hippy.uimanager.HippyViewEvent; import com.tencent.mtt.hippy.utils.PixelUtil; +import com.tencent.mtt.hippy.views.hippylist.HippyRecyclerViewWrapper; import com.tencent.mtt.hippy.views.view.HippyViewGroup; import com.tencent.mtt.supportui.views.recyclerview.RecyclerViewBase; @@ -104,6 +105,9 @@ float getCompactScrollY() { if (mContentView instanceof RecyclerViewBase) { return ((RecyclerViewBase) mContentView).getOffsetY(); } + if (mContentView instanceof HippyRecyclerViewWrapper) { + return ((HippyRecyclerViewWrapper) mContentView).computeVerticalScrollOffset(); + } return mContentView.getScrollY(); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyHorizontalScrollView.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyHorizontalScrollView.java index 56c612ea828..c347fa91b4b 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyHorizontalScrollView.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyHorizontalScrollView.java @@ -17,23 +17,30 @@ import android.animation.ValueAnimator; import android.content.Context; -import android.os.Build; -import android.util.Log; +import android.os.SystemClock; import android.view.MotionEvent; import android.view.View; import android.widget.HorizontalScrollView; +import androidx.annotation.NonNull; +import androidx.core.view.NestedScrollingChildHelper; +import androidx.core.view.NestedScrollingParent2; +import androidx.core.view.ViewCompat; +import com.tencent.mtt.hippy.HippyEngineContext; +import com.tencent.mtt.hippy.HippyInstanceContext; import com.tencent.mtt.hippy.common.HippyMap; import com.tencent.mtt.hippy.uimanager.HippyViewBase; import com.tencent.mtt.hippy.uimanager.NativeGestureDispatcher; import com.tencent.mtt.hippy.utils.I18nUtil; import com.tencent.mtt.hippy.utils.LogUtils; import com.tencent.mtt.hippy.utils.PixelUtil; +import com.tencent.mtt.hippy.views.common.HippyNestedScrollComponent.HippyNestedScrollTarget; +import com.tencent.mtt.hippy.views.common.HippyNestedScrollHelper; import com.tencent.mtt.supportui.views.ScrollChecker; import java.util.HashMap; @SuppressWarnings("deprecation") public class HippyHorizontalScrollView extends HorizontalScrollView implements HippyViewBase, - HippyScrollView, ScrollChecker.IScrollCheck { + HippyScrollView, ScrollChecker.IScrollCheck, HippyNestedScrollTarget, NestedScrollingParent2 { private NativeGestureDispatcher mGestureDispatcher; @@ -59,21 +66,38 @@ public class HippyHorizontalScrollView extends HorizontalScrollView implements H private boolean mPagingEnabled = false; - protected int mScrollEventThrottle = 400; // 400ms最多回调一次 + protected int mScrollEventThrottle = 10; private long mLastScrollEventTimeStamp = -1; - + private boolean mHasUnsentScrollEvent; + private int mScrollRange = 0; protected int mScrollMinOffset = 0; private int startScrollX = 0; private int mLastX = 0; private int initialContentOffset = 0; private boolean hasCompleteFirstBatch = false; + private final boolean isTvPlatform; + private HippyHorizontalScrollViewFocusHelper mFocusHelper = null; private HashMap scrollOffsetForReuse = new HashMap<>(); + private final Priority[] mNestedScrollPriority = { Priority.SELF, Priority.NOT_SET, Priority.NOT_SET, Priority.NOT_SET, Priority.NOT_SET }; + private final int[] mScrollConsumedPair = new int[2]; + private final int[] mScrollOffsetPair = new int[2]; + private final NestedScrollingChildHelper mChildHelper = new NestedScrollingChildHelper(this); + private int mNestedXOffset; + private int mNestedScrollAxesNonTouch; public HippyHorizontalScrollView(Context context) { super(context); mHippyOnScrollHelper = new HippyOnScrollHelper(); setHorizontalScrollBarEnabled(false); + setNestedScrollingEnabled(true); + + HippyEngineContext engineContext = ((HippyInstanceContext) context).getEngineContext(); + isTvPlatform = engineContext.isRunningOnTVPlatform(); + if (isTvPlatform) { + mFocusHelper = new HippyHorizontalScrollViewFocusHelper(this); + setFocusableInTouchMode(true); + } if (I18nUtil.isRTL()) { setRotationY(180f); @@ -133,35 +157,74 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { scrollTo(getScrollX(), getScrollY()); } + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (hasNestedScrollingParent() && mNestedXOffset != 0) { + // After the nested scroll occurs, the current View position has changed. The coordinate + // origin of ev.getX() and mLastTouchDownX is different, and the ev offset needs to be + // corrected. + MotionEvent transformEv = MotionEvent.obtain(ev); + transformEv.offsetLocation(mNestedXOffset, 0); + boolean result = super.dispatchTouchEvent(transformEv); + transformEv.recycle(); + return result; + } + return super.dispatchTouchEvent(ev); + } + @Override public boolean onTouchEvent(MotionEvent event) { + if (!mScrollEnabled) { + return false; + } int action = event.getAction() & MotionEvent.ACTION_MASK; - if (action == MotionEvent.ACTION_DOWN && !mDragging) { - mDragging = true; - if (mScrollBeginDragEventEnable) { - LogUtils.d("HippyHorizontalScrollView", "emitScrollBeginDragEvent"); - HippyScrollViewEventHelper.emitScrollBeginDragEvent(this); - } - } else if (action == MotionEvent.ACTION_UP && mDragging) { - if (mScrollEndDragEventEnable) { - LogUtils.d("HippyHorizontalScrollView", "emitScrollEndDragEvent"); - HippyScrollViewEventHelper.emitScrollEndDragEvent(this); - } + boolean result; + switch (action) { + case MotionEvent.ACTION_DOWN: + if (!mDragging) { + mDragging = true; + if (mScrollBeginDragEventEnable) { + LogUtils.d("HippyHorizontalScrollView", "emitScrollBeginDragEvent"); + HippyScrollViewEventHelper.emitScrollBeginDragEvent(this); + } + } + result = super.onTouchEvent(event); + if (result) { + mNestedXOffset = 0; + startNestedScroll(SCROLL_AXIS_HORIZONTAL); + } + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + if (mDragging) { + if (mHasUnsentScrollEvent) { + sendOnScrollEvent(); + } + if (mScrollEndDragEventEnable) { + LogUtils.d("HippyHorizontalScrollView", "emitScrollEndDragEvent"); + HippyScrollViewEventHelper.emitScrollEndDragEvent(this); + } - if(mPagingEnabled) { - post(new Runnable() { - @Override - public void run() { - doPageScroll(); - } - } - ); - } + if(mPagingEnabled) { + post(new Runnable() { + @Override + public void run() { + doPageScroll(); + } + } + ); + } - mDragging = false; + mDragging = false; + } + result = super.onTouchEvent(event); + stopNestedScroll(); + break; + default: + result = super.onTouchEvent(event); + break; } - boolean result = mScrollEnabled && super.onTouchEvent(event); if (mGestureDispatcher != null) { result |= mGestureDispatcher.handleTouchEvent(event); } @@ -170,16 +233,33 @@ public void run() { @Override public boolean onInterceptTouchEvent(MotionEvent event) { - if (!mScrollEnabled) { + // noinspection WrongConstant + if (!mScrollEnabled || getNestedScrollAxes() != SCROLL_AXIS_NONE) { return false; } int action = event.getAction() & MotionEvent.ACTION_MASK; - if (action == MotionEvent.ACTION_DOWN) { - startScrollX = getScrollX(); + boolean result; + try { + result = super.onInterceptTouchEvent(event); + } catch (Exception e) { + result = false; + } + switch (action) { + case MotionEvent.ACTION_DOWN: + startScrollX = getScrollX(); + mNestedXOffset = 0; + startNestedScroll(SCROLL_AXIS_HORIZONTAL); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + stopNestedScroll(); + break; + default: + break; } - if (super.onInterceptTouchEvent(event)) { + if (result && !mDragging) { if (mScrollBeginDragEventEnable) { LogUtils.d("HippyHorizontalScrollView", "emitScrollBeginDragEvent"); HippyScrollViewEventHelper.emitScrollBeginDragEvent(this); @@ -190,6 +270,42 @@ public boolean onInterceptTouchEvent(MotionEvent event) { return false; } + @Override + protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, + int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) { + if (!isTouchEvent || !hasNestedScrollingParent() + || HippyNestedScrollHelper.priorityOfX(this, deltaX) == Priority.NONE) { + // without nested scrolling + return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, + maxOverScrollX, maxOverScrollY, isTouchEvent); + } + int consumed = 0; + int unConsumed = deltaX; + mScrollConsumedPair[0] = 0; + mScrollConsumedPair[1] = 0; + if (dispatchNestedPreScroll(unConsumed, 0, mScrollConsumedPair, mScrollOffsetPair)) { + consumed = mScrollConsumedPair[0]; + unConsumed -= mScrollConsumedPair[0]; + mNestedXOffset += mScrollOffsetPair[0]; + if (unConsumed == 0) { + return false; + } + } + int myDx = unConsumed < 0 ? Math.max(unConsumed, -scrollX) + : Math.min(unConsumed, scrollRangeX - scrollX - 1); + if (myDx != 0) { + super.overScrollBy(myDx, 0, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, + maxOverScrollY, true); + consumed += myDx; + unConsumed -= myDx; + } + if (unConsumed != 0) { + dispatchNestedScroll(consumed, 0, unConsumed, 0, mScrollOffsetPair); + mNestedXOffset += mScrollOffsetPair[0]; + } + return false; + } + @Override public NativeGestureDispatcher getGestureDispatcher() { return mGestureDispatcher; @@ -210,35 +326,36 @@ protected void onScrollChanged(int x, int y, int oldX, int oldY) { if (mHippyOnScrollHelper.onScrollChanged(x, y)) { if (mScrollEventEnable) { - long currTime = System.currentTimeMillis(); + long currTime; int offsetX = Math.abs(x - mLastX); if (mScrollMinOffset > 0 && offsetX >= mScrollMinOffset) { mLastX = x; - } else if ((mScrollMinOffset == 0) && (currTime - mLastScrollEventTimeStamp - >= mScrollEventThrottle)) { + sendOnScrollEvent(); + } else if ((mScrollMinOffset == 0) && ((currTime = SystemClock.elapsedRealtime()) + - mLastScrollEventTimeStamp >= mScrollEventThrottle)) { mLastScrollEventTimeStamp = currTime; + sendOnScrollEvent(); } else { - return; + mHasUnsentScrollEvent = true; } - - HippyScrollViewEventHelper.emitScrollEvent(this); } mDoneFlinging = false; } - } - protected void doPageScroll() { - if (mMomentumScrollBeginEventEnable) { - HippyScrollViewEventHelper.emitScrollMomentumBeginEvent(this); - } - - smoothScrollToPage(); + private void sendOnScrollEvent() { + mHasUnsentScrollEvent = false; + HippyScrollViewEventHelper.emitScrollEvent(this); + } + private void scheduleScrollEnd() { Runnable runnable = new Runnable() { @Override public void run() { if (mDoneFlinging) { + if (mHasUnsentScrollEvent) { + sendOnScrollEvent(); + } if (mMomentumScrollEndEventEnable) { HippyScrollViewEventHelper.emitScrollMomentumEndEvent(HippyHorizontalScrollView.this); } @@ -248,10 +365,19 @@ public void run() { } } }; - postOnAnimationDelayed(runnable, HippyScrollViewEventHelper.MOMENTUM_DELAY); } + protected void doPageScroll() { + if (mMomentumScrollBeginEventEnable) { + HippyScrollViewEventHelper.emitScrollMomentumBeginEvent(this); + } + + smoothScrollToPage(); + + scheduleScrollEnd(); + } + @Override public void fling(int velocityX) { if (!mFlingEnabled || mPagingEnabled) { @@ -263,29 +389,16 @@ public void fling(int velocityX) { if (mMomentumScrollBeginEventEnable) { HippyScrollViewEventHelper.emitScrollMomentumBeginEvent(this); } - Runnable runnable = new Runnable() { - @Override - public void run() { - if (mDoneFlinging) { - if (mMomentumScrollEndEventEnable) { - HippyScrollViewEventHelper.emitScrollMomentumEndEvent(HippyHorizontalScrollView.this); - } - } else { - mDoneFlinging = true; - postOnAnimationDelayed(this, HippyScrollViewEventHelper.MOMENTUM_DELAY); - } - } - }; - - postOnAnimationDelayed(runnable, HippyScrollViewEventHelper.MOMENTUM_DELAY); + scheduleScrollEnd(); } private void smoothScrollToPage() { int width = getWidth(); - if (width <= 0) { + View view = getChildAt(0); + if (width <= 0 || view == null) { return; } - int maxPage = getChildAt(0).getWidth()/width; + int maxPage = view.getWidth()/width; int page = startScrollX / width; int offset = getScrollX() - startScrollX; if (offset == 0) { @@ -371,7 +484,15 @@ public void setInitialContentOffset(int offset) { @Override public void scrollToInitContentOffset() { + int scrollRange = mScrollRange; + View firstChild = getChildAt(0); + if (firstChild != null) { + mScrollRange = firstChild.getWidth(); + } if (hasCompleteFirstBatch) { + if (mScrollRange < scrollRange) { + scrollTo(getScrollX(), getScrollY()); + } return; } @@ -381,4 +502,162 @@ public void scrollToInitContentOffset() { hasCompleteFirstBatch = true; } + + @Override + public View focusSearch(View focused, int direction) { + if (isTvPlatform) { + return mFocusHelper.focusSearch(focused, direction); + } + return super.focusSearch(focused, direction); + } + + @Override + public void requestChildFocus(View child, View focused) { + if (isTvPlatform) { + mFocusHelper.scrollToFocusChild(focused); + } + super.requestChildFocus(child, focused); + } + + @Override + public void setNestedScrollPriority(int direction, Priority priority) { + mNestedScrollPriority[direction] = priority; + } + + @Override + public Priority getNestedScrollPriority(int direction) { + Priority result = mNestedScrollPriority[direction]; + if (result == Priority.NOT_SET) { + result = mNestedScrollPriority[DIRECTION_ALL]; + } + return result; + } + + @Override + public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes) { + return onStartNestedScroll(child, target, axes, ViewCompat.TYPE_TOUCH); + } + + @Override + public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes, int type) { + if (!mScrollEnabled) { + return false; + } + return (axes & SCROLL_AXIS_HORIZONTAL) != 0; + } + + @Override + public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes) { + super.onNestedScrollAccepted(child, target, axes); + startNestedScroll(SCROLL_AXIS_HORIZONTAL); + } + + @Override + public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes, int type) { + if (type == ViewCompat.TYPE_TOUCH) { + super.onNestedScrollAccepted(child, target, axes); + startNestedScroll(SCROLL_AXIS_HORIZONTAL); + } else { + mNestedScrollAxesNonTouch = axes; + mChildHelper.startNestedScroll(ViewCompat.SCROLL_AXIS_HORIZONTAL, type); + } + } + + @Override + public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { + onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, ViewCompat.TYPE_TOUCH); + } + + @Override + public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) { + if (mPagingEnabled && type == ViewCompat.TYPE_NON_TOUCH) { + return; + } + // Process the current View first + if (HippyNestedScrollHelper.priorityOfX(target, dxUnconsumed) == Priority.SELF) { + final int oldScrollX = getScrollX(); + scrollBy(dxUnconsumed, 0); + final int myConsumed = getScrollX() - oldScrollX; + dxConsumed += myConsumed; + dxUnconsumed -= myConsumed; + } + // Then dispatch to the parent for processing + int parentDx = HippyNestedScrollHelper.priorityOfX(this, dxUnconsumed) == Priority.NONE ? 0 : dxUnconsumed; + int parentDy = HippyNestedScrollHelper.priorityOfY(this, dyUnconsumed) == Priority.NONE ? 0 : dyUnconsumed; + if (parentDx != 0 || parentDy != 0) { + if (type == ViewCompat.TYPE_TOUCH) { + dispatchNestedScroll(dxConsumed, dyConsumed, parentDx, parentDy, null); + } else { + mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, parentDx, parentDy, null, type); + } + } + } + + @Override + public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed) { + onNestedPreScroll(target, dx, dy, consumed, ViewCompat.TYPE_TOUCH); + } + + @Override + public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) { + if (mPagingEnabled && type == ViewCompat.TYPE_NON_TOUCH) { + if (HippyNestedScrollHelper.priorityOfY(target, dy) == Priority.PARENT) { + consumed[0] += dx; + } + return; + } + // Dispatch to the parent for processing first + int parentDx = HippyNestedScrollHelper.priorityOfX(this, dx) == Priority.NONE ? 0 : dx; + int parentDy = HippyNestedScrollHelper.priorityOfY(this, dy) == Priority.NONE ? 0 : dy; + if (parentDx != 0 || parentDy != 0) { + // Temporarily store `consumed` to reuse the Array + int consumedX = consumed[0]; + int consumedY = consumed[1]; + consumed[0] = 0; + consumed[1] = 0; + if (type == ViewCompat.TYPE_TOUCH) { + dispatchNestedPreScroll(parentDx, parentDy, consumed, null); + } else { + mChildHelper.dispatchNestedPreScroll(parentDx, parentDy, consumed, null, type); + } + dx -= consumed[0]; + consumed[0] += consumedX; + consumed[1] += consumedY; + } + // Then process the current View + if (HippyNestedScrollHelper.priorityOfX(target, dx) == Priority.PARENT) { + final int oldScrollX = getScrollX(); + scrollBy(dx, 0); + final int myConsumed = getScrollX() - oldScrollX; + consumed[0] += myConsumed; + } + } + + @Override + public void onStopNestedScroll(View child) { + super.onStopNestedScroll(child); + if(mPagingEnabled) { + post(new Runnable() { + @Override + public void run() { + doPageScroll(); + } + }); + } + } + + @Override + public void onStopNestedScroll(@NonNull View target, int type) { + if (type == ViewCompat.TYPE_TOUCH) { + onStopNestedScroll(target); + } else { + mChildHelper.stopNestedScroll(type); + mNestedScrollAxesNonTouch = SCROLL_AXIS_NONE; + } + } + + @Override + public int getNestedScrollAxes() { + return super.getNestedScrollAxes() | mNestedScrollAxesNonTouch; + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyHorizontalScrollViewFocusHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyHorizontalScrollViewFocusHelper.java new file mode 100644 index 00000000000..38b42ae4167 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyHorizontalScrollViewFocusHelper.java @@ -0,0 +1,85 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.scroll; + +import android.graphics.Rect; +import android.view.View; +import com.tencent.mtt.hippy.utils.StrictFocusFinder; + +public class HippyHorizontalScrollViewFocusHelper { + + private HippyHorizontalScrollView mScrollView; + private Rect mFocusRect = new Rect(); + + public HippyHorizontalScrollViewFocusHelper(HippyHorizontalScrollView scrollView) { + this.mScrollView = scrollView; + } + + public View focusSearch(View focused, int direction) { + View nextFocus = StrictFocusFinder.getInstance().findNextFocus(mScrollView, focused, direction); + if (nextFocus == null && mScrollView.getParent() != null) { + return mScrollView.getParent().focusSearch(focused, direction); + } + return nextFocus; + } + + public void scrollToFocusChild(View child) { + child.getDrawingRect(mFocusRect); + mScrollView.offsetDescendantRectToMyCoords(child, mFocusRect); + int scrollX = computeScrollDelta(mFocusRect); + if (scrollX != 0) { + mScrollView.scrollBy(scrollX, 0); + } + } + + protected int computeScrollDelta(Rect rect) { + if (mScrollView.getChildCount() == 0) { + return 0; + } + int width = mScrollView.getWidth(); + int screenLeft = mScrollView.getScrollX(); + int screenRight = screenLeft + width; + int fadingEdge = mScrollView.getHorizontalFadingEdgeLength(); + if (rect.left > 0) { + screenLeft += fadingEdge; + } + if (rect.right < mScrollView.getChildAt(0).getWidth()) { + screenRight -= fadingEdge; + } + + int scrollXDelta = 0; + if (rect.right > screenRight && rect.left > screenLeft) { + if (rect.width() > width) { + scrollXDelta += (rect.left - screenLeft); + } else { + scrollXDelta += (rect.right - screenRight); + } + int right = mScrollView.getChildAt(0).getRight(); + int distanceToRight = right - screenRight; + scrollXDelta = Math.min(scrollXDelta, distanceToRight); + + } else if (rect.left < screenLeft && rect.right < screenRight) { + if (rect.width() > width) { + scrollXDelta -= (screenRight - rect.right); + } else { + scrollXDelta -= (screenLeft - rect.left); + } + scrollXDelta = Math.max(scrollXDelta, -mScrollView.getScrollX()); + } + return scrollXDelta; + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyOnScrollHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyOnScrollHelper.java index 2812437943d..17d0c296c62 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyOnScrollHelper.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyOnScrollHelper.java @@ -15,25 +15,15 @@ */ package com.tencent.mtt.hippy.views.scroll; -import android.os.SystemClock; - public class HippyOnScrollHelper { - private static final int MIN_EVENT_SEPARATION_MS = 10; - private int mPrevX = Integer.MIN_VALUE; private int mPrevY = Integer.MIN_VALUE; - private long mLastScrollEventTimeMs = -(MIN_EVENT_SEPARATION_MS + 1); public boolean onScrollChanged(int x, int y) { - long eventTime = SystemClock.uptimeMillis(); - boolean shouldDispatch = - eventTime - mLastScrollEventTimeMs > MIN_EVENT_SEPARATION_MS || mPrevX != x || mPrevY != y; - - mLastScrollEventTimeMs = eventTime; + boolean shouldDispatch = mPrevX != x || mPrevY != y; mPrevX = x; mPrevY = y; - return shouldDispatch; } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyScrollViewController.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyScrollViewController.java index dd78d000c47..51a0076c050 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyScrollViewController.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyScrollViewController.java @@ -1,15 +1,41 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.tencent.mtt.hippy.views.scroll; import android.content.Context; import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; +import com.tencent.mtt.hippy.HippyRootView; import com.tencent.mtt.hippy.annotation.HippyController; import com.tencent.mtt.hippy.annotation.HippyControllerProps; import com.tencent.mtt.hippy.common.HippyArray; import com.tencent.mtt.hippy.common.HippyMap; +import com.tencent.mtt.hippy.dom.node.NodeProps; +import com.tencent.mtt.hippy.uimanager.ControllerManager; import com.tencent.mtt.hippy.uimanager.HippyGroupController; +import com.tencent.mtt.hippy.uimanager.ListItemRenderNode; +import com.tencent.mtt.hippy.uimanager.RenderNode; +import com.tencent.mtt.hippy.uimanager.ScrollViewRenderNode; import com.tencent.mtt.hippy.utils.PixelUtil; +import com.tencent.mtt.hippy.views.list.HippyListView; +import com.tencent.mtt.hippy.views.view.HippyViewGroup; +import com.tencent.mtt.hippy.views.viewpager.HippyViewPager; +import com.tencent.mtt.supportui.views.recyclerview.BaseLayoutManager; @SuppressWarnings({"deprecation", "unused", "rawtypes"}) @HippyController(name = HippyScrollViewController.CLASS_NAME) @@ -21,24 +47,34 @@ public class HippyScrollViewController ex public static final String CLASS_NAME = "ScrollView"; + @Override + public RenderNode createRenderNode(int id, HippyMap props, String className, + HippyRootView hippyRootView, ControllerManager controllerManager, boolean lazy) { + return new ScrollViewRenderNode(id, props, className, hippyRootView, controllerManager, lazy); + } @Override protected View createViewImpl(Context context, HippyMap iniProps) { + boolean enableScrollEvent = false; + boolean isHorizontal = false; + if (iniProps != null) { + isHorizontal = iniProps.getBoolean("horizontal"); + enableScrollEvent = iniProps.getBoolean("onScroll"); + } - if (iniProps != null && iniProps.containsKey("horizontal") && iniProps - .getBoolean("horizontal")) { - return new HippyHorizontalScrollView(context); + View scrollView; + if (isHorizontal) { + scrollView = new HippyHorizontalScrollView(context); } else { - return new HippyVerticalScrollView(context); + scrollView = new HippyVerticalScrollView(context); } - - // return super.createViewImpl(context, iniProps); + ((HippyScrollView)scrollView).setScrollEventEnable(enableScrollEvent); + return scrollView; } @Override protected View createViewImpl(Context context) { return null; - // return new HippyScrollView(context); } @HippyControllerProps(name = "scrollEnabled", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = true) @@ -51,11 +87,6 @@ public void setShowScrollIndicator(HippyScrollView view, boolean flag) { view.showScrollIndicator(flag); } - @HippyControllerProps(name = "onScrollEnable", defaultType = HippyControllerProps.BOOLEAN) - public void setScrollEventEnable(HippyScrollView view, boolean flag) { - view.setScrollEventEnable(flag); - } - @HippyControllerProps(name = "onScrollBeginDrag", defaultType = HippyControllerProps.BOOLEAN) public void setScrollBeginDragEventEnable(HippyScrollView view, boolean flag) { view.setScrollBeginDragEventEnable(flag); @@ -106,6 +137,13 @@ public void setInitialContentOffset(HippyScrollView view, int offset) { view.setInitialContentOffset((int)PixelUtil.dp2px(offset)); } + @HippyControllerProps(name = NodeProps.OVERFLOW, defaultType = HippyControllerProps.STRING, defaultString = "visible") + public void setOverflow(HippyScrollView scrollView, String overflow) { + if (scrollView instanceof ViewGroup) { + HippyViewGroup.setOverflow(overflow, (ViewGroup) scrollView); + } + } + @Override public void onBatchComplete(View view) { super.onBatchComplete(view); diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyScrollViewEventHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyScrollViewEventHelper.java index 4209586d1b3..2d1032ab8c5 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyScrollViewEventHelper.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyScrollViewEventHelper.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.tencent.mtt.hippy.views.scroll; import android.view.ViewGroup; @@ -25,68 +26,71 @@ @SuppressWarnings({"deprecation", "unused"}) public class HippyScrollViewEventHelper { - public static final long MOMENTUM_DELAY = 20; - public static final String EVENT_TYPE_BEGIN_DRAG = "onScrollBeginDrag"; - public static final String EVENT_TYPE_END_DRAG = "onScrollEndDrag"; - public static final String EVENT_TYPE_SCROLL = "onScroll"; - public static final String EVENT_TYPE_MOMENTUM_BEGIN = "onMomentumScrollBegin"; - public static final String EVENT_TYPE_MOMENTUM_END = "onMomentumScrollEnd"; - public static final String EVENT_TYPE_ANIMATION_END = "onScrollAnimationEnd"; - - public static void emitScrollEvent(ViewGroup view) { - emitScrollEvent(view, EVENT_TYPE_SCROLL); - } - - public static void emitScrollBeginDragEvent(ViewGroup view) { - emitScrollEvent(view, EVENT_TYPE_BEGIN_DRAG); - } - - public static void emitScrollEndDragEvent(ViewGroup view) { - emitScrollEvent(view, EVENT_TYPE_END_DRAG); - } - - public static void emitScrollMomentumBeginEvent(ViewGroup view) { - emitScrollEvent(view, EVENT_TYPE_MOMENTUM_BEGIN); - } - - public static void emitScrollMomentumEndEvent(ViewGroup view) { - emitScrollEvent(view, EVENT_TYPE_MOMENTUM_END); - } - - protected static void emitScrollEvent(ViewGroup view, String scrollEventType) { - if (view == null) { - return; + + public static final long MOMENTUM_DELAY = 20; + public static final String EVENT_TYPE_BEGIN_DRAG = "onScrollBeginDrag"; + public static final String EVENT_TYPE_END_DRAG = "onScrollEndDrag"; + public static final String EVENT_TYPE_SCROLL = "onScroll"; + public static final String EVENT_TYPE_MOMENTUM_BEGIN = "onMomentumScrollBegin"; + public static final String EVENT_TYPE_MOMENTUM_END = "onMomentumScrollEnd"; + public static final String EVENT_TYPE_ANIMATION_END = "onScrollAnimationEnd"; + public static final String EVENT_TYPE_REFRESH = "onRefresh"; + public static final String EVENT_ON_END_REACHED = "onEndReached"; + + public static void emitScrollEvent(ViewGroup view) { + emitScrollEvent(view, EVENT_TYPE_SCROLL); + } + + public static void emitScrollBeginDragEvent(ViewGroup view) { + emitScrollEvent(view, EVENT_TYPE_BEGIN_DRAG); + } + + public static void emitScrollEndDragEvent(ViewGroup view) { + emitScrollEvent(view, EVENT_TYPE_END_DRAG); + } + + public static void emitScrollMomentumBeginEvent(ViewGroup view) { + emitScrollEvent(view, EVENT_TYPE_MOMENTUM_BEGIN); } - HippyMap contentInset = new HippyMap(); - contentInset.pushDouble("top", 0); - contentInset.pushDouble("bottom", 0); - contentInset.pushDouble("left", 0); - contentInset.pushDouble("right", 0); - - HippyMap contentOffset = new HippyMap(); - contentOffset.pushDouble("x", PixelUtil.px2dp(view.getScrollX())); - contentOffset.pushDouble("y", PixelUtil.px2dp(view.getScrollY())); - - HippyMap contentSize = new HippyMap(); - contentSize.pushDouble("width", PixelUtil - .px2dp(view.getChildCount() > 0 ? view.getChildAt(0).getWidth() : view.getWidth())); - contentSize.pushDouble("height", PixelUtil - .px2dp(view.getChildCount() > 0 ? view.getChildAt(0).getHeight() : view.getHeight())); - - HippyMap layoutMeasurement = new HippyMap(); - layoutMeasurement.pushDouble("width", PixelUtil.px2dp(view.getWidth())); - layoutMeasurement.pushDouble("height", PixelUtil.px2dp(view.getHeight())); - - HippyMap event = new HippyMap(); - event.pushMap("contentInset", contentInset); - event.pushMap("contentOffset", contentOffset); - event.pushMap("contentSize", contentSize); - event.pushMap("layoutMeasurement", layoutMeasurement); - - if (view.getContext() instanceof HippyInstanceContext) { - HippyEngineContext context = ((HippyInstanceContext) view.getContext()).getEngineContext(); - context.getModuleManager().getJavaScriptModule(EventDispatcher.class) - .receiveUIComponentEvent(view.getId(), scrollEventType, event); + + public static void emitScrollMomentumEndEvent(ViewGroup view) { + emitScrollEvent(view, EVENT_TYPE_MOMENTUM_END); + } + + protected static void emitScrollEvent(ViewGroup view, String scrollEventType) { + if (view == null) { + return; + } + HippyMap contentInset = new HippyMap(); + contentInset.pushDouble("top", 0); + contentInset.pushDouble("bottom", 0); + contentInset.pushDouble("left", 0); + contentInset.pushDouble("right", 0); + + HippyMap contentOffset = new HippyMap(); + contentOffset.pushDouble("x", PixelUtil.px2dp(view.getScrollX())); + contentOffset.pushDouble("y", PixelUtil.px2dp(view.getScrollY())); + + HippyMap contentSize = new HippyMap(); + contentSize.pushDouble("width", PixelUtil + .px2dp(view.getChildCount() > 0 ? view.getChildAt(0).getWidth() : view.getWidth())); + contentSize.pushDouble("height", PixelUtil + .px2dp(view.getChildCount() > 0 ? view.getChildAt(0).getHeight() : view.getHeight())); + + HippyMap layoutMeasurement = new HippyMap(); + layoutMeasurement.pushDouble("width", PixelUtil.px2dp(view.getWidth())); + layoutMeasurement.pushDouble("height", PixelUtil.px2dp(view.getHeight())); + + HippyMap event = new HippyMap(); + event.pushMap("contentInset", contentInset); + event.pushMap("contentOffset", contentOffset); + event.pushMap("contentSize", contentSize); + event.pushMap("layoutMeasurement", layoutMeasurement); + + if (view.getContext() instanceof HippyInstanceContext) { + HippyEngineContext context = ((HippyInstanceContext) view.getContext()).getEngineContext(); + context.getModuleManager().getJavaScriptModule(EventDispatcher.class) + .receiveUIComponentEvent(view.getId(), scrollEventType, event); + } } - } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyVerticalScrollView.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyVerticalScrollView.java index 54fa1108178..183af17620e 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyVerticalScrollView.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyVerticalScrollView.java @@ -17,16 +17,26 @@ import android.animation.ValueAnimator; import android.content.Context; -import android.os.Build; +import android.graphics.Rect; +import android.os.SystemClock; import android.view.MotionEvent; -import android.widget.ScrollView; +import android.view.View; +import androidx.annotation.NonNull; +import androidx.core.view.ViewCompat; +import androidx.core.widget.NestedScrollView; +import com.tencent.mtt.hippy.HippyEngineContext; +import com.tencent.mtt.hippy.HippyInstanceContext; import com.tencent.mtt.hippy.common.HippyMap; import com.tencent.mtt.hippy.uimanager.HippyViewBase; import com.tencent.mtt.hippy.uimanager.NativeGestureDispatcher; import com.tencent.mtt.hippy.utils.PixelUtil; +import com.tencent.mtt.hippy.views.common.HippyNestedScrollComponent.HippyNestedScrollTarget2; +import com.tencent.mtt.hippy.views.common.HippyNestedScrollHelper; +import java.util.ArrayList; @SuppressWarnings("deprecation") -public class HippyVerticalScrollView extends ScrollView implements HippyViewBase, HippyScrollView { +public class HippyVerticalScrollView extends NestedScrollView implements HippyViewBase, + HippyScrollView, HippyNestedScrollTarget2 { private NativeGestureDispatcher mGestureDispatcher; @@ -52,19 +62,34 @@ public class HippyVerticalScrollView extends ScrollView implements HippyViewBase private boolean mPagingEnabled = false; - protected int mScrollEventThrottle = 400; // 400ms最多回调一次 + protected int mScrollEventThrottle = 10; private long mLastScrollEventTimeStamp = -1; + private boolean mHasUnsentScrollEvent; protected int mScrollMinOffset = 0; + private int mScrollRange = 0; private int startScrollY = 0; private int mLastY = 0; private int initialContentOffset = 0; private boolean hasCompleteFirstBatch = false; + private HippyVerticalScrollViewFocusHelper mFocusHelper = null; + private final boolean isTvPlatform; + private final Priority[] mNestedScrollPriority = {Priority.SELF, Priority.NOT_SET, + Priority.NOT_SET, Priority.NOT_SET, Priority.NOT_SET}; + private final Runnable mDoPageScrollRunnable = this::doPageScroll; + private final Runnable mComputeScrollRunnable = HippyVerticalScrollView.super::computeScroll; public HippyVerticalScrollView(Context context) { super(context); mHippyOnScrollHelper = new HippyOnScrollHelper(); setVerticalScrollBarEnabled(false); + + HippyEngineContext hippyContext = ((HippyInstanceContext) context).getEngineContext(); + isTvPlatform = hippyContext.isRunningOnTVPlatform(); + if (isTvPlatform) { + mFocusHelper = new HippyVerticalScrollViewFocusHelper(this); + setFocusableInTouchMode(true); + } } public void setScrollEnabled(boolean enabled) { @@ -114,34 +139,34 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { @Override public boolean onTouchEvent(MotionEvent event) { + if (!mScrollEnabled) { + return false; + } int action = event.getAction() & MotionEvent.ACTION_MASK; if (action == MotionEvent.ACTION_DOWN && !mDragging) { mDragging = true; if (mScrollBeginDragEventEnable) { HippyScrollViewEventHelper.emitScrollBeginDragEvent(this); } - // 当手指触摸listview时,让父控件交出ontouch权限,不能滚动 - setParentScrollableIfNeed(false); - } else if (action == MotionEvent.ACTION_UP && mDragging) { + } else if ((action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) && mDragging) { + if (mHasUnsentScrollEvent) { + sendOnScrollEvent(); + } if (mScrollEndDragEventEnable) { HippyScrollViewEventHelper.emitScrollEndDragEvent(this); } if(mPagingEnabled) { - post(new Runnable() { - @Override - public void run() { - doPageScroll(); - } - } - ); + post(mDoPageScrollRunnable); } - // 当手指松开时,让父控件重新获取onTouch权限 - setParentScrollableIfNeed(true); mDragging = false; } - boolean result = mScrollEnabled && super.onTouchEvent(event); + if (!mDragging && isTvPlatform) { + mFocusHelper.handleRequestFocusFromTouch(event); + } + + boolean result = super.onTouchEvent(event); if (mGestureDispatcher != null) { result |= mGestureDispatcher.handleTouchEvent(event); } @@ -156,6 +181,9 @@ public boolean onInterceptTouchEvent(MotionEvent event) { int action = event.getAction() & MotionEvent.ACTION_MASK; if (action == MotionEvent.ACTION_DOWN) { + // reset state in case child View not calling stopNestedScroll(TYPE_NON_TOUCH) + onStopNestedScroll(this, ViewCompat.TYPE_NON_TOUCH); + startScrollY = getScrollY(); } @@ -169,14 +197,6 @@ public boolean onInterceptTouchEvent(MotionEvent event) { return false; } - // 设置父控件是否可以获取到触摸处理权限 - private void setParentScrollableIfNeed(boolean flag) { - // 若自己能上下滚动 - if (canScrollVertically(-1) || canScrollVertically(1)) { - getParent().requestDisallowInterceptTouchEvent(!flag); - } - } - @Override public NativeGestureDispatcher getGestureDispatcher() { return mGestureDispatcher; @@ -192,36 +212,36 @@ protected void onScrollChanged(int x, int y, int oldX, int oldY) { super.onScrollChanged(x, y, oldX, oldY); if (mHippyOnScrollHelper.onScrollChanged(x, y)) { if (mScrollEventEnable) { - long currTime = System.currentTimeMillis(); + long currTime; int offsetY = Math.abs(y - mLastY); if (mScrollMinOffset > 0 && offsetY >= mScrollMinOffset) { mLastY = y; - } else if ((mScrollMinOffset == 0) && (currTime - mLastScrollEventTimeStamp - >= mScrollEventThrottle)) { + sendOnScrollEvent(); + } else if ((mScrollMinOffset == 0) && ((currTime = SystemClock.elapsedRealtime()) + - mLastScrollEventTimeStamp >= mScrollEventThrottle)) { mLastScrollEventTimeStamp = currTime; + sendOnScrollEvent(); } else { - return; + mHasUnsentScrollEvent = true; } - - HippyScrollViewEventHelper.emitScrollEvent(this); } - mDoneFlinging = false; } - } - protected void doPageScroll() { - if (mMomentumScrollBeginEventEnable) { - HippyScrollViewEventHelper.emitScrollMomentumBeginEvent(this); - } - - smoothScrollToPage(); + private void sendOnScrollEvent() { + mHasUnsentScrollEvent = false; + HippyScrollViewEventHelper.emitScrollEvent(this); + } + private void scheduleScrollEnd() { Runnable runnable = new Runnable() { @Override public void run() { if (mDoneFlinging) { + if (mHasUnsentScrollEvent) { + sendOnScrollEvent(); + } if (mMomentumScrollEndEventEnable) { HippyScrollViewEventHelper.emitScrollMomentumEndEvent(HippyVerticalScrollView.this); } @@ -235,6 +255,16 @@ public void run() { postOnAnimationDelayed(runnable, HippyScrollViewEventHelper.MOMENTUM_DELAY); } + protected void doPageScroll() { + if (mMomentumScrollBeginEventEnable) { + HippyScrollViewEventHelper.emitScrollMomentumBeginEvent(this); + } + + smoothScrollToPage(); + + scheduleScrollEnd(); + } + @Override public void fling(int velocityY) { if (!mFlingEnabled || mPagingEnabled) { @@ -246,29 +276,16 @@ public void fling(int velocityY) { if (mMomentumScrollBeginEventEnable) { HippyScrollViewEventHelper.emitScrollMomentumBeginEvent(this); } - Runnable runnable = new Runnable() { - @Override - public void run() { - if (mDoneFlinging) { - if (mMomentumScrollEndEventEnable) { - HippyScrollViewEventHelper.emitScrollMomentumEndEvent(HippyVerticalScrollView.this); - } - } else { - mDoneFlinging = true; - postOnAnimationDelayed(this, HippyScrollViewEventHelper.MOMENTUM_DELAY); - } - } - }; - - postOnAnimationDelayed(runnable, HippyScrollViewEventHelper.MOMENTUM_DELAY); + scheduleScrollEnd(); } private void smoothScrollToPage() { int height = getHeight(); - if (height <= 0) { + View view = getChildAt(0); + if (height <= 0 || view == null) { return; } - int maxPage = getChildAt(0).getHeight()/height; + int maxPage = view.getHeight()/height; int page = startScrollY / height; int offset = getScrollY() - startScrollY; if (offset == 0) { @@ -336,7 +353,15 @@ public void setInitialContentOffset(int offset) { @Override public void scrollToInitContentOffset() { + int scrollRange = mScrollRange; + View firstChild = getChildAt(0); + if (firstChild != null) { + mScrollRange = firstChild.getHeight(); + } if (hasCompleteFirstBatch) { + if (mScrollRange < scrollRange) { + scrollTo(getScrollX(), getScrollY()); + } return; } @@ -346,4 +371,187 @@ public void scrollToInitContentOffset() { hasCompleteFirstBatch = true; } + + @Override + public View focusSearch(View focused, int direction) { + if (isTvPlatform) { + return mFocusHelper.focusSearch(focused, direction); + } + return super.focusSearch(focused, direction); + } + + @Override + protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { + if (getVisibility() != View.VISIBLE) { + return false; + } + + if (isTvPlatform) { + if (mFocusHelper.requestFocusInDescendants(direction, previouslyFocusedRect)) { + return true; + } + } + + return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); + } + + @Override + public void addFocusables(ArrayList views, int direction, int focusableMode) { + if (isTvPlatform) { + if (!mFocusHelper.addFocusablesImp(views, direction, focusableMode)) { + super.addFocusables(views, direction, focusableMode); + } + } else { + super.addFocusables(views, direction, focusableMode); + } + } + + @Override + public void requestChildFocus(View child, View focused) { + super.requestChildFocus(child, focused); + if (isTvPlatform) { + mFocusHelper.scrollToFocusChild(focused); + } + } + + @Override + public void setNestedScrollPriority(int direction, Priority priority) { + mNestedScrollPriority[direction] = priority; + } + + @Override + public Priority getNestedScrollPriority(int direction) { + Priority result = mNestedScrollPriority[direction]; + if (result == Priority.NOT_SET) { + result = mNestedScrollPriority[DIRECTION_ALL]; + } + return result; + } + + @Override + public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes, int type) { + if (!mScrollEnabled) { + return false; + } + return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; + } + + @Override + public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { + onNestedScrollInternal(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, ViewCompat.TYPE_TOUCH, null); + } + + @Override + public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, + int dyUnconsumed, int type) { + onNestedScrollInternal(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, null); + } + + @Override + public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, + int dyUnconsumed, int type, @NonNull int[] consumed) { + onNestedScrollInternal(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed); + } + + void onNestedScrollInternal(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, + int dyUnconsumed, int type, int[] consumed) { + if (mPagingEnabled && type == ViewCompat.TYPE_NON_TOUCH) { + // Because the non-touch scrolling of NestedScrollView does not call stopNestedScroll(), + // we don't respond to non-touch scrolling in paging mode, to avoid not calling doPageScroll() + // after scrolling ends. + return; + } + int myConsumed = 0; + // Process the current View first + if (HippyNestedScrollHelper.priorityOfY(target, dyUnconsumed) == Priority.SELF) { + final int oldScrollY = getScrollY(); + scrollBy(0, dyUnconsumed); + myConsumed = getScrollY() - oldScrollY; + dyConsumed += myConsumed; + dyUnconsumed -= myConsumed; + if (consumed != null) { + consumed[1] += myConsumed; + } + } + // Then dispatch to the parent for processing + int parentDx = HippyNestedScrollHelper.priorityOfX(this, dxUnconsumed) == Priority.NONE ? 0 : dxUnconsumed; + int parentDy = HippyNestedScrollHelper.priorityOfY(this, dyUnconsumed) == Priority.NONE ? 0 : dyUnconsumed; + if (parentDx != 0 || parentDy != 0) { + if (consumed == null) { + dispatchNestedScroll(dxConsumed, dyConsumed, parentDx, parentDy, null, type); + } else { + int consumedX = consumed[0]; + int consumedY = consumed[1] + myConsumed; + consumed[0] = 0; + consumed[1] = 0; + dispatchNestedScroll(dxConsumed, dyConsumed, parentDx, parentDy, null, type, consumed); + consumed[0] += consumedX; + consumed[1] += consumedY; + } + } + } + + @Override + public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) { + if (mPagingEnabled && type == ViewCompat.TYPE_NON_TOUCH) { + if (HippyNestedScrollHelper.priorityOfY(target, dy) == Priority.PARENT) { + consumed[1] += dy; + } + return; + } + // Dispatch to the parent for processing first + int parentDx = HippyNestedScrollHelper.priorityOfX(this, dx) == Priority.NONE ? 0 : dx; + int parentDy = HippyNestedScrollHelper.priorityOfY(this, dy) == Priority.NONE ? 0 : dy; + if (parentDx != 0 || parentDy != 0) { + // Temporarily store `consumed` to reuse the Array + int consumedX = consumed[0]; + int consumedY = consumed[1]; + consumed[0] = 0; + consumed[1] = 0; + dispatchNestedPreScroll(parentDx, parentDy, consumed, null, type); + dy -= consumed[1]; + consumed[0] += consumedX; + consumed[1] += consumedY; + } + // Then process the current View + if (HippyNestedScrollHelper.priorityOfY(target, dy) == Priority.PARENT) { + final int oldScrollY = getScrollY(); + scrollBy(0, dy); + final int myConsumed = getScrollY() - oldScrollY; + consumed[1] += myConsumed; + } + } + + @Override + public void onStopNestedScroll(@NonNull View target, int type) { + super.onStopNestedScroll(target, type); + if (mPagingEnabled) { + post(mDoPageScrollRunnable); + } + } + + @Override + public void computeScroll() { + // computeScroll() is triggered by the draw method of the ViewParent. If the RecyclerView is in + // the NestedScrollingParent chain, methods such as onNestedScroll may be called, causing the + // RecyclerView to removeView and causing NPE, so post execution is required. + if (hasNestedScrollingParent(ViewCompat.TYPE_NON_TOUCH)) { + post(mComputeScrollRunnable); + } else { + super.computeScroll(); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + /* + * post task is main thread,but node handle(add or remove) is in js thread, + * it may lead to use after free bugs in some case,such as inconsistency in recyclerView + */ + removeCallbacks(mComputeScrollRunnable); + removeCallbacks(mDoPageScrollRunnable); + // a hacky way to abort animated scroll + smoothScrollBy(0, 0); + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyVerticalScrollViewFocusHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyVerticalScrollViewFocusHelper.java new file mode 100644 index 00000000000..3e6054a7c59 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyVerticalScrollViewFocusHelper.java @@ -0,0 +1,215 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.scroll; + +import android.graphics.Rect; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import com.tencent.mtt.hippy.utils.LogUtils; +import com.tencent.mtt.hippy.utils.StrictFocusFinder; +import java.util.ArrayList; + +public class HippyVerticalScrollViewFocusHelper { + + private static final String TAG = "HippyVerticalScrollViewFocusHelper"; + private HippyVerticalScrollView mScrollView = null; + private Rect mFocusRect = new Rect(); + private int mFocusPosition = 0; + + public HippyVerticalScrollViewFocusHelper(HippyVerticalScrollView scrollView) { + this.mScrollView = scrollView; + } + + public View focusSearch(View focused, int direction) { + View nextFocus = StrictFocusFinder.getInstance().findNextFocus(mScrollView, focused, direction); + if (nextFocus == null && mScrollView.getParent() != null) { + return mScrollView.getParent().focusSearch(focused, direction); + } + return nextFocus; + } + + public void handleRequestFocusFromTouch(MotionEvent event) { + float posY = event.getY(); + ViewGroup liView = (ViewGroup) mScrollView.getChildAt(0); + if (liView == null) { + return; + } + int count = liView.getChildCount(); + for (int i = 0; i < count; ++i) { + View child = liView.getChildAt(i); + + if (isChildRequestOnTouch(posY, child)) { + child.requestFocusFromTouch(); + break; + } + } + } + + private boolean isChildRequestOnTouch(float posY, View child) { + return (child.getTop() - mScrollView.getScrollY()) < posY && posY < ( + child.getTop() - mScrollView.getScrollY() + child + .getHeight()); + } + + public int getFocusPosition() { + return mFocusPosition; + } + + private void setFocusPosition(int position) { + mFocusPosition = position; + } + + private int getFocusViewPosition(View view) { + ViewGroup liView = (ViewGroup) mScrollView.getChildAt(0); + if (liView == null) { + return 0; + } + for (int i = 0; i < liView.getChildCount(); i++) { + View child = liView.getChildAt(i); + if (child == view) { + return i; + } + } + + return 0; + } + + public void scrollToFocusChild(View child) { + setFocusPosition(getFocusViewPosition(child)); + child.getDrawingRect(mFocusRect); + mScrollView.offsetDescendantRectToMyCoords(child, mFocusRect); + int scrollY = computeScrollDelta(mFocusRect); + if (scrollY != 0) { + mScrollView.smoothScrollBy(0, scrollY); + } + } + + public boolean requestFocusInDescendants(int direction, Rect previouslyFocusedRect) { + final int focusPosition; + focusPosition = getFocusPosition(); + ViewGroup liView = (ViewGroup) mScrollView.getChildAt(0); + if (liView == null) { + return false; + } + View realFocuView = liView.getChildAt(focusPosition); + if (realFocuView == null) { + return false; + } + + boolean ret = (realFocuView != null && realFocuView + .requestFocus(direction, previouslyFocusedRect)); + LogUtils.d("HippyVerticalScrollView", "requestFocusInDescendants ret : " + ret); + if (!ret) { + if (Math.abs(focusPosition) <= Math.abs(focusPosition - liView.getChildCount())) { + for (int i = 0; i < liView.getChildCount(); i++) { + View v = liView.getChildAt(i); + if (v != null && v.getVisibility() == View.VISIBLE && v.requestFocus(direction, + previouslyFocusedRect)) { + ret = true; + break; + } + } + } else { + for (int i = liView.getChildCount() - 1; i >= 0; i--) { + View v = liView.getChildAt(i); + if (v != null && v.getVisibility() == View.VISIBLE && v.requestFocus(direction, + previouslyFocusedRect)) { + ret = true; + break; + } + } + } + } + return ret; + } + + public boolean addFocusablesImp(ArrayList views, int direction, int focusableMode) { + if (mScrollView.hasFocus() + || mScrollView.getDescendantFocusability() == ViewGroup.FOCUS_BLOCK_DESCENDANTS) { + return false; + } + + ViewGroup liView = (ViewGroup) mScrollView.getChildAt(0); + if (liView == null) { + return false; + } + ArrayList childViews = new ArrayList<>(); + for (int i = 0; i < liView.getChildCount(); i++) { + View child = liView.getChildAt(i); + if (child != null && child.getVisibility() == View.VISIBLE) { + child.addFocusables(childViews, direction, focusableMode); + } + } + + if (!childViews.isEmpty()) { + views.add(mScrollView); + } + + return true; + } + + protected int computeScrollDelta(Rect rect) { + if (mScrollView.getChildCount() == 0) { + return 0; + } + int height = mScrollView.getHeight(); + int screenTop = mScrollView.getScrollY(); + int screenBottom = screenTop + height; + int fadingEdge = mScrollView.getVerticalFadingEdgeLength(); + if (rect.top > 0) { + screenTop += fadingEdge; + } + View view = mScrollView.getChildAt(0); + if (view != null && rect.bottom < view.getHeight()) { + screenBottom -= fadingEdge; + } + + int scrollYDelta = 0; + LogUtils.i(TAG, + "computeScrollDelta height=" + height + ",screenTop=" + screenTop + ",screenBottom=" + + screenBottom + + ",fadingEdge=" + fadingEdge + ",rect=" + rect); + if (rect.bottom > screenBottom && rect.top > screenTop) { + if (rect.height() > height) { + scrollYDelta += (rect.top - screenTop); + } else { + scrollYDelta += (rect.bottom - screenBottom) + height / 4; + } + + int bottom = view == null ? mScrollView.getBottom() : view.getBottom(); + int distanceToBottom = bottom - screenBottom; + scrollYDelta = Math.min(scrollYDelta, distanceToBottom); + LogUtils.i(TAG, + "computeScrollDelta bottom=" + bottom + ",distanceToBottom=" + distanceToBottom + + ",scrollYDelta=" + + scrollYDelta); + + } else if (rect.top < screenTop && rect.bottom < screenBottom) { + if (rect.height() > height) { + scrollYDelta -= (screenBottom - rect.bottom); + } else { + scrollYDelta -= (screenTop - rect.top + height / 4); + } + + scrollYDelta = Math.max(scrollYDelta, -mScrollView.getScrollY()); + LogUtils.i(TAG, "computeScrollDelta getScrollY=" + mScrollView.getScrollY() + + ",scrollYDelta=" + scrollYDelta); + } + return scrollYDelta; + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/text/HippyTextView.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/text/HippyTextView.java index 41d4e909f18..dce01a7ac29 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/text/HippyTextView.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/text/HippyTextView.java @@ -22,7 +22,6 @@ import android.graphics.drawable.LayerDrawable; import android.text.*; import android.text.style.AbsoluteSizeSpan; -import android.text.style.ForegroundColorSpan; import android.view.MotionEvent; import android.view.View; @@ -30,6 +29,7 @@ import com.tencent.mtt.hippy.HippyInstanceContext; import com.tencent.mtt.hippy.adapter.exception.HippyExceptionHandlerAdapter; import com.tencent.mtt.hippy.dom.node.DomNode; +import com.tencent.mtt.hippy.dom.node.HippyForegroundColorSpan; import com.tencent.mtt.hippy.dom.node.HippyNativeGestureSpan; import com.tencent.mtt.hippy.dom.node.TextNode; import com.tencent.mtt.hippy.uimanager.HippyViewBase; @@ -221,13 +221,13 @@ private CommonBackgroundDrawable getBackGround() { } protected void setTextColor(int textColor) { - if (mLayout != null && mLayout.getText() instanceof SpannableStringBuilder) { - SpannableStringBuilder textSpan = (SpannableStringBuilder) mLayout.getText(); - ForegroundColorSpan[] spans = textSpan - .getSpans(0, mLayout.getText().length(), ForegroundColorSpan.class); + if (mLayout != null && mLayout.getText() instanceof Spannable) { + Spannable textSpan = (Spannable) mLayout.getText(); + HippyForegroundColorSpan[] spans = textSpan + .getSpans(0, mLayout.getText().length(), HippyForegroundColorSpan.class); boolean hasSpans = false; if (spans != null) { - for (ForegroundColorSpan span : spans) { + for (HippyForegroundColorSpan span : spans) { int start = textSpan.getSpanStart(span); int end = textSpan.getSpanEnd(span); textSpan.removeSpan(span); @@ -235,15 +235,16 @@ protected void setTextColor(int textColor) { if (start == 0) { spanFlags = Spannable.SPAN_INCLUSIVE_INCLUSIVE; } - textSpan.setSpan(new ForegroundColorSpan(textColor), start, end, spanFlags); + textSpan.setSpan( + new HippyForegroundColorSpan(textColor, span.getCustomColors()), start, end, spanFlags); } } if (spans == null || spans.length == 0) { - textSpan.setSpan(new ForegroundColorSpan(textColor), 0, textSpan.toString().length(), + textSpan + .setSpan(new HippyForegroundColorSpan(textColor, null), 0, textSpan.toString().length(), Spannable.SPAN_EXCLUSIVE_INCLUSIVE); } } - } @Override diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInput.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInput.java index c3111bf6b73..82d00b16ec8 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInput.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInput.java @@ -15,6 +15,9 @@ */ package com.tencent.mtt.hippy.views.textinput; +import android.graphics.BlendMode; +import android.graphics.BlendModeColorFilter; +import androidx.core.content.ContextCompat; import com.tencent.mtt.hippy.HippyEngineContext; import com.tencent.mtt.hippy.HippyInstanceContext; import com.tencent.mtt.hippy.common.HippyMap; @@ -552,6 +555,12 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { return false; } + public HippyMap jsIsFocused() { + HippyMap hippyMap = new HippyMap(); + hippyMap.pushBoolean("value", hasFocus()); + return hippyMap; + } + public void setBlurOrOnFocus(boolean blur) { if (blur) { setOnFocusChangeListener(this); @@ -622,47 +631,62 @@ public void setOnSelectListener(boolean change) { @SuppressWarnings("JavaReflectionMemberAccess") public void setCursorColor(int color) { - try { - Field field = TextView.class.getDeclaredField("mCursorDrawableRes"); - field.setAccessible(true); - int drawableResId = field.getInt(this); - field = TextView.class.getDeclaredField("mEditor"); - field.setAccessible(true); - Object editor = field.get(this); - Drawable drawable = null; - final int version = Build.VERSION.SDK_INT; - if (version >= 21) { - drawable = this.getContext().getDrawable(drawableResId); - } else if (version >= 16) { - drawable = this.getContext().getResources().getDrawable(drawableResId); - } - if (drawable == null) { - return; + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) { + // Pre-Android 10, there was no supported API to change the cursor color programmatically. + // In Android 9.0, they changed the underlying implementation, + // but also "dark greylisted" the new field, rendering it unusable. + return; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + Drawable cursorDrawable = getTextCursorDrawable(); + if (cursorDrawable != null) { + cursorDrawable.setColorFilter(new BlendModeColorFilter(color, BlendMode.SRC_IN)); + setTextCursorDrawable(cursorDrawable); } - drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN); - assert editor != null; - Class editorClass = editor - .getClass(); //有的ROM自己复写了,Editor类,所以之类里面没有mDrawableForCursor,这里需要遍历 - while (editorClass != null) { - try { - if (version >= 28) { - field = editorClass.getDeclaredField("mDrawableForCursor");//mCursorDrawable - field.setAccessible(true); - field.set(editor, drawable); - } else { - Drawable[] drawables = {drawable, drawable}; - field = editorClass.getDeclaredField("mCursorDrawable");//mCursorDrawable - field.setAccessible(true); - field.set(editor, drawables); + } else { + try { + Field field = TextView.class.getDeclaredField("mCursorDrawableRes"); + field.setAccessible(true); + int drawableResId = field.getInt(this); + field = TextView.class.getDeclaredField("mEditor"); + field.setAccessible(true); + Object editor = field.get(this); + Drawable drawable = null; + final int version = Build.VERSION.SDK_INT; + if (version >= 21) { + drawable = this.getContext().getDrawable(drawableResId); + } else if (version >= 16) { + drawable = this.getContext().getResources().getDrawable(drawableResId); + } + if (drawable == null) { + return; + } + drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN); + assert editor != null; + Class editorClass = editor + .getClass(); //有的ROM自己复写了,Editor类,所以之类里面没有mDrawableForCursor,这里需要遍历 + while (editorClass != null) { + try { + if (version >= 28) { + field = editorClass.getDeclaredField("mDrawableForCursor");//mCursorDrawable + field.setAccessible(true); + field.set(editor, drawable); + } else { + Drawable[] drawables = {drawable, drawable}; + field = editorClass.getDeclaredField("mCursorDrawable");//mCursorDrawable + field.setAccessible(true); + field.set(editor, drawables); + } + break; + } catch (Throwable e) { + LogUtils.d("HippyTextInput", "setCursorColor: " + e.getMessage()); } - break; - } catch (Throwable e) { - LogUtils.d("HippyTextInput", "setCursorColor: " + e.getMessage()); + editorClass = editorClass.getSuperclass(); //继续往上反射父亲 } - editorClass = editorClass.getSuperclass(); //继续往上反射父亲 + } catch (Throwable e) { + LogUtils.d("HippyTextInput", "setCursorColor: " + e.getMessage()); } - } catch (Throwable e) { - LogUtils.d("HippyTextInput", "setCursorColor: " + e.getMessage()); } } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInputController.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInputController.java index 7aff62319cf..c003aa22eea 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInputController.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInputController.java @@ -25,6 +25,7 @@ import android.text.Editable; import android.text.InputFilter; import android.text.InputType; +import android.text.Layout; import android.text.TextUtils; import android.text.method.PasswordTransformationMethod; import android.util.TypedValue; @@ -41,6 +42,7 @@ import com.tencent.mtt.hippy.dom.node.NodeProps; import com.tencent.mtt.hippy.dom.node.StyleNode; import com.tencent.mtt.hippy.dom.node.TextExtra; +import com.tencent.mtt.hippy.dom.node.TextNode; import com.tencent.mtt.hippy.modules.Promise; import com.tencent.mtt.hippy.uimanager.HippyViewController; import com.tencent.mtt.hippy.utils.LogUtils; @@ -67,6 +69,7 @@ public class HippyTextInputController extends HippyViewController= Build.VERSION_CODES.M) { + int strategyInt; + if (TextUtils.isEmpty(strategy) || TextNode.STRATEGY_SIMPLE.equals(strategy)) { + strategyInt = Layout.BREAK_STRATEGY_SIMPLE; + } else if (TextNode.STRATEGY_HIGH_QUALITY.equals(strategy)) { + strategyInt = Layout.BREAK_STRATEGY_HIGH_QUALITY; + } else if (TextNode.STRATEGY_BALANCED.equals(strategy)) { + strategyInt = Layout.BREAK_STRATEGY_BALANCED; + } else { + throw new RuntimeException("Invalid breakStrategy: " + strategy); + } + view.setBreakStrategy(strategyInt); + } + } + @Override public void dispatchFunction(final HippyTextInput view, String functionName, HippyArray params, Promise promise) { @@ -455,6 +466,11 @@ public void dispatchFunction(final HippyTextInput view, String functionName, Hip HippyMap resultMap = view.jsGetValue(); promise.resolve(resultMap); } + } else if (COMMAND_IS_FOCUSED.equals(functionName)) { + if (promise != null) { + HippyMap resultMap = view.jsIsFocused(); + promise.resolve(resultMap); + } } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/view/HippyViewGroup.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/view/HippyViewGroup.java index 34cd33a4fa5..bf2ced69266 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/view/HippyViewGroup.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/view/HippyViewGroup.java @@ -15,9 +15,12 @@ */ package com.tencent.mtt.hippy.views.view; +import android.view.ViewGroup; +import androidx.annotation.NonNull; import com.tencent.mtt.hippy.dom.node.NodeProps; import com.tencent.mtt.hippy.uimanager.IHippyZIndexViewGroup; import com.tencent.mtt.hippy.uimanager.ViewGroupDrawingOrderHelper; +import com.tencent.mtt.hippy.utils.LogUtils; import com.tencent.mtt.hippy.views.image.HippyImageView; import android.content.Context; @@ -33,13 +36,12 @@ @SuppressWarnings({"unused"}) public class HippyViewGroup extends HippyImageView implements IHippyZIndexViewGroup { + private static final String TAG = "HippyViewGroup"; private static final int LAYER_TYPE_NOT_SET = -1; private final ViewGroupDrawingOrderHelper mDrawingOrderHelper; float mDownX = 0; float mDownY = 0; boolean isHandlePullUp = false; - // private CommonBackgroundDrawable mBGDrawable; - // private NativeGestureDispatcher mGestureDispatcher; private String mOverflow; private Path mOverflowPath; private RectF mOverflowRect; @@ -51,20 +53,9 @@ public HippyViewGroup(Context context) { mDrawingOrderHelper = new ViewGroupDrawingOrderHelper(this); mOldLayerType = LAYER_TYPE_NOT_SET; setScaleType(ScaleType.ORIGIN); + //setClipChildren(false); } - // @Override - // protected void onLayout(boolean changed, int l, int t, int r, int b) - // { - // - // } - - // @Override - // public void requestLayout() - // { - // //super.requestLayout(); - // } - @SuppressWarnings("SuspiciousNameCombination") @Override protected void dispatchDraw(Canvas canvas) { @@ -125,17 +116,6 @@ protected void dispatchDraw(Canvas canvas) { } } super.dispatchDraw(canvas); - // String testString = "View ID:" + this.getId(); - // Paint mPaint = new Paint(); - // mPaint.setStrokeWidth(3); - // mPaint.setTextSize(40); - // mPaint.setColor(Color.RED); - // mPaint.setTextAlign(Paint.Align.LEFT); - // Rect bounds = new Rect(); - // mPaint.getTextBounds(testString, 0, testString.length(), bounds); - // Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt(); - // int baseline = (getMeasuredHeight() - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top; - // canvas.drawText(testString, getMeasuredWidth() / 2 - bounds.width() / 2, baseline, mPaint); } private void restoreLayerType() { @@ -144,75 +124,29 @@ private void restoreLayerType() { } } - // public void setBorderRadius(float radius, int position) - // { - // getBackGround().setBorderRadius(radius, position); - // } - // - // public void setBorderWidth(float width, int position) - // { - // getBackGround().setBorderWidth(width, position); - // } - // - // public void setBorderColor(int color, int position) - // { - // getBackGround().setBorderColor(color, position); - // } - // - // private CommonBackgroundDrawable getBackGround() - // { - // if (mBGDrawable == null) - // { - // mBGDrawable = new CommonBackgroundDrawable(); - // Drawable currBGDrawable = getBackground(); - // super.setBackgroundDrawable(null); - // if (currBGDrawable == null) - // { - // super.setBackgroundDrawable(mBGDrawable); - // } - // else - // { - // LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] { mBGDrawable, currBGDrawable }); - // super.setBackgroundDrawable(layerDrawable); - // } - // } - // return mBGDrawable; - // } - public void setOverflow(String overflow) { mOverflow = overflow; - //robinsli Android 支持 overflow: visible,超出容器之外的属性节点也可以正常显示 - if (!TextUtils.isEmpty(mOverflow)) { - switch (mOverflow) { + setOverflow(overflow, this); + } + + public static void setOverflow(String overflow, @NonNull ViewGroup viewGroup) { + if (!TextUtils.isEmpty(overflow)) { + switch (overflow) { case "visible": - setClipChildren(false); //可以超出父亲区域 + viewGroup.setClipChildren(false); + viewGroup.invalidate(); break; case "hidden": { - setClipChildren(true); //默认值是false + viewGroup.setClipChildren(true); + viewGroup.invalidate(); break; } + default: + LogUtils.w(TAG, "setOverflow: Unknown overflow type =" + overflow); } } - invalidate(); } - // @Override - // public void setBackgroundColor(int color) - // { - // getBackGround().setBackgroundColor(color); - // } - - // @Override - // public boolean onTouchEvent(MotionEvent event) - // { - // boolean result = super.onTouchEvent(event); - // if (mGestureDispatcher != null) - // { - // result |= mGestureDispatcher.handleTouchEvent(event); - // } - // return result; - // } - @Override public boolean onInterceptTouchEvent(MotionEvent ev) { int action = ev.getAction() & MotionEvent.ACTION_MASK; @@ -255,18 +189,6 @@ public boolean onInterceptTouchEvent(MotionEvent ev) { return result; } - // @Override - // public NativeGestureDispatcher getGestureDispatcher() - // { - // return mGestureDispatcher; - // } - - // @Override - // public void setGestureDispatcher(NativeGestureDispatcher dispatcher) - // { - // mGestureDispatcher = dispatcher; - // } - @Override protected int getChildDrawingOrder(int childCount, int index) { return mDrawingOrderHelper.getChildDrawingOrder(childCount, index); @@ -314,10 +236,4 @@ public void resetProps() { setClipChildren(true); //默认值是false // setAlpha(1.0f); } - - // @Override - // public void clear() - // { - // - // } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/view/HippyViewGroupController.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/view/HippyViewGroupController.java index e14a1868d7b..e45f471b2ac 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/view/HippyViewGroupController.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/view/HippyViewGroupController.java @@ -13,87 +13,134 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.tencent.mtt.hippy.views.view; +import static com.tencent.mtt.hippy.dom.node.NodeProps.BACKGROUND_RIPPLE; +import static com.tencent.mtt.hippy.uimanager.DrawableFactory.DrawableType.DRAWABLE_TYPE_RIPPLE; + import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.Log; import android.view.View; import com.tencent.mtt.hippy.HippyInstanceContext; import com.tencent.mtt.hippy.annotation.HippyController; import com.tencent.mtt.hippy.annotation.HippyControllerProps; +import com.tencent.mtt.hippy.common.HippyArray; +import com.tencent.mtt.hippy.common.HippyMap; import com.tencent.mtt.hippy.dom.node.NodeProps; +import com.tencent.mtt.hippy.uimanager.DrawableFactory; import com.tencent.mtt.hippy.uimanager.HippyGroupController; +import com.tencent.mtt.hippy.utils.LogUtils; import com.tencent.mtt.hippy.utils.PixelUtil; import com.tencent.mtt.hippy.views.image.HippyImageView; import java.util.WeakHashMap; +import androidx.annotation.RequiresApi; + @SuppressWarnings({"unused"}) @HippyController(name = HippyViewGroupController.CLASS_NAME) public class HippyViewGroupController extends HippyGroupController { - public static final String CLASS_NAME = "View"; - - public static final WeakHashMap mZIndexHash = new WeakHashMap<>(); - - - public static void setViewZIndex(View view, int zIndex) { - mZIndexHash.put(view, zIndex); - } - - public static void removeViewZIndex(View view) { - mZIndexHash.remove(view); - } - - public static Integer getViewZIndex(View view) { - return mZIndexHash.get(view); - } - - @Override - protected View createViewImpl(Context context) { - return new HippyViewGroup(context); - } - - @HippyControllerProps(name = NodeProps.OVERFLOW, defaultType = HippyControllerProps.STRING, defaultString = "visible") - public void setOverflow(HippyViewGroup hippyViewGroup, String overflow) { - hippyViewGroup.setOverflow(overflow); - } - - @HippyControllerProps(name = NodeProps.BACKGROUND_IMAGE, defaultType = HippyControllerProps.STRING) - public void setBackgroundImage(HippyViewGroup hippyViewGroup, String url) { - hippyViewGroup.setUrl(getInnerPath((HippyInstanceContext)hippyViewGroup.getContext(), url)); - } - - @HippyControllerProps(name = NodeProps.BACKGROUND_SIZE, defaultType = HippyControllerProps.STRING, defaultString = "origin") - public void setBackgroundImageSize(HippyImageView hippyImageView, String resizeModeValue) { - if ("contain".equals(resizeModeValue)) { - // 在保持图片宽高比的前提下缩放图片,直到宽度和高度都小于等于容器视图的尺寸 - // 这样图片完全被包裹在容器中,容器中可能留有空白 - hippyImageView.setScaleType(HippyImageView.ScaleType.CENTER_INSIDE); - } else if ("cover".equals(resizeModeValue)) { - // 在保持图片宽高比的前提下缩放图片,直到宽度和高度都大于等于容器视图的尺寸 - // 这样图片完全覆盖甚至超出容器,容器中不留任何空白 - hippyImageView.setScaleType(HippyImageView.ScaleType.CENTER_CROP); - } else if ("center".equals(resizeModeValue)) { - // 居中不拉伸 - hippyImageView.setScaleType(HippyImageView.ScaleType.CENTER); - } else if ("origin".equals(resizeModeValue)) { - // 不拉伸,居左上 - hippyImageView.setScaleType(HippyImageView.ScaleType.ORIGIN); - } else { - // stretch and other mode - // 拉伸图片且不维持宽高比,直到宽高都刚好填满容器 - hippyImageView.setScaleType(HippyImageView.ScaleType.FIT_XY); + public static final String CLASS_NAME = "View"; + private static final String FUNC_SET_PRESSED = "setPressed"; + private static final String FUNC_SET_HOTSPOT = "setHotspot"; + private static final WeakHashMap mZIndexHash = new WeakHashMap<>(); + + public static void setViewZIndex(View view, int zIndex) { + mZIndexHash.put(view, zIndex); + } + + public static void removeViewZIndex(View view) { + mZIndexHash.remove(view); + } + + public static Integer getViewZIndex(View view) { + return mZIndexHash.get(view); + } + + @Override + protected View createViewImpl(Context context) { + return new HippyViewGroup(context); + } + + @HippyControllerProps(name = NodeProps.OVERFLOW, defaultType = HippyControllerProps.STRING, defaultString = "visible") + public void setOverflow(HippyViewGroup hippyViewGroup, String overflow) { + hippyViewGroup.setOverflow(overflow); + } + + @HippyControllerProps(name = NodeProps.BACKGROUND_IMAGE, defaultType = HippyControllerProps.STRING) + public void setBackgroundImage(HippyViewGroup hippyViewGroup, String url) { + hippyViewGroup + .setUrl(getInnerPath((HippyInstanceContext) hippyViewGroup.getContext(), url)); } - } - @HippyControllerProps(name = NodeProps.BACKGROUND_POSITION_X, defaultType = HippyControllerProps.NUMBER) - public void setBackgroundImagePositionX(HippyViewGroup hippyViewGroup, int positionX) { - hippyViewGroup.setImagePositionX((int) PixelUtil.dp2px(positionX)); - } + @HippyControllerProps(name = NodeProps.BACKGROUND_SIZE, defaultType = HippyControllerProps.STRING, defaultString = "origin") + public void setBackgroundImageSize(HippyImageView hippyImageView, String resizeModeValue) { + if ("contain".equals(resizeModeValue)) { + // 在保持图片宽高比的前提下缩放图片,直到宽度和高度都小于等于容器视图的尺寸 + // 这样图片完全被包裹在容器中,容器中可能留有空白 + hippyImageView.setScaleType(HippyImageView.ScaleType.CENTER_INSIDE); + } else if ("cover".equals(resizeModeValue)) { + // 在保持图片宽高比的前提下缩放图片,直到宽度和高度都大于等于容器视图的尺寸 + // 这样图片完全覆盖甚至超出容器,容器中不留任何空白 + hippyImageView.setScaleType(HippyImageView.ScaleType.CENTER_CROP); + } else if ("center".equals(resizeModeValue)) { + // 居中不拉伸 + hippyImageView.setScaleType(HippyImageView.ScaleType.CENTER); + } else if ("origin".equals(resizeModeValue)) { + // 不拉伸,居左上 + hippyImageView.setScaleType(HippyImageView.ScaleType.ORIGIN); + } else { + // stretch and other mode + // 拉伸图片且不维持宽高比,直到宽高都刚好填满容器 + hippyImageView.setScaleType(HippyImageView.ScaleType.FIT_XY); + } + } + + @HippyControllerProps(name = NodeProps.BACKGROUND_POSITION_X, defaultType = HippyControllerProps.NUMBER) + public void setBackgroundImagePositionX(HippyViewGroup hippyViewGroup, int positionX) { + hippyViewGroup.setImagePositionX((int) PixelUtil.dp2px(positionX)); + } - @HippyControllerProps(name = NodeProps.BACKGROUND_POSITION_Y, defaultType = HippyControllerProps.NUMBER) - public void setBackgroundImagePositionY(HippyViewGroup hippyViewGroup, int positionY) { - hippyViewGroup.setImagePositionY((int) PixelUtil.dp2px(positionY)); - } + @HippyControllerProps(name = NodeProps.BACKGROUND_POSITION_Y, defaultType = HippyControllerProps.NUMBER) + public void setBackgroundImagePositionY(HippyViewGroup hippyViewGroup, int positionY) { + hippyViewGroup.setImagePositionY((int) PixelUtil.dp2px(positionY)); + } + + @RequiresApi(api = Build.VERSION_CODES.M) + @HippyControllerProps(name = BACKGROUND_RIPPLE, defaultType = HippyControllerProps.MAP) + public void setNativeBackground(HippyViewGroup hippyViewGroup, HippyMap params) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && params.size() > 0) { + Drawable drawable = DrawableFactory.createDrawable(DRAWABLE_TYPE_RIPPLE, params); + if (drawable != null) { + hippyViewGroup.setRippleDrawable(drawable); + } + } + } + + @Override + public void dispatchFunction(HippyViewGroup viewGroup, String functionName, HippyArray params) { + super.dispatchFunction(viewGroup, functionName, params); + switch (functionName) { + case FUNC_SET_PRESSED: { + boolean pressed = params.getBoolean(0); + viewGroup.setPressed(pressed); + break; + } + case FUNC_SET_HOTSPOT: { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + double x = params.getDouble(0); + double y = params.getDouble(1); + viewGroup.drawableHotspotChanged(PixelUtil.dp2px(x), PixelUtil.dp2px(y)); + } + break; + } + default: + break; + } + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/viewpager/HippyViewPager.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/viewpager/HippyViewPager.java index 9482321701f..98572f22e76 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/viewpager/HippyViewPager.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/viewpager/HippyViewPager.java @@ -15,21 +15,24 @@ */ package com.tencent.mtt.hippy.views.viewpager; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.view.MotionEvent; +import android.view.View; + +import androidx.annotation.NonNull; + import com.tencent.mtt.hippy.HippyInstanceContext; import com.tencent.mtt.hippy.modules.Promise; import com.tencent.mtt.hippy.uimanager.HippyViewBase; import com.tencent.mtt.hippy.uimanager.NativeGestureDispatcher; import com.tencent.mtt.hippy.utils.I18nUtil; import com.tencent.mtt.hippy.utils.LogUtils; +import com.tencent.mtt.hippy.views.common.HippyNestedScrollComponent.Priority; +import com.tencent.mtt.hippy.views.common.HippyNestedScrollHelper; import com.tencent.mtt.supportui.views.viewpager.ViewPager; -import android.content.Context; -import android.os.Handler; -import android.os.Looper; -import android.text.TextUtils; -import android.view.MotionEvent; -import android.view.View; - @SuppressWarnings({"unused"}) public class HippyViewPager extends ViewPager implements HippyViewBase { @@ -51,8 +54,17 @@ public void run() { private ViewPagerPageChangeListener mPageListener; private final Handler mHandler = new Handler(Looper.getMainLooper()); private Promise mCallBackPromise; - - private void init(Context context) { + private int mAxes; + /** + * Captured means scrolling occurs, consume the entire scrolling (or nested scrolling) event and + * do not dispatch to parent in this state + */ + private boolean mCaptured = false; + // Reusable int array to be passed to method calls that mutate it in order to "return" two ints. + private final int[] mScrollOffsetPair = new int[2]; + private int mNestedScrollOffset = 0; + + private void init(Context context, boolean isVertical) { setCallPageChangedOnFirstLayout(true); setEnableReLayoutOnAttachToWindow(false); @@ -61,6 +73,9 @@ private void init(Context context) { setAdapter(createAdapter(context)); setLeftDragOutSizeEnabled(false); setRightDragOutSizeEnabled(false); + // enable nested scrolling + setNestedScrollingEnabled(true); + mAxes = isVertical ? SCROLL_AXIS_VERTICAL : SCROLL_AXIS_HORIZONTAL; if (I18nUtil.isRTL()) { setRotationY(180f); @@ -77,12 +92,12 @@ public void onViewAdded(View child) { public HippyViewPager(Context context, boolean isVertical) { super(context, isVertical); - init(context); + init(context, isVertical); } public HippyViewPager(Context context) { super(context); - init(context); + init(context, false); } public void setCallBackPromise(Promise promise) { @@ -149,13 +164,44 @@ public HippyViewPagerAdapter getAdapter() { return (HippyViewPagerAdapter) super.getAdapter(); } + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (hasNestedScrollingParent() && mNestedScrollOffset != 0) { + // After the nested scroll occurs, the current View position has changed. The coordinate + // origin of ev.getX() and mLastMotionX/Y is different, and the ev offset needs to be + // corrected. + MotionEvent transformEv = MotionEvent.obtain(ev); + if (mAxes == SCROLL_AXIS_HORIZONTAL) { + transformEv.offsetLocation(mNestedScrollOffset, 0); + } else { + transformEv.offsetLocation(0, mNestedScrollOffset); + } + boolean result = super.dispatchTouchEvent(transformEv); + transformEv.recycle(); + return result; + } + return super.dispatchTouchEvent(ev); + } + @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - if (!isScrollEnabled()) { + if (!isScrollEnabled() || getNestedScrollAxes() != SCROLL_AXIS_NONE) { return false; } - - return super.onInterceptTouchEvent(ev); + boolean result = super.onInterceptTouchEvent(ev); + switch (ev.getAction() & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + mNestedScrollOffset = 0; + startNestedScroll(mAxes); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + stopNestedScroll(); + break; + default: + break; + } + return result; } @Override @@ -163,8 +209,23 @@ public boolean onTouchEvent(MotionEvent ev) { if (!isScrollEnabled()) { return false; } - - return super.onTouchEvent(ev); + boolean result = super.onTouchEvent(ev); + if (result) { + switch (ev.getAction() & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + mNestedScrollOffset = 0; + startNestedScroll(mAxes); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + mCaptured = false; + stopNestedScroll(); + break; + default: + break; + } + } + return result; } @Override @@ -244,18 +305,149 @@ public void triggerRequestLayout() { mHandler.post(mMeasureAndLayout); } - public void setOverflow(String overflow) { - if (!TextUtils.isEmpty(overflow)) { - switch (overflow) { - case "visible": - setClipChildren(false); //可以超出父亲区域 - break; - case "hidden": { - setClipChildren(true); //默认值是false - break; + @Override + public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes) { + if (!isScrollEnabled()) { + return false; + } + return (axes & mAxes) != 0; + } + + @Override + public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes) { + super.onNestedScrollAccepted(child, target, axes); + startNestedScroll(axes); + } + + @Override + public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed) { + // viewpager does not support nested scrolling, only when it cannot scroll, will the event from + // child be passed to the ancestor + if (!mCaptured) { + if (mAxes == SCROLL_AXIS_HORIZONTAL && dx != 0) { + if (HippyNestedScrollHelper.priorityOfX(target, dx) == Priority.PARENT) { + mCaptured = canScrollHorizontally(dx); + } + } else if (mAxes == SCROLL_AXIS_VERTICAL && dy != 0) { + if (HippyNestedScrollHelper.priorityOfY(target, dy) == Priority.PARENT) { + mCaptured = canScrollVertically(dy); + } + } + } + if (mCaptured) { + if (mAxes == SCROLL_AXIS_HORIZONTAL) { + fakeDragBy(-dx); + consumed[0] += dx; + } else { + fakeDragBy(-dy); + consumed[1] += dy; + } + } else if (dx != 0 || dy != 0) { + dispatchNestedPreScroll(dx, dy, consumed, null); + } + } + + @Override + public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { + // viewpager does not support nested scrolling, only when it cannot scroll, will the event from + // child be passed to the ancestor + if (!mCaptured) { + if (mAxes == SCROLL_AXIS_HORIZONTAL && dxUnconsumed != 0) { + if (HippyNestedScrollHelper.priorityOfX(target, dxUnconsumed) == Priority.SELF) { + mCaptured = canScrollHorizontally(dxUnconsumed); + } + } else if (mAxes == SCROLL_AXIS_VERTICAL && dyUnconsumed != 0) { + if (HippyNestedScrollHelper.priorityOfY(target, dyUnconsumed) == Priority.SELF) { + mCaptured = canScrollVertically(dyUnconsumed); + } + } + } + if (mCaptured) { + if (mAxes == SCROLL_AXIS_HORIZONTAL) { + fakeDragBy(-dxUnconsumed); + } else { + fakeDragBy(-dyUnconsumed); + } + } else if (dxUnconsumed != 0 || dyUnconsumed != 0) { + dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null); + } + } + + @Override + public boolean onNestedPreFling(View target, float velocityX, float velocityY) { + // Consume any fling event from child to prevent the child View from triggering non-touch + // scrolling at the same time when endFakeDrag() + return mCaptured; + } + + @Override + public void onStopNestedScroll(View child) { + // Nested scrolling API may trigger out of order, check fake dragging state to prevent + // ViewPager from throwing IllegalStateException + if (isFakeDragging()) { + endFakeDrag(); + } + mCaptured = false; + stopNestedScroll(); + } + + @Override + public void fakeDragBy(float offset) { + // Nested scrolling API may trigger out of order, check fake dragging state to prevent + // ViewPager from throwing IllegalStateException + if (isFakeDragging() || beginFakeDrag()) { + super.fakeDragBy(offset); + } + } + + @Override + protected boolean onStartDrag(boolean start) { + if (super.onStartDrag(start)) { + mCaptured = true; + return true; + } + return hasNestedScrollingParent(); + } + + @Override + protected boolean performDrag(float x) { + // Dispatch nested scrolling event only when it cannot scroll + if (!mCaptured) { + int dx = 0; + int dy = 0; + boolean horizontal = mAxes == SCROLL_AXIS_HORIZONTAL; + if (horizontal) { + dx = Math.round(mLastMotionX - x); + } else { + dy = Math.round(mLastMotionY - x); + } + if (dx == 0 && dy == 0) { + return false; + } + if (dispatchNestedPreScroll(dx, dy ,null, mScrollOffsetPair)) { + if (horizontal) { + mNestedScrollOffset += mScrollOffsetPair[0]; + mLastMotionX = x; + } else { + mNestedScrollOffset += mScrollOffsetPair[1]; + mLastMotionY = x; + } + return false; + } + mCaptured = horizontal ? horizontalCanScroll(dx) : verticalCanScroll(dy); + if (!mCaptured) { + if (dispatchNestedScroll(0, 0, dx, dy, mScrollOffsetPair)) { + if (horizontal) { + mNestedScrollOffset += mScrollOffsetPair[0]; + mLastMotionX = x; + } else { + mNestedScrollOffset += mScrollOffsetPair[1]; + mLastMotionY = x; + } } + return false; } } - invalidate(); + return super.performDrag(x); } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/viewpager/HippyViewPagerController.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/viewpager/HippyViewPagerController.java index aedee6604cc..9da6dd19ce7 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/viewpager/HippyViewPagerController.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/viewpager/HippyViewPagerController.java @@ -15,7 +15,6 @@ */ package com.tencent.mtt.hippy.views.viewpager; -import android.util.Log; import com.tencent.mtt.hippy.annotation.HippyController; import com.tencent.mtt.hippy.annotation.HippyControllerProps; import com.tencent.mtt.hippy.common.HippyArray; @@ -29,6 +28,7 @@ import android.content.Context; import android.view.View; import android.view.ViewGroup; +import com.tencent.mtt.hippy.views.view.HippyViewGroup; @SuppressWarnings({"deprecation", "unused"}) @@ -116,9 +116,14 @@ public void setPageMargin(HippyViewPager pager, float margin) { pager.setPageMargin((int) PixelUtil.dp2px(margin)); } + @HippyControllerProps(name = "offscreenPageLimit", defaultNumber = 0, defaultType = HippyControllerProps.NUMBER) + public void setOffscreenPageLimit(HippyViewPager pager, int limit) { + pager.setOffscreenPageLimit(limit); + } + @HippyControllerProps(name = NodeProps.OVERFLOW, defaultType = HippyControllerProps.STRING, defaultString = "visible") public void setOverflow(HippyViewPager pager, String overflow) { - pager.setOverflow(overflow); + HippyViewGroup.setOverflow(overflow, pager); } @Override diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/viewpager/ViewPagerPageChangeListener.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/viewpager/ViewPagerPageChangeListener.java index 8ba31980e0d..bb2278f0818 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/viewpager/ViewPagerPageChangeListener.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/viewpager/ViewPagerPageChangeListener.java @@ -46,7 +46,27 @@ public ViewPagerPageChangeListener(HippyViewPager pager) { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - mPageScrollEmitter.send(position, positionOffset); + // compute totalOffset from current position/offset to mLastPageIndex + float totalOffset = position - mLastPageIndex + positionOffset; + int toPosition; + if (totalOffset > 0) { + // scrolling forward, convert totalOffset into target position, and offset from (0, 1] + int pageDelta = (int) Math.ceil(totalOffset); + toPosition = mLastPageIndex + pageDelta; + if (pageDelta > 1) { + totalOffset -= pageDelta - 1; + } + } else if (totalOffset < 0) { + // scrolling backward, convert totalOffset into target position, and offset from [-1, 0) + int pageDelta = (int) Math.floor(totalOffset); + toPosition = mLastPageIndex + pageDelta; + if (pageDelta < -1) { + totalOffset -= pageDelta + 1; + } + } else { // not scrolled + toPosition = mLastPageIndex; + } + mPageScrollEmitter.send(toPosition, totalOffset); } @Override diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/waterfalllist/HippyWaterfallLayoutManager.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/waterfalllist/HippyWaterfallLayoutManager.java index 37416487673..763bff355cf 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/waterfalllist/HippyWaterfallLayoutManager.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/waterfalllist/HippyWaterfallLayoutManager.java @@ -121,42 +121,24 @@ public int getHeaderHeight(int index) { int[] calculateColumnHeightsBefore(int position, boolean caculateOffsetmap) { // #lizard forgives int[] columnHeights = new int[mColumns]; - SparseArray> items = new SparseArray<>(); - int n = 0; + // SparseArray> items = new SparseArray<>(); HippyWaterfallAdapter adapter = (HippyWaterfallAdapter) mRecyclerView.getAdapter(); - if (mHasContainBannerView) { - position += 1; - } - for (int i = 0; i < position; i++) { - int targetColumnIndex = 0; - for (int j = 0; j < columnHeights.length; j++) { - if (columnHeights[targetColumnIndex] > columnHeights[j]) { - targetColumnIndex = j; - } - } + int myHeight = adapter.getItemHeight(i) + adapter.getItemMaigin(RecyclerAdapter.LOCATION_TOP, i) + + adapter.getItemMaigin(RecyclerAdapter.LOCATION_BOTTOM, i); - if (mHasContainBannerView) { - if (i == 0 || i == 1) { - n = 0; - } else if (i > 1) { - n = i - 1; - } - } else { - n = i; + if (i == 0 && mHasContainBannerView) { + Arrays.fill(columnHeights, myHeight); + continue; } - - int myHeight = adapter.getItemHeight(n) + adapter - .getItemMaigin(RecyclerAdapter.LOCATION_TOP, n) - + adapter.getItemMaigin(RecyclerAdapter.LOCATION_BOTTOM, n); - RenderNode node = adapter.getItemNode(i); if (node instanceof PullFooterRenderNode) { int height = getHightestColumnHeight(columnHeights) + myHeight; Arrays.fill(columnHeights, height); } else { + int targetColumnIndex = getShortestColumnIndex(columnHeights); columnHeights[targetColumnIndex] += myHeight; } } @@ -166,51 +148,12 @@ int[] calculateColumnHeightsBefore(int position, boolean caculateOffsetmap) { // calculate the height of every column after the item with index position. public int[] calculateColumnHeightsAfter(int position) { // #lizard forgives - int[] columnHeights = new int[mColumns]; - SparseArray> items = new SparseArray<>(); - int n = 0; - HippyWaterfallAdapter adapter = (HippyWaterfallAdapter) mRecyclerView.getAdapter(); - - if (mHasContainBannerView) { - position += 1; - } - - for (int i = 0; i <= position; i++) { - int targetColumnIndex = 0; - for (int j = 0; j < columnHeights.length; j++) { - if (columnHeights[targetColumnIndex] > columnHeights[j]) { - targetColumnIndex = j; - } - } - - if (mHasContainBannerView) { - if (i == 0 || i == 1) { - n = 0; - } else if (i > 1) { - n = i - 1; - } - } else { - n = i; - } - - int myHeight = adapter.getItemHeight(n) + adapter - .getItemMaigin(RecyclerAdapter.LOCATION_TOP, n) - + adapter.getItemMaigin(RecyclerAdapter.LOCATION_BOTTOM, n); - - RenderNode node = adapter.getItemNode(i); - if (node instanceof PullFooterRenderNode) { - int height = getHightestColumnHeight(columnHeights) + myHeight; - Arrays.fill(columnHeights, height); - } else { - columnHeights[targetColumnIndex] += myHeight; - } - } - return columnHeights; + return calculateColumnHeightsBefore(position + 1, false); } public static int getShortestColumnIndex(int[] columnHeights) { int shortestColumnIndex = 0; - for (int j = 0; j < columnHeights.length; ++j) { + for (int j = 1; j < columnHeights.length; ++j) { if (columnHeights[shortestColumnIndex] > columnHeights[j]) { shortestColumnIndex = j; } @@ -375,7 +318,7 @@ protected int fill(RecyclerViewBase.Recycler recycler, RenderState renderState, int index = renderState.mCurrentPosition; int firstItemWidth = itemWidth; if (mHasContainBannerView && index == 0) { - firstItemWidth = (getWidth() - getPaddingLeft() - getPaddingRight()); + firstItemWidth = getWidth(); } // int currentRenderState = renderState.hasMore(state); View view = getNextView(recycler, renderState, state); @@ -414,7 +357,9 @@ protected int fill(RecyclerViewBase.Recycler recycler, RenderState renderState, if (params instanceof LayoutParams) { int targetColumn = ((WaterFallRenderState) mRenderState).targetColumn; ((LayoutParams) params).mLocateAtColumn = targetColumn; - if (!isFooterView(view)) { + if (mHasContainBannerView && index == 0) { + setChildPadding(0, index, view, targetColumn); + } else if (!isFooterView(view)) { setChildPadding(itemGapH, index, view, targetColumn); } } @@ -436,7 +381,7 @@ protected int fill(RecyclerViewBase.Recycler recycler, RenderState renderState, contentLayout = new ViewGroup.LayoutParams(params); } if (!mHasContainBannerView || index != 0 || !mBannerViewMatch) { - if (!isFooterView(view)) { + if (!isFooterView(view) && !(mHasContainBannerView && index == 0)) { if (contentLayout.width > 0) { contentLayout.width -= itemGapH; } @@ -518,8 +463,12 @@ protected int fill(RecyclerViewBase.Recycler recycler, RenderState renderState, } else { // the layout derection of waterfall will not care about // left-hander. - left = ((WaterFallRenderState) mRenderState).targetColumn * itemWidth - + getPaddingLeft(); + if (mHasContainBannerView && index == 0) { + left = 0; + } else { + left = ((WaterFallRenderState) mRenderState).targetColumn * itemWidth + + getPaddingLeft(); + } right = left + mOrientationHelper.getDecoratedMeasurementInOther(view); if (renderState.mLayoutDirection == RenderState.LAYOUT_START) { // renderState.mOffset = a; diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/waterfalllist/HippyWaterfallView.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/waterfalllist/HippyWaterfallView.java index 864344c2483..678933607d1 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/waterfalllist/HippyWaterfallView.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/waterfalllist/HippyWaterfallView.java @@ -158,6 +158,14 @@ public void setListData() { @Override public void run() { dispatchLayout(); + // check end reached + int itemCount = mLayout.getItemCount(); + boolean hasContent = itemCount > 1 + || (itemCount == 1 && !(mAdapter.getItemNode(0) instanceof PullFooterRenderNode)); + if (hasContent) { + mEndChecker.reset(); + mEndChecker.check(HippyWaterfallView.this); + } } }; } @@ -264,7 +272,7 @@ public void onScrollStateChanged(int oldState, int newState) { @Override public void onScrolled(int x, int y) { super.onScrolled(x, y); - mEndChecker.onScroll(this, y); + mEndChecker.check(this); } public void startRefresh(int type) { @@ -458,9 +466,14 @@ public ContentHolder onCreateContentViewWithPos(ViewGroup parent, int position, NodeHolder contentHolder = new NodeHolder(); RenderNode contentViewRenderNode = mHippyContext.getRenderManager() .getRenderNode(getId()).getChildAt(position); - contentViewRenderNode.setLazy(false); - contentHolder.mContentView = contentViewRenderNode.createViewRecursive(); + if (!contentViewRenderNode.isIsLazyLoad()) { + int id = contentViewRenderNode.getId(); + contentHolder.mContentView = mHippyContext.getRenderManager().getControllerManager().findView(id); // contentViewRenderNode.findView(id); + } else { + contentViewRenderNode.setLazy(false); + contentHolder.mContentView = contentViewRenderNode.createViewRecursive(); + } FooterUtil.checkFooterBinding(mParentRecyclerView, contentHolder.mContentView); contentHolder.mBindNode = contentViewRenderNode; diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/waterfalllist/HippyWaterfallViewController.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/waterfalllist/HippyWaterfallViewController.java index 92f54406d1a..03cee3cef17 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/waterfalllist/HippyWaterfallViewController.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/waterfalllist/HippyWaterfallViewController.java @@ -164,6 +164,11 @@ public void setOnExposureReport(HippyWaterfallView listView, boolean enable) { listView.setEnableExposureReport(enable); } + @HippyControllerProps(name = "scrollEventThrottle", defaultType = HippyControllerProps.NUMBER, defaultNumber = 30.0D) + public void setScrollEventThrottle(HippyWaterfallView listView, int scrollEventThrottle) { + listView.setScrollEventThrottle(scrollEventThrottle); + } + // #lizard forgives @Override public void dispatchFunction(HippyWaterfallView listView, String functionName, diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/waterfalllist/WaterfallEndChecker.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/waterfalllist/WaterfallEndChecker.java index 1f5ca00e561..70f1b06c091 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/waterfalllist/WaterfallEndChecker.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/waterfalllist/WaterfallEndChecker.java @@ -1,7 +1,23 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.tencent.mtt.hippy.views.waterfalllist; import android.view.View; import com.tencent.mtt.hippy.views.waterfalllist.HippyWaterfallView.HippyWaterfallEvent; +import com.tencent.mtt.supportui.views.recyclerview.RecyclerViewBase; /** * @author hengyangji @@ -10,18 +26,28 @@ public class WaterfallEndChecker { private boolean isVerticalEnd = false; - public void onScroll(HippyWaterfallView waterfallView, int y) { - boolean currentVerticalEnd = checkVerticalEnd(waterfallView, y); + public void reset() { + isVerticalEnd = false; + } + + public void check(HippyWaterfallView waterfallView) { + boolean currentVerticalEnd = checkVerticalEnd(waterfallView); if (!isVerticalEnd && currentVerticalEnd) { new HippyWaterfallEvent("onEndReached").send(waterfallView, null); } isVerticalEnd = currentVerticalEnd; } - private boolean checkVerticalEnd(HippyWaterfallView waterfallView, int y) { + private boolean checkVerticalEnd(HippyWaterfallView waterfallView) { HippyWaterfallLayoutManager layoutManager = (HippyWaterfallLayoutManager)waterfallView.getLayoutManager(); int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition(); - boolean scrollToLastItem = lastVisibleItemPosition == layoutManager.getItemCount() - 1; + RecyclerViewBase.Adapter adapter = waterfallView.getAdapter(); + int preloadItemNumber = adapter == null ? 0 : adapter.getPreloadThresholdInItemNumber(); + int endPosition = layoutManager.getItemCount() - 1; + if (lastVisibleItemPosition > endPosition - preloadItemNumber) { + return true; + } + boolean scrollToLastItem = lastVisibleItemPosition == endPosition; if (scrollToLastItem) { //滑到最后一位了 View lastView = waterfallView.findViewByPosition(lastVisibleItemPosition); return lastView.getBottom() <= waterfallView.getBottom(); diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/webview/HippyWebView.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/webview/HippyWebView.java index 97b1b421782..e26ab64cc51 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/webview/HippyWebView.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/webview/HippyWebView.java @@ -53,6 +53,7 @@ private void initWebView() { final HippyViewEvent mEventonLoadEnd = new HippyViewEvent("onLoadEnd"); final HippyViewEvent mEventonLoadStart = new HippyViewEvent("onLoadStart"); final String mMessageUrlPre = "hippy://postMessage?data="; + private boolean mLoadEndTriggered; @Override public void onReceivedError(WebView view, WebResourceRequest request, @@ -66,7 +67,16 @@ public void onReceivedError(WebView view, WebResourceRequest request, event.pushInt("errorCode", Integer.MAX_VALUE); } mEventOnError.send(HippyWebView.this, event); - super.onReceivedError(view, request, error); + if (request.isForMainFrame()) { + String msg = error.getErrorCode() + "," + error.getDescription().toString(); + notifyLoadEnd(request.getUrl().toString(), false, msg); + } + } + + @Override + public void onReceivedError(WebView view, int errorCode, String description, + String failingUrl) { + notifyLoadEnd(failingUrl, false, errorCode + "," + description); } @Override @@ -100,16 +110,27 @@ public void onPageFinished(WebView view, String url) { HippyMap event = new HippyMap(); event.pushString("url", url); mEventonLoad.send(HippyWebView.this, event); - mEventonLoadEnd.send(HippyWebView.this, event); - super.onPageFinished(view, url); + notifyLoadEnd(url, true, ""); + } + + private void notifyLoadEnd(String url, boolean success, String msg) { + if (mLoadEndTriggered) { + return; + } + mLoadEndTriggered = true; + HippyMap event = new HippyMap(); + event.pushString("url", url); + event.pushBoolean("success", success); + event.pushString("error", msg); + mEventonLoadEnd.send(HippyWebView.this, event); } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { + mLoadEndTriggered = false; HippyMap event = new HippyMap(); event.pushString("url", url); mEventonLoadStart.send(HippyWebView.this, event); - super.onPageStarted(view, url, favicon); } }); mWebView.setWebChromeClient(new WebChromeClient()); @@ -143,4 +164,9 @@ public NativeGestureDispatcher getGestureDispatcher() { @Override public void setGestureDispatcher(NativeGestureDispatcher dispatcher) { } + + @Override + public void setBackgroundColor(int color) { + mWebView.setBackgroundColor(color); + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/websocket/HybiParser.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/websocket/HybiParser.java index c43c6f5cd1c..bdffbef70c7 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/websocket/HybiParser.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/websocket/HybiParser.java @@ -114,7 +114,7 @@ public void start(HappyDataInputStream stream) throws IOException { break; } } - mClient.getListener().onDisconnect(0, "EOF"); + mClient.getListener().onDisconnect(0, WebSocketClient.DISCONNECT_REASON_EOF); } private void parseOpcode(byte data) throws ProtocolError { diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/websocket/WebSocketClient.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/websocket/WebSocketClient.java index b86900fa36e..7f9f372938e 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/websocket/WebSocketClient.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/websocket/WebSocketClient.java @@ -37,6 +37,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; @SuppressWarnings({"unused", "FieldCanBeLocal"}) public class WebSocketClient { @@ -53,13 +54,17 @@ public class WebSocketClient { private final Handler mHandler; private final List

mExtraHeaders; private final HybiParser mParser; - private boolean mConnected; + private AtomicBoolean mConnected; + public static final String DISCONNECT_REASON_EOF = "EOF"; + public static final String DISCONNECT_REASON_SSL = "SSL"; + public static final String DISCONNECT_REASON_CONNECT = "CONNECT"; + public static final String DISCONNECT_REASON_CLOSE = "closed"; public WebSocketClient(URI uri, WebSocketListener listener, List
extraHeaders) { mURI = uri; mListener = listener; mExtraHeaders = extraHeaders; - mConnected = false; + mConnected = new AtomicBoolean(false); mParser = new HybiParser(this); mHandlerThread = new HandlerThread("websocket-thread"); @@ -82,8 +87,8 @@ public void connect() { public void run() { try { int port = (mURI.getPort() != -1) ? mURI.getPort() - : ((mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? 443 - : 80); + : ((mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? 443 + : 80); String path = TextUtils.isEmpty(mURI.getPath()) ? "/" : mURI.getPath(); if (!TextUtils.isEmpty(mURI.getQuery())) { @@ -94,9 +99,9 @@ public void run() { URI origin = new URI(originScheme, "//" + mURI.getHost(), null); SocketFactory factory = - (mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) - ? getSSLSocketFactory() - : SocketFactory.getDefault(); + (mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) + ? getSSLSocketFactory() + : SocketFactory.getDefault(); mSocket = factory.createSocket(mURI.getHost(), port); PrintWriter out = new PrintWriter(mSocket.getOutputStream()); @@ -117,7 +122,7 @@ public void run() { out.flush(); HybiParser.HappyDataInputStream stream = new HybiParser.HappyDataInputStream( - mSocket.getInputStream()); + mSocket.getInputStream()); // Read HTTP response status line. StatusLine statusLine = parseStatusLine(readLine(stream)); @@ -125,8 +130,8 @@ public void run() { throw new ConnectException("WebSocketClient received no reply from server."); } else if (statusLine.code != SC_SWITCHING_PROTOCOLS) { throw new ConnectException( - "WebSocketClient connect error: code=" + statusLine.code + ",message=" - + statusLine.message); + "WebSocketClient connect error: code=" + statusLine.code + ",message=" + + statusLine.message); } // Read HTTP response headers. @@ -140,32 +145,36 @@ public void run() { throw new ConnectException("SHA-1 algorithm not found"); } else if (!expected.equals(header.getValue().trim())) { throw new ConnectException( - "Invalid Sec-WebSocket-Accept, expected: " + expected + ", got: " + header - .getValue()); + "Invalid Sec-WebSocket-Accept, expected: " + expected + ", got: " + header + .getValue()); } } } mListener.onConnect(); - - mConnected = true; + mConnected.set(true); // Now decode websocket frames. mParser.start(stream); } catch (EOFException ex) { Log.d(TAG, "WebSocket EOF!", ex); - mListener.onDisconnect(0, "EOF"); - mConnected = false; + mListener.onDisconnect(0, DISCONNECT_REASON_EOF); + mConnected.set(false); } catch (SSLException ex) { // Connection reset by peer Log.d(TAG, "Websocket SSL error!", ex); - mListener.onDisconnect(0, "SSL"); - mConnected = false; + mListener.onDisconnect(0, DISCONNECT_REASON_SSL); + mConnected.set(false); + } catch (ConnectException ex) { + // WebSocketClient received no reply from server. + Log.d(TAG, "Websocket Connect error!", ex); + mListener.onDisconnect(0, DISCONNECT_REASON_CONNECT); + mConnected.set(false); } catch (Throwable ex) { mListener.onError(new Exception(ex)); } finally { - if (!mConnected && mSocket != null) { + if (!mConnected.get() && mSocket != null) { try { mSocket.close(); } catch (Throwable ex) { @@ -191,10 +200,10 @@ public void run() { Log.d(TAG, "Error while disconnecting", ex); mListener.onError(new Exception(ex)); } - mListener.onDisconnect(0, "closed"); + mListener.onDisconnect(0, DISCONNECT_REASON_CLOSE); mSocket = null; } - mConnected = false; + mConnected.set(false); } }); } @@ -205,7 +214,7 @@ public void send(String data) { } public void send(byte[] data) { - sendFrame(mParser.frame(data)); + sendFrame(data); } public void requestClose(int code, String reason) { @@ -214,7 +223,7 @@ public void requestClose(int code, String reason) { } public boolean isConnected() { - return mConnected; + return mConnected.get(); } private StatusLine parseStatusLine(String line) throws IOException { @@ -291,7 +300,7 @@ public void run() { } private SSLSocketFactory getSSLSocketFactory() - throws NoSuchAlgorithmException, KeyManagementException { + throws NoSuchAlgorithmException, KeyManagementException { SSLContext context = SSLContext.getInstance("TLS"); context.init(null, sTrustManagers, null); return context.getSocketFactory(); diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/footer/FooterExposureHelper.java b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/footer/FooterExposureHelper.java deleted file mode 100644 index 93dee1d719c..00000000000 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/footer/FooterExposureHelper.java +++ /dev/null @@ -1,123 +0,0 @@ -/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. - * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.tencent.mtt.nxeasy.recyclerview.helper.footer; - -import android.graphics.Rect; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; -import android.view.View; -import android.view.View.OnAttachStateChangeListener; - -public class FooterExposureHelper extends RecyclerView.OnScrollListener implements - OnAttachStateChangeListener { - - private View exposureView; - private IFooterLoadMoreListener footerListener; - private boolean isViewVisible = false; - private float visibleRate = 0.3f; - private Runnable checkVisibleRunnable; - - public FooterExposureHelper() { - checkVisibleRunnable = new Runnable() { - @Override - public void run() { - if (isViewChangeToVisible(exposureView)) { - notifyFooterAppeared(); - } - } - }; - } - - /** - * 设置显示view的面积伐值,超过比例就回调显示 - * - * @param visibleRate (0,1] 1: 100%显示 - */ - public void setVisibleRate(float visibleRate) { - if (visibleRate > 0) { - this.visibleRate = Math.min(visibleRate, 1); - } - } - - /** - * @param exposureView 设置需要监控曝光的View - */ - public void setExposureView(View exposureView) { - if (this.exposureView != null) { - this.exposureView.removeCallbacks(checkVisibleRunnable); - this.exposureView.removeOnAttachStateChangeListener(this); - } - isViewVisible = false; - this.exposureView = exposureView; - if (this.exposureView != null) { - this.exposureView.addOnAttachStateChangeListener(this); - } - } - - public void setFooterListener(IFooterLoadMoreListener footerListener) { - this.footerListener = footerListener; - } - - /** - * 判断一个view在屏幕上是否可见 - */ - public boolean isViewChangeToVisible(View view) { - if (isViewVisible) { - return false; - } - if (!view.isShown()) { - isViewVisible = false; - return false; - } - Rect bounds = new Rect(); - boolean ret = view.getGlobalVisibleRect(bounds); - int totalArea = view.getWidth() * view.getHeight(); - - if (!ret || totalArea == 0) { - isViewVisible = false; - return false; - } - int viewedArea = bounds.width() * bounds.height(); - isViewVisible = viewedArea * 1f / totalArea > visibleRate; - return isViewVisible; - } - - void notifyFooterAppeared() { - if (footerListener != null) { - footerListener.onFooterLoadMore(); - } - } - - @Override - public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { - //onLayout排版的时候会调用过来,这里不能在排版的时候去请求数据,需要post一下 - if (exposureView != null) { - exposureView.removeCallbacks(checkVisibleRunnable); - exposureView.post(checkVisibleRunnable); - } - } - - @Override - public void onViewDetachedFromWindow(View v) { - isViewVisible = false; - } - - @Override - public void onViewAttachedToWindow(View v) { - - } -} diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/footer/IFooterLoadMoreListener.java b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/footer/IFooterLoadMoreListener.java deleted file mode 100644 index bf0cbcc9101..00000000000 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/footer/IFooterLoadMoreListener.java +++ /dev/null @@ -1,22 +0,0 @@ -/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. - * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.tencent.mtt.nxeasy.recyclerview.helper.footer; - -public interface IFooterLoadMoreListener { - - void onFooterLoadMore(); -} diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/footer/IFooterLoadingView.java b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/footer/IFooterLoadingView.java deleted file mode 100644 index ef96e99abef..00000000000 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/footer/IFooterLoadingView.java +++ /dev/null @@ -1,33 +0,0 @@ -/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. - * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.tencent.mtt.nxeasy.recyclerview.helper.footer; - -import android.view.View; - -public interface IFooterLoadingView { - - int LOAD_STATUS_NONE = 0; - int LOAD_STATUS_LOADING = 1; - int LOAD_STATUS_FAILED = 2; - int LOAD_STATUS_END = 3; - - View getView(); - - int getHeight(); - - void setLoadingStatus(int loadingStatus); -} diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/header/HeaderRefreshHelper.java b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/header/HeaderRefreshHelper.java deleted file mode 100644 index e2fc4dc5bda..00000000000 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/header/HeaderRefreshHelper.java +++ /dev/null @@ -1,284 +0,0 @@ -/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. - * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.tencent.mtt.nxeasy.recyclerview.helper.header; - -import android.animation.Animator; -import android.animation.ValueAnimator; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnTouchListener; -import android.view.ViewConfiguration; -import android.view.ViewGroup.LayoutParams; -import com.tencent.mtt.nxeasy.recyclerview.helper.AnimatorListenerBase; - -public class HeaderRefreshHelper implements OnTouchListener { - - public static final int DURATION = 200; - protected View headerView; - protected IHeaderRefreshView headerRefreshView; - protected float lastRawY = -1; - protected float downRawY = -1; - protected boolean enable = true; - /// header 被下拉过程中显示出来 - private boolean isHeaderDragShowing; - private int refreshStatus = -1; - private IHeaderRefreshListener headerRefreshListener; - private ValueAnimator animator; - private ILayoutRequester layoutRequester; - private IHeaderStatusListener headerStatusListener; - - public void setEnable(boolean enable) { - this.enable = enable; - } - - public void setHeaderRefreshListener(IHeaderRefreshListener headerRefreshListener) { - this.headerRefreshListener = headerRefreshListener; - } - - public void setHeaderStatusListener(IHeaderStatusListener headerStatusListener) { - this.headerStatusListener = headerStatusListener; - } - - public void setLayoutRequester(ILayoutRequester layoutRequester) { - this.layoutRequester = layoutRequester; - } - - public void setHeaderRefreshView(IHeaderRefreshView headerLoadingView) { - this.headerRefreshView = headerLoadingView; - headerView = this.headerRefreshView.getView(); - setRefreshStatus(IHeaderRefreshView.HEADER_STATUS_FOLDED); - } - - public void onRefreshDone() { - if (refreshStatus == IHeaderRefreshView.HEADER_STATUS_REFRESHING) { - setRefreshStatus(IHeaderRefreshView.HEADER_STATUS_TO_FOLD); - smoothScrollTo(getVisibleHeight(), 0); - } - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - if (!enable) { - return false; - } - if (lastRawY == -1) { - lastRawY = event.getRawY(); - } - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - lastRawY = event.getRawY(); - downRawY = event.getRawY(); - break; - case MotionEvent.ACTION_MOVE: - //下拉的时候除以2,放慢拉动的速度,调节拉动的手感 - int deltaY = (int) (event.getRawY() - lastRawY) / 2; - lastRawY = event.getRawY(); - if (isStartMove(event) && canHandleTouchEvent()) { - endAnimation(); - setVisibleHeight(deltaY + getVisibleHeight()); - isHeaderDragShowing = isHeaderDragShowing || getVisibleHeight() > 0; - if (isHeaderDragShowing) { - onMove(); - } - } - break; - default: - isHeaderDragShowing = false; - lastRawY = -1; - downRawY = -1; - if (canHandleTouchEvent()) { - onRelease(); - } - break; - } - return isHeaderDragShowing && getVisibleHeight() > 0; - } - - private boolean isStartMove(MotionEvent event) { - return Math.abs(event.getRawY() - downRawY - getTouchSlop()) > 0; - } - - private int getTouchSlop() { - final ViewConfiguration vc = ViewConfiguration.get(headerView.getContext()); - return vc.getScaledTouchSlop(); - } - - /** - * 松口手后,需要回弹,可能进行两个状态的流转 - */ - private void onRelease() { - if (refreshStatus == IHeaderRefreshView.HEADER_STATUS_DRAGGING) { - if (isViewExposure(headerView)) { - setRefreshStatus(IHeaderRefreshView.HEADER_STATUS_DRAG_TO_REFRESH); - } else { - setRefreshStatus(IHeaderRefreshView.HEADER_STATUS_TO_FOLD); - } - } - if (isViewExposure(headerView)) { - smoothScrollTo(getVisibleHeight(), headerRefreshView.getContentHeight()); - } else { - smoothScrollTo(getVisibleHeight(), 0); - } - } - - private void setRefreshStatus(int newStatus) { - if (headerStatusListener != null) { - headerStatusListener.onHeaderStatusChanged(refreshStatus, newStatus); - } - this.refreshStatus = newStatus; - } - - private void onMove() { - setRefreshStatus(IHeaderRefreshView.HEADER_STATUS_DRAGGING); - headerRefreshView.onStartDrag(); - } - - /** - * 下拉之后,当正在刷新的时候,将位置从下拉到的位置恢复规定的位置的动画 - * - * @param destHeight 规定的高度 - */ - private void smoothScrollTo(int fromHeight, int destHeight) { - endAnimation(); - animator = ValueAnimator.ofInt(fromHeight, destHeight); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - setVisibleHeight((int) animation.getAnimatedValue()); - } - }); - animator.addListener(new AnimatorListenerBase() { - @Override - public void onAnimationEnd(Animator animation) { - if (isGoingToRefresh()) { - gotoRefresh(); - } - if (refreshStatus == IHeaderRefreshView.HEADER_STATUS_TO_FOLD) { - setFolded(); - } - } - }); - animator.setDuration(DURATION).start(); - } - - void gotoRefresh() { - headerRefreshListener.onHeaderRefreshing(refreshStatus); - setRefreshStatus(IHeaderRefreshView.HEADER_STATUS_REFRESHING); - headerRefreshView.onRefreshing(); - } - - private void setFolded() { - setRefreshStatus(IHeaderRefreshView.HEADER_STATUS_FOLDED); - headerRefreshView.onFolded(); - } - - - /** - * 返回当前的可视高度 - */ - public int getVisibleHeight() { - if (!enable) { - return 0; - } - LayoutParams layoutParams = headerView.getLayoutParams(); - if (layoutParams == null) { - return 0; - } - return layoutParams.height; - } - - private void setVisibleHeight(int height) { - LayoutParams layoutParams = headerView.getLayoutParams(); - layoutParams.height = Math.max(height, 0); - headerView.setLayoutParams(layoutParams); - if (layoutRequester != null) { - layoutRequester.requestLayout(); - } - headerRefreshView.onHeaderHeightChanged(Math.max(getVisibleHeight(), 0)); - } - - private boolean canHandleTouchEvent() { - return headerView.isShown(); - } - - /** - * 判断View是否已经完全显示出来 - */ - private boolean isViewExposure(View view) { - return view.getHeight() >= headerRefreshView.getContentHeight(); - } - - /** - * 触发刷新 - */ - public void triggerRefresh() { - if (refreshStatus == IHeaderRefreshView.HEADER_STATUS_FOLDED) { - setRefreshStatus(IHeaderRefreshView.HEADER_STATUS_CLICK_TO_REFRESH); - smoothScrollTo(0, headerRefreshView.getContentHeight()); - } - } - - public void triggerRefresh(boolean hasAnimation) { - if (hasAnimation) { - triggerRefresh(); - } else { - setVisibleHeight(headerRefreshView.getContentHeight()); - gotoRefresh(); - } - } - - /** - * 是否处于手动的拖动过程中 - * - * @return - */ - public boolean isDragging() { - return refreshStatus == IHeaderRefreshView.HEADER_STATUS_DRAGGING; - } - - public boolean isGoingToRefresh() { - return refreshStatus == IHeaderRefreshView.HEADER_STATUS_DRAG_TO_REFRESH || - refreshStatus == IHeaderRefreshView.HEADER_STATUS_CLICK_TO_REFRESH; - } - - /** - * 改变header的高度 - * - * @param dy dy>0 header的高度变小,反之变大 - */ - public void rollBackHeaderHeight(int dy) { - setVisibleHeight(getVisibleHeight() - dy); - } - - /** - * 收起header,将状态置为folded状态 - */ - public void reset() { - endAnimation(); - setVisibleHeight(0); - setFolded(); - } - - private void endAnimation() { - if (animator != null) { - animator.removeAllListeners(); - animator.removeAllUpdateListeners(); - animator.end(); - animator = null; - } - } -} diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/header/IHeaderRefreshListener.java b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/header/IHeaderRefreshListener.java deleted file mode 100644 index 2daefe5e067..00000000000 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/header/IHeaderRefreshListener.java +++ /dev/null @@ -1,26 +0,0 @@ -/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. - * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.tencent.mtt.nxeasy.recyclerview.helper.header; - -public interface IHeaderRefreshListener { - - /** - * @param refreshWay 刷新触发的方式,目前只要两种方式 {@link IHeaderRefreshView#HEADER_STATUS_CLICK_TO_REFRESH} - * {@link IHeaderRefreshView#HEADER_STATUS_DRAG_TO_REFRESH} - */ - void onHeaderRefreshing(int refreshWay); -} diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/header/IHeaderRefreshView.java b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/header/IHeaderRefreshView.java deleted file mode 100644 index b763d5a1245..00000000000 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/header/IHeaderRefreshView.java +++ /dev/null @@ -1,48 +0,0 @@ -/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. - * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.tencent.mtt.nxeasy.recyclerview.helper.header; - -import android.view.View; - -public interface IHeaderRefreshView { - - /** - * 状态机: 1:HEADER_STATUS_FOLDED -> HEADER_STATUS_DRAGGING 2:HEADER_STATUS_DRAGGING-> - * HEADER_STATUS_TO_REFRESH ,或者HEADER_STATUS_DRAGGING-> HEADER_STATUS_TO_FOLD - * 3:HEADER_STATUS_DRAG_TO_REFRESH -> HEADER_STATUS_REFRESHING 4:HEADER_STATUS_CLICK_TO_REFRESH -> - * HEADER_STATUS_REFRESHING 5:HEADER_STATUS_REFRESHING -> HEADER_STATUS_TO_FOLD - * 6:HEADER_STATUS_TO_FOLD -> HEADER_STATUS_FOLDED - */ - int HEADER_STATUS_FOLDED = 0;//收起状态 - int HEADER_STATUS_DRAGGING = 1;//拖动状态 - int HEADER_STATUS_DRAG_TO_REFRESH = 2;//下拉拖动触发刷新 - int HEADER_STATUS_CLICK_TO_REFRESH = 3;//外部点击触发刷新 - int HEADER_STATUS_REFRESHING = 4;//正在刷新 - int HEADER_STATUS_TO_FOLD = 5;//去向收起 - - View getView(); - - void onStartDrag(); - - void onHeaderHeightChanged(int sumOffset); - - void onRefreshing(); - - int getContentHeight(); - - void onFolded(); -} diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/header/IHeaderStatusListener.java b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/header/IHeaderStatusListener.java deleted file mode 100644 index b7ec17007d8..00000000000 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/header/IHeaderStatusListener.java +++ /dev/null @@ -1,22 +0,0 @@ -/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. - * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.tencent.mtt.nxeasy.recyclerview.helper.header; - -public interface IHeaderStatusListener { - - void onHeaderStatusChanged(int oldStatus, int newStatus); -} diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/header/ILayoutRequester.java b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/header/ILayoutRequester.java deleted file mode 100644 index 54fc3e46ec2..00000000000 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/header/ILayoutRequester.java +++ /dev/null @@ -1,22 +0,0 @@ -/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. - * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.tencent.mtt.nxeasy.recyclerview.helper.header; - -public interface ILayoutRequester { - - void requestLayout(); -} diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IHeaderAttachListener.java b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IHeaderAttachListener.java deleted file mode 100644 index 62183c40b0a..00000000000 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IHeaderAttachListener.java +++ /dev/null @@ -1,31 +0,0 @@ -/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. - * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tencent.mtt.nxeasy.recyclerview.helper.skikcy; - -import androidx.recyclerview.widget.RecyclerView.ViewHolder; -import android.view.View; - -public interface IHeaderAttachListener { - - /** - * header被摘下来,需要对header进行还原或者回收对处理 - * - * @param aboundHeader HeaderView对应的Holder - * @param currentHeaderView headerView的实体内容 - */ - void onHeaderDetached(ViewHolder aboundHeader, View currentHeaderView); - -} diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IHeaderHost.java b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IHeaderHost.java deleted file mode 100644 index 6a89970ff3f..00000000000 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IHeaderHost.java +++ /dev/null @@ -1,29 +0,0 @@ -/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. - * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tencent.mtt.nxeasy.recyclerview.helper.skikcy; - -import android.view.View; -import android.view.ViewTreeObserver.OnGlobalLayoutListener; -import android.widget.FrameLayout.LayoutParams; - -public interface IHeaderHost { - - void attachHeader(View headerView, LayoutParams layoutParams); - - void addOnLayoutListener(OnGlobalLayoutListener listener); - - void removeOnLayoutListener(OnGlobalLayoutListener listener); -} diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/StickyHeaderHelper.java b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/StickyHeaderHelper.java deleted file mode 100644 index b02b693c595..00000000000 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/StickyHeaderHelper.java +++ /dev/null @@ -1,242 +0,0 @@ -/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. - * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.tencent.mtt.nxeasy.recyclerview.helper.skikcy; - -import static androidx.recyclerview.widget.RecyclerView.HORIZONTAL; -import static androidx.recyclerview.widget.RecyclerView.VERTICAL; - -import androidx.recyclerview.widget.EasyRecyclerView; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.OnScrollListener; -import androidx.recyclerview.widget.RecyclerView.ViewHolder; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.widget.FrameLayout.LayoutParams; - -public class StickyHeaderHelper extends OnScrollListener implements - ViewTreeObserver.OnGlobalLayoutListener { - - private static final int INVALID_POSITION = -1; - private final IHeaderAttachListener headerAttachListener; - private EasyRecyclerView recyclerView; - private IStickyItemsProvider stickyItemsProvider; - private StickyViewFactory stickyViewFactory; - private ViewHolder headerOrgViewHolder; - private boolean orgViewHolderCanRecyclable = false; - private View currentHeaderView; - private int orientation; - private int currentStickPos = -1; - private IHeaderHost headerHost; - - public StickyHeaderHelper(final EasyRecyclerView recyclerView, - IStickyItemsProvider stickyItemsProvider, - IHeaderAttachListener headerAttachListener, IHeaderHost headerHost) { - this.recyclerView = recyclerView; - this.headerAttachListener = headerAttachListener; - this.stickyItemsProvider = stickyItemsProvider; - stickyViewFactory = new StickyViewFactory(recyclerView); - this.headerHost = headerHost; - orientation = recyclerView.getLayoutManager().canScrollVertically() ? VERTICAL : HORIZONTAL; - } - - /** - * 1、寻找stickyHeader 2、挂载stickyHeader 3、设置stickyHeader的偏移 - */ - @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - int newStickyPosition = getStickyItemPosition(); - if (currentStickPos != newStickyPosition) { - detachSticky(); - attachSticky(newStickyPosition); - } - offsetSticky(); - } - - /** - * 如果当前stickHolder和新的stickyHolder 不一样,那么把当前的stickyHolder删除掉,并还原HeaderView的Translation - */ - private void detachSticky() { - if (headerOrgViewHolder != null) { - removeViewFromParent(this.currentHeaderView); - currentHeaderView.setTranslationY(0); - currentHeaderView.setTranslationX(0); - returnHeaderBackToList(); - headerHost.removeOnLayoutListener(this); - } - currentStickPos = -1; - headerOrgViewHolder = null; - } - - /** - * 还原Header到List中去 1、ViewHolder正好是之前的ViewHolder,直接将headerView返回给headerOrgContainer - * 2、position相同,但是ViewHolder已经是不同了,出现在header的Item滑动到屏幕外,又滑回来,重新创建了一个ViewHolder - * 3、对于被顶出去的headView,是无法还原到list中的,需要把headView进行回收处理,如果不回收,Hippy场景无法重新创建View - */ - private void returnHeaderBackToList() { - headerOrgViewHolder.setIsRecyclable(orgViewHolderCanRecyclable); - if (headerAttachListener != null) { - headerAttachListener.onHeaderDetached(headerOrgViewHolder, currentHeaderView); - } else { - ViewHolder viewHolderToReturn = recyclerView - .findViewHolderForAdapterPosition(headerOrgViewHolder.getAdapterPosition()); - if (viewHolderToReturn != null && viewHolderToReturn.itemView instanceof ViewGroup) { - ViewGroup itemView = (ViewGroup) viewHolderToReturn.itemView; - //已经有孩子了,就不要加了,这个可能是新创建的ViewHolder已经有了内容 - if (itemView.getChildCount() <= 0) { - itemView.addView(this.currentHeaderView); - } - } - } - } - - /** - * 将stickyItemPosition对应的View挂载到RecyclerView的父亲上面 - */ - private void attachSticky(int newStickyPosition) { - if (newStickyPosition != INVALID_POSITION) { - headerOrgViewHolder = stickyViewFactory.getHeaderForPosition(newStickyPosition); - currentStickPos = newStickyPosition; - Log.d("returnHeader", "attachSticky:" + headerOrgViewHolder); - currentHeaderView = ((ViewGroup) headerOrgViewHolder.itemView).getChildAt(0); - removeViewFromParent(currentHeaderView); - //内容被取走了,不能被回收,避免view滑出屏幕,回收再利用,此时已经不能再被别人用了 - orgViewHolderCanRecyclable = headerOrgViewHolder.isRecyclable(); - headerOrgViewHolder.setIsRecyclable(false); - currentHeaderView.setVisibility(View.INVISIBLE); - LayoutParams layoutParams = new LayoutParams( - LayoutParams.MATCH_PARENT, 0); - ViewGroup.LayoutParams lp = headerOrgViewHolder.itemView.getLayoutParams(); - layoutParams.height = lp != null ? lp.height : LayoutParams.WRAP_CONTENT; - headerHost.addOnLayoutListener(this); - headerHost.attachHeader(currentHeaderView, layoutParams); - } - } - - /** - * 设置吸顶的View的偏移 在下一个吸顶view和当前吸顶的view交汇的时候,需要把当前吸顶view往上面移动,慢慢会把当前的吸顶view顶出屏幕 - */ - private void offsetSticky() { - if (headerOrgViewHolder != null) { - float offset = getOffset(findNextSticky(currentStickPos)); - if (orientation == VERTICAL) { - currentHeaderView.setTranslationY(offset); - } else { - currentHeaderView.setTranslationX(offset); - } - } - } - - /** - * 找到屏幕中,下一个即将吸顶的view,主要用于计算当前吸顶的HeaderView的Offset, 下一个即将吸顶的View会慢慢把当前正在吸顶的HeaderView慢慢顶出屏幕外 - */ - private View findNextSticky(int currentStickyPos) { - for (int i = 0; i < recyclerView.getChildCount(); i++) { - View nextStickyView = recyclerView.getChildAt(i); - int nextStickyPos = recyclerView.getChildLayoutPosition(nextStickyView); - if (nextStickyPos > currentStickyPos && stickyItemsProvider - .isStickyPosition(nextStickyPos)) { - return nextStickyView; - } - } - return null; - } - - /** - * 当nextStickyView和当前stickyView重叠的时候,是应该把当前的view移出屏幕外 支持水平排版和垂直排版 - */ - private float getOffset(View nextStickyView) { - float offset = 0; - View stickView = this.currentHeaderView; - if (stickView != null && nextStickyView != null) { - if (orientation == VERTICAL) { - if (nextStickyView.getY() < stickView.getHeight()) { - offset = nextStickyView.getY() - stickView.getHeight(); - } - } else { - if (stickView.isShown()) { - offset = -stickView.getWidth(); - } else if (nextStickyView.getX() < stickView.getWidth()) { - offset = stickView.getX() - stickView.getWidth(); - } - } - } - return offset; - } - - /** - * 高度确定后,设置HeaderView为可见状态,并且重新刷新offset的正确位置,解决下拉header上屏的闪烁问题 - */ - @Override - public void onGlobalLayout() { - if (currentHeaderView != null) { - currentHeaderView.setVisibility(View.VISIBLE); - offsetSticky(); - } - } - - /** - * 找到距离顶部最近的一个stickyItem的位置 - * - * @return INVALID_POSITION,没有找到stickyItem - */ - public int getStickyItemPosition() { - if (recyclerView.getChildCount() <= 0) { - return INVALID_POSITION; - } - int positionToSticky = INVALID_POSITION; - int startPosition = recyclerView.getFirstChildPosition(); - View firstView = recyclerView.getChildAt(0); - if (firstView.getY() >= 0) { - startPosition--;//当前view已经完全露出,需要往从前面一个开始寻找 - } - for (int i = startPosition; i >= 0; i--) { - if (stickyItemsProvider.isStickyPosition(i)) { - positionToSticky = i; - break; - } - } - if (positionToSticky != INVALID_POSITION) { - if (positionToSticky != recyclerView.getFirstChildPosition()) { - //positionToSticky已经被滑出屏幕,此时positionToSticky可以直接返回 - return positionToSticky; - } else { - //stickyItem和第一个孩子位置一样,如果完全和吸顶位置重合,不需要进行吸顶 - positionToSticky = - !headerAwayFromEdge(firstView) ? INVALID_POSITION : positionToSticky; - } - } - return positionToSticky; - } - - /** - * headerToCopy == null表示,headerToCopy 已经完全移除屏幕外 headerToCopy != null ,getY()<0 部分移动屏幕外 - * - * @param headerToCopy 即将被选中吸顶的view - */ - private boolean headerAwayFromEdge(View headerToCopy) { - return headerToCopy != null && (orientation == VERTICAL ? headerToCopy.getY() < 0 - : headerToCopy.getX() < 0); - } - - private void removeViewFromParent(View view) { - if (view.getParent() instanceof ViewGroup) { - ((ViewGroup) view.getParent()).removeView(view); - } - } -} diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/StickyViewFactory.java b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/StickyViewFactory.java deleted file mode 100644 index c81a1477e36..00000000000 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/StickyViewFactory.java +++ /dev/null @@ -1,47 +0,0 @@ -/* Tencent is pleased to support the open source community by making easy-recyclerview-helper available. - * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.tencent.mtt.nxeasy.recyclerview.helper.skikcy; - -import androidx.recyclerview.widget.EasyRecyclerView; -import androidx.recyclerview.widget.RecyclerView.ViewHolder; - -public final class StickyViewFactory implements IHeaderViewFactory { - - private final EasyRecyclerView recyclerView; - - public StickyViewFactory(EasyRecyclerView recyclerView) { - this.recyclerView = recyclerView; - } - - /** - * 根据position的位置,获取到一个实体到ViewHolder 1、先在已经上屏的view中,找到一个ViewHolder 2、第一步没有找到,就重新通过recyclerView去获取一个,这种获取的viewHolder可能是cache里面的,也可能 - * 是新创建的,不用再次进行bindViewHolder的操作 - * - * @param position 指定要获取到ViewHolder到位置 - * @return 返回对应到ViewHolder,不会返回Null - */ - public ViewHolder getHeaderForPosition(int position) { - if (position < 0) { - return null; - } - ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(position); - if (viewHolder == null) { - viewHolder = recyclerView.getViewHolderForPosition(position); - } - return viewHolder; - } -} \ No newline at end of file diff --git a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/asyncimage/AsyncImageView.java b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/asyncimage/AsyncImageView.java index 7a45b80a1e3..049ca5093f4 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/asyncimage/AsyncImageView.java +++ b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/asyncimage/AsyncImageView.java @@ -30,11 +30,15 @@ import android.graphics.Color; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; +import android.os.Build; import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; import java.util.ArrayList; +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + /** * Created by leonardgong on 2017/12/7 0007. */ @@ -62,6 +66,7 @@ public class AsyncImageView extends ViewGroup implements Animator.AnimatorListen protected int mTintColor; protected ScaleType mScaleType; protected Drawable mContentDrawable; + protected Drawable mRippleDrawable; private boolean mIsAttached; protected IImageLoaderAdapter mImageAdapter; @@ -112,7 +117,10 @@ public void setUrl(String url) if (isAttached()) { onDrawableDetached(); + resetContent(); fetchImageByUrl(mUrl, SOURCE_TYPE_SRC); + } else { + mSourceDrawable = null; } } } @@ -184,6 +192,16 @@ public void setDefaultSource(String defaultSource) if (!TextUtils.equals(mDefaultSourceUrl, defaultSource)) { mDefaultSourceUrl = defaultSource; + if (mDefaultSourceDrawable != null) { + if (isAttached()) { + mDefaultSourceDrawable.onDrawableDetached(); + } + mDefaultSourceDrawable = null; + } + if (mSourceDrawable == null) { + mContentDrawable = null; + resetBackgroundDrawable(); + } fetchImageByUrl(mDefaultSourceUrl, SOURCE_TYPE_DEFAULT_SRC); } } @@ -319,7 +337,7 @@ public void onRequestFail(Throwable throwable, String source) } protected void handleImageRequest(IDrawableTarget resultDrawable, int sourceType, Object requestInfo) { - if (resultDrawable == null) { + if (!hasImage(resultDrawable)) { if (sourceType == SOURCE_TYPE_SRC) { mSourceDrawable = null; if (mDefaultSourceDrawable != null) { @@ -346,6 +364,10 @@ protected void handleImageRequest(IDrawableTarget resultDrawable, int sourceType } } + protected boolean hasImage(IDrawableTarget resultDrawable) { + return resultDrawable != null; + } + protected ContentDrawable generateContentDrawable() { return new ContentDrawable(); @@ -386,11 +408,12 @@ protected void onAttachedToWindow() { mIsAttached = true; super.onAttachedToWindow(); - if (mDefaultSourceDrawable != null && shouldFetchImage()) - { - mDefaultSourceDrawable.onDrawableAttached(); - setContent(SOURCE_TYPE_DEFAULT_SRC); - } + if (mDefaultSourceDrawable != null) { + mDefaultSourceDrawable.onDrawableAttached(); + } + if (shouldFetchImage()) { + resetContent(); + } fetchImageByUrl(mUrl, SOURCE_TYPE_SRC); onDrawableAttached(); @@ -414,9 +437,13 @@ protected void onDrawableDetached() protected void resetContent() { - mContentDrawable = null; - mBGDrawable = null; - super.setBackgroundDrawable(null); + mSourceDrawable = null; + if (mDefaultSourceDrawable != null) { + updateContentDrawableProperty(SOURCE_TYPE_DEFAULT_SRC); + } else { + mContentDrawable = null; + } + resetBackgroundDrawable(); } protected void onSetContent(String url) @@ -448,33 +475,14 @@ protected Bitmap getBitmap() return null; } - protected void setContent(int sourceType) - { - if (mContentDrawable != null) - { - if (!shouldSetContent()) - { + protected void setContent(int sourceType) { + if (mContentDrawable != null) { + if (!shouldSetContent()) { return; } - onSetContent(mUrl); updateContentDrawableProperty(sourceType); - - if (mBGDrawable != null) - { - if (mContentDrawable instanceof ContentDrawable) - { - ((ContentDrawable) mContentDrawable).setBorder(mBGDrawable.getBorderRadiusArray(), mBGDrawable.getBorderWidthArray()); - ((ContentDrawable) mContentDrawable).setShadowOffsetX(mBGDrawable.getShadowOffsetX()); - ((ContentDrawable) mContentDrawable).setShadowOffsetY(mBGDrawable.getShadowOffsetY()); - ((ContentDrawable) mContentDrawable).setShadowRadius(mBGDrawable.getShadowRadius()); - } - setBackgroundDrawable(new LayerDrawable(new Drawable[] { mBGDrawable, mContentDrawable })); - } - else - { - setBackgroundDrawable(mContentDrawable); - } + resetBackgroundDrawable(); afterSetContent(mUrl); } } @@ -484,18 +492,10 @@ protected void updateContentDrawableProperty(int sourceType) { return; } - Bitmap bitmap = null; - switch (sourceType) { - case SOURCE_TYPE_SRC: - if (mSourceDrawable != null){ - bitmap = mSourceDrawable.getBitmap(); - } - break; - case SOURCE_TYPE_DEFAULT_SRC: - if (mDefaultSourceDrawable != null && (mUrlFetchState != IMAGE_LOADED || mSourceDrawable == null)) { - bitmap = mDefaultSourceDrawable.getBitmap(); - } - break; + Bitmap bitmap = getBitmap(); + if (sourceType == SOURCE_TYPE_DEFAULT_SRC && mDefaultSourceDrawable != null + && (mUrlFetchState != IMAGE_LOADED || mSourceDrawable == null)) { + bitmap = mDefaultSourceDrawable.getBitmap(); } if (bitmap != null) { @@ -506,6 +506,17 @@ protected void updateContentDrawableProperty(int sourceType) { ((ContentDrawable) mContentDrawable).setImagePositionX(mImagePositionX); ((ContentDrawable) mContentDrawable).setImagePositionY(mImagePositionY); } + if (mBGDrawable != null) { + ((ContentDrawable) mContentDrawable) + .setBorder(mBGDrawable.getBorderRadiusArray(), + mBGDrawable.getBorderWidthArray()); + ((ContentDrawable) mContentDrawable) + .setShadowOffsetX(mBGDrawable.getShadowOffsetX()); + ((ContentDrawable) mContentDrawable) + .setShadowOffsetY(mBGDrawable.getShadowOffsetY()); + ((ContentDrawable) mContentDrawable) + .setShadowRadius(mBGDrawable.getShadowRadius()); + } } protected void handleGetImageStart() @@ -649,10 +660,7 @@ public void setShadowOpacity(float opacity) public void setShadowRadius(float radius) { getBackGround().setShadowRadius(Math.abs(radius)); - if (radius != 0) - { - setLayerType(View.LAYER_TYPE_SOFTWARE, null); - } + invalidate(); } @@ -705,4 +713,35 @@ private BackgroundDrawable getBackGround() } return mBGDrawable; } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public void setRippleDrawable(@NonNull Drawable rippleDrawable) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + mRippleDrawable = rippleDrawable; + resetBackgroundDrawable(); + } + } + + private void resetBackgroundDrawable() { + ArrayList drawableList = new ArrayList<>(); + if (mBGDrawable != null) { + drawableList.add(mBGDrawable); + } + if (mContentDrawable != null) { + drawableList.add(mContentDrawable); + } + if (mRippleDrawable != null) { + drawableList.add(mRippleDrawable); + } + if (drawableList.size() > 0) { + Drawable[] drawables = new Drawable[drawableList.size()]; + for (int i = 0; i < drawableList.size(); i++) { + drawables[i] = drawableList.get(i); + } + LayerDrawable layerDrawable = new LayerDrawable(drawables); + super.setBackground(layerDrawable); + } else { + super.setBackground(null); + } + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/asyncimage/BackgroundDrawable.java b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/asyncimage/BackgroundDrawable.java index ea2cae20bec..166040961e4 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/asyncimage/BackgroundDrawable.java +++ b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/asyncimage/BackgroundDrawable.java @@ -53,7 +53,7 @@ public class BackgroundDrawable extends BaseDrawable private DashPathEffect mDotPathEffect = new DashPathEffect(new float[] { 2, 2}, 0); private Path mPathWithBorder; private final Paint mPaint; - private int mBackgroundColor; + private int mBackgroundColor = Color.TRANSPARENT; private RectF mTempRectForBorderRadius; private Path mPathForBorderRadius; private boolean mNeedUpdateBorderPath = false; @@ -80,7 +80,7 @@ public BackgroundDrawable() { mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); } - + @Override public void setBounds(int left, int top, int right, int bottom) { @@ -168,7 +168,7 @@ else if(mBorderStyle == 2 && dotBorderWidth > 0) mBitmapCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); drawBGShadow(mBitmapCanvas); - + if (!hasBorderRadius) { drawBG(mBitmapCanvas); @@ -183,7 +183,7 @@ else if(mBorderStyle == 2 && dotBorderWidth > 0) else { drawBGShadow(canvas); - + if (!hasBorderRadius) { drawBG(canvas); @@ -200,7 +200,7 @@ public void setGradientAngle(String angle) { } public void setGradientColors(ArrayList colors) { - int size = colors.size(); + int size = colors == null ? 0 : colors.size(); if (size > 0) { gradientColors = new int[size]; for (int i = 0; i < size; i++) { @@ -212,7 +212,7 @@ public void setGradientColors(ArrayList colors) { } public void setGradientPositions(ArrayList positions) { - int size = positions.size(); + int size = positions == null ? 0 : positions.size(); if (size > 0) { int lastPos = 0; float lastValue = 0.0f; @@ -416,7 +416,6 @@ private void drawBGShadow (Canvas canvas) mShadowPaint.setAntiAlias(true); mShadowPaint.setAlpha(opacity); mShadowPaint.setShadowLayer(mShadowRadius, mShadowOffsetX, mShadowOffsetY, mShadowColor); - mShadowPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP)); canvas.drawRoundRect(mShadowRect, borderRadius, borderRadius, mShadowPaint); } @@ -424,12 +423,12 @@ private void drawBGWithRadius(Canvas canvas) { // updateStyle border with radius path updatePath(); - + assert mPathForBorderRadius != null; // draw bg color boolean useGradientPaint = initGradientPaint(); if (useGradientPaint) { canvas.drawPath(mPathForBorderRadius, gradientPaint); - } else if (mBackgroundColor != 0) { + } else { mPaint.setColor(mBackgroundColor); mPaint.setStyle(Paint.Style.FILL); canvas.drawPath(mPathForBorderRadius, mPaint); @@ -680,7 +679,7 @@ private void drawBG(Canvas canvas) boolean useGradientPaint = initGradientPaint(); if (useGradientPaint) { canvas.drawRect(mRect, gradientPaint); - } else if (mBackgroundColor != 0) { + } else { mPaint.setColor(mBackgroundColor); mPaint.setStyle(Paint.Style.FILL); canvas.drawRect(mRect, mPaint); @@ -802,19 +801,15 @@ public Bitmap generateBitmap(int width, int height) { private void updatePath() { - if (!mNeedUpdateBorderPath) - { - return; - } - mNeedUpdateBorderPath = false; - if (mPathForBorderRadius == null) - { + if (mPathForBorderRadius == null) { mPathForBorderRadius = new Path(); mTempRectForBorderRadius = new RectF(); } - + if (!mNeedUpdateBorderPath) { + return; + } + mNeedUpdateBorderPath = false; mPathForBorderRadius.reset(); - mTempRectForBorderRadius.set(mRect); float fullBorderWidth = mBorderWidthArray == null ? 0 : mBorderWidthArray[0]; if (fullBorderWidth > 1) diff --git a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/asyncimage/ContentDrawable.java b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/asyncimage/ContentDrawable.java index 5e09666668c..e3678822099 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/asyncimage/ContentDrawable.java +++ b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/asyncimage/ContentDrawable.java @@ -32,7 +32,6 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; -import android.graphics.drawable.Drawable; import android.os.Build; import com.tencent.mtt.supportui.views.asyncimage.AsyncImageView.ScaleType; @@ -56,7 +55,6 @@ public class ContentDrawable extends BaseDrawable private boolean mNeedUpdateBorderPath; private Path mBorderPath; - private RectF mTempRectForBorderRadius; private int mImagePositionX; private int mImagePositionY; @@ -123,16 +121,13 @@ private void updatePath() { if (mNeedUpdateBorderPath && mContentBitmap != null) { - if (mBorderPath == null) - { - mBorderPath = new Path(); - } + if (mBorderPath == null) { + mBorderPath = new Path(); + } else { + mBorderPath.rewind(); + } mNeedUpdateBorderPath = false; - if (mTempRectForBorderRadius == null) - { - mTempRectForBorderRadius = new RectF(); - } - mTempRectForBorderRadius.set(mRect); + RectF tempRectF = new RectF(mRect); // calc scale here int bitmapWidth = mContentBitmap.getWidth(); int bitmapHeight = mContentBitmap.getHeight(); @@ -157,17 +152,17 @@ private void updatePath() break; case ORIGIN: // 不拉伸,居左上 - mTempRectForBorderRadius.top = 0; - mTempRectForBorderRadius.bottom = bitmapHeight; - mTempRectForBorderRadius.left = 0; - mTempRectForBorderRadius.right = bitmapWidth; + tempRectF.top = 0; + tempRectF.bottom = bitmapHeight; + tempRectF.left = 0; + tempRectF.right = bitmapWidth; break; case CENTER: // 居中不拉伸 - mTempRectForBorderRadius.top = (boundHeight - bitmapHeight) / 2; - mTempRectForBorderRadius.bottom = (boundHeight + bitmapHeight) / 2; - mTempRectForBorderRadius.left = (boundWidth - bitmapWidth) / 2; - mTempRectForBorderRadius.right = (boundWidth + bitmapWidth) / 2; + tempRectF.top = (boundHeight - bitmapHeight) / 2; + tempRectF.bottom = (boundHeight + bitmapHeight) / 2; + tempRectF.left = (boundWidth - bitmapWidth) / 2; + tempRectF.right = (boundWidth + bitmapWidth) / 2; break; case CENTER_INSIDE: // 在保持图片宽高比的前提下缩放图片,直到宽度和高度都小于等于容器视图的尺寸 @@ -176,17 +171,17 @@ private void updatePath() { if (xScale > yScale) { // y到顶 - mTempRectForBorderRadius.top = 0; - mTempRectForBorderRadius.bottom = boundHeight; - mTempRectForBorderRadius.left = (int) ((boundWidth - bitmapWidth * yScale) / 2); - mTempRectForBorderRadius.right = (int) ((boundWidth + bitmapWidth * yScale) / 2); + tempRectF.top = 0; + tempRectF.bottom = boundHeight; + tempRectF.left = (int) ((boundWidth - bitmapWidth * yScale) / 2); + tempRectF.right = (int) ((boundWidth + bitmapWidth * yScale) / 2); } else { // x到顶 - mTempRectForBorderRadius.top = (int) ((boundHeight - bitmapHeight * xScale) / 2); - mTempRectForBorderRadius.bottom = (int) ((boundHeight + bitmapHeight * xScale) / 2); - mTempRectForBorderRadius.left = 0; - mTempRectForBorderRadius.right = boundWidth; + tempRectF.top = (int) ((boundHeight - bitmapHeight * xScale) / 2); + tempRectF.bottom = (int) ((boundHeight + bitmapHeight * xScale) / 2); + tempRectF.left = 0; + tempRectF.right = boundWidth; } } break; @@ -196,52 +191,42 @@ private void updatePath() break; } - mTempRectForBorderRadius.top += mImagePositionY; - mTempRectForBorderRadius.bottom += mImagePositionY; - mTempRectForBorderRadius.left += mImagePositionX; - mTempRectForBorderRadius.right += mImagePositionX; + tempRectF.top += mImagePositionY; + tempRectF.bottom += mImagePositionY; + tempRectF.left += mImagePositionX; + tempRectF.right += mImagePositionX; float fullBorderWidth = mBorderWidthArray == null ? 0 : mBorderWidthArray[0]; - if (fullBorderWidth > 1) - { - mTempRectForBorderRadius.inset(fullBorderWidth * 0.5f, fullBorderWidth * 0.5f); - } - if (CommonTool.hasPositiveItem(mBorderRadiusArray)) - { - - float topLeftRadius = mBorderRadiusArray[1]; - if (topLeftRadius == 0 && mBorderRadiusArray[0] > 0) - { - topLeftRadius = mBorderRadiusArray[0]; - } - float topRightRadius = mBorderRadiusArray[2]; - if (topRightRadius == 0 && mBorderRadiusArray[0] > 0) - { - topRightRadius = mBorderRadiusArray[0]; - } - float bottomRightRadius = mBorderRadiusArray[3]; - if (bottomRightRadius == 0 && mBorderRadiusArray[0] > 0) - { - bottomRightRadius = mBorderRadiusArray[0]; - } - float bottomLeftRadius = mBorderRadiusArray[4]; - if (bottomLeftRadius == 0 && mBorderRadiusArray[0] > 0) - { - bottomLeftRadius = mBorderRadiusArray[0]; - } - - mBorderPath.addRoundRect(mTempRectForBorderRadius, new float[] { topLeftRadius, topLeftRadius, topRightRadius, topRightRadius, - bottomRightRadius, bottomRightRadius, bottomLeftRadius, bottomLeftRadius }, Path.Direction.CW); - } - else - { - // no border radius - mBorderPath.addRect(mTempRectForBorderRadius, Path.Direction.CW); + mBorderPath.addRect(tempRectF, Path.Direction.CW); + + if (CommonTool.hasPositiveItem(mBorderRadiusArray)) { + Path tempPath = new Path(); + + float fullRadius = mBorderRadiusArray[0]; + float topLeftRadius = calculateBorderRadius(mBorderRadiusArray[1], fullRadius, fullBorderWidth); + float topRightRadius = calculateBorderRadius(mBorderRadiusArray[2], fullRadius, fullBorderWidth); + float bottomRightRadius = calculateBorderRadius(mBorderRadiusArray[3], fullRadius, fullBorderWidth); + float bottomLeftRadius = calculateBorderRadius(mBorderRadiusArray[4], fullRadius, fullBorderWidth); + + tempRectF.set(mRect); + tempRectF.inset(fullBorderWidth, fullBorderWidth); + tempPath.addRoundRect(tempRectF, new float[] { topLeftRadius, topLeftRadius, topRightRadius, topRightRadius, + bottomRightRadius, bottomRightRadius, bottomLeftRadius, bottomLeftRadius }, Path.Direction.CW); + mBorderPath.op(tempPath, Path.Op.INTERSECT); + } else if (fullBorderWidth > 0) { + Path tempPath = new Path(); + tempPath.addRect(fullBorderWidth, fullBorderWidth, boundWidth - fullBorderWidth,boundHeight - fullBorderWidth, + Path.Direction.CW); + mBorderPath.op(tempPath, Path.Op.INTERSECT); } } } + private static float calculateBorderRadius(float value, float fullValue, float inset) { + return Math.max(0, (value != 0 ? value : fullValue) - inset * .5f); + } + @Override protected void onBoundsChange(Rect bounds) { @@ -263,7 +248,7 @@ public void draw(Canvas canvas) updateContentRegion(); updatePath(); - + if (mContentBitmap != null) { Matrix matrix = new Matrix(); diff --git a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/BaseLayoutManager.java b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/BaseLayoutManager.java index 4c158e9e7e6..6b4ff1e9ef8 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/BaseLayoutManager.java +++ b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/BaseLayoutManager.java @@ -1,3 +1,18 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.tencent.mtt.supportui.views.recyclerview; import java.util.ArrayList; @@ -728,7 +743,7 @@ else if (mOrientation == HORIZONTAL) } else { - //niuniuyang:如果内容小于屏幕高度了,如果mOffset不是原点,必须把list的offset重新回到原点 + //如果内容小于屏幕高度了,如果mOffset不是原点,必须把list的offset重新回到原点 int gap = mRecyclerView.mOffsetY - mOrientationHelper.getStartAfterPadding(); if (gap != 0) { @@ -1257,19 +1272,21 @@ private int scrollBy(int dy, RecyclerViewBase.Recycler recycler, RecyclerViewBas // } // ensureSuspensionState(layoutDirection); updateRenderState(layoutDirection, absDy, true, state); - final int freeScroll = mRenderState.mScrollingOffset; + //final int freeScroll = mRenderState.mScrollingOffset; //Log.e("RecyclerView", "start scroll---------------------dy=" + dy); - final int consumed = freeScroll + fill(recycler, mRenderState, state, false); - if (consumed < 0) - { - if (DEBUG) - { + //final int consumed = freeScroll + fill(recycler, mRenderState, state, false); + fill(recycler, mRenderState, state, false); + //if (consumed < 0) + //{ + // if (DEBUG) + // { // Log.d(TAG, "Don't have any more elements to scroll"); - } - return 0; + // } + // return 0; - } - final int scrolled = absDy > consumed ? layoutDirection * consumed : dy; + //} + //final int scrolled = absDy > consumed ? layoutDirection * consumed : dy; + final int scrolled = dy; mOrientationHelper.offsetChildren(-scrolled); if (DEBUG) { diff --git a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/IBlockTouchListener.java b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/IBlockTouchListener.java index 381acd018f2..3615f70a607 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/IBlockTouchListener.java +++ b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/IBlockTouchListener.java @@ -1,7 +1,7 @@ package com.tencent.mtt.supportui.views.recyclerview; /** - * Created by niuniuyang on 2020-03-06. + * Created by on 2020-03-06. * Description */ public interface IBlockTouchListener { diff --git a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/RecyclerAdapter.java b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/RecyclerAdapter.java index 31037e6a29f..1c9cbe1ae63 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/RecyclerAdapter.java +++ b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/RecyclerAdapter.java @@ -1,3 +1,18 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.tencent.mtt.supportui.views.recyclerview; import static com.tencent.mtt.supportui.views.recyclerview.RecyclerViewItem.ITEM_VIEW_DEFAULT_HEIGHT; @@ -1044,10 +1059,10 @@ public View getFooterView(int position) } } - public int getDefaultFooterHeight() - { - return 108; - } + // public int getDefaultFooterHeight() + // { + // return 108; + // } // 用户上拉触发了加载,footer开始转圈,业务开始拉去数据 @Override diff --git a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/RecyclerViewBase.java b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/RecyclerViewBase.java index 17aa10198c9..95d34cc2d85 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/RecyclerViewBase.java +++ b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/RecyclerViewBase.java @@ -2215,7 +2215,7 @@ protected void dispatchLayout() } // mCount = mAdapter.getItemCount() + mAdapter.getHeaderViewCount() + // mAdapter.getFooterViewCount(); - //niuniuyang: 对于itemchanged,选中的item移动到屏幕外时,此时执行删除操作,item是不会做动画的。 + //对于itemchanged,选中的item移动到屏幕外时,此时执行删除操作,item是不会做动画的。 //也就无法通过onAnimationsFinished退出编辑态,这里需要加一个事件,通知出去,退出编辑态 if (animateChangesSimple && !mPostedAnimatorRunner) { @@ -2331,9 +2331,7 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) } handleOnLayoutChange(); } - eatRequestLayout(); - dispatchLayout(); - resumeRequestLayout(false); + if (changed) { if (mIsChangingMode) @@ -3121,7 +3119,7 @@ public void smoothScrollBy(int dx, int dy, int vx, int vy, boolean careSpringBac smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, vx, vy), careSpringBackMaxDistance); } - /* private */float distanceInfluenceForSnapDuration(float f) + /* private */float distanceInfluenceForSnapDuration(double f) { f -= 0.5f; // center the values about 0. f *= 0.3f * Math.PI / 2.0f; diff --git a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/Scroller.java b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/Scroller.java index 54b8cef753f..81b2d2454fc 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/Scroller.java +++ b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/Scroller.java @@ -494,6 +494,8 @@ public boolean isFling() */ public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) { + float newVelocityX = velocityX; + float newVelocityY = velocityY; // Continue a scroll or fling in progress if (mFlywheel && !mFinished) { @@ -510,15 +512,15 @@ public void fling(int startX, int startY, int velocityX, int velocityY, int minX float oldVelocityY = ndy * oldVel; if (Math.signum(velocityX) == Math.signum(oldVelocityX) && Math.signum(velocityY) == Math.signum(oldVelocityY)) { - velocityX += oldVelocityX; - velocityY += oldVelocityY; + newVelocityX += oldVelocityX; + newVelocityY += oldVelocityY; } } mMode = FLING_MODE; mFinished = false; - float velocity = (float) Math.sqrt((double) velocityX * (double) velocityX + (double) velocityY * (double) velocityY); + float velocity = (float) Math.sqrt((double) newVelocityX * (double) newVelocityX + (double) newVelocityY * (double) newVelocityY); mVelocity = velocity; final double l = Math.log(START_TENSION * velocity / ALPHA); @@ -527,8 +529,8 @@ public void fling(int startX, int startY, int velocityX, int velocityY, int minX mStartX = startX; mStartY = startY; - float coeffX = velocity == 0 ? 1.0f : velocityX / velocity; - float coeffY = velocity == 0 ? 1.0f : velocityY / velocity; + float coeffX = velocity == 0 ? 1.0f : newVelocityX / velocity; + float coeffY = velocity == 0 ? 1.0f : newVelocityY / velocity; int totalDistance = (int) (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l)); mDistance = (int) (totalDistance * Math.signum(velocity)); diff --git a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/viewpager/ViewPager.java b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/viewpager/ViewPager.java index 5d5886213de..a7f3f52bd08 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/viewpager/ViewPager.java +++ b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/viewpager/ViewPager.java @@ -1,14 +1,20 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.tencent.mtt.supportui.views.viewpager; -import com.tencent.mtt.hippy.utils.LogUtils; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; - -import com.tencent.mtt.supportui.utils.ViewCompatTool; -import com.tencent.mtt.supportui.views.ScrollChecker; - import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -36,6 +42,12 @@ import android.view.accessibility.AccessibilityEvent; import android.view.animation.Interpolator; import android.widget.Scroller; +import com.tencent.mtt.supportui.utils.ViewCompatTool; +import com.tencent.mtt.supportui.views.ScrollChecker; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; /** * Created by leonardgong on 2018/4/19 0007. @@ -128,7 +140,6 @@ public float getInterpolation(float t) /* private */ ViewPagerAdapter mAdapter; /* private */ int mCurItem; - /* private */ int mLastItem = INVALID_SCREEN; // Index // of // currently @@ -561,11 +572,6 @@ protected void onDetachedFromWindow() mMinPage = Integer.MIN_VALUE; mMaxPage = Integer.MAX_VALUE; } - - if (newState == SCROLL_STATE_IDLE) { - mLastItem = mCurItem; - } - mScrollState = newState; } @@ -1120,7 +1126,7 @@ protected void drawableStateChanged() // purely linear fashion. Instead, we use this method to moderate the effect // that the distance // of travel has on the overall snap duration. - float distanceInfluenceForSnapDuration(float f) + float distanceInfluenceForSnapDuration(double f) { f -= 0.5f; // center the values about 0. f *= 0.3f * Math.PI / 2.0f; @@ -2324,9 +2330,9 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) mFirstLayout = false; } - public int getTotalLength() + public float getTotalLength() { - int total = 0; + float total = 0; for (int i = 0; i < mAdapter.getCount(); i++) { total += mIsVertical ? getHeight() * mAdapter.getPageSize(i) : getWidth() * mAdapter.getPageSize(i); @@ -2439,33 +2445,10 @@ protected boolean pageScrolled(int pos) } final ItemInfo ii = infoForCurrentScrollPosition(); final float marginOffset = (float) mPageMargin / size; - int targetPage = ii.position; - final float offsetBaseRight = (((float) pos / size) - (ii.offset + ii.sizeFactor)) / (ii.sizeFactor + marginOffset); - final float offsetBaseLeft = (((float) pos / size) - ii.offset) / (ii.sizeFactor + marginOffset); - - if (mLastItem == INVALID_SCREEN) { - mLastItem = mCurItem; - } - - if (offsetBaseLeft == 0) { - if (targetPage == mLastItem) { - pageOffset = 0; - } else { - pageOffset = (targetPage < mLastItem) ? -1.0f : 1.0f; - } - } else { - if (targetPage >= mLastItem) { - pageOffset = offsetBaseLeft; - targetPage = ii.position + 1; - } else { - pageOffset = offsetBaseRight; - } - } - + final int currentPage = ii.position; + pageOffset = (((float) pos / size) - ii.offset) / (ii.sizeFactor + marginOffset); mCalledSuper = false; - pageOffset = (float)(Math.round(pageOffset*1000))/1000; - LogUtils.d(TAG, "pageScrolled: targetPage=" + targetPage + ", pageOffset=" + pageOffset); - onPageScrolled(targetPage, pageOffset, offsetPixels); + onPageScrolled(currentPage, pageOffset, offsetPixels); if (!mCalledSuper) { throw new IllegalStateException("onPageScrolled did not call superclass implementation"); @@ -2849,7 +2832,7 @@ public boolean onInterceptTouchEvent(MotionEvent ev) if (mIsVertical) { if (!mScrollEnabled - || (dy != 0 && !isGutterDrag(mLastMotionY, dy) && (!ignoreCheck && checkChildCanScroll((int) dx, (int) x, (int) y)))) + || (dy != 0 && !isGutterDrag(mLastMotionY, dy) && (!ignoreCheck && checkChildCanScroll((int) dy, (int) x, (int) y)))) { // Nested view has scrollable area under this point. Let // it @@ -2881,7 +2864,7 @@ public boolean onInterceptTouchEvent(MotionEvent ev) if (DEBUG) Log.v(TAG, "Starting drag!"); - if (onStartDrag(dx < 0)) + if (onStartDrag((mIsVertical ? dy : dx) < 0)) { mIsBeingDragged = true; setScrollState(SCROLL_STATE_DRAGGING); @@ -3416,7 +3399,7 @@ public void snapToScreen(int whichScreen, int velocity, boolean settle, boolean final int screenDelta = Math.max(1, Math.abs(whichScreen - mCurrentScreen)); final int newX = whichScreen * getWidth(); final int delta = newX - getScrollX(); - int duration = (screenDelta + 1) * 100; + float duration = (screenDelta + 1) * 100; if (DEBUG) { Log.d("galleryTMY", "snap" + " " + whichScreen + ",newX=" + newX); @@ -3894,29 +3877,29 @@ protected void onDraw(Canvas canvas) } } - @Override - public boolean canScrollHorizontally(int direction) { - if (!mScrollEnabled) { - return false; - } - return horizontalCanScroll(direction); - } + @Override + public boolean canScrollHorizontally(int direction) { + if (!mScrollEnabled) { + return false; + } + return horizontalCanScroll(direction); + } - @Override - public boolean canScrollVertically(int direction) { - if (!mScrollEnabled) { - return false; - } - return verticalCanScroll(direction); - } + @Override + public boolean canScrollVertically(int direction) { + if (!mScrollEnabled) { + return false; + } + return verticalCanScroll(direction); + } - protected boolean onStartDrag(boolean left) { - if (left) { - return horizontalCanScroll(1); - } else { - return horizontalCanScroll(-1); + protected boolean onStartDrag(boolean start) { + if (mIsVertical) { + return verticalCanScroll(start ? 1 : -1); + } else { + return horizontalCanScroll(start ? 1 : -1); + } } - } /** * Start a fake drag of the pager. @@ -3980,12 +3963,12 @@ public void endFakeDrag() velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId); mPopulatePending = true; - final int width = getClientWidth(); - final int scrollX = getScrollX(); + final int size = getClientSize(); + final int scrollPos = mIsVertical ? getScrollY() : getScrollX(); final ItemInfo ii = infoForCurrentScrollPosition(); final int currentPage = ii.position; - final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.sizeFactor; - final int totalDelta = (int) (mLastMotionX - mInitialMotionX); + final float pageOffset = (((float) scrollPos / size) - ii.offset) / ii.sizeFactor; + final int totalDelta = (int) (mIsVertical ? mLastMotionY - mInitialMotionY : mLastMotionX - mInitialMotionX); int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, totalDelta); setCurrentItemInternal(nextPage, true, true, 0, initialVelocity); endDrag(); @@ -3997,53 +3980,64 @@ public void endFakeDrag() * Fake drag by an offset in pixels. You must have called * {@link #beginFakeDrag()} first. * - * @param xOffset Offset in pixels to drag by. + * @param offset Offset in pixels to drag by. * @see #beginFakeDrag() * @see #endFakeDrag() */ - public void fakeDragBy(float xOffset) + public void fakeDragBy(float offset) { if (!mFakeDragging) { throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first."); } - mLastMotionX += xOffset; + if (mIsVertical) { + mLastMotionY += offset; + } else { + mLastMotionX += offset; + } - float oldScrollX = getScrollX(); - float scrollX = oldScrollX - xOffset; - final int width = getClientWidth(); + float oldScrollPos = mIsVertical ? getScrollY() : getScrollX(); + float scrollPos = oldScrollPos - offset; + final int range = mIsVertical ? getClientHeight() : getClientWidth(); - float leftBound = width * mFirstOffset; - float rightBound = width * mLastOffset; + float startBound = range * mFirstOffset; + float endBound = range * mLastOffset; final ItemInfo firstItem = mItems.get(0); final ItemInfo lastItem = mItems.get(mItems.size() - 1); if (firstItem.position != 0) { - leftBound = firstItem.offset * width; + startBound = firstItem.offset * range; } if (lastItem.position != mAdapter.getCount() - 1) { - rightBound = lastItem.offset * width; + endBound = lastItem.offset * range; } - if (scrollX < leftBound) + if (scrollPos < startBound) { - scrollX = leftBound; + scrollPos = startBound; } - else if (scrollX > rightBound) + else if (scrollPos > endBound) { - scrollX = rightBound; + scrollPos = endBound; } // Don't lose the rounded component - mLastMotionX += scrollX - (int) scrollX; - scrollTo((int) scrollX, getScrollY()); - pageScrolled((int) scrollX); + if (mIsVertical) { + mLastMotionY += scrollPos - (int) scrollPos; + scrollTo(getScrollX(), (int) scrollPos); + } else { + mLastMotionX += scrollPos - (int) scrollPos; + scrollTo((int) scrollPos, getScrollY()); + } + pageScrolled((int) scrollPos); // Synthesize an event for the VelocityTracker. final long time = SystemClock.uptimeMillis(); - final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE, mLastMotionX, 0, 0); + final float x = mIsVertical ? 0 : mLastMotionX; + final float y = mIsVertical ? mLastMotionY : 0; + final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE, x, y, 0); mVelocityTracker.addMovement(ev); ev.recycle(); } @@ -4735,13 +4729,21 @@ public void setScrollEnabled(boolean enabled) @Override public boolean verticalCanScroll(int dis) { - return false; + if (!mCanScroll || !mIsVertical) { + return false; + } + if (dis < 0) { + return mCurItem > 0; + } else if (dis > 0) { + return mCurItem < getPageCount() - 1; + } + return false; } @Override public boolean horizontalCanScroll(int dis) { - if (!mCanScroll) + if (!mCanScroll || mIsVertical) { return false; } diff --git a/android/sdk/src/main/java/com/tencent/smtt/flexbox/FlexNode.java b/android/sdk/src/main/java/com/tencent/smtt/flexbox/FlexNode.java index 0e5a0f4c118..19a3ecfb857 100644 --- a/android/sdk/src/main/java/com/tencent/smtt/flexbox/FlexNode.java +++ b/android/sdk/src/main/java/com/tencent/smtt/flexbox/FlexNode.java @@ -134,7 +134,6 @@ public FlexNode() { protected void finalize() throws Throwable { try { nativeFlexNodeFree(mNativeFlexNode); - mFlexNodeStyle = null; } finally { super.finalize(); } diff --git a/android/sdk/src/main/java/com/tencent/smtt/flexbox/FlexNodeStyle.java b/android/sdk/src/main/java/com/tencent/smtt/flexbox/FlexNodeStyle.java index 4c784046dad..9eb5d7e382b 100644 --- a/android/sdk/src/main/java/com/tencent/smtt/flexbox/FlexNodeStyle.java +++ b/android/sdk/src/main/java/com/tencent/smtt/flexbox/FlexNodeStyle.java @@ -126,7 +126,6 @@ private static Object createFlexValue(float value, int unit) { protected void finalize() throws Throwable { try { nativeFlexNodeStyleFree(mNativePointer); - mNativePointer = 0; } finally { super.finalize(); } @@ -196,6 +195,10 @@ public void setJustifyContent(FlexJustify justifyContent) { order = 7; break; } + case 5: {//SPACE_EVENLY; + order = 8; + break; + } case 0: default: { order = 1;//default FLEX_START diff --git a/android/sdk/src/main/jni/CMakeLists.txt b/android/sdk/src/main/jni/CMakeLists.txt index 1a477789bbe..46d8a6cf3e8 100644 --- a/android/sdk/src/main/jni/CMakeLists.txt +++ b/android/sdk/src/main/jni/CMakeLists.txt @@ -1,157 +1,94 @@ -cmake_minimum_required(VERSION 3.4.1) +# +# Tencent is pleased to support the open source community by making +# Hippy available. +# +# Copyright (C) 2022 THL A29 Limited, a Tencent company. +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +cmake_minimum_required(VERSION 3.14) project("hippy") -set(CMAKE_VERBOSE_MAKEFILE on) -set(CMAKE_CXX_VISIBILITY_PRESET hidden) - -if (${ANDROID_STL} STREQUAL "c++_static") - string(APPEND CMAKE_SHARED_LINKER_FLAGS " -Wl,--exclude-libs,libc++_static.a") - string(APPEND CMAKE_SHARED_LINKER_FLAGS " -Wl,--exclude-libs,libc++abi.a") -endif() +get_filename_component(PROJECT_ROOT_DIR "${PROJECT_SOURCE_DIR}/../../../../.." REALPATH) -# region ABI_COMPILE_OPTIONS -set(ABI_COMPILE_OPTIONS - -fno-rtti - -fno-threadsafe-statics - -fvisibility-inlines-hidden - -std=c++14 - --param=ssp-buffer-size=4 - -Werror - -fno-exceptions - -fno-strict-aliasing - -Wall - -fexceptions - -Wno-unused-parameter - -Wno-missing-field-initializers - -pipe - -fPIC - -Wno-unused-local-typedefs - -funwind-tables - -fstack-protector - -fno-short-enums - -Os - -g - -fdata-sections - -ffunction-sections - -fomit-frame-pointer) +include("${PROJECT_ROOT_DIR}/buildconfig/cmake/GlobalPackagesModule.cmake") +include("${PROJECT_ROOT_DIR}/buildconfig/cmake/compiler_toolchain.cmake") -message("ANDROID_ABI: ${ANDROID_ABI}") -if (${ANDROID_ABI} STREQUAL "armeabi-v7a") - set(ABI_COMPILE_OPTIONS ${ABI_COMPILE_OPTIONS} - -march=armv7-a - -mtune=generic-armv7-a - -mfpu=vfpv3-d16 - -mfloat-abi=softfp - -mthumb) -elseif (${ANDROID_ABI} STREQUAL "arm64-v8a") - # (Empty) -elseif (${ANDROID_ABI} STREQUAL "x86") - set(ABI_COMPILE_OPTIONS ${ABI_COMPILE_OPTIONS} - -march=i686 - -mtune=intel - -m32 - -mssse3 - -mfpmath=sse) -elseif (${ANDROID_ABI} STREQUAL "x86_64") - set(ABI_COMPILE_OPTIONS ${ABI_COMPILE_OPTIONS} - -march=x86-64 - -mtune=intel - -m64 - -mpopcnt - -msse4.2) -else() - message(FATAL_ERROR "${ANDROID_ABI} is not supported") -endif() - -if (${HIDDEN_LIBRARY_SYMBOL} STREQUAL "true") - set(ABI_COMPILE_OPTIONS ${ABI_COMPILE_OPTIONS} -fvisibility=hidden) -endif() - -message("ABI_COMPILE_OPTIONS: ${ABI_COMPILE_OPTIONS}") -add_compile_options(${ABI_COMPILE_OPTIONS}) +set(CMAKE_VERBOSE_MAKEFILE on) +set(CMAKE_VISIBILITY_INLINES_HIDDEN on) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_EXTENSIONS off) + +# region library +add_library(${PROJECT_NAME} SHARED) +target_include_directories(${PROJECT_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/include) +target_compile_options(${PROJECT_NAME} PRIVATE ${COMPILE_OPTIONS}) +if (HIDDEN_LIBRARY_SYMBOL) + target_link_options(${PROJECT_NAME} PRIVATE + "-Wl,--version-script=${PROJECT_SOURCE_DIR}/minimum_exports.lst") +endif () +if (${ANDROID_STL} STREQUAL "c++_static") + target_link_options(${PROJECT_NAME} PRIVATE + "-Wl,--exclude-libs,libc++_static.a" + "-Wl,--exclude-libs,libc++abi.a") +endif () +# enable Safe ICF (Identical Code Folding) to optimize binary size +target_link_options(${PROJECT_NAME} PRIVATE + "-Wl,--icf=safe") # endregion -# region subdirectory -add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../../../../core/third_party/base out) -set(ENABLE_INSPECTOR true) -if (${JS_ENGINE} STREQUAL "V8") - get_filename_component(V8_COMPONENT_PATH ${V8_COMPONENT} ABSOLUTE) - add_subdirectory(${V8_COMPONENT_PATH}) -endif() +# region core +add_subdirectory(${PROJECT_ROOT_DIR}/core ${CMAKE_CURRENT_BINARY_DIR}/core) +target_link_libraries(${PROJECT_NAME} PRIVATE core) # endregion -# region global definitions -if (${ENABLE_INSPECTOR} STREQUAL "true") - add_definitions("-DENABLE_INSPECTOR") -endif() -add_definitions("-DOS_ANDROID") -add_definitions("-DANDROID") +# region v8 +GlobalPackages_Add(v8) +target_link_libraries(${PROJECT_NAME} PRIVATE v8) +get_target_property(V8_WITHOUT_INSPECTOR v8 INTERFACE_V8_WITHOUT_INSPECTOR) +if (V8_WITHOUT_INSPECTOR) + target_compile_definitions(${PROJECT_NAME} PRIVATE "V8_WITHOUT_INSPECTOR") +endif () # endregion -# region source -get_filename_component(CORE_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../core" REALPATH) -file(GLOB_RECURSE CORE_SRC ${CORE_SRC_DIR}/src/*.cc) -file(GLOB_RECURSE JNI_SRC ${PROJECT_SOURCE_DIR}/src/*.cc) -if (${ENABLE_INSPECTOR} STREQUAL "false") - file(GLOB_RECURSE INSPECTOR_SRC ${PROJECT_SOURCE_DIR}/src/inspector/*) - list(REMOVE_ITEM JNI_SRC ${INSPECTOR_SRC}) -endif() - -message("CORE_SRC_DIR: ${CORE_SRC_DIR}") -message("CORE_SRC: ${CORE_SRC}") -message("JNI_SRC: ${JNI_SRC}") +# region source set +set(SOURCE_SET + src/bridge/adr_bridge.cc + src/bridge/entry.cc + src/bridge/java2js.cc + src/bridge/js2java.cc + src/bridge/runtime.cc + src/jni/convert_utils.cc + src/jni/exception_handler.cc + src/jni/java_turbo_module.cc + src/jni/jni_env.cc + src/jni/jni_register.cc + src/jni/jni_utils.cc + src/jni/scoped_java_ref.cc + src/jni/turbo_module_manager.cc + src/jni/uri.cc + src/loader/adr_loader.cc + src/performance/memory.cc + src/v8/heap_limit.cc + src/v8/request_interrupt.cc + src/v8/interrupt_queue.cc + src/v8/stack_trace.cc) +# This is a top-level shared library, +# so the source code visibility is always PRIVATE. +target_sources(${PROJECT_NAME} PRIVATE ${SOURCE_SET}) # endregion -set(HIPPY_DEPS android log tdf_base) - -message("JS_ENGINE:" ${JS_ENGINE}) -if (${JS_ENGINE} STREQUAL "V8") - # region remove jsc code - file(GLOB_RECURSE JSC_SRC ${CORE_SRC_DIR}/src/napi/jsc/*) - message("JSC_SRC: ${JSC_SRC}") - list(REMOVE_ITEM CORE_SRC ${JSC_SRC}) - # endregion - # region library - if (${V8_LINKING_MODE} STREQUAL "shared") - add_library(v8 SHARED IMPORTED) - elseif (${V8_LINKING_MODE} STREQUAL "static") - string(APPEND CMAKE_SHARED_LINKER_FLAGS " -Wl,--exclude-libs,${V8_LIBRARY_NAME}") - add_library(v8 STATIC IMPORTED) - else() - message(FATAL_ERROR "V8_LINKING_MODE expected to be `shared` or `static`, but received ${V8_LINKING_MODE}") - endif() - set_property(TARGET v8 PROPERTY IMPORTED_LOCATION ${V8_LIBRARY_PATH}/${V8_LIBRARY_NAME}) - list(APPEND HIPPY_DEPS v8) - # endregion - foreach(INCLUDE_DIRECTORY ${V8_INCLUDE_DIRECTORIES}) - include_directories(${INCLUDE_DIRECTORY}) - endforeach() - foreach(DEFINITION ${V8_DEFINITIONS}) - add_definitions(${DEFINITION}) - endforeach() -elseif (${JS_ENGINE} STREQUAL "JSC") - # region remove v8 code - file(GLOB_RECURSE V8_SRC ${CORE_SRC_DIR}/src/napi/v8/*) - message("V8_SRC: ${V8_SRC}") - list(REMOVE_ITEM CORE_SRC ${V8_SRC}) - # endregion -else() - message(FATAL_ERROR "${JS_ENGINE} is not supported") -endif() - -include_directories(${PROJECT_SOURCE_DIR}/include) -include_directories(${CORE_SRC_DIR}/include) -include_directories(${PROJECT_SOURCE_DIR}) -include_directories(${CORE_SRC_DIR}/third_party/base/include) - -add_library(${CMAKE_PROJECT_NAME} SHARED ${CORE_SRC} ${URL_PARSER_SRC} ${JNI_SRC}) -target_link_libraries(${CMAKE_PROJECT_NAME} ${HIPPY_DEPS}) - -if ((${JS_ENGINE} STREQUAL "V8") AND (${V8_LINKING_MODE} STREQUAL "shared")) - foreach(LIBRARY_DEP ${V8_LIBRARY_DEPS}) - add_custom_command( - TARGET ${CMAKE_PROJECT_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${V8_LIBRARY_PATH}/${LIBRARY_DEP} $) - endforeach() -endif() +add_subdirectory(${PROJECT_ROOT_DIR}/layout/android ${CMAKE_CURRENT_BINARY_DIR}/layout/android) diff --git a/android/sdk/src/main/jni/HippyAndroidSdk.sln b/android/sdk/src/main/jni/HippyAndroidSdk.sln deleted file mode 100644 index ec8a2ca23e9..00000000000 --- a/android/sdk/src/main/jni/HippyAndroidSdk.sln +++ /dev/null @@ -1,59 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28803.452 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "HippyAndroidSdk", "HippyAndroidSdk.vcxproj", "{E0581439-C51A-4F6C-9ABF-D697C33333B0}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core", "..\..\..\..\..\core\core.vcxproj", "{33620009-97B0-440A-88C7-AF0AFD05DAD8}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|ARM = Debug|ARM - Debug|ARM64 = Debug|ARM64 - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|ARM = Release|ARM - Release|ARM64 = Release|ARM64 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E0581439-C51A-4F6C-9ABF-D697C33333B0}.Debug|ARM.ActiveCfg = Debug|ARM - {E0581439-C51A-4F6C-9ABF-D697C33333B0}.Debug|ARM.Build.0 = Debug|ARM - {E0581439-C51A-4F6C-9ABF-D697C33333B0}.Debug|ARM.Deploy.0 = Debug|ARM - {E0581439-C51A-4F6C-9ABF-D697C33333B0}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {E0581439-C51A-4F6C-9ABF-D697C33333B0}.Debug|ARM64.Build.0 = Debug|ARM64 - {E0581439-C51A-4F6C-9ABF-D697C33333B0}.Debug|x64.ActiveCfg = Debug|x64 - {E0581439-C51A-4F6C-9ABF-D697C33333B0}.Debug|x64.Build.0 = Debug|x64 - {E0581439-C51A-4F6C-9ABF-D697C33333B0}.Debug|x86.ActiveCfg = Debug|x86 - {E0581439-C51A-4F6C-9ABF-D697C33333B0}.Debug|x86.Build.0 = Debug|x86 - {E0581439-C51A-4F6C-9ABF-D697C33333B0}.Release|ARM.ActiveCfg = Release|ARM - {E0581439-C51A-4F6C-9ABF-D697C33333B0}.Release|ARM.Build.0 = Release|ARM - {E0581439-C51A-4F6C-9ABF-D697C33333B0}.Release|ARM.Deploy.0 = Release|ARM - {E0581439-C51A-4F6C-9ABF-D697C33333B0}.Release|ARM64.ActiveCfg = Release|ARM64 - {E0581439-C51A-4F6C-9ABF-D697C33333B0}.Release|ARM64.Build.0 = Release|ARM64 - {E0581439-C51A-4F6C-9ABF-D697C33333B0}.Release|x64.ActiveCfg = Release|x64 - {E0581439-C51A-4F6C-9ABF-D697C33333B0}.Release|x64.Build.0 = Release|x64 - {E0581439-C51A-4F6C-9ABF-D697C33333B0}.Release|x86.ActiveCfg = Release|x86 - {E0581439-C51A-4F6C-9ABF-D697C33333B0}.Release|x86.Build.0 = Release|x86 - {33620009-97B0-440A-88C7-AF0AFD05DAD8}.Debug|ARM.ActiveCfg = Debug|ARM - {33620009-97B0-440A-88C7-AF0AFD05DAD8}.Debug|ARM.Build.0 = Debug|ARM - {33620009-97B0-440A-88C7-AF0AFD05DAD8}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {33620009-97B0-440A-88C7-AF0AFD05DAD8}.Debug|ARM64.Build.0 = Debug|ARM64 - {33620009-97B0-440A-88C7-AF0AFD05DAD8}.Debug|x64.ActiveCfg = Debug|ARM - {33620009-97B0-440A-88C7-AF0AFD05DAD8}.Debug|x86.ActiveCfg = Debug|ARM - {33620009-97B0-440A-88C7-AF0AFD05DAD8}.Release|ARM.ActiveCfg = Release|ARM - {33620009-97B0-440A-88C7-AF0AFD05DAD8}.Release|ARM.Build.0 = Release|ARM - {33620009-97B0-440A-88C7-AF0AFD05DAD8}.Release|ARM64.ActiveCfg = Release|ARM64 - {33620009-97B0-440A-88C7-AF0AFD05DAD8}.Release|ARM64.Build.0 = Release|ARM64 - {33620009-97B0-440A-88C7-AF0AFD05DAD8}.Release|x64.ActiveCfg = Release|ARM - {33620009-97B0-440A-88C7-AF0AFD05DAD8}.Release|x86.ActiveCfg = Release|ARM - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {F20660D0-F7FD-4BDF-8ABF-B6935DA89A50} - EndGlobalSection -EndGlobal diff --git a/android/sdk/src/main/jni/HippyAndroidSdk.vcxproj b/android/sdk/src/main/jni/HippyAndroidSdk.vcxproj deleted file mode 100644 index 20431e3a29b..00000000000 --- a/android/sdk/src/main/jni/HippyAndroidSdk.vcxproj +++ /dev/null @@ -1,224 +0,0 @@ - - - - - Debug - ARM - - - Release - ARM - - - Debug - ARM64 - - - Release - ARM64 - - - - - - - - {33620009-97b0-440a-88c7-af0afd05dad8} - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {e0581439-c51a-4f6c-9abf-d697c33333b0} - Android - hippy - 14.0 - Android - 3.0 - - - - DynamicLibrary - true - Clang_5_0 - android-28 - - - DynamicLibrary - false - Clang_5_0 - - - DynamicLibrary - true - Clang_5_0 - android-16 - - - DynamicLibrary - false - Clang_5_0 - android-16 - - - - - - - - $(ProjectDir)..\..\..\build\outputs\arm64-v8a\ - $(ProjectDir)..\..\..\build\intermediates\$(Platform)\$(Configuration)\ - .;$(IncludePath) - - - - - .;$(IncludePath) - $(ProjectDir)..\..\..\build\outputs\armeabi-v7a\ - $(ProjectDir)..\..\..\build\intermediates\$(Platform)\$(Configuration)\ - lib$(RootNamespace)bridge - - - .;$(IncludePath) - $(ProjectDir)..\..\..\build\outputs\armeabi-v7a\ - $(ProjectDir)..\..\..\build\intermediates\$(Platform)\$(Configuration)\ - - - - NotUsing - - - c++14 - __ANDROID_API__=$(AndroidAPILevelNumber);OS_ANDROID;DEBUG - - - cd ../../../.. && set skipCmakeAndNinja=1 && gradle :example:assembleDebug - Building example-debug.apk - - - $(ProjectDir)..\..\..\build\outputs\arm64-v8a\$(TargetName)$(TargetExt) - third_party\v8\arm64-v8a\libmtt_shared.so;third_party\v8\arm64-v8a\libmttv8.so - -lm - - - - - NotUsing - - - c++14 - - - - - Use - ../../../../../core/include/core/core.h - c++14 - __ANDROID_API__=$(AndroidAPILevelNumber);OS_ANDROID;DEBUG;V8_IMMINENT_DEPRECATION_WARNINGS;V8_DEPRECATION_WARNINGS - .\third_party\v8\maintenance\x5-lite\include;.;.\include;..\..\..\..\..\core\include;..\..\..\..\..\core\third_party\base\include;%(AdditionalIncludeDirectories) - - - third_party\v8\maintenance\x5-lite\libs\armeabi-v7a\libmtt_shared.so;third_party\v8\maintenance\x5-lite\libs\armeabi-v7a\libmttv8.so - $(ProjectDir)..\..\..\build\outputs\armeabi-v7a\$(TargetName)$(TargetExt) - - - m;atomic;%(LibraryDependencies) - - - cd ../../../../../examples/android-demo && set skipCmakeAndNinja=1 && gradlew :example:assembleDebug - - - Building example-debug.apk - - - - - - - - - - - - - NotUsing - - - c++14 - __ANDROID_API__=$(AndroidAPILevelNumber);OS_ANDROID - MinSize - true - - - third_party\v8\armeabi-v7a\libmtt_shared.so;third_party\v8\armeabi-v7a\libmttv8.so - $(ProjectDir)..\..\..\build\outputs\armeabi-v7a\$(TargetName)$(TargetExt) - -lm - - - cd ../../../.. && set skipCmakeAndNinja=1 && gradlew :example:assembleDebug - - - Building example-debug.apk - - - - - diff --git a/android/sdk/src/main/jni/HippyAndroidSdk.vcxproj.user b/android/sdk/src/main/jni/HippyAndroidSdk.vcxproj.user deleted file mode 100644 index 58087a787cc..00000000000 --- a/android/sdk/src/main/jni/HippyAndroidSdk.vcxproj.user +++ /dev/null @@ -1,15 +0,0 @@ - - - - true - - - $(ProjectDir)..\..\..\..\..\examples\android-demo\example\build\outputs\apk\debug\example-debug.apk - AndroidDebugger - $(ProjectDir)..\..\..\build\intermediates\ARM\Debug - - - $(ProjectDir)..\..\..\..\example\build\outputs\apk\example-debug.apk - AndroidDebugger - - \ No newline at end of file diff --git a/android/sdk/src/main/jni/include/bridge/adr_bridge.h b/android/sdk/src/main/jni/include/bridge/adr_bridge.h new file mode 100644 index 00000000000..3ad7afb8907 --- /dev/null +++ b/android/sdk/src/main/jni/include/bridge/adr_bridge.h @@ -0,0 +1,49 @@ +/* + * + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include "core/inspector/bridge.h" + +#include + +#include "jni/scoped_java_ref.h" + +namespace hippy { + class ADRBridge: public Bridge { + public: + ADRBridge(JNIEnv* j_env, jobject j_obj): ref_(std::make_shared(j_env, j_obj)){} + virtual ~ADRBridge() = default; +#ifndef V8_WITHOUT_INSPECTOR + virtual void SendResponse(std::unique_ptr message) override; + virtual void SendNotification(std::unique_ptr message) override; +#endif + inline jobject GetObj() { + return ref_->GetObj(); + } + inline std::shared_ptr GetRef() { + return ref_; + } + private: + std::shared_ptr ref_; + }; +} diff --git a/android/sdk/src/main/jni/include/bridge/entry.h b/android/sdk/src/main/jni/include/bridge/entry.h index a0110c02f91..a3b532b084c 100644 --- a/android/sdk/src/main/jni/include/bridge/entry.h +++ b/android/sdk/src/main/jni/include/bridge/entry.h @@ -27,7 +27,14 @@ namespace hippy { namespace bridge { -void InitNativeLogHandler(JNIEnv* j_env, jobject j_object, jobject j_logger); +void setNativeLogHandler(JNIEnv* j_env, __unused jobject j_object, jobject j_logger); + +jint CreateSnapshot(JNIEnv* j_env, + __unused jobject j_obj, + jobjectArray j_script_array, + jstring j_base_uri, + jstring j_snapshot_uri, + jstring j_config); jlong InitInstance(JNIEnv* j_env, jobject j_object, @@ -36,16 +43,18 @@ jlong InitInstance(JNIEnv* j_env, jboolean j_bridge_param_json, jboolean j_is_dev_module, jobject j_callback, - jlong j_group_id); + jlong j_group_id, + jobject j_vm_init_param); void DestroyInstance(JNIEnv* j_env, jobject j_object, jlong j_runtime_id, jboolean j_single_thread_mode, + jboolean j_is_reload, jobject j_callback); jboolean RunScriptFromUri(JNIEnv* j_env, - jobject j_obj, + __unused jobject j_obj, jstring j_uri, jobject j_aasset_manager, jboolean j_can_use_code_cache, @@ -53,5 +62,13 @@ jboolean RunScriptFromUri(JNIEnv* j_env, jlong j_runtime_id, jobject j_cb); +void RunScript(JNIEnv* j_env, __unused jobject, jlong j_runtime_id, jstring j_script); + +void RunInJsThread(JNIEnv *j_env, + jobject j_object, + jlong j_runtime_id, + jobject j_callback); + + } // namespace bridge } // namespace hippy diff --git a/android/sdk/src/main/jni/include/bridge/java2js.h b/android/sdk/src/main/jni/include/bridge/java2js.h index fe6847c0cdd..169f004c871 100644 --- a/android/sdk/src/main/jni/include/bridge/java2js.h +++ b/android/sdk/src/main/jni/include/bridge/java2js.h @@ -27,7 +27,10 @@ namespace hippy { namespace bridge { -void CallJavaMethod(jobject j_obj, jlong j_value, jstring j_msg = nullptr); +void CallJavaMethod(jobject j_obj, + jlong j_ret_code, + jstring j_ret_content = nullptr, + jstring j_payload = nullptr); void CallFunctionByHeapBuffer(JNIEnv* j_env, jobject j_obj, diff --git a/android/sdk/src/main/jni/include/bridge/js2java.h b/android/sdk/src/main/jni/include/bridge/js2java.h index 81ff1ff2abf..9704a05be96 100644 --- a/android/sdk/src/main/jni/include/bridge/js2java.h +++ b/android/sdk/src/main/jni/include/bridge/js2java.h @@ -29,7 +29,7 @@ namespace hippy { namespace bridge { -void CallJava(hippy::napi::CBDataTuple* data); +void CallJava(const hippy::napi::CallbackInfo& info, int32_t runtime_id); } // namespace bridge } // namespace hippy diff --git a/android/sdk/src/main/jni/include/bridge/runtime.h b/android/sdk/src/main/jni/include/bridge/runtime.h index 8eb4eec5cc7..ed96b177247 100644 --- a/android/sdk/src/main/jni/include/bridge/runtime.h +++ b/android/sdk/src/main/jni/include/bridge/runtime.h @@ -25,24 +25,28 @@ #include #include +#include #include #include "core/core.h" -#include "jni/turbo_module_runtime.h" +#include "jni/java_turbo_module.h" #include "jni/scoped_java_ref.h" -#ifdef ENABLE_INSPECTOR -#include "inspector/v8_inspector_client_impl.h" -#endif +#include "v8/interrupt_queue.h" class Runtime { public: - Runtime(std::shared_ptr bridge, bool enable_v8_serialization, bool is_dev); + using Bridge = hippy::Bridge; + using CtxValue = hippy::napi::CtxValue; +#ifndef V8_WITHOUT_INSPECTOR + using V8InspectorContext = hippy::inspector::V8InspectorContext; +#endif + Runtime(std::shared_ptr bridge, bool enable_v8_serialization, bool is_dev); inline bool IsEnableV8Serialization() { return enable_v8_serialization_; } inline bool IsDebug() { return is_debug_; } inline int32_t GetId() { return id_; } inline int64_t GetGroupId() { return group_id_; } - inline std::shared_ptr GetBridge() { return bridge_; } + inline std::shared_ptr GetBridge() { return bridge_; } inline std::shared_ptr GetEngine() { return engine_; } inline std::shared_ptr GetScope() { return scope_; } inline std::shared_ptr GetBridgeFunc() { @@ -56,31 +60,63 @@ class Runtime { } inline void SetEngine(std::shared_ptr engine) { engine_ = engine; } inline void SetScope(std::shared_ptr scope) { scope_ = scope; } +#ifndef V8_WITHOUT_INSPECTOR + inline void SetInspectorContext(std::shared_ptr inspector_context) { + inspector_context_ = inspector_context; + } + inline std::shared_ptr GetInspectorContext() { return inspector_context_; } +#endif + inline std::shared_ptr GetTurboManager() { + return turbo_manager_; + } + + inline void SetTurboModuleManager(std::shared_ptr turbo_manager) { + turbo_manager_ = turbo_manager; + } - inline std::shared_ptr GetTurboModuleRuntime() { - return turbo_module_runtime_; + inline std::any GetData(uint8_t slot) { + return slot_[slot]; } - inline void SetTurboModuleRuntime( - std::shared_ptr turbo_module_runtime) { - turbo_module_runtime_ = turbo_module_runtime; + inline bool HasData(uint8_t slot) { + return slot_.find(slot) != slot_.end(); + } + inline void SetData(uint8_t slot, std::any data) { + slot_[slot] = data; + } + inline void SetInterruptQueue(std::shared_ptr queue) { + interrupt_queue_ = queue; + } + inline std::shared_ptr GetInterruptQueue() { + return interrupt_queue_; + } + inline auto GetNearHeapLimitCallback() { + return near_heap_limit_cb_; + } + inline void SetNearHeapLimitCallback(std::function cb) { + near_heap_limit_cb_ = cb; } - static void Insert(std::shared_ptr runtime); + static void Insert(const std::shared_ptr& runtime); static std::shared_ptr Find(int32_t id); static std::shared_ptr Find(v8::Isolate* isolate); static bool Erase(int32_t id); - static bool Erase(std::shared_ptr runtime); - static bool ReleaseKey(int64_t id); + static bool Erase(const std::shared_ptr& runtime); private: bool enable_v8_serialization_; bool is_debug_; int64_t group_id_; - std::shared_ptr bridge_; + std::shared_ptr bridge_; std::string serializer_reused_buffer_; std::shared_ptr engine_; std::shared_ptr scope_; std::shared_ptr bridge_func_; int32_t id_; - std::shared_ptr turbo_module_runtime_; + std::unordered_map slot_; + std::shared_ptr interrupt_queue_; + std::function near_heap_limit_cb_; +#ifndef V8_WITHOUT_INSPECTOR + std::shared_ptr inspector_context_; +#endif + std::shared_ptr turbo_manager_; }; diff --git a/android/sdk/src/main/jni/include/hippy.h b/android/sdk/src/main/jni/include/hippy.h index 7a5e2a2588c..ce355945820 100644 --- a/android/sdk/src/main/jni/include/hippy.h +++ b/android/sdk/src/main/jni/include/hippy.h @@ -3,7 +3,7 @@ * Tencent is pleased to support the open source community by making * Hippy available. * - * Copyright (C) 2019 THL A29 Limited, a Tencent company. + * Copyright (C) 2019-2022 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,14 +26,15 @@ #include "bridge/java2js.h" #include "bridge/js2java.h" #include "bridge/runtime.h" -#include "bridge/serializer.h" +#include "core/vm/v8/serializer.h" #include "jni/exception_handler.h" #include "jni/jni_env.h" #include "jni/jni_register.h" #include "jni/jni_utils.h" #include "jni/scoped_java_ref.h" #include "loader/adr_loader.h" -#ifdef ENABLE_INSPECTOR +#include "performance/memory.h" +#ifndef V8_WITHOUT_INSPECTOR #include "inspector/v8_channel_impl.h" #include "inspector/v8_inspector_client_impl.h" #endif diff --git a/android/sdk/src/main/jni/include/inspector/v8_inspector_client_impl.h b/android/sdk/src/main/jni/include/inspector/v8_inspector_client_impl.h deleted file mode 100644 index 883c7e7d492..00000000000 --- a/android/sdk/src/main/jni/include/inspector/v8_inspector_client_impl.h +++ /dev/null @@ -1,111 +0,0 @@ -/* - * - * Tencent is pleased to support the open source community by making - * Hippy available. - * - * Copyright (C) 2019 THL A29 Limited, a Tencent company. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -#pragma once - -#include -#include - -#include "base/unicode_string_view.h" -#include "core/core.h" -#include "jni/scoped_java_ref.h" -#include "v8_channel_impl.h" - -namespace hippy { -namespace inspector { - -class V8InspectorClientImpl : public v8_inspector::V8InspectorClient { - public: - using unicode_string_view = tdf::base::unicode_string_view; - - explicit V8InspectorClientImpl(std::shared_ptr scope); - ~V8InspectorClientImpl() = default; - - void Reset(std::shared_ptr scope, std::shared_ptr bridge); - void Connect(std::shared_ptr bridge); - - void SendMessageToV8(const unicode_string_view& params); - void CreateContext(); - void DestroyContext(); - v8::Local ensureDefaultContextInGroup( - int contextGroupId) override; - - void runMessageLoopOnPause(int contextGroupId) override; - void quitMessageLoopOnPause() override; - void runIfWaitingForDebugger(int contextGroupId) override; - - void muteMetrics(int contextGroupId) override {} - void unmuteMetrics(int contextGroupId) override {} - - void beginUserGesture() override {} - void endUserGesture() override {} - - std::unique_ptr valueSubtype( - v8::Local) override { - return nullptr; - } - bool formatAccessorsAsProperties(v8::Local) override { - return false; - } - bool isInspectableHeapObject(v8::Local) override { return true; } - - void beginEnsureAllContextsInGroup(int contextGroupId) override {} - void endEnsureAllContextsInGroup(int contextGroupId) override {} - - void installAdditionalCommandLineAPI(v8::Local, - v8::Local) override {} - void consoleAPIMessage(int contextGroupId, - v8::Isolate::MessageErrorLevel level, - const v8_inspector::StringView& message, - const v8_inspector::StringView& url, - unsigned lineNumber, - unsigned columnNumber, - v8_inspector::V8StackTrace*) override {} - - v8::MaybeLocal memoryInfo(v8::Isolate*, - v8::Local) override { - return v8::MaybeLocal(); - } - - void consoleTime(const v8_inspector::StringView& title) override {} - void consoleTimeEnd(const v8_inspector::StringView& title) override {} - void consoleTimeStamp(const v8_inspector::StringView& title) override {} - void consoleClear(int contextGroupId) override {} - double currentTimeMS() override { return 0; } - typedef void (*TimerCallback)(void*); - void startRepeatingTimer(double, TimerCallback, void* data) override {} - void cancelTimer(void* data) override {} - - // TODO(dgozman): this was added to support service worker shadow page. We - // should not connect at all. - bool canExecuteScripts(int contextGroupId) override { return true; } - - void maxAsyncCallStackDepthChanged(int depth) override {} - - private: - std::shared_ptr scope_; - std::unique_ptr inspector_; - std::unique_ptr channel_; - std::unique_ptr session_; -}; - -} // namespace inspector -} // namespace hippy diff --git a/android/sdk/src/main/jni/include/jni/convert_utils.h b/android/sdk/src/main/jni/include/jni/convert_utils.h index 66adc06c191..11ea3c9afe1 100644 --- a/android/sdk/src/main/jni/include/jni/convert_utils.h +++ b/android/sdk/src/main/jni/include/jni/convert_utils.h @@ -24,20 +24,17 @@ #include -#include "core/napi/js_native_api_types.h" -#include "hippy.h" - -#ifndef ANDROID_DEMO_CONVERT_UTILS_H -#define ANDROID_DEMO_CONVERT_UTILS_H +#include "core/core.h" +#include "scoped_java_ref.h" struct JNIArgs { JNIArgs(size_t count) : args_(count) {} std::vector args_; - std::vector global_refs_; + std::vector> global_refs_; }; -template +template std::string ToString(T v) { std::ostringstream stream; stream << v; @@ -51,75 +48,77 @@ struct MethodInfo { class ConvertUtils { public: + using Ctx = hippy::napi::Ctx; + using CtxValue = hippy::napi::CtxValue; + static bool Init(); - static bool Destory(); + static bool Destroy(); static std::vector GetMethodArgTypesFromSignature( const std::string &method_signature); - static std::shared_ptr ConvertJSIArgsToJNIArgs( - hippy::napi::TurboEnv &turbo_env, + static std::tuple> ConvertJSIArgsToJNIArgs( + const std::shared_ptr& ctx, const std::string &module_name, const std::string &method_name, const std::vector &method_arg_types, - const std::vector> &arg_values); + const std::vector> &arg_values); - static std::shared_ptr ConvertMethodResultToJSValue( - hippy::napi::TurboEnv &turbo_env, - const jobject &obj, + static std::tuple> ConvertMethodResultToJSValue( + const std::shared_ptr& ctx, + const std::shared_ptr &obj, const MethodInfo &method_info, - const jvalue *args); + const jvalue *args, + const std::shared_ptr& scope); - static jobject ToJObject(hippy::napi::TurboEnv &turbo_env, - const std::shared_ptr &value); + static std::tuple ToJObject(const std::shared_ptr& ctx, + const std::shared_ptr &value); - static jobject ToHippyMap( - hippy::napi::TurboEnv &turbo_env, - const std::shared_ptr &value); + static std::tuple ToHippyMap( + const std::shared_ptr& ctx, + const std::shared_ptr &value); - static jobject ToHippyArray( - hippy::napi::TurboEnv &turbo_env, - const std::shared_ptr &value); + static std::tuple ToHippyArray( + const std::shared_ptr& ctx, + const std::shared_ptr &value); - static std::shared_ptr ToJsValueInArray( - hippy::napi::TurboEnv &turbo_env, - const jobject &array, + static std::tuple> ToJsValueInArray( + const std::shared_ptr& ctx, + jobject array, int index); - static std::shared_ptr ToJsArray( - hippy::napi::TurboEnv &turbo_env, - const jobject &array); + static std::tuple> ToJsArray( + const std::shared_ptr& ctx, + jobject array); - static std::shared_ptr ToJsMap( - hippy::napi::TurboEnv &turbo_env, - const jobject &map); + static std::tuple> ToJsMap( + const std::shared_ptr& ctx, + jobject map); - static bool HandleBasicType( - hippy::napi::TurboEnv &turbo_env, + static std::tuple HandleBasicType( + const std::shared_ptr& ctx, const std::string &type, jvalue &j_args, - const std::shared_ptr &value); + const std::shared_ptr &value); - static bool HandleObjectType( - hippy::napi::TurboEnv &turbo_env, + static std::tuple HandleObjectType( + const std::shared_ptr& ctx, const std::string &module_name, const std::string &method_name, const std::string &type, jvalue &j_args, - const std::shared_ptr &value, - std::vector &global_refs); - - static void ThrowException(const std::shared_ptr &ctx, - const std::string &info); + const std::shared_ptr &value, + std::vector> &global_refs); static std::unordered_map GetMethodMap( const std::string &method_map_str); - static std::shared_ptr ToHostObject( - hippy::napi::TurboEnv &turbo_env, + static std::shared_ptr ToHostObject( + const std::shared_ptr& ctx, jobject &j_obj, - std::string name); + std::string name, + std::shared_ptr scope); }; static jclass hippy_array_clazz; @@ -141,31 +140,32 @@ static jclass float_clazz; static jclass long_clazz; static jclass boolean_clazz; static jmethodID integer_constructor; +static jmethodID int_value; static jmethodID double_constructor; static jmethodID double_value; static jmethodID float_constructor; +static jmethodID float_value; static jmethodID long_constructor; +static jmethodID long_value; static jmethodID boolean_constructor; static jmethodID boolean_value; static jclass promise_clazz; static jmethodID promise_constructor; -const std::string kint = "I"; -const std::string kdouble = "D"; -const std::string kfloat = "F"; -const std::string klong = "J"; -const std::string kboolean = "Z"; -const std::string kInteger = "Ljava/lang/Integer;"; -const std::string kDouble = "Ljava/lang/Double;"; -const std::string kFloat = "Ljava/lang/Float;"; -const std::string kLong = "Ljava/lang/Long;"; -const std::string kString = "Ljava/lang/String;"; -const std::string kBoolean = "Ljava/lang/Boolean;"; -const std::string kHippyArray = "Lcom/tencent/mtt/hippy/common/HippyArray;"; -const std::string kHippyMap = "Lcom/tencent/mtt/hippy/common/HippyMap;"; -const std::string kPromise = "Lcom/tencent/mtt/hippy/modules/Promise;"; -const std::string kvoid = "V"; -const std::string kUnSupportedType = "Lcom/invalid;"; - -#endif // ANDROID_DEMO_CONVERT_UTILS_H +constexpr char kInt[] = "I"; +constexpr char kDouble[] = "D"; +constexpr char kFloat[] = "F"; +constexpr char kLong[] = "J"; +constexpr char kBoolean[] = "Z"; +constexpr char kVoid[] = "V"; +constexpr char kIntegerObject[] = "Ljava/lang/Integer;"; +constexpr char kDoubleObject[] = "Ljava/lang/Double;"; +constexpr char kFloatObject[] = "Ljava/lang/Float;"; +constexpr char kLongObject[] = "Ljava/lang/Long;"; +constexpr char kBooleanObject[] = "Ljava/lang/Boolean;"; +constexpr char kString[] = "Ljava/lang/String;"; +constexpr char kHippyArray[] = "Lcom/tencent/mtt/hippy/common/HippyArray;"; +constexpr char kHippyMap[] = "Lcom/tencent/mtt/hippy/common/HippyMap;"; +constexpr char kPromise[] = "Lcom/tencent/mtt/hippy/modules/Promise;"; +constexpr char kUnSupportedType[] = "Lcom/invalid;"; diff --git a/android/sdk/src/main/jni/include/jni/exception_handler.h b/android/sdk/src/main/jni/include/jni/exception_handler.h index ef2beef5924..533c942381c 100644 --- a/android/sdk/src/main/jni/include/jni/exception_handler.h +++ b/android/sdk/src/main/jni/include/jni/exception_handler.h @@ -35,7 +35,7 @@ class ExceptionHandler { ExceptionHandler() = default; ~ExceptionHandler() = default; - static void ReportJsException(std::shared_ptr runtime, + static void ReportJsException(const std::shared_ptr& runtime, const unicode_string_view& desc, const unicode_string_view& stack); }; diff --git a/android/sdk/src/main/jni/include/jni/java_turbo_module.h b/android/sdk/src/main/jni/include/jni/java_turbo_module.h index f53c9adfbe8..3cc4f4c4857 100644 --- a/android/sdk/src/main/jni/include/jni/java_turbo_module.h +++ b/android/sdk/src/main/jni/include/jni/java_turbo_module.h @@ -26,45 +26,61 @@ #include -#include "convert_utils.h" -#include "core/napi/js_native_turbo.h" -#include "core/napi/v8/js_native_turbo_v8.h" -#include "hippy.h" +#include "core/core.h" -#ifndef HIPPYJSI_JAVATURBOMODULE_H -#define HIPPYJSI_JAVATURBOMODULE_H +#include "convert_utils.h" +#include "scoped_java_ref.h" -class JavaTurboModule : public hippy::napi::HippyTurboModule { +class JavaTurboModule { public: - JavaTurboModule(const std::string &name, std::shared_ptr &impl); + using Ctx = hippy::napi::Ctx; + using CtxValue = hippy::napi::CtxValue; + using FuncWrapper = hippy::napi::FuncWrapper; + using PropertyDescriptor = hippy::napi::PropertyDescriptor; + + struct TurboWrapper { + JavaTurboModule* module; + std::shared_ptr name; + std::unique_ptr func_wrapper; - ~JavaTurboModule(); + TurboWrapper(JavaTurboModule* module, const std::shared_ptr& name) { + this->module = module; + this->name = name; + this->func_wrapper = nullptr; + } + + void SetFunctionWrapper(std::unique_ptr wrapper) { + func_wrapper = std::move(wrapper); + } + }; + + JavaTurboModule(const std::string& name, std::shared_ptr& impl, const std::shared_ptr& ctx); std::shared_ptr impl_; - jclass impl_j_clazz_; + std::shared_ptr impl_j_clazz_; + + std::string name; + + std::unique_ptr wrapper_holder_; // methodName, signature std::unordered_map method_map_; - virtual std::shared_ptr InvokeJavaMethod( - hippy::napi::TurboEnv &turbo_env, - const std::shared_ptr &prop_name, - const std::shared_ptr &this_val, - const std::shared_ptr *args, - size_t count); - - void InitPropertyMap(); + std::shared_ptr constructor; + std::unique_ptr constructor_wrapper; + std::unordered_map, std::unique_ptr> turbo_wrapper_map; + std::unordered_map, std::shared_ptr> func_map; + std::shared_ptr properties[1]; - virtual std::shared_ptr Get( - hippy::napi::TurboEnv &, - const std::shared_ptr &prop_name) override; + std::shared_ptr InvokeJavaMethod( + const std::shared_ptr& prop_name, + const hippy::napi::CallbackInfo& info, + void* data); - virtual void DeleteGlobalRef(const std::shared_ptr &jni_args); + void InitPropertyMap(); static void Init(); - static void Destory(); + static void Destroy(); }; - -#endif // HIPPYJSI_JAVATURBOMODULE_H diff --git a/android/sdk/src/main/jni/include/jni/jni_env.h b/android/sdk/src/main/jni/include/jni/jni_env.h index 6757ff20378..de837da7ddc 100644 --- a/android/sdk/src/main/jni/include/jni/jni_env.h +++ b/android/sdk/src/main/jni/include/jni/jni_env.h @@ -48,7 +48,6 @@ class JNIEnvironment { inline JNIWrapper GetMethods() { return wrapper_; } void init(JavaVM* vm, JNIEnv* env); JNIEnv* AttachCurrentThread(); - void DetachCurrentThread(); private: static std::shared_ptr instance_; diff --git a/android/sdk/src/main/jni/include/jni/jni_register.h b/android/sdk/src/main/jni/include/jni/jni_register.h index f428c79b4d5..16010256cee 100644 --- a/android/sdk/src/main/jni/include/jni/jni_register.h +++ b/android/sdk/src/main/jni/include/jni/jni_register.h @@ -25,8 +25,8 @@ #include #include - -#include "core/core.h" +#include +#include class JNIRegisterData { public: @@ -50,6 +50,9 @@ class JNIRegister { static bool RegisterMethods(JNIEnv *j_env); JNIRegister() = default; + JNIRegister(const JNIRegister &) = delete; + JNIRegister &operator=(const JNIRegister &) = delete; + bool RegisterJNIModule(const char *module, const char *name, const char *signature, @@ -71,8 +74,6 @@ class JNIRegister { private: std::unordered_map> jni_modules_; - - DISALLOW_COPY_AND_ASSIGN(JNIRegister); }; #define REGISTER_JNI_INTERNAL(clazz, name, signature, pointer, is_static, key) \ diff --git a/android/sdk/src/main/jni/include/jni/jni_utils.h b/android/sdk/src/main/jni/include/jni/jni_utils.h index 54e12a76d56..afef1ffbdea 100644 --- a/android/sdk/src/main/jni/include/jni/jni_utils.h +++ b/android/sdk/src/main/jni/include/jni/jni_utils.h @@ -27,9 +27,6 @@ #include #include "base/unicode_string_view.h" -#include "v8/v8.h" - -struct HippyBuffer; class JniUtils { using unicode_string_view = tdf::base::unicode_string_view; @@ -51,10 +48,8 @@ class JniUtils { jbyteArray byte_array, jsize j_offset = 0, jsize j_length = -1); - static std::u16string CovertJStringToChars(JNIEnv* j_env, jstring j_str); static unicode_string_view::u8string ToU8String(JNIEnv* j_env, jstring j_str); static unicode_string_view ToStrView(JNIEnv* j_env, jstring j_str); - static void printCurrentThreadID(); }; diff --git a/android/sdk/src/main/jni/include/jni/scoped_java_ref.h b/android/sdk/src/main/jni/include/jni/scoped_java_ref.h index 809cba5d689..8df04737c9f 100644 --- a/android/sdk/src/main/jni/include/jni/scoped_java_ref.h +++ b/android/sdk/src/main/jni/include/jni/scoped_java_ref.h @@ -24,16 +24,15 @@ #include -#include "core/core.h" - class JavaRef { public: JavaRef(JNIEnv* env, jobject obj); ~JavaRef(); + JavaRef(const JavaRef &) = delete; + JavaRef &operator=(const JavaRef &) = delete; + jobject GetObj() { return obj_; } private: jobject obj_; - - DISALLOW_COPY_AND_ASSIGN(JavaRef); }; diff --git a/android/sdk/src/main/jni/include/jni/turbo_module_manager.h b/android/sdk/src/main/jni/include/jni/turbo_module_manager.h index 0b0443c8842..bf1716588af 100644 --- a/android/sdk/src/main/jni/include/jni/turbo_module_manager.h +++ b/android/sdk/src/main/jni/include/jni/turbo_module_manager.h @@ -28,9 +28,7 @@ class TurboModuleManager { public: static void Init(); - static void Destory(); + static void Destroy(); }; int Install(JNIEnv *, jobject, jlong); - -void Uninstall(JNIEnv *, jobject, jlong); diff --git a/android/sdk/src/main/jni/include/jni/turbo_module_runtime.h b/android/sdk/src/main/jni/include/jni/turbo_module_runtime.h deleted file mode 100644 index a18cfcc6a32..00000000000 --- a/android/sdk/src/main/jni/include/jni/turbo_module_runtime.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * - * Tencent is pleased to support the open source community by making - * Hippy available. - * - * Copyright (C) 2019 THL A29 Limited, a Tencent company. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -#pragma once - -#include - -#include - -#include "jni_env.h" - -#ifndef ANDROID_DEMO_TURBOMODULERUNTIME_H -#define ANDROID_DEMO_TURBOMODULERUNTIME_H - -class TurboModuleRuntime { - public: - jobject turbo_module_manager_obj_; - std::shared_ptr turbo_env_; - std::unordered_map> - module_cache_; - - explicit TurboModuleRuntime(jobject obj) { - JNIEnv* env = JNIEnvironment::GetInstance()->AttachCurrentThread(); - turbo_module_manager_obj_ = env->NewGlobalRef(obj); - env->DeleteLocalRef(obj); - } - - ~TurboModuleRuntime() { - TDF_BASE_DLOG(INFO) << "~TurboModuleRuntime()"; - JNIEnv* env = JNIEnvironment::GetInstance()->AttachCurrentThread(); - if (turbo_module_manager_obj_) { - env->DeleteGlobalRef(turbo_module_manager_obj_); - } - - if (turbo_env_) { - turbo_env_.reset(); - } - - if (!module_cache_.empty()) { - module_cache_.clear(); - } - } -}; - -#endif // ANDROID_DEMO_TURBOMODULERUNTIME_H diff --git a/android/sdk/src/main/jni/include/jni/uri.h b/android/sdk/src/main/jni/include/jni/uri.h index 83e4d2d7b3d..3419e6e7c68 100644 --- a/android/sdk/src/main/jni/include/jni/uri.h +++ b/android/sdk/src/main/jni/include/jni/uri.h @@ -39,7 +39,7 @@ class Uri { unicode_string_view GetScheme(); unicode_string_view Normalize(); static bool Init(); - static bool Destory(); + static bool Destroy(); private: jobject j_obj_uri_; diff --git a/android/sdk/src/main/jni/include/loader/adr_loader.h b/android/sdk/src/main/jni/include/loader/adr_loader.h index c9012f5d02c..8090d66ee4b 100644 --- a/android/sdk/src/main/jni/include/loader/adr_loader.h +++ b/android/sdk/src/main/jni/include/loader/adr_loader.h @@ -46,16 +46,18 @@ bool ReadAsset(const tdf::base::unicode_string_view& path, auto asset = AAssetManager_open(aasset_manager, asset_path, AASSET_MODE_STREAMING); if (asset) { - int size = AAsset_getLength(asset); - if (is_auto_fill) { - size += 1; + size_t size; + if (!hippy::base::numeric_cast(AAsset_getLength(asset) + (is_auto_fill ? 1:0), + size)) { + AAsset_close(asset); + return false; } bytes.resize(size); - int offset = 0; + size_t offset = 0; int readbytes; while ((readbytes = AAsset_read(asset, &bytes[0] + offset, bytes.size() - offset)) > 0) { - offset += readbytes; + offset += static_cast(readbytes); } if (is_auto_fill) { bytes.back() = '\0'; @@ -75,8 +77,8 @@ class ADRLoader : public hippy::base::UriLoader { using unicode_string_view = tdf::base::unicode_string_view; using u8string = unicode_string_view::u8string; - ADRLoader(); - virtual ~ADRLoader() {} + ADRLoader() = default; + virtual ~ADRLoader() = default; virtual bool RequestUntrustedContent(const unicode_string_view& uri, std::function cb); @@ -84,26 +86,27 @@ class ADRLoader : public hippy::base::UriLoader { u8string& str); inline void SetBridge(std::shared_ptr bridge) { bridge_ = bridge; } - inline void SetAAssetManager(AAssetManager* aasset_manager) { - aasset_manager_ = aasset_manager; - } inline void SetWorkerTaskRunner(std::weak_ptr runner) { runner_ = runner; } std::function GetRequestCB(int64_t request_id); - int64_t SetRequestCB(std::function cb); + int64_t SetRequestCB(const std::function& cb); + + static void Init(); + static void Destroy(); private: + static AAssetManager* GetAAssetManager(); bool LoadByFile(const unicode_string_view& path, - std::function cb); + const std::function& cb); bool LoadByAsset(const unicode_string_view& file_path, - std::function cb, + const std::function& cb, bool is_auto_fill = false); - bool LoadByHttp(const unicode_string_view& uri, - std::function cb); + bool LoadByJni(const unicode_string_view& uri, + const std::function& cb); std::shared_ptr bridge_; - AAssetManager* aasset_manager_; std::weak_ptr runner_; std::unordered_map> request_map_; + std::mutex mutex_; }; diff --git a/android/sdk/src/main/jni/include/performance/memory.h b/android/sdk/src/main/jni/include/performance/memory.h new file mode 100644 index 00000000000..a7e3b11ecd1 --- /dev/null +++ b/android/sdk/src/main/jni/include/performance/memory.h @@ -0,0 +1,64 @@ +/* + * + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2022 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include +#include +#include "core/base/file.h" + +#ifndef V8_WITHOUT_INSPECTOR +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-conversion" +#include "v8/v8-profiler.h" +#pragma clang diagnostic pop +#endif + +namespace hippy { +namespace bridge { + +// [Heap] GetHeapStatistics +jboolean GetHeapStatistics(JNIEnv *j_env, + jobject j_object, + jlong j_runtime_id, + jobject j_callback); +// [Heap] GetHeapCodeStatistics +jboolean GetHeapCodeStatistics(JNIEnv *j_env, + jobject j_object, + jlong j_runtime_id, + jobject j_callback); +// [Heap] GetHeapSpaceStatistics +jboolean GetHeapSpaceStatistics(JNIEnv *j_env, + jobject j_object, + jlong j_runtime_id, + jobject j_callback); +// [Heap] WriteHeapSnapshot +// Creating a heap snapshot requires memory about twice the size of the heap at the time the snapshot is created. +// This results in the risk of OOM killers terminating the process. +jboolean WriteHeapSnapshot(JNIEnv *j_env, + jobject j_object, + jlong j_runtime_id, + jstring j_heap_snapshot_path, + jobject j_callback); + +} // namespace bridge +} // namespace hippy diff --git a/android/sdk/src/main/jni/include/v8/heap_limit.h b/android/sdk/src/main/jni/include/v8/heap_limit.h new file mode 100644 index 00000000000..890e27dcbf9 --- /dev/null +++ b/android/sdk/src/main/jni/include/v8/heap_limit.h @@ -0,0 +1,40 @@ +/* + * + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2022 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include + +#include "jni/jni_register.h" + +namespace hippy { +inline namespace driver { +inline namespace v8_engine { + +void AddNearHeapLimitCallback(JNIEnv *j_env, + jobject j_object, + jlong j_runtime_id, + jobject j_callback); + +} +} +} diff --git a/android/sdk/src/main/jni/include/v8/interrupt_queue.h b/android/sdk/src/main/jni/include/v8/interrupt_queue.h new file mode 100644 index 00000000000..0c94cee70c4 --- /dev/null +++ b/android/sdk/src/main/jni/include/v8/interrupt_queue.h @@ -0,0 +1,79 @@ +/* + * + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include +#include +#include +#include + +#include "core/base/task.h" +#include "core/base/task_runner.h" +#include "base/persistent_object_map.h" + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wconversion" +#include "v8/v8.h" +#pragma clang diagnostic pop + +namespace hippy { +inline namespace driver { +inline namespace runtime { + +class InterruptQueue: public std::enable_shared_from_this { + public: + using Task = base::Task; + using TaskRunner = base::TaskRunner; + using PersistentObjectMap = footstone::PersistentObjectMap>; + + InterruptQueue(v8::Isolate* isolate_); + ~InterruptQueue() = default; + + inline uint32_t GetId() { return id_; } + + inline void SetTaskRunner(std::shared_ptr task_runner) { + task_runner_ = task_runner; + } + + void PostTask(std::unique_ptr task); + void Run(); + + static inline PersistentObjectMap& GetPersistentMap() { + return persistent_map_; + } + + private: + uint32_t id_; + v8::Isolate* isolate_; + std::queue> task_queue_; + std::mutex queue_mutex_; + std::shared_ptr task_runner_; + + static std::atomic g_id; + static PersistentObjectMap persistent_map_; +}; + +} // namespace runtime +} // namespace driver +} // namespace hippy + diff --git a/android/sdk/src/main/jni/include/v8/request_interrupt.h b/android/sdk/src/main/jni/include/v8/request_interrupt.h new file mode 100644 index 00000000000..fd8fb75559a --- /dev/null +++ b/android/sdk/src/main/jni/include/v8/request_interrupt.h @@ -0,0 +1,40 @@ +/* + * + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2022 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include + +#include "jni/jni_register.h" + +namespace hippy { +inline namespace driver { +inline namespace v8_engine { + +void RequestInterrupt(JNIEnv *j_env, + jobject j_object, + jlong j_runtime_id, + jobject j_callback); + +} +} +} diff --git a/android/sdk/src/main/jni/include/v8/stack_trace.h b/android/sdk/src/main/jni/include/v8/stack_trace.h new file mode 100644 index 00000000000..b9bdbd632dc --- /dev/null +++ b/android/sdk/src/main/jni/include/v8/stack_trace.h @@ -0,0 +1,40 @@ +/* + * + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2022 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include + +#include "jni/jni_register.h" + +namespace hippy { +inline namespace driver { +inline namespace v8_engine { + +void GetCurrentStackTrace(JNIEnv *j_env, + jobject j_object, + jlong j_runtime_id, + jobject j_callback); + +} +} +} diff --git a/android/sdk/src/main/jni/minimum_exports.lst b/android/sdk/src/main/jni/minimum_exports.lst new file mode 100644 index 00000000000..f939b4c062e --- /dev/null +++ b/android/sdk/src/main/jni/minimum_exports.lst @@ -0,0 +1,30 @@ +# +# Tencent is pleased to support the open source community by making +# Hippy available. +# +# Copyright (C) 2022 THL A29 Limited, a Tencent company. +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Linker script that exports only JNI_OnLoad and JNI_OnUnload. +# Minimum symbols required for Hippy to work. + +{ + global: + JNI_OnLoad; + JNI_OnUnload; + local: + *; +}; diff --git a/android/sdk/src/main/jni/src/bridge/adr_bridge.cc b/android/sdk/src/main/jni/src/bridge/adr_bridge.cc new file mode 100644 index 00000000000..c1be753635a --- /dev/null +++ b/android/sdk/src/main/jni/src/bridge/adr_bridge.cc @@ -0,0 +1,82 @@ +/* + * + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "bridge/adr_bridge.h" + +#include "core/base/common.h" +#include "jni/jni_env.h" + +namespace hippy { + +#ifndef V8_WITHOUT_INSPECTOR +void ADRBridge::SendResponse(std::unique_ptr message) { + if (message->string().is8Bit()) { + return; + } + + const uint16_t* source = message->string().characters16(); + auto len = hippy::base::checked_numeric_cast( + message->string().length() * sizeof(*source)); + std::shared_ptr instance = JNIEnvironment::GetInstance(); + JNIEnv* j_env = instance->AttachCurrentThread(); + jbyteArray msg = j_env->NewByteArray(len); + j_env->SetByteArrayRegion( + msg, 0, len, + reinterpret_cast(reinterpret_cast(source))); + + if (instance->GetMethods().j_inspector_channel_method_id && ref_) { + j_env->CallVoidMethod(ref_->GetObj(), + instance->GetMethods().j_inspector_channel_method_id, + msg); + JNIEnvironment::ClearJEnvException(j_env); + } + + j_env->DeleteLocalRef(msg); +} + +void ADRBridge::SendNotification(std::unique_ptr message) { + if (message->string().is8Bit()) { + return; + } + + const uint16_t* source = message->string().characters16(); + auto len = hippy::base::checked_numeric_cast( + message->string().length() * sizeof(*source)); + std::shared_ptr instance = JNIEnvironment::GetInstance(); + JNIEnv* j_env = instance->AttachCurrentThread(); + jbyteArray msg = j_env->NewByteArray(len); + j_env->SetByteArrayRegion( + msg, 0, len, + reinterpret_cast(reinterpret_cast(source))); + + if (instance->GetMethods().j_inspector_channel_method_id && ref_) { + j_env->CallVoidMethod(ref_->GetObj(), + instance->GetMethods().j_inspector_channel_method_id, + msg); + JNIEnvironment::ClearJEnvException(j_env); + } + + j_env->DeleteLocalRef(msg); +} +#endif + +} diff --git a/android/sdk/src/main/jni/src/bridge/entry.cc b/android/sdk/src/main/jni/src/bridge/entry.cc index 6b1138ee7ec..ac9568d606d 100644 --- a/android/sdk/src/main/jni/src/bridge/entry.cc +++ b/android/sdk/src/main/jni/src/bridge/entry.cc @@ -32,70 +32,160 @@ #include #include +#include "bridge/adr_bridge.h" #include "bridge/java2js.h" #include "bridge/js2java.h" #include "bridge/runtime.h" -#include "core/base/string_view_utils.h" #include "core/core.h" +#include "core/napi/v8/v8_ctx.h" +#include "core/napi/v8/v8_ctx_value.h" +#include "core/vm/v8/v8_vm.h" +#include "core/vm/v8/snapshot_data.h" #include "jni/turbo_module_manager.h" #include "jni/exception_handler.h" #include "jni/java_turbo_module.h" #include "jni/jni_env.h" #include "jni/jni_register.h" +#include "jni/jni_utils.h" #include "jni/uri.h" #include "loader/adr_loader.h" +using unicode_string_view = tdf::base::unicode_string_view; +using u8string = unicode_string_view::u8string; +using RegisterMap = hippy::base::RegisterMap; +using RegisterFunction = hippy::base::RegisterFunction; +using Ctx = hippy::napi::Ctx; +using V8Ctx = hippy::napi::V8Ctx; +using StringViewUtils = hippy::base::StringViewUtils; +using HippyFile = hippy::base::HippyFile; +using VM = hippy::vm::VM; +using V8VM = hippy::vm::V8VM; +using V8SnapshotVM = hippy::vm::V8SnapshotVM; +using V8VMInitParam = hippy::vm::V8VMInitParam; +#ifndef V8_WITHOUT_INSPECTOR +using V8InspectorClientImpl = hippy::inspector::V8InspectorClientImpl; +#endif + +constexpr char kLogTag[] = "native"; +constexpr char kGlobalKey[] = "global"; +constexpr char kNativeGlobalKey[] = "__HIPPYNATIVEGLOBAL__"; +constexpr char kCallNativesKey[] = "hippyCallNatives"; +constexpr char kCurDir[] = "__HIPPYCURDIR__"; +constexpr char kCodeCacheFailCountFilePostfix[] = "_fail_count"; +const uint8_t kCodeCacheMaxFailCount = 3; + +std::vector external_references{}; + +void HandleUncaughtJsError(v8::Local message, + v8::Local data) { + TDF_BASE_DLOG(INFO) << "HandleUncaughtJsError begin"; + + if (message.IsEmpty()) { + TDF_BASE_DLOG(ERROR) << "HandleUncaughtJsError message is empty"; + return; + } + + v8::Isolate* isolate = message->GetIsolate(); + std::shared_ptr runtime = Runtime::Find(isolate); + if (!runtime) { + return; + } + + auto scope = runtime->GetScope(); + if (!scope) { + return; + } + auto context = scope->GetContext(); + if (!context) { + return; + } + auto ctx = std::static_pointer_cast(context); + auto description = ctx->GetMsgDesc(message); + auto stack = ctx->GetStackInfo(message); + + TDF_BASE_LOG(ERROR) << "HandleUncaughtJsError, runtime_id = " + << runtime->GetId() + << ", desc = " + << description + << ", stack = " + << stack; + ExceptionHandler::ReportJsException(runtime, description, stack); + auto error = ctx->CreateError(message); + ctx->HandleUncaughtException(error); + TDF_BASE_DLOG(INFO) << "HandleUncaughtJsError end"; +} + namespace hippy { namespace bridge { -REGISTER_STATIC_JNI("com/tencent/mtt/hippy/HippyEngine", - "initNativeLogHandler", - "(Lcom/tencent/mtt/hippy/IHippyNativeLogHandler;)V", - InitNativeLogHandler) +REGISTER_STATIC_JNI("com/tencent/mtt/hippy/HippyEngine", // NOLINT(cert-err58-cpp) + "setNativeLogHandler", + "(Lcom/tencent/mtt/hippy/adapter/HippyLogAdapter;)V", + setNativeLogHandler) + +REGISTER_STATIC_JNI("com/tencent/mtt/hippy/bridge/HippyBridgeImpl", // NOLINT(cert-err58-cpp) + "createSnapshot", + "([Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", + CreateSnapshot) -REGISTER_JNI("com/tencent/mtt/hippy/bridge/HippyBridgeImpl", +REGISTER_JNI("com/tencent/mtt/hippy/bridge/HippyBridgeImpl", // NOLINT(cert-err58-cpp) "initJSFramework", - "([BZZZLcom/tencent/mtt/hippy/bridge/NativeCallback;J)J", + "([BZZZLcom/tencent/mtt/hippy/bridge/NativeCallback;" + "JLcom/tencent/mtt/hippy/HippyEngine$V8InitParams;)J", InitInstance) -REGISTER_JNI("com/tencent/mtt/hippy/bridge/HippyBridgeImpl", +REGISTER_JNI("com/tencent/mtt/hippy/bridge/HippyBridgeImpl", // NOLINT(cert-err58-cpp) "runScriptFromUri", "(Ljava/lang/String;Landroid/content/res/AssetManager;ZLjava/lang/" "String;JLcom/tencent/mtt/hippy/bridge/NativeCallback;)Z", RunScriptFromUri) -REGISTER_JNI("com/tencent/mtt/hippy/bridge/HippyBridgeImpl", +REGISTER_JNI("com/tencent/mtt/hippy/bridge/HippyBridgeImpl", // NOLINT(cert-err58-cpp) "destroy", - "(JZLcom/tencent/mtt/hippy/bridge/NativeCallback;)V", + "(JZZLcom/tencent/mtt/hippy/bridge/NativeCallback;)V", DestroyInstance) -using unicode_string_view = tdf::base::unicode_string_view; -using u8string = unicode_string_view::u8string; -using RegisterMap = hippy::base::RegisterMap; -using RegisterFunction = hippy::base::RegisterFunction; -using Ctx = hippy::napi::Ctx; -using StringViewUtils = hippy::base::StringViewUtils; -using HippyFile = hippy::base::HippyFile; -using V8VM = hippy::napi::V8VM; -#ifdef ENABLE_INSPECTOR -using V8InspectorClientImpl = hippy::inspector::V8InspectorClientImpl; -std::shared_ptr global_inspector = nullptr; -#endif +REGISTER_JNI("com/tencent/mtt/hippy/bridge/HippyBridgeImpl", // NOLINT(cert-err58-cpp) + "runScript", + "(JLjava/lang/String;)V", + RunScript) + +REGISTER_JNI("com/tencent/mtt/hippy/bridge/HippyBridgeImpl", // NOLINT(cert-err58-cpp) + "runInJsThread", + "(JLcom/tencent/mtt/hippy/common/Callback;)V", + RunInJsThread) + static std::unordered_map, uint32_t>> reuse_engine_map; static std::mutex engine_mutex; +static std::mutex log_mutex; +static std::mutex code_cache_file_mutex; +static bool is_inited = false; -static const int64_t kDefaultEngineId = -1; -static const int64_t kDebuggerEngineId = -9999; -static const uint32_t kRuntimeSlotIndex = 0; +constexpr int64_t kDefaultEngineId = -1; +constexpr int64_t kDebuggerEngineId = -9999; +constexpr uint32_t kRuntimeSlotIndex = 0; +// -1 means single isolate multi-context mode +constexpr int32_t kReuseRuntimeId = -1; enum INIT_CB_STATE { + SNAPSHOT_INVALID = -2, RUN_SCRIPT_ERROR = -1, SUCCESS = 0, }; -void InitNativeLogHandler(JNIEnv* j_env, jobject j_object, jobject j_logger) { +void NativeCallback(const hippy::napi::CallbackInfo& info, void* data) { + auto scope_wrapper = reinterpret_cast(std::any_cast(info.GetSlot())); + auto scope = scope_wrapper->scope.lock(); + TDF_BASE_CHECK(scope); + auto v8_ctx = std::static_pointer_cast(scope->GetContext()); + TDF_BASE_CHECK(v8_ctx->HasFuncExternalData(data)); + auto runtime_id = static_cast(reinterpret_cast(v8_ctx->GetFuncExternalData(data))); + hippy::bridge::CallJava(info, runtime_id); +} + +void setNativeLogHandler(JNIEnv* j_env, __unused jobject j_object, jobject j_logger) { if (!j_logger) { return; } @@ -106,98 +196,153 @@ void InitNativeLogHandler(JNIEnv* j_env, jobject j_object, jobject j_logger) { } jmethodID j_method = - j_env->GetMethodID(j_cls, "onReceiveNativeLogMessage", "(Ljava/lang/String;)V"); + j_env->GetMethodID(j_cls, "onReceiveLogMessage", "(ILjava/lang/String;Ljava/lang/String;)V"); if (!j_method) { return; } std::shared_ptr logger = std::make_shared(j_env, j_logger); - tdf::base::LogMessage::SetDelegate([logger, j_method]( - const std::ostringstream& stream, - tdf::base::LogSeverity severity) { - std::shared_ptr instance = JNIEnvironment::GetInstance(); - JNIEnv* j_env = instance->AttachCurrentThread(); - - std::string str = stream.str(); - jstring j_logger_str = j_env->NewStringUTF((str.c_str())); - j_env->CallVoidMethod(logger->GetObj(), j_method, j_logger_str); - j_env->DeleteLocalRef(j_logger_str); - }); + { + std::lock_guard lock(log_mutex); + if (!is_inited) { + tdf::base::LogMessage::InitializeDelegate([logger, j_method]( + const std::ostringstream& stream, + tdf::base::LogSeverity severity) { + std::shared_ptr instance = JNIEnvironment::GetInstance(); + JNIEnv* j_env = instance->AttachCurrentThread(); + + std::string str = stream.str(); + jstring j_logger_str = j_env->NewStringUTF((str.c_str())); + jstring j_tag_str = j_env->NewStringUTF(kLogTag); + jint j_level = static_cast(severity); + j_env->CallVoidMethod(logger->GetObj(), j_method, j_level, j_tag_str, j_logger_str); + JNIEnvironment::ClearJEnvException(j_env); + j_env->DeleteLocalRef(j_tag_str); + j_env->DeleteLocalRef(j_logger_str); + }); + is_inited = true; + } + } +} + +void RunScript(JNIEnv* j_env, __unused jobject, jlong j_runtime_id, jstring j_script) { + TDF_BASE_DLOG(INFO) << "RunScript, j_runtime_id = " << j_runtime_id; + auto runtime = Runtime::Find(hippy::base::checked_numeric_cast(j_runtime_id)); + if (!runtime) { + TDF_BASE_DLOG(WARNING) << "HippyBridgeImpl RunScript, j_runtime_id invalid"; + return; + } + const unicode_string_view script = JniUtils::ToStrView(j_env, j_script); + TDF_BASE_DLOG(INFO) << "RunScript, script = " << script; + auto runner = runtime->GetEngine()->GetJSRunner(); + std::shared_ptr task = std::make_shared(); + task->callback = [runtime, script{std::move(script)}] () mutable { + auto context = std::static_pointer_cast(runtime->GetScope()->GetContext()); + auto ret = context->RunScript(script, ""); + }; + runner->PostTask(task); +} + +// Code cache file corruption prevention strategy: +// Record count before RunScript, clean count after RunScript, and if only start recording three times in a row, clean the code cache file. +bool CheckUseCodeCacheBeforeRunScript(const unicode_string_view& code_cache_path) { + unicode_string_view fail_count_path = code_cache_path + unicode_string_view(kCodeCacheFailCountFilePostfix); + u8string content; + HippyFile::ReadFile(fail_count_path, content, false); + if (content.length() == 1) { + uint8_t count = content[0]; + if (count >= kCodeCacheMaxFailCount) { + TDF_BASE_DLOG(INFO) << "Check code cache, count >= 3"; + // need to remove code cache file + return false; + } else { + TDF_BASE_DLOG(INFO) << "Check code cache, count = " << static_cast(count); + ++count; + content[0] = count; + HippyFile::SaveFile(fail_count_path, content); + } + } else { + TDF_BASE_DLOG(INFO) << "Check code cache, no count"; + content.clear(); + content.push_back(1); + HippyFile::SaveFile(fail_count_path, content); + } + return true; } -bool RunScript(std::shared_ptr runtime, - const unicode_string_view& file_name, - bool is_use_code_cache, - const unicode_string_view& code_cache_dir, - const unicode_string_view& uri, - AAssetManager* asset_manager) { - TDF_BASE_LOG(INFO) << "RunScript begin, file_name = " << file_name - << ", is_use_code_cache = " << is_use_code_cache - << ", code_cache_dir = " << code_cache_dir - << ", uri = " << uri - << ", asset_manager = " << asset_manager; +void CheckUseCodeCacheAfterRunScript(const unicode_string_view& code_cache_path) { + unicode_string_view fail_count_path = code_cache_path + unicode_string_view(kCodeCacheFailCountFilePostfix); + HippyFile::RmFile(fail_count_path); +} + +bool RunScriptInternal(const std::shared_ptr& runtime, + const unicode_string_view& file_name, + bool is_use_code_cache, + const unicode_string_view& code_cache_dir, + const unicode_string_view& uri, + AAssetManager* asset_manager, + std::chrono::time_point &load_start, + std::chrono::time_point &load_end) { + TDF_BASE_LOG(INFO) << "RunScriptInternal begin, file_name = " << file_name + << ", is_use_code_cache = " << is_use_code_cache + << ", code_cache_dir = " << code_cache_dir + << ", uri = " << uri + << ", asset_manager = " << asset_manager; unicode_string_view script_content; bool read_script_flag; unicode_string_view code_cache_content; uint64_t modify_time = 0; - std::shared_ptr task_runner; + load_start = std::chrono::system_clock::now(); + auto engine = runtime->GetEngine(); + auto task_runner = engine->GetWorkerTaskRunner(); unicode_string_view code_cache_path; if (is_use_code_cache) { if (!asset_manager) { - auto time1 = std::chrono::time_point_cast( - std::chrono::system_clock::now()) - .time_since_epoch() - .count(); modify_time = HippyFile::GetFileModifytime(uri); - auto time2 = std::chrono::time_point_cast( - std::chrono::system_clock::now()) - .time_since_epoch() - .count(); - - TDF_BASE_DLOG(INFO) << "GetFileModifytime cost %lld microseconds" - << time2 - time1; } code_cache_path = code_cache_dir + file_name + unicode_string_view("_") + unicode_string_view(std::to_string(modify_time)); std::promise read_file_promise; - std::future read_file_future = read_file_promise.get_future(); - std::unique_ptr task = std::make_unique(); - task->func_ = - hippy::base::MakeCopyable([p = std::move(read_file_promise), - code_cache_path, code_cache_dir]() mutable { - u8string content; - HippyFile::ReadFile(code_cache_path, content, true); - if (content.empty()) { - TDF_BASE_DLOG(INFO) << "Read code cache failed"; - int ret = HippyFile::RmFullPath(code_cache_dir); - TDF_BASE_DLOG(INFO) << "RmFullPath ret = " << ret; - HIPPY_USE(ret); - } else { - TDF_BASE_DLOG(INFO) << "Read code cache succ"; - } - p.set_value(std::move(content)); - }); - - std::shared_ptr engine = runtime->GetEngine(); - task_runner = engine->GetWorkerTaskRunner(); - task_runner->PostTask(std::move(task)); + auto read_file_future = read_file_promise.get_future(); + auto task = std::make_unique(); + task->func_ = hippy::base::MakeCopyable([p = std::move(read_file_promise), + code_cache_path, code_cache_dir]() mutable { + std::lock_guard lock(code_cache_file_mutex); + u8string content; + HippyFile::ReadFile(code_cache_path, content, true); + if (content.empty()) { + TDF_BASE_DLOG(INFO) << "Read code cache failed"; + int ret = HippyFile::RmFullPath(code_cache_dir); + TDF_BASE_DLOG(INFO) << "RmFullPath ret = " << ret; + HIPPY_USE(ret); + } else { + TDF_BASE_DLOG(INFO) << "Read code cache succ"; + if (!CheckUseCodeCacheBeforeRunScript(code_cache_path)) { + int ret = HippyFile::RmFullPath(code_cache_dir); + TDF_BASE_DLOG(INFO) << "RmFullPath on check, ret = " << ret; + HIPPY_USE(ret); + content.clear(); + } + } + p.set_value(std::move(content)); + }); + task_runner->PostPromiseTask(std::move(task)); u8string content; - read_script_flag = runtime->GetScope()->GetUriLoader() - ->RequestUntrustedContent(uri, content); + read_script_flag = runtime->GetScope()->GetUriLoader()->RequestUntrustedContent(uri, content); if (read_script_flag) { script_content = unicode_string_view(std::move(content)); } code_cache_content = read_file_future.get(); } else { u8string content; - read_script_flag = runtime->GetScope()->GetUriLoader() - ->RequestUntrustedContent(uri, content); + read_script_flag = runtime->GetScope()->GetUriLoader()->RequestUntrustedContent(uri, content); if (read_script_flag) { script_content = unicode_string_view(std::move(content)); } } + load_end = std::chrono::system_clock::now(); TDF_BASE_DLOG(INFO) << "uri = " << uri << "read_script_flag = " << read_script_flag @@ -206,27 +351,29 @@ bool RunScript(std::shared_ptr runtime, if (!read_script_flag || StringViewUtils::IsEmpty(script_content)) { TDF_BASE_LOG(WARNING) << "read_script_flag = " << read_script_flag << ", script content empty, uri = " << uri; + if (is_use_code_cache) { + std::lock_guard lock(code_cache_file_mutex); + CheckUseCodeCacheAfterRunScript(code_cache_path); + } return false; } auto ret = std::static_pointer_cast( - runtime->GetScope()->GetContext()) - ->RunScript(script_content, file_name, is_use_code_cache, - &code_cache_content); + runtime->GetScope()->GetContext())->RunScript( + script_content, file_name, is_use_code_cache,&code_cache_content, true); if (is_use_code_cache) { if (!StringViewUtils::IsEmpty(code_cache_content)) { std::unique_ptr task = std::make_unique(); task->func_ = [code_cache_path, code_cache_dir, code_cache_content] { + std::lock_guard lock(code_cache_file_mutex); int check_dir_ret = HippyFile::CheckDir(code_cache_dir, F_OK); TDF_BASE_DLOG(INFO) << "check_parent_dir_ret = " << check_dir_ret; if (check_dir_ret) { HippyFile::CreateDir(code_cache_dir, S_IRWXU); } - size_t pos = - StringViewUtils::FindLastOf(code_cache_path, EXTEND_LITERAL('/')); - unicode_string_view code_cache_parent_dir = - StringViewUtils::SubStr(code_cache_path, 0, pos); + size_t pos = StringViewUtils::FindLastOf(code_cache_path, EXTEND_LITERAL('/')); + unicode_string_view code_cache_parent_dir = StringViewUtils::SubStr(code_cache_path, 0, pos); int check_parent_dir_ret = HippyFile::CheckDir(code_cache_parent_dir, F_OK); TDF_BASE_DLOG(INFO) @@ -237,35 +384,115 @@ bool RunScript(std::shared_ptr runtime, std::string u8_code_cache_content = StringViewUtils::ToU8StdStr(code_cache_content); - bool save_file_ret = - HippyFile::SaveFile(code_cache_path, u8_code_cache_content); + bool save_file_ret = HippyFile::SaveFile(code_cache_path, u8_code_cache_content); TDF_BASE_LOG(INFO) << "code cache save_file_ret = " << save_file_ret; HIPPY_USE(save_file_ret); + + CheckUseCodeCacheAfterRunScript(code_cache_path); }; task_runner->PostTask(std::move(task)); } } - bool flag = !!ret; + bool flag = (ret != nullptr); TDF_BASE_LOG(INFO) << "runScript end, flag = " << flag; return flag; } +enum class CreateSnapshotResult { + kSuccess, kFailed, kRunScriptError, kSnapshotBlobInvalid, kSaveSnapshotFailed +}; + +jint CreateSnapshot(JNIEnv* j_env, + __unused jobject j_obj, + jobjectArray j_script_array, + jstring j_base_path, + jstring j_snapshot_uri, + jstring j_config) { + auto time_begin = std::chrono::time_point_cast( + std::chrono::system_clock::now()) + .time_since_epoch() + .count(); + auto vm = std::make_shared(); + auto engine = std::make_shared(); + engine->SyncInit(vm); + + auto base_path = JniUtils::ToStrView(j_env, j_base_path); + auto global_config = JniUtils::ToStrView(j_env, j_config); + TDF_BASE_LOG(INFO) << "CreateSnapshot global_config = " << global_config; + auto context_cb = [global_config, base_path](void* wrapper) { + TDF_BASE_CHECK(wrapper); + auto* scope_wrapper = reinterpret_cast(wrapper); + TDF_BASE_CHECK(scope_wrapper); + auto scope = scope_wrapper->scope.lock(); + TDF_BASE_CHECK(scope); + auto ctx = scope->GetContext(); + auto global_object = ctx->GetGlobalObject(); + auto user_global_object_key = ctx->CreateString(kGlobalKey); + ctx->SetProperty(global_object, user_global_object_key, global_object); + auto native_global_key = ctx->CreateString(kNativeGlobalKey); + auto global_config_object = V8VM::ParseJson(ctx, global_config); + ctx->SetProperty(global_object, native_global_key, global_config_object); + auto key = ctx->CreateString(kCurDir); + auto value = ctx->CreateString(base_path); + ctx->SetProperty(global_object, key, value); + }; + std::unique_ptr scope_cb_map = std::make_unique(); + scope_cb_map->insert({hippy::base::kContextCreatedCBKey, context_cb}); + auto scope = engine->SyncCreateScope(std::move(scope_cb_map)); + auto v8_ctx = std::static_pointer_cast(scope->GetContext()); + auto cnt = j_env->GetArrayLength(j_script_array); + for (auto i = 0; i < cnt; ++i) { + auto j_script = reinterpret_cast(j_env->GetObjectArrayElement(j_script_array, i)); + auto script = JniUtils::ToStrView(j_env, j_script); + hippy::napi::V8TryCatch try_catch(true, v8_ctx); + auto result = v8_ctx->RunScript(script, ""); + if (try_catch.HasCaught()) { + TDF_BASE_LOG(ERROR) << "RunScript error, error = " << try_catch.GetExceptionMsg(); + return static_cast(CreateSnapshotResult::kRunScriptError); + } + } + auto creator = vm->snapshot_creator_; + v8_ctx->SetDefaultContext(creator); + TDF_BASE_LOG(INFO) << "CreateBlob"; + v8_ctx = nullptr; + scope = nullptr; + auto blob = creator->CreateBlob(v8::SnapshotCreator::FunctionCodeHandling::kKeep); +#if (V8_MAJOR_VERSION >= 9) + if (!blob.IsValid()) { + return static_cast(CreateSnapshotResult::kSnapshotBlobInvalid); + } +#endif + SnapshotData snapshot_data; + snapshot_data.WriteMetaData(blob); + auto time_end = std::chrono::time_point_cast( + std::chrono::system_clock::now()) + .time_since_epoch() + .count(); + TDF_BASE_LOG(INFO) << "blob size = " << blob.raw_size << ", buffer size = " << snapshot_data.buffer_holder.size() + << ", cost = " << (time_end - time_begin); + auto snapshot_uri = JniUtils::ToStrView(j_env, j_snapshot_uri); + bool save_file_ret = HippyFile::SaveFile(snapshot_uri, snapshot_data.buffer_holder); + if (!save_file_ret) { + return static_cast(CreateSnapshotResult::kSaveSnapshotFailed); + } + return static_cast(CreateSnapshotResult::kSuccess); +} + jboolean RunScriptFromUri(JNIEnv* j_env, - jobject j_obj, + __unused jobject j_obj, jstring j_uri, jobject j_aasset_manager, jboolean j_can_use_code_cache, jstring j_code_cache_dir, jlong j_runtime_id, jobject j_cb) { - TDF_BASE_DLOG(INFO) << "runScriptFromUri begin, j_runtime_id = " - << j_runtime_id; - std::shared_ptr runtime = Runtime::Find(j_runtime_id); + TDF_BASE_DLOG(INFO) << "runScriptFromUri begin, j_runtime_id = " << j_runtime_id; + auto runtime = Runtime::Find(hippy::base::checked_numeric_cast(j_runtime_id)); if (!runtime) { TDF_BASE_DLOG(WARNING) << "HippyBridgeImpl runScriptFromUri, j_runtime_id invalid"; - return false; + return JNI_FALSE; } auto time_begin = std::chrono::time_point_cast( @@ -274,7 +501,7 @@ jboolean RunScriptFromUri(JNIEnv* j_env, .count(); if (!j_uri) { TDF_BASE_DLOG(WARNING) << "HippyBridgeImpl runScriptFromUri, j_uri invalid"; - return false; + return JNI_FALSE; } const unicode_string_view uri = JniUtils::ToStrView(j_env, j_uri); const unicode_string_view code_cache_dir = @@ -292,18 +519,16 @@ jboolean RunScriptFromUri(JNIEnv* j_env, std::shared_ptr ctx = runtime->GetScope()->GetContext(); std::shared_ptr task = std::make_shared(); task->callback = [ctx, base_path] { - ctx->SetGlobalStrVar("__HIPPYCURDIR__", base_path); + auto key = ctx->CreateString(kCurDir); + auto value = ctx->CreateString(base_path); + auto global = ctx->GetGlobalObject(); + ctx->SetProperty(global, key, value); }; runner->PostTask(task); - std::shared_ptr loader = std::make_shared(); - loader->SetBridge(runtime->GetBridge()); - loader->SetWorkerTaskRunner(runtime->GetEngine()->GetWorkerTaskRunner()); - runtime->GetScope()->SetUriLoader(loader); AAssetManager* aasset_manager = nullptr; if (j_aasset_manager) { aasset_manager = AAssetManager_fromJava(j_env, j_aasset_manager); - loader->SetAAssetManager(aasset_manager); } std::shared_ptr save_object = std::make_shared(j_env, j_cb); @@ -311,62 +536,42 @@ jboolean RunScriptFromUri(JNIEnv* j_env, task->callback = [runtime, save_object_ = std::move(save_object), script_name, j_can_use_code_cache, code_cache_dir, uri, aasset_manager, time_begin] { - TDF_BASE_DLOG(INFO) << "runScriptFromUri enter tast"; - bool flag = RunScript(runtime, script_name, j_can_use_code_cache, - code_cache_dir, uri, aasset_manager); + TDF_BASE_DLOG(INFO) << "runScriptFromUri enter"; + std::chrono::time_point load_start; + std::chrono::time_point load_end; + bool flag = RunScriptInternal(runtime, script_name, j_can_use_code_cache, + code_cache_dir, uri, aasset_manager, load_start, load_end); auto time_end = std::chrono::time_point_cast( std::chrono::system_clock::now()) .time_since_epoch() .count(); - TDF_BASE_DLOG(INFO) << "runScriptFromUri = " << (time_end - time_begin) - << ", uri = " << uri; - + TDF_BASE_DLOG(INFO) << "runScriptFromUri = " << (time_end - time_begin) << ", uri = " << uri; + + JNIEnv* j_env = JNIEnvironment::GetInstance()->AttachCurrentThread(); + auto load_start_millis = std::chrono::time_point_cast(load_start) + .time_since_epoch() + .count(); + auto load_end_millis = std::chrono::time_point_cast(load_end) + .time_since_epoch() + .count(); + std::string payload = "{\"load_start_millis\":" + std::to_string(load_start_millis) + + ", \"load_end_millis\": "+ std::to_string(load_end_millis) + "}"; + jstring j_payload = JniUtils::StrViewToJString(j_env, unicode_string_view(payload)); if (flag) { - hippy::bridge::CallJavaMethod(save_object_->GetObj(), - INIT_CB_STATE::SUCCESS); + hippy::bridge::CallJavaMethod(save_object_->GetObj(), INIT_CB_STATE::SUCCESS, nullptr, j_payload); } else { - JNIEnv* j_env = JNIEnvironment::GetInstance()->AttachCurrentThread(); jstring j_msg = JniUtils::StrViewToJString(j_env, u"run script error"); - CallJavaMethod(save_object_->GetObj(), INIT_CB_STATE::RUN_SCRIPT_ERROR, - j_msg); + hippy::bridge::CallJavaMethod(save_object_->GetObj(), INIT_CB_STATE::RUN_SCRIPT_ERROR, j_msg, j_payload); j_env->DeleteLocalRef(j_msg); } + j_env->DeleteLocalRef(j_payload); return flag; }; runner->PostTask(task); - return true; -} - -void HandleUncaughtJsError(v8::Local message, - v8::Local error) { - TDF_BASE_DLOG(INFO) << "HandleUncaughtJsError begin"; - - if (error.IsEmpty()) { - TDF_BASE_DLOG(ERROR) << "HandleUncaughtJsError error is empty"; - return; - } - - v8::Isolate* isolate = message->GetIsolate(); - std::shared_ptr runtime = Runtime::Find(isolate); - if (!runtime) { - return; - } - - std::shared_ptr ctx = - std::static_pointer_cast( - runtime->GetScope()->GetContext()); - TDF_BASE_LOG(ERROR) << "HandleUncaughtJsError error desc = " - << ctx->GetMsgDesc(message) - << ", stack = " << ctx->GetStackInfo(message); - ExceptionHandler::ReportJsException(runtime, ctx->GetMsgDesc(message), - ctx->GetStackInfo(message)); - ctx->ThrowExceptionToJS( - std::make_shared(isolate, error)); - - TDF_BASE_DLOG(INFO) << "HandleUncaughtJsError end"; + return JNI_TRUE; } jlong InitInstance(JNIEnv* j_env, @@ -376,86 +581,169 @@ jlong InitInstance(JNIEnv* j_env, jboolean j_enable_v8_serialization, jboolean j_is_dev_module, jobject j_callback, - jlong j_group_id) { + jlong j_group_id, + jobject j_vm_init_param) { TDF_BASE_LOG(INFO) << "InitInstance begin, j_single_thread_mode = " << static_cast(j_single_thread_mode) - << ", j_bridge_param_json = " + << ", j_enable_v8_serialization = " << static_cast(j_enable_v8_serialization) << ", j_is_dev_module = " << static_cast(j_is_dev_module) << ", j_group_id = " << j_group_id; - std::shared_ptr runtime = - std::make_shared(std::make_shared(j_env, j_object), - j_enable_v8_serialization, j_is_dev_module); + auto bridge = std::make_shared(j_env, j_object); + auto runtime = std::make_shared(std::move(bridge), j_enable_v8_serialization, j_is_dev_module); int32_t runtime_id = runtime->GetId(); Runtime::Insert(runtime); - RegisterFunction vm_cb = [runtime_id](void* vm) { + int64_t group = j_group_id; + + bool use_snapshot = false; + std::shared_ptr param; + if (j_vm_init_param) { + jclass cls = j_env->GetObjectClass(j_vm_init_param); + jfieldID init_field = j_env->GetFieldID(cls, "initialHeapSize", "J"); + auto initial_heap_size_in_bytes = j_env->GetLongField(j_vm_init_param, init_field); + jfieldID max_field = j_env->GetFieldID(cls, "maximumHeapSize", "J"); + auto maximum_heap_size_in_bytes = j_env->GetLongField(j_vm_init_param, max_field); + param = std::make_shared(); + param->initial_heap_size_in_bytes = + hippy::base::checked_numeric_cast(initial_heap_size_in_bytes); + param->maximum_heap_size_in_bytes = + hippy::base::checked_numeric_cast(maximum_heap_size_in_bytes); + TDF_BASE_CHECK(param->initial_heap_size_in_bytes <= param->maximum_heap_size_in_bytes); + auto type_field = j_env->GetFieldID(cls, "type", "I"); + auto j_type = j_env->GetIntField(j_vm_init_param,type_field); + param->type = static_cast(j_type); + auto j_uri_field = j_env->GetFieldID(cls, "uri", "Ljava/lang/String;"); + if (param->type == V8VMInitParam::V8VMSnapshotType::kUseSnapshot) { + bool is_valid = false; + do { + auto j_uri = reinterpret_cast(j_env->GetObjectField(j_vm_init_param, j_uri_field)); + // Currently only support the file protocol + if (j_uri) { + auto uri = JniUtils::ToStrView(j_env, j_uri); + auto uri_obj = Uri::Create(uri); + if (!uri_obj) { + is_valid = false; + break; + } + auto path = uri_obj->GetPath(); + is_valid = HippyFile::ReadFile(uri, param->snapshot_data.buffer_holder, false); + if (!is_valid) { + break; + } + is_valid = param->snapshot_data.ReadMetadata(); + } else { + auto j_blob_field = j_env->GetFieldID(cls, "blob", "Ljava/nio/ByteBuffer;"); + auto j_buffer = j_env->GetObjectField(j_vm_init_param, j_blob_field); + if (j_buffer) { + auto capacity = hippy::base::checked_numeric_cast(j_env->GetDirectBufferCapacity(j_buffer)); + if (capacity <= 0) { + is_valid = false; + break; + } + auto buffer_pointer = reinterpret_cast(j_env->GetDirectBufferAddress(j_buffer)); + param->snapshot_data.external_buffer_holder = std::make_shared(j_env, j_buffer); + is_valid = param->snapshot_data.ReadMetaData(buffer_pointer, capacity); + } else { + is_valid = false; + break; + } + } + if (!is_valid) { + break; + } +#if (V8_MAJOR_VERSION >= 9) + is_valid = param->snapshot_data.startup_data.IsValid(); +#endif + } while (false); + if (!is_valid) { + TDF_BASE_LOG(ERROR) << "snapshot invalid"; + hippy::bridge::CallJavaMethod(j_callback, INIT_CB_STATE::SNAPSHOT_INVALID); + return 0; + } + use_snapshot = true; + } + } + + auto vm_cb = [group, runtime_id](void* vm) { V8VM* v8_vm = reinterpret_cast(vm); v8::Isolate* isolate = v8_vm->isolate_; v8::HandleScope handle_scope(isolate); + if (group == kDefaultEngineId) { + TDF_BASE_LOG(INFO) << "isolate->SetData runtime_id = " << runtime_id; + isolate->SetData(kRuntimeSlotIndex, reinterpret_cast(runtime_id)); + } else { + TDF_BASE_LOG(INFO) << "isolate->SetData runtime_id = " << kReuseRuntimeId; + isolate->SetData(kRuntimeSlotIndex, reinterpret_cast(kReuseRuntimeId)); + } isolate->AddMessageListener(HandleUncaughtJsError); - isolate->SetData(kRuntimeSlotIndex, reinterpret_cast(runtime_id)); + auto runtime = Runtime::Find(runtime_id); + auto interrupt_queue = std::make_shared(isolate); + interrupt_queue->SetTaskRunner(runtime->GetEngine()->GetJSRunner()); + runtime->SetInterruptQueue(interrupt_queue); + auto& map = InterruptQueue::GetPersistentMap(); + map.Insert(interrupt_queue->GetId(), interrupt_queue); +#ifndef V8_WITHOUT_INSPECTOR + if (runtime->IsDebug()) { + auto inspector = std::make_shared(runtime->GetEngine()->GetJSRunner()); + runtime->GetEngine()->SetInspectorClient(inspector); + } +#endif }; - std::unique_ptr engine_cb_map = std::make_unique(); + auto engine_cb_map = std::make_unique(); engine_cb_map->insert(std::make_pair(hippy::base::kVMCreateCBKey, vm_cb)); - unicode_string_view global_config = - JniUtils::JByteArrayToStrView(j_env, j_global_config); - - TDF_BASE_LOG(INFO) << "global_config = " << global_config; - std::shared_ptr task = std::make_shared(); - std::shared_ptr save_object = - std::make_shared(j_env, j_callback); - - RegisterFunction context_cb = [runtime, global_config, - runtime_id](void* scopeWrapper) { - TDF_BASE_LOG(INFO) << "InitInstance register hippyCallNatives, runtime_id = " << runtime_id; - TDF_BASE_DCHECK(scopeWrapper); - ScopeWrapper* wrapper = reinterpret_cast(scopeWrapper); - TDF_BASE_DCHECK(wrapper); - std::shared_ptr scope = wrapper->scope_.lock(); - if (!scope) { - TDF_BASE_DLOG(ERROR) << "register hippyCallNatives, scope error"; - return; - } -#ifdef ENABLE_INSPECTOR - if (runtime->IsDebug()) { - if (!global_inspector) { - global_inspector = std::make_shared(scope); - global_inspector->Connect(runtime->GetBridge()); - } else { - global_inspector->Reset(scope, runtime->GetBridge()); + auto global_config = JniUtils::JByteArrayToStrView(j_env, j_global_config); + TDF_BASE_DLOG(INFO) << "global_config = " << global_config; + auto task = std::make_shared(); + auto save_object = std::make_shared(j_env, j_callback); + + auto context_cb = [runtime, global_config, runtime_id](void* wrapper) { + TDF_BASE_CHECK(wrapper); + auto* scope_wrapper = reinterpret_cast(wrapper); + TDF_BASE_CHECK(scope_wrapper); + auto scope = scope_wrapper->scope.lock(); + TDF_BASE_CHECK(scope); +#ifndef V8_WITHOUT_INSPECTOR + if (runtime->IsDebug()) { + auto inspector_client = runtime->GetEngine()->GetInspectorClient(); + if (inspector_client) { + inspector_client->CreateInspector(scope); + auto inspector_context = inspector_client->CreateInspectorContext(scope, runtime->GetBridge()); + runtime->SetInspectorContext(inspector_context); + } } - global_inspector->CreateContext(); - } #endif - std::shared_ptr ctx = scope->GetContext(); - ctx->RegisterGlobalInJs(); - hippy::base::RegisterFunction fn = - TO_REGISTER_FUNCTION(hippy::bridge::CallJava, hippy::napi::CBDataTuple); - ctx->RegisterNativeBinding("hippyCallNatives", fn, reinterpret_cast(runtime_id)); - bool ret = ctx->SetGlobalJsonVar("__HIPPYNATIVEGLOBAL__", global_config); - if (!ret) { - TDF_BASE_DLOG(ERROR) << "register __HIPPYNATIVEGLOBAL__ failed"; - ExceptionHandler::ReportJsException(runtime, u"global_config parse error", - global_config); - } + auto ctx = scope->GetContext(); + auto global_object = ctx->GetGlobalObject(); + auto user_global_object_key = ctx->CreateString(kGlobalKey); + ctx->SetProperty(global_object, user_global_object_key, global_object); + TDF_BASE_DLOG(INFO) << "bridge bind runtime_id = " << runtime_id; + auto func_wrapper = std::make_unique(NativeCallback, + reinterpret_cast(runtime_id)); + auto native_func_cb = ctx->CreateFunction(func_wrapper); + scope->SaveFuncWrapper(std::move(func_wrapper)); + auto call_natives_key = ctx->CreateString(kCallNativesKey); + ctx->SetProperty(global_object, call_natives_key, native_func_cb, hippy::napi::PropertyAttribute::ReadOnly); + auto native_global_key = ctx->CreateString(kNativeGlobalKey); + auto global_config_object = VM::ParseJson(ctx, global_config); + ctx->SetProperty(global_object, native_global_key, global_config_object); + + auto loader = std::make_shared(); + auto bridge = std::static_pointer_cast(runtime->GetBridge()); + loader->SetBridge(bridge->GetRef()); + loader->SetWorkerTaskRunner(runtime->GetEngine()->GetWorkerTaskRunner()); + scope->SetUriLoader(loader); }; - std::unique_ptr scope_cb_map = std::make_unique(); - scope_cb_map->insert( - std::make_pair(hippy::base::kContextCreatedCBKey, context_cb)); RegisterFunction scope_cb = [save_object_ = std::move(save_object)](void*) { TDF_BASE_LOG(INFO) << "run scope cb"; - hippy::bridge::CallJavaMethod(save_object_->GetObj(), - INIT_CB_STATE::SUCCESS); + hippy::bridge::CallJavaMethod(save_object_->GetObj(),INIT_CB_STATE::SUCCESS); }; - - scope_cb_map->insert( - std::make_pair(hippy::base::KScopeInitializedCBKey, scope_cb)); - - int64_t group = j_group_id; + std::unique_ptr scope_cb_map = std::make_unique(); + scope_cb_map->insert({hippy::base::kContextCreatedCBKey, context_cb}); + scope_cb_map->insert({hippy::base::KScopeInitializedCBKey, scope_cb}); std::shared_ptr engine; if (j_is_dev_module) { std::lock_guard lock(engine_mutex); @@ -470,9 +758,10 @@ jlong InitInstance(JNIEnv* j_env, v8::Isolate* isolate = v8_vm->isolate_; isolate->SetData(kRuntimeSlotIndex, reinterpret_cast(runtime_id)); } else { - engine = std::make_shared(std::move(engine_cb_map)); - runtime->SetEngine(engine); + engine = std::make_shared(); reuse_engine_map[group] = std::make_pair(engine, 1); + runtime->SetEngine(engine); + engine->AsyncInit(param, std::move(engine_cb_map)); } } else if (group != kDefaultEngineId) { std::lock_guard lock(engine_mutex); @@ -482,64 +771,74 @@ jlong InitInstance(JNIEnv* j_env, engine = std::get>(it->second); runtime->SetEngine(engine); std::get(it->second) += 1; - std::shared_ptr v8_vm = std::static_pointer_cast(engine->GetVM()); - v8::Isolate* isolate = v8_vm->isolate_; - isolate->SetData(kRuntimeSlotIndex, reinterpret_cast(-1)); - // -1 means single isolate multi-context mode TDF_BASE_DLOG(INFO) << "engine cnt = " << std::get(it->second) << ", use_count = " << engine.use_count(); } else { TDF_BASE_DLOG(INFO) << "engine create"; - engine = std::make_shared(std::move(engine_cb_map)); + engine = std::make_shared(); runtime->SetEngine(engine); reuse_engine_map[group] = std::make_pair(engine, 1); + engine->AsyncInit(param, std::move(engine_cb_map)); } } else { // kDefaultEngineId TDF_BASE_DLOG(INFO) << "default create engine"; - engine = std::make_shared(std::move(engine_cb_map)); + engine = std::make_shared(); runtime->SetEngine(engine); + engine->AsyncInit(param, std::move(engine_cb_map)); } - runtime->SetScope( - runtime->GetEngine()->CreateScope("", std::move(scope_cb_map))); + std::unordered_map init_param = { + { hippy::base::kUseSnapshot, use_snapshot ? "1" : "0" } + }; + runtime->SetScope(engine->AsyncCreateScope("", std::move(init_param), std::move(scope_cb_map))); TDF_BASE_DLOG(INFO) << "group = " << group; runtime->SetGroupId(group); TDF_BASE_LOG(INFO) << "InitInstance end, runtime_id = " << runtime_id; - return runtime_id; } -void DestroyInstance(JNIEnv* j_env, - jobject j_object, +void DestroyInstance(__unused JNIEnv* j_env, + __unused jobject j_object, jlong j_runtime_id, - jboolean j_single_thread_mode, + __unused jboolean j_single_thread_mode, + jboolean j_is_reload, jobject j_callback) { TDF_BASE_DLOG(INFO) << "DestroyInstance begin, j_runtime_id = " << j_runtime_id; - int32_t runtime_id = static_cast(j_runtime_id); + auto runtime_id = static_cast(j_runtime_id); std::shared_ptr runtime = Runtime::Find(runtime_id); if (!runtime) { - TDF_BASE_DLOG(WARNING) << "HippyBridgeImpl destroy, j_runtime_id invalid"; + TDF_BASE_LOG(WARNING) << "HippyBridgeImpl destroy, j_runtime_id invalid"; return; } std::shared_ptr task = std::make_shared(); - task->callback = [runtime, runtime_id] { - TDF_BASE_LOG(INFO) << "js destroy begin, runtime_id " << runtime_id; -#ifdef ENABLE_INSPECTOR + std::shared_ptr cb = std::make_shared(j_env, j_callback); + auto is_reload = static_cast(j_is_reload); + task->callback = [runtime, runtime_id, cb, is_reload] { + TDF_BASE_LOG(INFO) << "js destroy begin, runtime_id = " << runtime_id << ", is_reload = " << is_reload; + if (!runtime->GetScope()) { + Runtime::Erase(runtime); + TDF_BASE_LOG(INFO) << "scope is null, js destroy end"; + hippy::bridge::CallJavaMethod(cb->GetObj(), INIT_CB_STATE::SUCCESS); + return; + } +#ifndef V8_WITHOUT_INSPECTOR if (runtime->IsDebug()) { - global_inspector->DestroyContext(); - global_inspector->Reset(nullptr, runtime->GetBridge()); + auto inspector_client = runtime->GetEngine()->GetInspectorClient(); + if (inspector_client) { + auto inspector_context = runtime->GetInspectorContext(); + inspector_client->DestroyInspectorContext(is_reload, inspector_context); + } } else { runtime->GetScope()->WillExit(); } #else runtime->GetScope()->WillExit(); #endif - TDF_BASE_LOG(INFO) << "SetScope nullptr"; - runtime->SetScope(nullptr); TDF_BASE_LOG(INFO) << "erase runtime"; Runtime::Erase(runtime); TDF_BASE_LOG(INFO) << "js destroy end"; + hippy::bridge::CallJavaMethod(cb->GetObj(), INIT_CB_STATE::SUCCESS); }; int64_t group = runtime->GetGroupId(); if (group == kDebuggerEngineId) { @@ -567,14 +866,35 @@ void DestroyInstance(JNIEnv* j_env, TDF_BASE_DLOG(FATAL) << "engine not find"; } } - hippy::bridge::CallJavaMethod(j_callback, INIT_CB_STATE::SUCCESS); TDF_BASE_DLOG(INFO) << "destroy end"; } +void RunInJsThread(JNIEnv *j_env, + jobject j_object, + jlong j_runtime_id, + jobject j_callback) { + auto runtime = Runtime::Find(hippy::base::checked_numeric_cast(j_runtime_id)); + TDF_BASE_CHECK(runtime); + auto cb = std::make_shared(j_env, j_callback); + auto task_runner = runtime->GetEngine()->GetJSRunner(); + TDF_BASE_CHECK(task_runner); + auto task = std::make_unique(); + task->callback = [cb]() { + auto j_env = JNIEnvironment::GetInstance()->AttachCurrentThread(); + auto j_callback = cb->GetObj(); + auto j_cb_class = j_env->GetObjectClass(j_callback); + auto j_cb_method_id = j_env->GetMethodID(j_cb_class, "callback", + "(Ljava/lang/Object;Ljava/lang/Throwable;)V"); + j_env->CallVoidMethod(j_callback, j_cb_method_id, nullptr, nullptr); + JNIEnvironment::ClearJEnvException(j_env); + }; + task_runner->PostTask(std::move(task)); +} + } // namespace bridge } // namespace hippy -jint JNI_OnLoad(JavaVM* j_vm, void* reserved) { +jint JNI_OnLoad(JavaVM* j_vm, __unused void* reserved) { JNIEnv* j_env; jint onLoad_err = -1; if ((j_vm)->GetEnv(reinterpret_cast(&j_env), JNI_VERSION_1_4) != @@ -592,6 +912,7 @@ jint JNI_OnLoad(JavaVM* j_vm, void* reserved) { JNIEnvironment::GetInstance()->init(j_vm, j_env); + ADRLoader::Init(); Uri::Init(); JavaTurboModule::Init(); ConvertUtils::Init(); @@ -600,13 +921,13 @@ jint JNI_OnLoad(JavaVM* j_vm, void* reserved) { return JNI_VERSION_1_4; } -void JNI_OnUnload(JavaVM* j_vm, void* reserved) { - hippy::napi::V8VM::PlatformDestroy(); - - Uri::Destory(); - JavaTurboModule::Destory(); - ConvertUtils::Destory(); - TurboModuleManager::Destory(); +void JNI_OnUnload(__unused JavaVM* j_vm, __unused void* reserved) { + V8VM::PlatformDestroy(); + Uri::Destroy(); + JavaTurboModule::Destroy(); + ConvertUtils::Destroy(); + TurboModuleManager::Destroy(); + ADRLoader::Destroy(); JNIEnvironment::DestroyInstance(); } diff --git a/android/sdk/src/main/jni/src/bridge/java2js.cc b/android/sdk/src/main/jni/src/bridge/java2js.cc index e6bbb4a665d..167b1a2c07d 100644 --- a/android/sdk/src/main/jni/src/bridge/java2js.cc +++ b/android/sdk/src/main/jni/src/bridge/java2js.cc @@ -24,8 +24,10 @@ #include "bridge/js2java.h" #include "bridge/runtime.h" -#include "core/base/string_view_utils.h" +#include "core/vm/v8/v8_vm.h" #include "jni/jni_register.h" +#include "jni/jni_utils.h" +#include "jni/jni_env.h" namespace hippy { namespace bridge { @@ -36,51 +38,51 @@ enum CALLFUNCTION_CB_STATE { SUCCESS = 0, }; -REGISTER_JNI( - "com/tencent/mtt/hippy/bridge/HippyBridgeImpl", - "callFunction", - "(Ljava/lang/String;JLcom/tencent/mtt/hippy/bridge/NativeCallback;[BII)V", - CallFunctionByHeapBuffer) +REGISTER_JNI( // NOLINT(cert-err58-cpp) + "com/tencent/mtt/hippy/bridge/HippyBridgeImpl", + "callFunction", + "(Ljava/lang/String;JLcom/tencent/mtt/hippy/bridge/NativeCallback;[BII)V", + CallFunctionByHeapBuffer) -REGISTER_JNI("com/tencent/mtt/hippy/bridge/HippyBridgeImpl", - "callFunction", - "(Ljava/lang/String;JLcom/tencent/mtt/hippy/bridge/" - "NativeCallback;Ljava/nio/ByteBuffer;II)V", - CallFunctionByDirectBuffer) +REGISTER_JNI( // NOLINT(cert-err58-cpp) + "com/tencent/mtt/hippy/bridge/HippyBridgeImpl", + "callFunction", + "(Ljava/lang/String;JLcom/tencent/mtt/hippy/bridge/" + "NativeCallback;Ljava/nio/ByteBuffer;II)V", + CallFunctionByDirectBuffer) using unicode_string_view = tdf::base::unicode_string_view; using bytes = std::string; using Ctx = hippy::napi::Ctx; +using V8Ctx = hippy::napi::V8Ctx; using CtxValue = hippy::napi::CtxValue; using StringViewUtils = hippy::base::StringViewUtils; -#ifdef ENABLE_INSPECTOR -using V8InspectorClientImpl = hippy::inspector::V8InspectorClientImpl; -extern std::shared_ptr global_inspector; -#endif +using VM = hippy::vm::VM; +using V8VM = hippy::vm::V8VM; const char kHippyBridgeName[] = "hippyBridge"; void CallFunction(JNIEnv* j_env, - jobject j_obj, + __unused jobject j_obj, jstring j_action, jlong j_runtime_id, jobject j_callback, bytes buffer_data, std::shared_ptr buffer_owner) { TDF_BASE_DLOG(INFO) << "CallFunction j_runtime_id = " << j_runtime_id; - std::shared_ptr runtime = Runtime::Find(j_runtime_id); + auto runtime = Runtime::Find(hippy::base::checked_numeric_cast(j_runtime_id)); if (!runtime) { TDF_BASE_DLOG(WARNING) << "CallFunction j_runtime_id invalid"; return; } - std::shared_ptr runner = - runtime->GetEngine()->GetJSRunner(); + std::shared_ptr runner = runtime->GetEngine()->GetJSRunner(); if (!j_action) { TDF_BASE_DLOG(WARNING) << "CallFunction j_action invalid"; return; } unicode_string_view action_name = JniUtils::ToStrView(j_env, j_action); + TDF_BASE_DLOG(INFO) << "CallFunction action_name = " << action_name; std::shared_ptr cb = std::make_shared(j_env, j_callback); std::shared_ptr task = std::make_shared(); task->callback = [runtime, cb_ = std::move(cb), action_name, @@ -92,19 +94,20 @@ void CallFunction(JNIEnv* j_env, TDF_BASE_DLOG(WARNING) << "CallFunction scope invalid"; return; } - std::shared_ptr context = scope->GetContext(); + auto context = scope->GetContext(); if (!runtime->GetBridgeFunc()) { TDF_BASE_DLOG(INFO) << "init bridge func"; - unicode_string_view name(kHippyBridgeName); - std::shared_ptr fn = context->GetJsFn(name); + auto func_name = context->CreateString(kHippyBridgeName); + auto global_object = context->GetGlobalObject(); + auto fn = context->GetProperty(global_object, func_name); bool is_fn = context->IsFunction(fn); TDF_BASE_DLOG(INFO) << "is_fn = " << is_fn; - if (!is_fn) { - jstring j_msg = - JniUtils::StrViewToJString(j_env, u"hippyBridge not find"); + auto j_action = JniUtils::StrViewToJString(j_env, action_name); + auto j_msg = JniUtils::StrViewToJString(j_env, u"hippyBridge not find"); CallJavaMethod(cb_->GetObj(), CALLFUNCTION_CB_STATE::NO_METHOD_ERROR, - j_msg); + j_msg, j_action); + j_env->DeleteLocalRef(j_action); j_env->DeleteLocalRef(j_msg); return; } else { @@ -114,27 +117,27 @@ void CallFunction(JNIEnv* j_env, TDF_BASE_DCHECK(action_name.encoding() == unicode_string_view::Encoding::Utf16); if (runtime->IsDebug() && - !action_name.utf16_value().compare(u"onWebsocketMsg")) { -#ifdef ENABLE_INSPECTOR + action_name.utf16_value() == u"onWebsocketMsg") { +#ifndef V8_WITHOUT_INSPECTOR std::u16string str(reinterpret_cast(&buffer_data_[0]), buffer_data_.length() / sizeof(char16_t)); - global_inspector->SendMessageToV8( - std::move(unicode_string_view(std::move(str)))); + auto inspector_client = runtime->GetEngine()->GetInspectorClient(); + if (inspector_client) { + inspector_client->SendMessageToV8(runtime->GetInspectorContext(), unicode_string_view(std::move(str))); + } #endif - CallJavaMethod(cb_->GetObj(), CALLFUNCTION_CB_STATE::SUCCESS); + jstring j_action = JniUtils::StrViewToJString(j_env, action_name); + CallJavaMethod(cb_->GetObj(), CALLFUNCTION_CB_STATE::SUCCESS, nullptr, j_action); + j_env->DeleteLocalRef(j_action); return; } std::shared_ptr action = context->CreateString(action_name); std::shared_ptr params; if (runtime->IsEnableV8Serialization()) { - v8::Isolate* isolate = std::static_pointer_cast( - runtime->GetEngine()->GetVM()) - ->isolate_; + v8::Isolate* isolate = std::static_pointer_cast(runtime->GetEngine()->GetVM())->isolate_; v8::HandleScope handle_scope(isolate); - v8::Local ctx = std::static_pointer_cast( - runtime->GetScope()->GetContext()) - ->context_persistent_.Get(isolate); + v8::Local ctx = std::static_pointer_cast(runtime->GetScope()->GetContext())->context_persistent_.Get(isolate); hippy::napi::V8TryCatch try_catch(true, context); v8::ValueDeserializer deserializer( isolate, reinterpret_cast(buffer_data_.c_str()), @@ -145,6 +148,7 @@ void CallFunction(JNIEnv* j_env, params = std::make_shared( isolate, ret.ToLocalChecked()); } else { + jstring j_action = JniUtils::StrViewToJString(j_env, action_name); jstring j_msg; if (try_catch.HasCaught()) { unicode_string_view msg = try_catch.GetExceptionMsg(); @@ -154,7 +158,8 @@ void CallFunction(JNIEnv* j_env, } CallJavaMethod( cb_->GetObj(), - hippy::bridge::CALLFUNCTION_CB_STATE::DESERIALIZER_FAILED, j_msg); + hippy::bridge::CALLFUNCTION_CB_STATE::DESERIALIZER_FAILED, j_msg, j_action); + j_env->DeleteLocalRef(j_action); j_env->DeleteLocalRef(j_msg); return; } @@ -164,7 +169,7 @@ void CallFunction(JNIEnv* j_env, unicode_string_view buf_str(std::move(str)); TDF_BASE_DLOG(INFO) << "action_name = " << action_name << ", buf_str = " << buf_str; - params = context->CreateObject(buf_str); + params = VM::ParseJson(context, buf_str); } if (!params) { params = context->CreateNull(); @@ -172,7 +177,9 @@ void CallFunction(JNIEnv* j_env, std::shared_ptr argv[] = {action, params}; context->CallFunction(runtime->GetBridgeFunc(), 2, argv); - CallJavaMethod(cb_->GetObj(), CALLFUNCTION_CB_STATE::SUCCESS); + jstring j_action = JniUtils::StrViewToJString(j_env, action_name); + CallJavaMethod(cb_->GetObj(), CALLFUNCTION_CB_STATE::SUCCESS, nullptr, j_action); + j_env->DeleteLocalRef(j_action); }; runner->PostTask(task); @@ -200,15 +207,18 @@ void CallFunctionByDirectBuffer(JNIEnv* j_env, jobject j_buffer, jint j_offset, jint j_length) { - char* buffer_address = - static_cast(j_env->GetDirectBufferAddress(j_buffer)); + char* buffer_address = static_cast(j_env->GetDirectBufferAddress(j_buffer)); TDF_BASE_CHECK(buffer_address != nullptr); CallFunction(j_env, j_obj, j_action, j_runtime_id, j_callback, - bytes(buffer_address + j_offset, j_length), + bytes(buffer_address + j_offset, + hippy::base::checked_numeric_cast(j_length)), std::make_shared(j_env, j_buffer)); } -void CallJavaMethod(jobject j_obj, jlong j_value, jstring j_msg) { +void CallJavaMethod(jobject j_obj, + jlong j_ret_code, + jstring j_ret_content, + jstring j_payload) { if (!j_obj) { TDF_BASE_DLOG(INFO) << "CallJavaMethod j_obj is nullptr"; return; @@ -222,17 +232,15 @@ void CallJavaMethod(jobject j_obj, jlong j_value, jstring j_msg) { } jmethodID j_cb_id = - j_env->GetMethodID(j_class, "Callback", "(JLjava/lang/String;)V"); + j_env->GetMethodID(j_class, "nativeCallback", "(JLjava/lang/String;Ljava/lang/String;)V"); if (!j_cb_id) { TDF_BASE_LOG(ERROR) << "CallJavaMethod j_cb_id error"; return; } - j_env->CallVoidMethod(j_obj, j_cb_id, j_value, j_msg); + j_env->CallVoidMethod(j_obj, j_cb_id, j_ret_code, j_ret_content, j_payload); JNIEnvironment::ClearJEnvException(j_env); - if (j_class) { - j_env->DeleteLocalRef(j_class); - } + j_env->DeleteLocalRef(j_class); } } // namespace bridge diff --git a/android/sdk/src/main/jni/src/bridge/js2java.cc b/android/sdk/src/main/jni/src/bridge/js2java.cc index 7be21f62477..1520edbbe70 100644 --- a/android/sdk/src/main/jni/src/bridge/js2java.cc +++ b/android/sdk/src/main/jni/src/bridge/js2java.cc @@ -20,6 +20,7 @@ * */ +#include "bridge/adr_bridge.h" #include "bridge/js2java.h" #include @@ -27,144 +28,116 @@ #include "base/logging.h" #include "base/unicode_string_view.h" #include "bridge/runtime.h" -#include "bridge/serializer.h" -#include "core/base/string_view_utils.h" +#include "core/scope.h" +#include "core/vm/v8/serializer.h" #include "jni/jni_env.h" +#include "jni/jni_utils.h" using unicode_string_view = tdf::base::unicode_string_view; using StringViewUtils = hippy::base::StringViewUtils; +using Ctx = hippy::napi::Ctx; namespace hippy { namespace bridge { -void CallJava(hippy::napi::CBDataTuple *data) { - TDF_BASE_DLOG(INFO) << "CallJava"; - int32_t runtime_id = static_cast(reinterpret_cast(data->cb_tuple_.data_)); - std::shared_ptr runtime = Runtime::Find(runtime_id); +void CallJava(const hippy::napi::CallbackInfo& info, int32_t runtime_id) { + TDF_BASE_DLOG(INFO) << "CallJava runtime_id = " << runtime_id; + auto runtime = Runtime::Find(runtime_id); if (!runtime) { + TDF_BASE_DLOG(INFO) << "CallJava runtime not found"; return; } - const v8::FunctionCallbackInfo &info = data->info_; - v8::Isolate *isolate = info.GetIsolate(); - if (!isolate) { - TDF_BASE_DLOG(ERROR) << "CallJava isolate error"; - return; - } - - v8::HandleScope handle_scope(isolate); - std::shared_ptr v8_ctx = - std::static_pointer_cast( - runtime->GetScope()->GetContext()); - v8::Local context = v8_ctx->context_persistent_.Get(isolate); - v8::Context::Scope context_scope(context); - if (context.IsEmpty()) { - TDF_BASE_DLOG(ERROR) << "CallJava context empty"; - return; - } + auto scope_wrapper = reinterpret_cast(std::any_cast(info.GetSlot())); + auto scope = scope_wrapper->scope.lock(); + TDF_BASE_CHECK(scope); + auto context = scope->GetContext(); - jstring j_module_name = nullptr; + jstring j_module_name; std::shared_ptr instance = JNIEnvironment::GetInstance(); JNIEnv *j_env = instance->AttachCurrentThread(); - if (info.Length() >= 1 && !info[0].IsEmpty()) { - v8::MaybeLocal module_maybe_str = info[0]->ToString(context); - if (module_maybe_str.IsEmpty()) { - isolate->ThrowException( - v8::Exception::TypeError( - v8::String::NewFromOneByte(isolate, - reinterpret_cast("module name error")) - .ToLocalChecked())); + if (info[0]) { + unicode_string_view module_name; + if (!context->GetValueString(info[0], &module_name)) { + info.GetExceptionValue()->Set(context,"module name error"); return; } - unicode_string_view module_name = v8_ctx->ToStringView(module_maybe_str.ToLocalChecked()); j_module_name = JniUtils::StrViewToJString(j_env, module_name); TDF_BASE_DLOG(INFO) << "CallJava module_name = " << module_name; } else { - isolate->ThrowException( - v8::Exception::Error( - v8::String::NewFromOneByte(isolate, - reinterpret_cast("info error")) - .ToLocalChecked())); + info.GetExceptionValue()->Set(context, "info error"); return; } - jstring j_module_func = nullptr; - if (info.Length() >= 2 && !info[1].IsEmpty()) { - v8::MaybeLocal func_maybe_str = info[1]->ToString(context); - if (func_maybe_str.IsEmpty()) { - isolate->ThrowException( - v8::Exception::TypeError( - v8::String::NewFromOneByte(isolate, - reinterpret_cast("func name error")) - .ToLocalChecked())); + jstring j_module_func; + if (info[1]) { + unicode_string_view fn_name; + if (!context->GetValueString(info[1], &fn_name)) { + info.GetExceptionValue()->Set(context,"func name error"); return; } - unicode_string_view module_func = v8_ctx->ToStringView(func_maybe_str.ToLocalChecked()); - j_module_func = JniUtils::StrViewToJString(j_env, module_func); - TDF_BASE_DLOG(INFO) << "CallJava module_func = " << module_func; + j_module_func = JniUtils::StrViewToJString(j_env, fn_name); + TDF_BASE_DLOG(INFO) << "CallJava fn_name = " << fn_name; } else { - isolate->ThrowException( - v8::Exception::Error( - v8::String::NewFromOneByte(isolate, - reinterpret_cast("info error")) - .ToLocalChecked())); + info.GetExceptionValue()->Set(context, "info error"); return; } jstring j_cb_id = nullptr; - if (info.Length() >= 3 && !info[2].IsEmpty()) { - v8::MaybeLocal cb_id_maybe_str = info[2]->ToString(context); - if (!cb_id_maybe_str.IsEmpty()) { - unicode_string_view cb_id = v8_ctx->ToStringView(cb_id_maybe_str.ToLocalChecked()); - j_cb_id = JniUtils::StrViewToJString(j_env, cb_id); - TDF_BASE_DLOG(INFO) << "CallJava cb_id = " << cb_id; + if (info[2]) { + unicode_string_view cb_id_str; + double cb_id; + if (context->GetValueString(info[2], &cb_id_str)) { + TDF_BASE_DLOG(INFO) << "CallJava cb_id = " << cb_id_str; + j_cb_id = JniUtils::StrViewToJString(j_env, cb_id_str); + } else if (context->GetValueNumber(info[2], &cb_id)) { + cb_id_str = std::to_string(cb_id); + TDF_BASE_DLOG(INFO) << "CallJava cb_id = " << cb_id_str; + j_cb_id = JniUtils::StrViewToJString(j_env, cb_id_str); } } std::string buffer_data; - if (info.Length() >= 4 && !info[3].IsEmpty() && info[3]->IsObject()) { + if (info[3] && context->IsObject(info[3])) { if (runtime->IsEnableV8Serialization()) { - Serializer serializer(isolate, context, runtime->GetBuffer()); - serializer.WriteHeader(); - serializer.WriteValue(info[3]); - std::pair pair = serializer.Release(); - buffer_data = - std::string(reinterpret_cast(pair.first), pair.second); + auto v8_ctx = std::static_pointer_cast(context); + buffer_data = v8_ctx->GetSerializationBuffer(info[3], runtime->GetBuffer()); } else { - std::shared_ptr obj = - std::make_shared(isolate, info[3]); unicode_string_view json; - TDF_BASE_DCHECK(v8_ctx->GetValueJson(obj, &json)); + auto flag = context->GetValueJson(info[3], &json); + TDF_BASE_DCHECK(flag); TDF_BASE_DLOG(INFO) << "CallJava json = " << json; - buffer_data = StringViewUtils::ToU8StdStr(json); + if (flag) { + buffer_data = StringViewUtils::ToU8StdStr(json); + } } } - uint8_t transfer_type = 0; - if (info.Length() >= 5 && !info[4].IsEmpty() && info[4]->IsNumber()) { - transfer_type = - static_cast(info[4]->NumberValue(context).FromMaybe(0)); + int32_t transfer_type = 0; + if (info[4]) { + context->GetValueNumber(info[4], &transfer_type); } TDF_BASE_DLOG(INFO) << "CallNative transfer_type = " << transfer_type; - jobject j_buffer = nullptr; - jmethodID j_method = nullptr; + jobject j_buffer; + jmethodID j_method; if (transfer_type == 1) { // Direct j_buffer = j_env->NewDirectByteBuffer( const_cast(reinterpret_cast(buffer_data.c_str())), - buffer_data.length()); + hippy::base::checked_numeric_cast(buffer_data.length())); j_method = instance->GetMethods().j_call_natives_direct_method_id; } else { // Default - j_buffer = j_env->NewByteArray(buffer_data.length()); + auto buffer_size = hippy::base::checked_numeric_cast(buffer_data.length()); + j_buffer = j_env->NewByteArray(buffer_size); j_env->SetByteArrayRegion( - reinterpret_cast(j_buffer), 0, buffer_data.length(), + reinterpret_cast(j_buffer), 0, buffer_size, reinterpret_cast(buffer_data.c_str())); j_method = instance->GetMethods().j_call_natives_method_id; } - j_env->CallVoidMethod(runtime->GetBridge()->GetObj(), j_method, j_module_name, + auto bridge = std::static_pointer_cast(runtime->GetBridge()); + j_env->CallVoidMethod(bridge->GetObj(), j_method, j_module_name, j_module_func, j_cb_id, j_buffer); - JNIEnvironment::ClearJEnvException(j_env); // delete local ref diff --git a/android/sdk/src/main/jni/src/bridge/runtime.cc b/android/sdk/src/main/jni/src/bridge/runtime.cc index 47db489e1dd..707d034425d 100644 --- a/android/sdk/src/main/jni/src/bridge/runtime.cc +++ b/android/sdk/src/main/jni/src/bridge/runtime.cc @@ -25,6 +25,8 @@ #include #include +constexpr int32_t kReuseRuntimeId = -1; + using V8Ctx = hippy::napi::V8Ctx; static std::unordered_map> RuntimeMap; @@ -32,12 +34,13 @@ static std::mutex mutex; static std::atomic global_runtime_key{0}; -Runtime::Runtime(std::shared_ptr bridge, bool enable_v8_serialization, bool is_dev) - : enable_v8_serialization_(enable_v8_serialization), is_debug_(is_dev), bridge_(bridge) { +Runtime::Runtime(std::shared_ptr bridge, bool enable_v8_serialization, bool is_dev) + : enable_v8_serialization_(enable_v8_serialization), is_debug_(is_dev), group_id_(0), + bridge_(std::move(bridge)), interrupt_queue_(nullptr) { id_ = global_runtime_key.fetch_add(1); } -void Runtime::Insert(std::shared_ptr runtime) { +void Runtime::Insert(const std::shared_ptr& runtime) { std::lock_guard lock(mutex); RuntimeMap[runtime->id_] = runtime; } @@ -57,14 +60,21 @@ std::shared_ptr Runtime::Find(v8::Isolate *isolate) { if (!isolate) { return nullptr; } - int32_t runtime_id = + auto runtime_id = static_cast(reinterpret_cast(isolate->GetData(kRuntimeSlotIndex))); - if (runtime_id == -1) {// -1 means single isolate multi context mode + TDF_BASE_LOG(INFO) << "Runtime::Find runtime_id = " << runtime_id; + if (runtime_id == kReuseRuntimeId) {// -1 means single isolate multi context mode v8::Local context = isolate->GetCurrentContext(); std::lock_guard lock(mutex); - for (auto p: RuntimeMap) { - std::shared_ptr scope = p.second->GetScope(); + for (const auto& p: RuntimeMap) { + auto scope = p.second->GetScope(); + if (!scope) { + continue; + } std::shared_ptr ctx = std::static_pointer_cast(scope->GetContext()); + if (!ctx) { + continue; + } if (ctx->context_persistent_ == context) { return p.second; } @@ -86,6 +96,6 @@ bool Runtime::Erase(int32_t id) { return true; } -bool Runtime::Erase(std::shared_ptr runtime) { +bool Runtime::Erase(const std::shared_ptr& runtime) { return Runtime::Erase(runtime->id_); } diff --git a/android/sdk/src/main/jni/src/inspector/v8_channel_impl.cc b/android/sdk/src/main/jni/src/inspector/v8_channel_impl.cc deleted file mode 100644 index 814d8e1849f..00000000000 --- a/android/sdk/src/main/jni/src/inspector/v8_channel_impl.cc +++ /dev/null @@ -1,85 +0,0 @@ -/* - * - * Tencent is pleased to support the open source community by making - * Hippy available. - * - * Copyright (C) 2019 THL A29 Limited, a Tencent company. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -#include "inspector/v8_channel_impl.h" - -#include - -#include "jni/jni_env.h" - -namespace hippy { -namespace inspector { - -V8ChannelImpl::V8ChannelImpl(std::shared_ptr bridge) - : bridge_(bridge) {} - -void V8ChannelImpl::sendResponse( - int callId, - std::unique_ptr message) { - if (message->string().is8Bit()) { - return; - } - - const uint16_t* source = message->string().characters16(); - int len = message->string().length(); - std::shared_ptr instance = JNIEnvironment::GetInstance(); - JNIEnv* j_env = instance->AttachCurrentThread(); - jbyteArray msg = j_env->NewByteArray(len * sizeof(*source)); - j_env->SetByteArrayRegion( - msg, 0, len * sizeof(*source), - reinterpret_cast(reinterpret_cast(source))); - - if (instance->GetMethods().j_inspector_channel_method_id && bridge_) { - j_env->CallVoidMethod(bridge_->GetObj(), - instance->GetMethods().j_inspector_channel_method_id, - msg); - } - - j_env->DeleteLocalRef(msg); -} - -void V8ChannelImpl::sendNotification( - std::unique_ptr message) { - if (message->string().is8Bit()) { - return; - } - - const uint16_t* source = message->string().characters16(); - int len = message->string().length(); - std::shared_ptr instance = JNIEnvironment::GetInstance(); - JNIEnv* j_env = instance->AttachCurrentThread(); - jbyteArray msg = j_env->NewByteArray(len * sizeof(*source)); - j_env->SetByteArrayRegion( - msg, 0, len * sizeof(*source), - reinterpret_cast(reinterpret_cast(source))); - - if (instance->GetMethods().j_inspector_channel_method_id && bridge_) { - j_env->CallVoidMethod(bridge_->GetObj(), - instance->GetMethods().j_inspector_channel_method_id, - msg); - } - - j_env->DeleteLocalRef(msg); -} - -} // namespace inspector -} // namespace hippy diff --git a/android/sdk/src/main/jni/src/inspector/v8_inspector_client_impl.cc b/android/sdk/src/main/jni/src/inspector/v8_inspector_client_impl.cc deleted file mode 100644 index 1813c1e5758..00000000000 --- a/android/sdk/src/main/jni/src/inspector/v8_inspector_client_impl.cc +++ /dev/null @@ -1,142 +0,0 @@ -/* - * - * Tencent is pleased to support the open source community by making - * Hippy available. - * - * Copyright (C) 2019 THL A29 Limited, a Tencent company. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -#include "inspector/v8_inspector_client_impl.h" - -#include "core/core.h" - -namespace hippy { -namespace inspector { - -V8InspectorClientImpl::V8InspectorClientImpl(std::shared_ptr scope) - : scope_(scope) { - std::shared_ptr ctx = - std::static_pointer_cast(scope_->GetContext()); - v8::Isolate* isolate = ctx->isolate_; - v8::HandleScope handle_scope(isolate); - inspector_ = v8_inspector::V8Inspector::create(isolate, this); -} - -void V8InspectorClientImpl::Reset(std::shared_ptr scope, - std::shared_ptr bridge) { - scope_ = scope; - channel_->SetBridge(bridge); -} - -void V8InspectorClientImpl::Connect(std::shared_ptr bridge) { - channel_ = std::make_unique(bridge); - session_ = inspector_->connect(1, channel_.get(), v8_inspector::StringView()); -} - -void V8InspectorClientImpl::CreateContext() { - std::shared_ptr ctx = - std::static_pointer_cast(scope_->GetContext()); - v8::Isolate* isolate = ctx->isolate_; - v8::HandleScope handle_scope(isolate); - v8::Local context = - v8::Local::New(isolate, ctx->context_persistent_); - v8::Context::Scope context_scope(context); - uint8_t name_uint8[] = "Hippy"; - inspector_->contextCreated(v8_inspector::V8ContextInfo( - context, 1, v8_inspector::StringView(name_uint8, arraysize(name_uint8)))); -} - -void V8InspectorClientImpl::SendMessageToV8(const unicode_string_view& params) { - if (channel_) { - unicode_string_view::Encoding encoding = params.encoding(); - v8_inspector::StringView message_view; - switch (encoding) { - case unicode_string_view::Encoding::Latin1: { - std::string str = params.latin1_value(); - if (!str.compare("chrome_socket_closed")) { - session_ = inspector_->connect(1, channel_.get(), - v8_inspector::StringView()); - return; - } - message_view = v8_inspector::StringView( - reinterpret_cast(str.c_str()), str.length()); - break; - } - case unicode_string_view::Encoding::Utf16: { - std::u16string str = params.utf16_value(); - if (!str.compare(u"chrome_socket_closed")) { - session_ = inspector_->connect(1, channel_.get(), - v8_inspector::StringView()); - return; - } - message_view = v8_inspector::StringView( - reinterpret_cast(str.c_str()), str.length()); - break; - } - default: - TDF_BASE_DLOG(INFO) << "encoding = " << static_cast(encoding); - TDF_BASE_NOTREACHED(); - break; - } - session_->dispatchProtocolMessage(message_view); - } -} - -void V8InspectorClientImpl::DestroyContext() { - TDF_BASE_DLOG(INFO) << "V8InspectorClientImpl DestroyContext"; - std::shared_ptr ctx = - std::static_pointer_cast(scope_->GetContext()); - if (!ctx) { - TDF_BASE_DLOG(ERROR) << "V8InspectorClientImpl ctx error"; - return; - } - v8::Isolate* isolate = ctx->isolate_; - v8::HandleScope handle_scope(isolate); - v8::Local context = - v8::Local::New(isolate, ctx->context_persistent_); - v8::Context::Scope context_scope(context); - TDF_BASE_DLOG(INFO) << "inspector contextDestroyed begin"; - inspector_->contextDestroyed(context); - TDF_BASE_DLOG(INFO) << "inspector contextDestroyed end"; -} - -v8::Local V8InspectorClientImpl::ensureDefaultContextInGroup( - int contextGroupId) { - std::shared_ptr ctx = - std::static_pointer_cast(scope_->GetContext()); - v8::Isolate* isolate = ctx->isolate_; - v8::HandleScope handle_scope(isolate); - v8::Local context = - v8::Local::New(isolate, ctx->context_persistent_); - v8::Context::Scope context_scope(context); - return context; -} - -void V8InspectorClientImpl::runMessageLoopOnPause(int contextGroupId) { - scope_->GetTaskRunner()->PauseThreadForInspector(); -} - -void V8InspectorClientImpl::quitMessageLoopOnPause() { - scope_->GetTaskRunner()->ResumeThreadForInspector(); -} - -void V8InspectorClientImpl::runIfWaitingForDebugger(int contextGroupId) { - scope_->GetTaskRunner()->ResumeThreadForInspector(); -} - -} // namespace inspector -} // namespace hippy diff --git a/android/sdk/src/main/jni/src/jni/convert_utils.cc b/android/sdk/src/main/jni/src/jni/convert_utils.cc index b7352cc17a2..f017df2bbba 100644 --- a/android/sdk/src/main/jni/src/jni/convert_utils.cc +++ b/android/sdk/src/main/jni/src/jni/convert_utils.cc @@ -22,7 +22,11 @@ #include "jni/convert_utils.h" -#include "core/napi/v8/js_native_turbo_v8.h" +#include +#include + +#include "jni/jni_env.h" +#include "jni/jni_utils.h" #include "jni/java_turbo_module.h" using namespace hippy::napi; @@ -30,20 +34,11 @@ using unicode_string_view = tdf::base::unicode_string_view; using StringViewUtils = hippy::base::StringViewUtils; bool IsBasicNumberType(const std::string &type) { - return type == kint || type == kdouble || type == kfloat || type == klong; + return type == kInt || type == kDouble || type == kFloat || type == kLong; } bool IsNumberObject(const std::string &type) { - return type == kInteger || type == kDouble || type == kFloat || type == kLong; -} - -void ThrowConvertTypeException(const int64_t &index, const std::string &info) { - std::string exception_info = std::string("ConvertTypeException: ") - .append("argument index = ") - .append(ToString(index)) - .append(", ") - .append(info); - throw std::runtime_error(exception_info); + return type == kIntegerObject || type == kDoubleObject || type == kFloatObject || type == kLongObject; } /** @@ -61,303 +56,372 @@ void ThrowConvertTypeException(const int64_t &index, const std::string &info) { * @param arg_values * @return */ -std::shared_ptr ConvertUtils::ConvertJSIArgsToJNIArgs( - TurboEnv &turbo_env, + +std::tuple> ConvertUtils::ConvertJSIArgsToJNIArgs( + const std::shared_ptr& ctx, const std::string &module_name, const std::string &method_name, const std::vector &method_arg_types, const std::vector> &arg_values) { - std::shared_ptr ctx = turbo_env.context_; std::shared_ptr context = std::static_pointer_cast(ctx); - int actual_arg_count = arg_values.size(); - std::shared_ptr jni_args = - std::make_shared(actual_arg_count); + auto actual_arg_count = arg_values.size(); + std::shared_ptr jni_args = std::make_shared(actual_arg_count); auto &global_refs = jni_args->global_refs_; - for (int i = 0; i < actual_arg_count; i++) { - try { - std::string type = method_arg_types.at(i); + for (size_t i = 0; i < actual_arg_count; i++) { + std::string type = method_arg_types.at(i); - jvalue *j_args = &jni_args->args_[i]; - std::shared_ptr value = arg_values.at(i); + jvalue *j_args = &jni_args->args_[i]; + std::shared_ptr value = arg_values.at(i); - // basic type - if (HandleBasicType(turbo_env, type, *j_args, value)) { - continue; - } + // basic type + auto base_tuple = HandleBasicType(ctx, type, *j_args, value); + if (!std::get<0>(base_tuple)) { + return std::make_tuple(false, std::get<1>(base_tuple), + static_cast>(nullptr)); + } + if (std::get<2>(base_tuple)) { + continue; + } - // unSupport Object type - if (kUnSupportedType == type) { - throw std::runtime_error( - std::string("Unsupported type: ").append(type).c_str()); - } + // unSupport Object type + if (kUnSupportedType == type) { + return std::make_tuple(false, "Unsupported type: " + type, + static_cast>(nullptr)); + } - // NullOrUndefined - if (context->IsNullOrUndefined(value)) { - j_args->l = nullptr; - continue; - } + // NullOrUndefined + if (context->IsNullOrUndefined(value)) { + j_args->l = nullptr; + continue; + } - // Object - HandleObjectType(turbo_env, module_name, method_name, type, *j_args, - value, global_refs); - } catch (std::runtime_error &error) { - ThrowConvertTypeException(i, error.what()); + // Object + auto obj_tuple = HandleObjectType(ctx, module_name, method_name, type, *j_args, + value, global_refs); + if (!std::get<0>(obj_tuple)) { + return std::make_tuple(false, std::get<1>(obj_tuple), + static_cast>(nullptr)); } } JNIEnv *env = JNIEnvironment::GetInstance()->AttachCurrentThread(); if (JNIEnvironment::ClearJEnvException(env)) { - throw std::runtime_error( - "JNI Exception occured when convertJSIArgsToJNIArgs"); + return std::make_tuple(false, "JNI Exception occurred when convertJSIArgsToJNIArgs ", + static_cast>(nullptr)); } - return jni_args; + return std::make_tuple(true, "", jni_args); } -bool ConvertUtils::HandleBasicType(TurboEnv &turbo_env, - const std::string &type, - jvalue &j_args, - const std::shared_ptr &value) { - std::shared_ptr ctx = turbo_env.context_; - std::shared_ptr context = std::static_pointer_cast(ctx); +std::tuple ConvertUtils::HandleBasicType(const std::shared_ptr& ctx, + const std::string &type, + jvalue &j_args, + const std::shared_ptr &value) { + auto context = std::static_pointer_cast(ctx); // number if (IsBasicNumberType(type)) { - double num; - if (!context->GetValueNumber(value, &num)) { - throw std::runtime_error("Must be int/long/float/double."); - } + if (type == kInt) { + int32_t num; + if (!context->GetValueNumber(value, &num)) { + return std::make_tuple(false, "value must be int", false); + } - if (type == kint) { // int j_args.i = num; - } else if (type == kdouble) { // double - j_args.d = num; - } else if (type == kfloat) { // float - j_args.f = num; - } else if (type == klong) { // long - j_args.j = num; - } - return true; + } else { + double num; + if (!context->GetValueNumber(value, &num)) { + return std::make_tuple(false, "value must be long/float/double", false); + } + + if (type == kDouble) { // double + j_args.d = num; + } else if (type == kFloat) { // float + j_args.f = static_cast(num); + } else if (type == kLong) { // long + if (!hippy::base::numeric_cast(num, j_args.j)) { + return std::make_tuple(false, "value out of jlong boundary", false); + } + } + } + + return std::make_tuple(true, "", true); } // boolean if (type == "Z") { bool b; if (!context->GetValueBoolean(value, &b)) { - throw std::runtime_error("Must be boolean."); + return std::make_tuple(false, "value must be boolean", false); } j_args.z = b; - return true; + return std::make_tuple(true, "", true); } - return false; + return std::make_tuple(true, "", false); } -bool ConvertUtils::HandleObjectType(TurboEnv &turbo_env, - const std::string &module_name, - const std::string &method_name, - const std::string &type, - jvalue &j_args, - const std::shared_ptr &value, - std::vector &global_refs) { - std::shared_ptr ctx = turbo_env.context_; - std::shared_ptr context = std::static_pointer_cast(ctx); - - JNIEnv *env = JNIEnvironment::GetInstance()->AttachCurrentThread(); +std::tuple +ConvertUtils::HandleObjectType(const std::shared_ptr& ctx, + const std::string &module_name, + const std::string &method_name, + const std::string &type, + jvalue &j_args, + const std::shared_ptr &value, + std::vector> &global_refs) { + auto v8_ctx = std::static_pointer_cast(ctx); - auto make_global = [&global_refs, env](jobject obj) -> jobject { - jobject global_obj = env->NewGlobalRef(obj); - global_refs.push_back(global_obj); - env->DeleteLocalRef(obj); - return global_obj; - }; + JNIEnv *j_env = JNIEnvironment::GetInstance()->AttachCurrentThread(); // Promise if (type == kPromise) { unicode_string_view str_view; std::string str; - if (turbo_env.context_->GetValueString(value, &str_view)) { + if (v8_ctx->GetValueString(value, &str_view)) { str = StringViewUtils::ToU8StdStr(str_view); } else { - throw std::runtime_error("Must be String."); + return std::make_tuple(false, "value must be string", false); } - TDF_BASE_DLOG(INFO) << "Promise callId %s", str.c_str(); - - jstring module_name_str = env->NewStringUTF(module_name.c_str()); - jstring method_name_str = env->NewStringUTF(method_name.c_str()); - jstring call_id_str = env->NewStringUTF(str.c_str()); - jobject tmp = env->NewObject(promise_clazz, promise_constructor, nullptr, - module_name_str, method_name_str, call_id_str); - env->DeleteLocalRef(module_name_str); - env->DeleteLocalRef(method_name_str); - env->DeleteLocalRef(call_id_str); - j_args.l = make_global(tmp); - return true; + TDF_BASE_DLOG(INFO) << "Promise callId " << str.c_str(); + jstring module_name_str = j_env->NewStringUTF(module_name.c_str()); + jstring method_name_str = j_env->NewStringUTF(method_name.c_str()); + jstring call_id_str = j_env->NewStringUTF(str.c_str()); + jobject j_obj = j_env->NewObject(promise_clazz, promise_constructor, static_cast(nullptr), + module_name_str, method_name_str, call_id_str); + j_env->DeleteLocalRef(module_name_str); + j_env->DeleteLocalRef(method_name_str); + j_env->DeleteLocalRef(call_id_str); + auto ref = std::make_shared(j_env, j_obj); + j_env->DeleteLocalRef(j_obj); + global_refs.push_back(ref); + j_args.l = ref->GetObj(); + return std::make_tuple(true, "", true); } // HippyArray if (type == kHippyArray) { - if (!context->IsArray(value)) { - throw std::runtime_error("Must be Array."); + if (!ctx->IsArray(value)) { + return std::make_tuple(false, "value must be array", false); } - j_args.l = make_global(ToHippyArray(turbo_env, value)); - return true; + auto to_array_tuple = ToHippyArray(ctx, value); + if (!std::get<0>(to_array_tuple)) { + return std::make_tuple(false, std::get<1>(to_array_tuple), false); + } + auto j_obj = std::get<2>(to_array_tuple); + auto ref = std::make_shared(j_env, j_obj); + j_env->DeleteLocalRef(j_obj); + global_refs.push_back(ref); + j_args.l = ref->GetObj(); + return std::make_tuple(true, "", true); } // HippyMap if (type == kHippyMap) { - if (!context->IsMap(value)) { - throw std::runtime_error("Must be Map."); + if (!ctx->IsMap(value)) { + return std::make_tuple(false, "value must be map", false); } - - j_args.l = make_global(ToHippyMap(turbo_env, value)); - return true; + auto to_map_tuple = ToHippyMap(ctx, value); + if (!std::get<0>(to_map_tuple)) { + return std::make_tuple(false, std::get<1>(to_map_tuple), false); + } + auto j_obj = std::get<2>(to_map_tuple); + auto ref = std::make_shared(j_env, j_obj); + j_env->DeleteLocalRef(j_obj); + global_refs.push_back(ref); + j_args.l = ref->GetObj(); + return std::make_tuple(true, "", true); } // Boolean - if (type == kBoolean) { + if (type == kBooleanObject) { bool b; - if (!context->GetValueBoolean(value, &b)) { - throw std::runtime_error("Must be Boolean."); + if (!ctx->GetValueBoolean(value, &b)) { + return std::make_tuple(false, "value must be boolean", false); } - j_args.l = - make_global(env->NewObject(boolean_clazz, boolean_constructor, b)); - return true; + auto j_obj = j_env->NewObject(boolean_clazz, boolean_constructor, b); + auto ref = std::make_shared(j_env, j_obj); + j_env->DeleteLocalRef(j_obj); + global_refs.push_back(ref); + j_args.l = ref->GetObj(); + return std::make_tuple(true, "", true); } // String if (type == kString) { unicode_string_view str_view; std::string str; - if (turbo_env.context_->GetValueString(value, &str_view)) { + if (ctx->GetValueString(value, &str_view)) { str = StringViewUtils::ToU8StdStr(str_view); } else { - throw std::runtime_error("Must be String."); + return std::make_tuple(false, "value must be string", false); } - - j_args.l = make_global(env->NewStringUTF(str.c_str())); - return true; + auto j_obj = j_env->NewStringUTF(str.c_str()); + auto ref = std::make_shared(j_env, j_obj); + j_env->DeleteLocalRef(j_obj); + global_refs.push_back(ref); + j_args.l = ref->GetObj(); + return std::make_tuple(true, "", true); } // Number Object if (IsNumberObject(type)) { - double num; - if (!context->GetValueNumber(value, &num)) { - throw std::runtime_error("Integer/Double/Float/Long."); - } - - if (type == kInteger) { // Integer - j_args.l = make_global( - env->NewObject(integer_clazz, integer_constructor, (int)num)); - } else if (type == kDouble) { // Double - j_args.l = - make_global(env->NewObject(double_clazz, double_constructor, num)); - } else if (type == kFloat) { // Float - j_args.l = make_global( - env->NewObject(float_clazz, float_constructor, (float)num)); - } else if (type == kLong) { // Long - j_args.l = make_global( - env->NewObject(long_clazz, long_constructor, (int64_t)num)); + if (type == kIntegerObject) { + int32_t num; + if (!ctx->GetValueNumber(value, &num)) { + return std::make_tuple(true, "value must be int", false); + } + auto j_obj = j_env->NewObject( + integer_clazz, integer_constructor, num); + auto ref = std::make_shared(j_env, j_obj); + j_env->DeleteLocalRef(j_obj); + global_refs.push_back(ref); + j_args.l = ref->GetObj(); } else { - return false; + double num; + if (!ctx->GetValueNumber(value, &num)) { + return std::make_tuple(true, "value must be long/float/double", false); + } + + if (type == kDoubleObject) { + auto j_obj = j_env->NewObject( + double_clazz, double_constructor, num); + auto ref = std::make_shared(j_env, j_obj); + j_env->DeleteLocalRef(j_obj); + global_refs.push_back(ref); + j_args.l = ref->GetObj(); + } else if (type == kFloatObject) { + auto j_obj = j_env->NewObject( + float_clazz, float_constructor, static_cast(num)); + auto ref = std::make_shared(j_env, j_obj); + j_env->DeleteLocalRef(j_obj); + global_refs.push_back(ref); + j_args.l = ref->GetObj(); + } else if (type == kLongObject) { + jlong jlong_value; + if (!hippy::base::numeric_cast(num, jlong_value)) { + return std::make_tuple(true, "value out of jlong boundary", false); + } + auto j_obj = j_env->NewObject( + long_clazz, long_constructor, jlong_value); + auto ref = std::make_shared(j_env, j_obj); + j_env->DeleteLocalRef(j_obj); + global_refs.push_back(ref); + j_args.l = ref->GetObj(); + } else { + return std::make_tuple(false, "", false); + } } - return true; + return std::make_tuple(true, "", true); } - std::shared_ptr host_object = turbo_env.GetHostObject(value); + auto host_object = reinterpret_cast(v8_ctx->GetExternal(value)); if (host_object) { - std::shared_ptr j_turbo_module = - std::static_pointer_cast(host_object); - j_args.l = j_turbo_module->impl_->GetObj(); - return true; + j_args.l = host_object->impl_->GetObj(); + return std::make_tuple(true, "", true); } - return false; + return std::make_tuple(false, "", false); } -jobject ConvertUtils::ToHippyMap(TurboEnv &turbo_env, - const std::shared_ptr &value) { - std::shared_ptr ctx = turbo_env.context_; - std::shared_ptr context = std::static_pointer_cast(ctx); +std::tuple ConvertUtils::ToHippyMap(const std::shared_ptr& ctx, + const std::shared_ptr &value) { + std::shared_ptr v8_ctx = std::static_pointer_cast(ctx); - JNIEnv *env = JNIEnvironment::GetInstance()->AttachCurrentThread(); - jobject obj = env->NewObject(hippy_map_clazz, hippy_map_constructor); - std::shared_ptr array = context->ConvertMapToArray(value); + JNIEnv *j_env = JNIEnvironment::GetInstance()->AttachCurrentThread(); + jobject obj = j_env->NewObject(hippy_map_clazz, hippy_map_constructor); + std::shared_ptr array = v8_ctx->ConvertMapToArray(value); - int array_len = context->GetArrayLength(array); - for (int i = 0; i < array_len; i = i + 2) { + auto array_len = v8_ctx->GetArrayLength(array); + for (uint32_t i = 0; i < array_len; i = i + 2) { // key - std::shared_ptr key = context->CopyArrayElement(array, i); + std::shared_ptr key = v8_ctx->CopyArrayElement(array, i); unicode_string_view str_view; std::string key_str; - if (turbo_env.context_->GetValueString(key, &str_view)) { + if (ctx->GetValueString(key, &str_view)) { key_str = StringViewUtils::ToU8StdStr(str_view); } else { - throw std::runtime_error("Key must be String in Map."); + return std::make_tuple(false, "key must be string in map", static_cast(nullptr)); } - jobject key_j_obj = env->NewStringUTF(key_str.c_str()); - TDF_BASE_DLOG(INFO) << "key %s", key_str.c_str(); + jobject key_j_obj = j_env->NewStringUTF(key_str.c_str()); + TDF_BASE_DLOG(INFO) << "key " << key_str.c_str(); // value - std::shared_ptr item = context->CopyArrayElement(array, i + 1); - jobject value_j_obj = ToJObject(turbo_env, item); - env->CallVoidMethod(obj, hippy_map_push_object, key_j_obj, value_j_obj); - - env->DeleteLocalRef(key_j_obj); - env->DeleteLocalRef(value_j_obj); + std::shared_ptr item = v8_ctx->CopyArrayElement(array, i + 1); + auto to_jobject_tuple = ToJObject(ctx, item); + if (!std::get<0>(to_jobject_tuple)) { + j_env->DeleteLocalRef(key_j_obj); + return std::make_tuple(false, std::get<1>(to_jobject_tuple), static_cast(nullptr)); + } + jobject value_j_obj = std::get<2>(to_jobject_tuple); + j_env->CallVoidMethod(obj, hippy_map_push_object, key_j_obj, value_j_obj); + JNIEnvironment::ClearJEnvException(j_env); + j_env->DeleteLocalRef(key_j_obj); + j_env->DeleteLocalRef(value_j_obj); } - return obj; + return std::make_tuple(true, "", obj); } -jobject ConvertUtils::ToHippyArray(TurboEnv &turbo_env, - const std::shared_ptr &value) { - std::shared_ptr ctx = turbo_env.context_; - std::shared_ptr context = std::static_pointer_cast(ctx); - JNIEnv *env = JNIEnvironment::GetInstance()->AttachCurrentThread(); - jobject obj = env->NewObject(hippy_array_clazz, hippy_array_constructor); - int array_len = context->GetArrayLength(value); - for (int i = 0; i < array_len; i++) { - std::shared_ptr item = context->CopyArrayElement(value, i); - jobject j_obj = ToJObject(turbo_env, item); - env->CallVoidMethod(obj, hippy_array_push_object, j_obj); - env->DeleteLocalRef(j_obj); +std::tuple ConvertUtils::ToHippyArray(const std::shared_ptr& ctx, + const std::shared_ptr &value) { + std::shared_ptr v8_ctx = std::static_pointer_cast(ctx); + JNIEnv *j_env = JNIEnvironment::GetInstance()->AttachCurrentThread(); + jobject obj = j_env->NewObject(hippy_array_clazz, hippy_array_constructor); + auto array_len = v8_ctx->GetArrayLength(value); + for (uint32_t i = 0; i < array_len; i++) { + std::shared_ptr item = v8_ctx->CopyArrayElement(value, i); + auto to_jobject_tuple = ToJObject(ctx, item); + if (!std::get<0>(to_jobject_tuple)) { + return to_jobject_tuple; + } + jobject j_obj = std::get<2>(to_jobject_tuple); + j_env->CallVoidMethod(obj, hippy_array_push_object, j_obj); + JNIEnvironment::ClearJEnvException(j_env); + j_env->DeleteLocalRef(j_obj); } - return obj; + return std::make_tuple(true, "", obj); } -jobject ConvertUtils::ToJObject(TurboEnv &turbo_env, - const std::shared_ptr &value) { +std::tuple ConvertUtils::ToJObject(const std::shared_ptr& ctx, + const std::shared_ptr &value) { double num; bool b; std::string str; jobject result; - std::shared_ptr ctx = turbo_env.context_; - std::shared_ptr context = std::static_pointer_cast(ctx); + std::shared_ptr v8_ctx = std::static_pointer_cast(ctx); JNIEnv *env = JNIEnvironment::GetInstance()->AttachCurrentThread(); unicode_string_view str_view; - if (context->GetValueNumber(value, &num)) { + if (ctx->GetValueNumber(value, &num)) { result = env->NewObject(double_clazz, double_constructor, num); - } else if (context->GetValueString(value, &str_view)) { + } else if (ctx->GetValueString(value, &str_view)) { str = StringViewUtils::ToU8StdStr(str_view); result = env->NewStringUTF(str.c_str()); - } else if (context->GetValueBoolean(value, &b)) { + } else if (ctx->GetValueBoolean(value, &b)) { result = env->NewObject(boolean_clazz, boolean_constructor, b); - } else if (context->IsArray(value)) { - result = ToHippyArray(turbo_env, value); - } else if (context->IsMap(value)) { - result = ToHippyMap(turbo_env, value); - } else if (context->IsNullOrUndefined(value)) { + } else if (ctx->IsArray(value)) { + auto array_tuple = ToHippyArray(ctx, value); + if (!std::get<0>(array_tuple)) { + return array_tuple; + } + result = std::get<2>(array_tuple); + } else if (ctx->IsMap(value)) { + auto map_tuple = ToHippyMap(ctx, value); + if (!std::get<0>(map_tuple)) { + return map_tuple; + } + result = std::get<2>(map_tuple); + } else if (ctx->IsNullOrUndefined(value)) { result = nullptr; } else { - throw std::runtime_error("UnSupported Type in HippyArray or HippyMap."); + return std::make_tuple(false, "unsupported type in HippyArray or HippyMap", + static_cast(nullptr)); } - return result; + return std::make_tuple(true, "", result); } std::unordered_map ConvertUtils::GetMethodMap( @@ -367,8 +431,7 @@ std::unordered_map ConvertUtils::GetMethodMap( return method_map; } - TDF_BASE_DLOG(INFO) << "initMethodMap origin string %s", - method_map_str.c_str(); + TDF_BASE_DLOG(INFO) << "initMethodMap origin string" << method_map_str.c_str(); bool is_name = true; std::string method_name; @@ -395,8 +458,8 @@ std::unordered_map ConvertUtils::GetMethodMap( MethodInfo method_info; method_info.signature_ = method_sig; method_map[method_name] = method_info; - TDF_BASE_DLOG(INFO) << "initMethodMap %s=%s", method_name.c_str(), - method_sig.c_str(); + TDF_BASE_DLOG(INFO) << "initMethodMap " << method_name.c_str() << "=" << + method_sig.c_str(); method_name.clear(); method_sig.clear(); break; @@ -449,178 +512,213 @@ std::vector ConvertUtils::GetMethodArgTypesFromSignature( return method_args; } -void ConvertUtils::ThrowException(const std::shared_ptr &ctx, - const std::string &info) { - std::shared_ptr v8_ctx = std::static_pointer_cast(ctx); - v8::HandleScope handle_scope(v8_ctx->isolate_); - v8::Local context = - v8_ctx->context_persistent_.Get(v8_ctx->isolate_); - v8::Context::Scope context_scope(context); - - TDF_BASE_LOG(ERROR) << info.c_str(); - v8_ctx->isolate_->ThrowException( - v8::String::NewFromUtf8(v8_ctx->isolate_, info.c_str()).ToLocalChecked()); -} - -std::shared_ptr ConvertUtils::ToHostObject(TurboEnv &turbo_env, +std::shared_ptr ConvertUtils::ToHostObject(const std::shared_ptr& ctx, jobject &j_obj, - std::string name) { + std::string name, + std::shared_ptr scope) { if (!j_obj) { - return turbo_env.context_->CreateNull(); + return ctx->CreateNull(); } JNIEnv *env = JNIEnvironment::GetInstance()->AttachCurrentThread(); std::shared_ptr ret = std::make_shared(env, j_obj); - std::shared_ptr host_obj = - std::make_shared(name, ret); - return turbo_env.CreateObject(host_obj); + auto host_obj = std::make_shared(name, ret, ctx); + auto instance = ctx->NewInstance(host_obj->constructor, 0, nullptr, host_obj.get()); + scope->SetTurboInstance(name, instance); + scope->SetTurboHostObject(name, host_obj); + return instance; } -std::shared_ptr ConvertUtils::ConvertMethodResultToJSValue( - TurboEnv &turbo_env, - const jobject &obj, - const MethodInfo &method_info, - const jvalue *args) { - std::shared_ptr ctx = turbo_env.context_; - std::shared_ptr ret = ctx->CreateUndefined(); - JNIEnv *env = JNIEnvironment::GetInstance()->AttachCurrentThread(); +std::tuple> ConvertUtils::ConvertMethodResultToJSValue( + const std::shared_ptr& ctx, + const std::shared_ptr& obj, + const MethodInfo& method_info, + const jvalue* args, + const std::shared_ptr& scope) { + auto ret = ctx->CreateUndefined(); + JNIEnv *j_env = JNIEnvironment::GetInstance()->AttachCurrentThread(); std::string return_type = method_info.signature_.substr( method_info.signature_.find_last_of(')') + 1); - if (klong == return_type) { - jlong result = env->CallLongMethodA(obj, method_info.method_id_, args); - ret = ctx->CreateNumber(result); - } else if (kint == return_type) { - jint result = env->CallIntMethodA(obj, method_info.method_id_, args); + if (kLong == return_type) { + auto result = j_env->CallLongMethodA(obj->GetObj(), method_info.method_id_, args); + ret = ctx->CreateNumber(hippy::base::checked_numeric_cast(result)); + } else if (kInt == return_type) { + jint result = j_env->CallIntMethodA(obj->GetObj(), method_info.method_id_, args); ret = ctx->CreateNumber(result); - } else if (kfloat == return_type) { - jfloat result = env->CallFloatMethodA(obj, method_info.method_id_, args); + } else if (kFloat == return_type) { + jfloat result = j_env->CallFloatMethodA(obj->GetObj(), method_info.method_id_, args); ret = ctx->CreateNumber(result); - } else if (kdouble == return_type) { - jdouble result = env->CallDoubleMethodA(obj, method_info.method_id_, args); + } else if (kDouble == return_type) { + jdouble result = j_env->CallDoubleMethodA(obj->GetObj(), method_info.method_id_, args); ret = ctx->CreateNumber(result); } else if (kString == return_type) { auto result_str = - (jstring)env->CallObjectMethodA(obj, method_info.method_id_, args); + (jstring) j_env->CallObjectMethodA(obj->GetObj(), method_info.method_id_, args); + JNIEnvironment::ClearJEnvException(j_env); if (!result_str) { ret = ctx->CreateNull(); } else { - unicode_string_view str_view = JniUtils::ToStrView(env, result_str); - env->DeleteLocalRef(result_str); + unicode_string_view str_view = JniUtils::ToStrView(j_env, result_str); + j_env->DeleteLocalRef(result_str); ret = ctx->CreateString(str_view); } - } else if (kboolean == return_type) { + } else if (kBoolean == return_type) { auto result = - (jboolean)env->CallBooleanMethodA(obj, method_info.method_id_, args); + (jboolean) j_env->CallBooleanMethodA(obj->GetObj(), method_info.method_id_, args); + JNIEnvironment::ClearJEnvException(j_env); ret = ctx->CreateBoolean(result); - } else if (kvoid == return_type) { - env->CallVoidMethodA(obj, method_info.method_id_, args); + } else if (kVoid == return_type) { + j_env->CallVoidMethodA(obj->GetObj(), method_info.method_id_, args); + JNIEnvironment::ClearJEnvException(j_env); } else if (kHippyArray == return_type) { - auto array = env->CallObjectMethodA(obj, method_info.method_id_, args); - ret = ToJsArray(turbo_env, array); - env->DeleteLocalRef(array); + auto array = j_env->CallObjectMethodA(obj->GetObj(), method_info.method_id_, args); + JNIEnvironment::ClearJEnvException(j_env); + auto tuple = ToJsArray(ctx, array); + if (!std::get<0>(tuple)) { + return tuple; + } + ret = std::get<2>(tuple); + j_env->DeleteLocalRef(array); } else if (kHippyMap == return_type) { - auto map = env->CallObjectMethodA(obj, method_info.method_id_, args); - ret = ToJsMap(turbo_env, map); - env->DeleteLocalRef(map); + auto map = j_env->CallObjectMethodA(obj->GetObj(), method_info.method_id_, args); + JNIEnvironment::ClearJEnvException(j_env); + auto tuple = ToJsMap(ctx, map); + if (!std::get<0>(tuple)) { + return tuple; + } + ret = std::get<2>(tuple); + j_env->DeleteLocalRef(map); } else { - auto ret_obj = env->CallObjectMethodA(obj, method_info.method_id_, args); - ret = ToHostObject(turbo_env, ret_obj, method_info.signature_); - env->DeleteLocalRef(ret_obj); + auto ret_obj = j_env->CallObjectMethodA(obj->GetObj(), method_info.method_id_, args); + JNIEnvironment::ClearJEnvException(j_env); + ret = ToHostObject(ctx, ret_obj, method_info.signature_, scope); + j_env->DeleteLocalRef(ret_obj); } - return ret; + return std::make_tuple(true, "", ret); } -std::shared_ptr ConvertUtils::ToJsValueInArray(TurboEnv &turbo_env, - const jobject &array, - int index) { - std::shared_ptr ctx = turbo_env.context_; - JNIEnv *env = JNIEnvironment::GetInstance()->AttachCurrentThread(); +std::tuple> ConvertUtils::ToJsValueInArray(const std::shared_ptr& ctx, + jobject array, + int index) { + JNIEnv *j_env = JNIEnvironment::GetInstance()->AttachCurrentThread(); std::shared_ptr result = ctx->CreateNull(); - auto sig = (jstring)env->CallObjectMethod(array, hippy_array_get_sig, index); + auto sig = (jstring) j_env->CallObjectMethod(array, hippy_array_get_sig, index); if (!sig) { - return result; + return std::make_tuple(true, "", result); } - unicode_string_view str_view = JniUtils::ToStrView(env, sig); + unicode_string_view str_view = JniUtils::ToStrView(j_env, sig); std::string signature = StringViewUtils::ToU8StdStr(str_view); - env->DeleteLocalRef(sig); - TDF_BASE_DLOG(INFO) << "toJsValueInArray %s", signature.c_str(); + j_env->DeleteLocalRef(sig); + TDF_BASE_DLOG(INFO) << "toJsValueInArray " << signature.c_str(); if (kUnSupportedType == signature) { - std::string info = - std::string(kUnSupportedType).append(" when toJsValueInArray"); - throw std::runtime_error(info); + return std::make_tuple(false, "toJsValueInArray error", + static_cast>(nullptr)); } - auto obj = env->CallObjectMethod(array, hippy_array_get, index); + auto obj = j_env->CallObjectMethod(array, hippy_array_get, index); if (IsNumberObject(signature)) { - jdouble d = env->CallDoubleMethod(obj, double_value); + jdouble d = 0; + if (kIntegerObject == signature) { + d = j_env->CallIntMethod(reinterpret_cast(obj), int_value); + } else if (kDoubleObject == signature) { + d = j_env->CallDoubleMethod(reinterpret_cast(obj), double_value); + } else if (kFloatObject == signature) { + d = j_env->CallFloatMethod(reinterpret_cast(obj), float_value); + } else if (kLongObject == signature) { + d = static_cast(j_env->CallLongMethod(reinterpret_cast(obj), + long_value)); + } result = ctx->CreateNumber(d); } else if (kString == signature) { - unicode_string_view obj_str_view = JniUtils::ToStrView(env, (jstring)(obj)); + unicode_string_view obj_str_view = JniUtils::ToStrView(j_env, reinterpret_cast(obj)); result = ctx->CreateString(obj_str_view); - } else if (kBoolean == signature) { - jboolean b = env->CallBooleanMethod(obj, boolean_value); + } else if (kBooleanObject == signature) { + jboolean b = j_env->CallBooleanMethod(reinterpret_cast(obj), boolean_value); result = ctx->CreateBoolean(b); } else if (kHippyArray == signature) { - result = ToJsArray(turbo_env, obj); + auto tuple = ToJsArray(ctx, obj); + if (!std::get<0>(tuple)) { + return tuple; + } + result = std::get<2>(tuple); } else if (kHippyMap == signature) { - result = ToJsMap(turbo_env, obj); + auto tuple = ToJsMap(ctx, obj); + if (!std::get<0>(tuple)) { + return tuple; + } + result = std::get<2>(tuple); } else if (!obj) { - result = turbo_env.context_->CreateNull(); + result = ctx->CreateNull(); } else { - throw std::runtime_error("UnSupported Type in HippyArray or HippyMap"); + j_env->DeleteLocalRef(obj); + return std::make_tuple(false, "UnSupported Type in HippyArray or HippyMap", + static_cast>(nullptr)); } - env->DeleteLocalRef(obj); - return result; + j_env->DeleteLocalRef(obj); + return std::make_tuple(true, "", result); } -std::shared_ptr ConvertUtils::ToJsArray(TurboEnv &turbo_env, - const jobject &array) { - std::shared_ptr ctx = turbo_env.context_; +std::tuple> +ConvertUtils::ToJsArray(const std::shared_ptr& ctx, jobject array) { if (!array) { - return ctx->CreateNull(); + return std::make_tuple(true, "", ctx->CreateNull()); } std::shared_ptr v8_ctx = std::static_pointer_cast(ctx); JNIEnv *env = JNIEnvironment::GetInstance()->AttachCurrentThread(); - int size = env->CallIntMethod(array, hippy_array_size); + auto size = env->CallIntMethod(array, hippy_array_size); if (size <= 0) { - return ctx->CreateNull(); + return std::make_tuple(true, "", ctx->CreateNull()); } std::shared_ptr value[size]; for (int i = 0; i < size; i++) { - value[i] = ToJsValueInArray(turbo_env, array, i); + auto value_tuple = ToJsValueInArray(ctx, array, i); + if (!std::get<0>(value_tuple)) { + return value_tuple; + } + value[i] = std::get<2>(value_tuple); } - return ctx->CreateArray(size, value); + return std::make_tuple(true, "", ctx->CreateArray(static_cast(size), value)); } -std::shared_ptr ConvertUtils::ToJsMap(TurboEnv &turbo_env, - const jobject &map) { - std::shared_ptr ctx = turbo_env.context_; +std::tuple> ConvertUtils::ToJsMap(const std::shared_ptr& ctx, + jobject map) { if (!map) { - return ctx->CreateNull(); + return std::make_tuple(true, "", ctx->CreateNull()); } JNIEnv *env = JNIEnvironment::GetInstance()->AttachCurrentThread(); jobject array = env->CallObjectMethod(map, to_hippy_array); if (!array) { - return ctx->CreateNull(); + return std::make_tuple(true, "", ctx->CreateNull()); } - int size = env->CallIntMethod(array, hippy_array_size); + auto size = env->CallIntMethod(array, hippy_array_size); if (size <= 0) { - return ctx->CreateNull(); + return std::make_tuple(true, "", ctx->CreateNull()); } std::shared_ptr v8_ctx = std::static_pointer_cast(ctx); std::shared_ptr value[size]; - for (int i = 0; i < size; i++) { - value[i] = ToJsValueInArray(turbo_env, array, i); + for (auto i = 0; i < size; i++) { + auto value_tuple = ToJsValueInArray(ctx, array, i); + if (!std::get<0>(value_tuple)) { + return value_tuple; + } + value[i] = std::get<2>(value_tuple); + } + std::map, std::shared_ptr> param; + for (auto i = 0; i < size; i += 2) { + param[value[i]] = value[i + 1]; } - return v8_ctx->CreateMap(size, value); + env->DeleteLocalRef(array); + return std::make_tuple(true, "", v8_ctx->CreateMap(param)); } bool ConvertUtils::Init() { @@ -629,7 +727,7 @@ bool ConvertUtils::Init() { JNIEnv *env = JNIEnvironment::GetInstance()->AttachCurrentThread(); jclass hippy_array_clazz_local = env->FindClass("com/tencent/mtt/hippy/common/HippyArray"); - hippy_array_clazz = (jclass)env->NewGlobalRef(hippy_array_clazz_local); + hippy_array_clazz = (jclass) env->NewGlobalRef(hippy_array_clazz_local); hippy_array_constructor = env->GetMethodID(hippy_array_clazz, "", "()V"); hippy_array_push_object = env->GetMethodID(hippy_array_clazz, "pushObject", @@ -643,7 +741,7 @@ bool ConvertUtils::Init() { jclass hippy_map_clazz_local = env->FindClass("com/tencent/mtt/hippy/common/HippyMap"); - hippy_map_clazz = (jclass)env->NewGlobalRef(hippy_map_clazz_local); + hippy_map_clazz = (jclass) env->NewGlobalRef(hippy_map_clazz_local); hippy_map_constructor = env->GetMethodID(hippy_map_clazz, "", "()V"); hippy_map_push_object = env->GetMethodID( hippy_map_clazz, "pushObject", "(Ljava/lang/String;Ljava/lang/Object;)V"); @@ -653,35 +751,38 @@ bool ConvertUtils::Init() { env->DeleteLocalRef(hippy_map_clazz_local); jclass integer_clazz_local = env->FindClass("java/lang/Integer"); - integer_clazz = (jclass)env->NewGlobalRef(integer_clazz_local); + integer_clazz = (jclass) env->NewGlobalRef(integer_clazz_local); integer_constructor = env->GetMethodID(integer_clazz, "", "(I)V"); + int_value = env->GetMethodID(integer_clazz, "intValue", "()I"); env->DeleteLocalRef(integer_clazz_local); jclass double_clazz_local = env->FindClass("java/lang/Double"); - double_clazz = (jclass)env->NewGlobalRef(double_clazz_local); + double_clazz = (jclass) env->NewGlobalRef(double_clazz_local); double_constructor = env->GetMethodID(double_clazz, "", "(D)V"); double_value = env->GetMethodID(double_clazz, "doubleValue", "()D"); env->DeleteLocalRef(double_clazz_local); jclass float_clazz_local = env->FindClass("java/lang/Float"); - float_clazz = (jclass)env->NewGlobalRef(float_clazz_local); + float_clazz = (jclass) env->NewGlobalRef(float_clazz_local); float_constructor = env->GetMethodID(float_clazz, "", "(F)V"); + float_value = env->GetMethodID(float_clazz, "floatValue", "()F"); env->DeleteLocalRef(float_clazz_local); jclass long_clazz_local = env->FindClass("java/lang/Long"); - long_clazz = (jclass)env->NewGlobalRef(long_clazz_local); + long_clazz = (jclass) env->NewGlobalRef(long_clazz_local); long_constructor = env->GetMethodID(long_clazz, "", "(J)V"); + long_value = env->GetMethodID(long_clazz, "longValue", "()J"); env->DeleteLocalRef(long_clazz_local); jclass boolean_clazz_local = env->FindClass("java/lang/Boolean"); - boolean_clazz = (jclass)(env->NewGlobalRef(boolean_clazz_local)); + boolean_clazz = (jclass) (env->NewGlobalRef(boolean_clazz_local)); boolean_constructor = env->GetMethodID(boolean_clazz, "", "(Z)V"); boolean_value = env->GetMethodID(boolean_clazz, "booleanValue", "()Z"); env->DeleteLocalRef(boolean_clazz_local); jclass promise_clazz_local = env->FindClass("com/tencent/mtt/hippy/modules/PromiseImpl"); - promise_clazz = (jclass)(env->NewGlobalRef(promise_clazz_local)); + promise_clazz = (jclass) (env->NewGlobalRef(promise_clazz_local)); promise_constructor = env->GetMethodID(promise_clazz, "", "(Lcom/tencent/mtt/hippy/HippyEngineContext;Ljava/lang/" @@ -690,7 +791,7 @@ bool ConvertUtils::Init() { return true; } -bool ConvertUtils::Destory() { +bool ConvertUtils::Destroy() { TDF_BASE_DLOG(INFO) << "enter destroy"; hippy_array_constructor = nullptr; hippy_array_push_object = nullptr; @@ -702,10 +803,13 @@ bool ConvertUtils::Destory() { to_hippy_array = nullptr; integer_constructor = nullptr; + int_value = nullptr; double_constructor = nullptr; double_value = nullptr; float_constructor = nullptr; + float_value = nullptr; long_constructor = nullptr; + long_value = nullptr; boolean_constructor = nullptr; boolean_value = nullptr; diff --git a/android/sdk/src/main/jni/src/jni/exception_handler.cc b/android/sdk/src/main/jni/src/jni/exception_handler.cc index 788c2fe9a4e..3b7513b8378 100644 --- a/android/sdk/src/main/jni/src/jni/exception_handler.cc +++ b/android/sdk/src/main/jni/src/jni/exception_handler.cc @@ -22,14 +22,14 @@ #include "jni/exception_handler.h" -#include "core/base/string_view_utils.h" +#include "bridge/adr_bridge.h" #include "core/core.h" #include "jni/jni_env.h" #include "jni/jni_utils.h" using StringViewUtils = hippy::base::StringViewUtils; -void ExceptionHandler::ReportJsException(std::shared_ptr runtime, +void ExceptionHandler::ReportJsException(const std::shared_ptr& runtime, const unicode_string_view& desc, const unicode_string_view& stack) { TDF_BASE_DLOG(INFO) << "ReportJsException begin"; @@ -39,11 +39,13 @@ void ExceptionHandler::ReportJsException(std::shared_ptr runtime, jstring j_stack_trace = JniUtils::StrViewToJString(j_env, stack); if (runtime->GetBridge()) { - j_env->CallVoidMethod(runtime->GetBridge()->GetObj(), + auto bridge = std::static_pointer_cast(runtime->GetBridge()); + j_env->CallVoidMethod(bridge->GetObj(), JNIEnvironment::GetInstance() ->GetMethods() .j_report_exception_method_id, j_exception, j_stack_trace); + JNIEnvironment::ClearJEnvException(j_env); } j_env->DeleteLocalRef(j_exception); diff --git a/android/sdk/src/main/jni/src/jni/java_turbo_module.cc b/android/sdk/src/main/jni/src/jni/java_turbo_module.cc index 525ffe80aa9..34cc96b5193 100644 --- a/android/sdk/src/main/jni/src/jni/java_turbo_module.cc +++ b/android/sdk/src/main/jni/src/jni/java_turbo_module.cc @@ -22,11 +22,8 @@ #include "jni/java_turbo_module.h" -#include "core/base/string_view_utils.h" -#include "core/napi/js_native_api_types.h" -#include "core/napi/v8/js_native_turbo_v8.h" -#include "hippy.h" -#include "jni/convert_utils.h" +#include "jni/jni_env.h" +#include "jni/jni_utils.h" using namespace hippy::napi; using unicode_string_view = tdf::base::unicode_string_view; @@ -35,181 +32,152 @@ using StringViewUtils = hippy::base::StringViewUtils; static jclass argument_utils_clazz; static jmethodID get_methods_signature; -std::shared_ptr JavaTurboModule::InvokeJavaMethod( - TurboEnv &turbo_env, - const std::shared_ptr &prop_name, - const std::shared_ptr &this_val, - const std::shared_ptr *args, - size_t count) { +std::shared_ptr JavaTurboModule::InvokeJavaMethod(const std::shared_ptr& prop_name, + const CallbackInfo& info, + void* data) { TDF_BASE_DLOG(INFO) << "[turbo-perf] enter invokeJavaMethod"; - std::shared_ptr ctx = turbo_env.context_; - std::shared_ptr v8_ctx = std::static_pointer_cast(ctx); - v8::HandleScope handle_scope(v8_ctx->isolate_); - v8::Local context = - v8_ctx->context_persistent_.Get(v8_ctx->isolate_); - v8::Context::Scope context_scope(context); - + auto scope_wrapper = reinterpret_cast(std::any_cast(info.GetSlot())); + auto scope = scope_wrapper->scope.lock(); + TDF_BASE_CHECK(scope); + auto context = scope->GetContext(); // methodName & signature unicode_string_view str_view; std::string method; - if (v8_ctx->GetValueString(prop_name, &str_view)) { + if (context->GetValueString(prop_name, &str_view)) { method = StringViewUtils::ToU8StdStr(str_view); } - MethodInfo method_info = method_map_[method]; + auto method_info = method_map_[method]; if (method_info.signature_.empty()) { - std::string exception_info = std::string("MethodUnsupportedException: ") - .append(name_) - .append(".") - .append(method); - ConvertUtils::ThrowException(ctx, exception_info); - return ctx->CreateUndefined(); + std::string exception_info = "MethodUnsupportedException: " + name + "." + method; + context->ThrowException(unicode_string_view(exception_info)); + return context->CreateUndefined(); } - TDF_BASE_DLOG(INFO) << "invokeJavaMethod, method= %s", method.c_str(); + TDF_BASE_DLOG(INFO) << "invokeJavaMethod, method = " << method.c_str(); // arguments count - std::vector> arg_values; - arg_values.reserve(count); - for (int i = 0; i < count; i++) { - arg_values.push_back(args[i]); + std::vector> argv; + for (size_t i = 0; i < info.Length(); ++i) { + argv.push_back(info[i]); } - std::string call_info = std::string(name_).append(".").append(method); - std::vector method_arg_types = - ConvertUtils::GetMethodArgTypesFromSignature(method_info.signature_); - int expected_count = method_arg_types.size(); - int actual_count = arg_values.size(); - if (expected_count != actual_count) { - std::string exception_info = std::string("ArgCountException: ") - .append(call_info) - .append(": ExpectedArgCount=") - .append(ToString(expected_count)) - .append(", ActualArgCount = ") - .append(ToString(actual_count)); - ConvertUtils::ThrowException(ctx, exception_info); - return ctx->CreateUndefined(); + std::string call_info = name + "." + method; + std::vector method_arg_types = ConvertUtils::GetMethodArgTypesFromSignature(method_info.signature_); + auto expected_count = method_arg_types.size(); + if (expected_count != info.Length()) { + std::string exception = "ArgCountException: " + call_info + ": ExpectedArgCount = " + std::to_string(expected_count) + + ", ActualArgCount = " + std::to_string(info.Length()); + context->ThrowException(unicode_string_view(exception)); + return context->CreateUndefined(); } // methodId JNIEnv *env = JNIEnvironment::GetInstance()->AttachCurrentThread(); if (!method_info.method_id_) { - method_info.method_id_ = env->GetMethodID(impl_j_clazz_, method.c_str(), + method_info.method_id_ = env->GetMethodID((jclass)(impl_j_clazz_->GetObj()), + method.c_str(), method_info.signature_.c_str()); if (!method_info.method_id_) { JNIEnvironment::ClearJEnvException(env); - - std::string exception_info = std::string("NullMethodIdException: ") - .append(call_info) - .append(": Signature=") - .append(method_info.signature_); - ConvertUtils::ThrowException(ctx, exception_info); - return ctx->CreateUndefined(); + std::string exception = "NullMethodIdException: " + call_info + ": Signature = " + method_info.signature_; + context->ThrowException(unicode_string_view(exception)); + return context->CreateUndefined(); } method_map_[method] = method_info; } - std::shared_ptr jni_args = nullptr; - std::shared_ptr ret = ctx->CreateUndefined(); - - try { - // args convert - TDF_BASE_DLOG(INFO) << "[turbo-perf] enter convertJSIArgsToJNIArgs"; - jni_args = ConvertUtils::ConvertJSIArgsToJNIArgs( - turbo_env, name_, method, method_arg_types, arg_values); - TDF_BASE_DLOG(INFO) << "[turbo-perf] exit convertJSIArgsToJNIArgs"; - - TDF_BASE_DLOG(INFO) << "[turbo-perf] enter convertMethodResultToJSValue"; - - // call method - ret = ConvertUtils::ConvertMethodResultToJSValue( - turbo_env, impl_->GetObj(), method_info, jni_args->args_.data()); - TDF_BASE_DLOG(INFO) << "[turbo-perf] exit convertMethodResultToJSValue"; - - } catch (std::runtime_error &e) { - std::string exception_info = - std::string(call_info).append(" ").append(e.what()); - ConvertUtils::ThrowException(ctx, exception_info); - ret = ctx->CreateUndefined(); + std::shared_ptr jni_args; + // args convert + TDF_BASE_DLOG(INFO) << "[turbo-perf] enter convertJSIArgsToJNIArgs"; + auto jni_tuple = ConvertUtils::ConvertJSIArgsToJNIArgs( + context, name, method, method_arg_types, argv); + TDF_BASE_DLOG(INFO) << "[turbo-perf] exit convertJSIArgsToJNIArgs"; + if (!std::get<0>(jni_tuple)) { + context->ThrowException(unicode_string_view(std::get<1>(jni_tuple))); + return context->CreateUndefined(); + } + jni_args = std::get<2>(jni_tuple); + TDF_BASE_DLOG(INFO) << "[turbo-perf] enter convertMethodResultToJSValue"; + + // call method + auto js_tuple = ConvertUtils::ConvertMethodResultToJSValue( + context, impl_, method_info, jni_args->args_.data(), scope); + TDF_BASE_DLOG(INFO) << "[turbo-perf] exit convertMethodResultToJSValue"; + if (!std::get<0>(js_tuple)) { + context->ThrowException(unicode_string_view(std::get<1>(js_tuple))); + return context->CreateUndefined(); } - DeleteGlobalRef(jni_args); TDF_BASE_DLOG(INFO) << "[turbo-perf] exit invokeJavaMethod"; if (JNIEnvironment::ClearJEnvException( - JNIEnvironment::GetInstance()->AttachCurrentThread())) { + JNIEnvironment::GetInstance()->AttachCurrentThread())) { TDF_BASE_LOG(ERROR) << "ClearJEnvException when %s", call_info.c_str(); - return ctx->CreateUndefined(); + return context->CreateUndefined(); } - return ret; -} - -void JavaTurboModule::DeleteGlobalRef(const std::shared_ptr &jni_args) { - TDF_BASE_DLOG(INFO) << "enter deleteGlobalRef"; - JNIEnv *env = JNIEnvironment::GetInstance()->AttachCurrentThread(); - if (!jni_args || jni_args->global_refs_.empty()) { - return; - } - - TDF_BASE_DLOG(INFO) << "deleteGlobalRef size %d", - jni_args->global_refs_.size(); - for (auto global_ref : jni_args->global_refs_) { - if (global_ref) { - env->DeleteGlobalRef(global_ref); - } - } + return std::get<2>(js_tuple); } void JavaTurboModule::InitPropertyMap() { - JNIEnv *env = JNIEnvironment::GetInstance()->AttachCurrentThread(); - jclass obj_clazz = env->GetObjectClass(impl_->GetObj()); - impl_j_clazz_ = (jclass)env->NewGlobalRef(obj_clazz); - auto methods_sig = (jstring)env->CallStaticObjectMethod( + JNIEnv *j_env = JNIEnvironment::GetInstance()->AttachCurrentThread(); + jclass obj_clazz = j_env->GetObjectClass(impl_->GetObj()); + impl_j_clazz_ = std::make_shared(j_env, j_env->NewGlobalRef(obj_clazz)); + auto methods_sig = (jstring) j_env->CallStaticObjectMethod( argument_utils_clazz, get_methods_signature, impl_->GetObj()); if (methods_sig) { - unicode_string_view str_view = JniUtils::ToStrView(env, methods_sig); + unicode_string_view str_view = JniUtils::ToStrView(j_env, methods_sig); std::string method_map_str = StringViewUtils::ToU8StdStr(str_view); method_map_ = ConvertUtils::GetMethodMap(method_map_str); - env->DeleteLocalRef(methods_sig); + j_env->DeleteLocalRef(methods_sig); } - env->DeleteLocalRef(obj_clazz); + j_env->DeleteLocalRef(obj_clazz); } -JavaTurboModule::JavaTurboModule(const std::string &name, - std::shared_ptr &impl) - : HippyTurboModule(name), impl_(impl) { +JavaTurboModule::JavaTurboModule(const std::string& name, + std::shared_ptr& impl, + const std::shared_ptr& ctx) + : impl_(impl), impl_j_clazz_(nullptr), name(name) { InitPropertyMap(); -} - -JavaTurboModule::~JavaTurboModule() { - TDF_BASE_DLOG(INFO) << "~JavaTurboModule %s", name_.c_str(); - - if (impl_) { - impl_.reset(); - } - - if (impl_j_clazz_) { - JNIEnvironment::GetInstance()->AttachCurrentThread()->DeleteGlobalRef( - impl_j_clazz_); - } - - if (!method_map_.empty()) { - method_map_.clear(); - } -} - -std::shared_ptr JavaTurboModule::Get( - TurboEnv &turbo_env, - const std::shared_ptr &prop_name) { - return turbo_env.CreateFunction( - prop_name, 0, - [=](TurboEnv &env, const std::shared_ptr &thisVal, - const std::shared_ptr *args, size_t count) { - return InvokeJavaMethod(env, prop_name, thisVal, args, count); - }); + auto getter = std::make_unique([](const CallbackInfo& info, void* data) { + auto scope_wrapper = reinterpret_cast(std::any_cast(info.GetSlot())); + auto scope = scope_wrapper->scope.lock(); + TDF_BASE_CHECK(scope); + auto ctx = scope->GetContext(); + auto module = reinterpret_cast(data); + auto name = info[0]; + if (!name) { + return; + } + auto func_object = module->func_map[name]; + if (func_object) { + info.GetReturnValue()->Set(func_object); + return; + } + auto turbo_wrapper = std::make_unique(module, name); + auto func_wrapper = std::make_unique([](const CallbackInfo& info, void* data) { + auto scope_wrapper = reinterpret_cast(std::any_cast(info.GetSlot())); + auto scope = scope_wrapper->scope.lock(); + TDF_BASE_CHECK(scope); + auto ctx = scope->GetContext(); + auto v8_ctx = std::static_pointer_cast(ctx); + TDF_BASE_CHECK(v8_ctx->HasFuncExternalData(data)); + auto wrapper = reinterpret_cast(v8_ctx->GetFuncExternalData(data)); + TDF_BASE_CHECK(wrapper && wrapper->module && wrapper->name); + auto result = wrapper->module->InvokeJavaMethod(wrapper->name, info, data); + info.GetReturnValue()->Set(result); + }, turbo_wrapper.get()); + func_object = ctx->CreateFunction(func_wrapper); + turbo_wrapper->SetFunctionWrapper(std::move(func_wrapper)); + module->turbo_wrapper_map[name] = std::move(turbo_wrapper); + module->func_map[name] = func_object; + info.GetReturnValue()->Set(func_object); + }, this); + constructor = ctx->DefineProxy(getter); + constructor_wrapper = std::move(getter); } void JavaTurboModule::Init() { @@ -217,14 +185,14 @@ void JavaTurboModule::Init() { jclass argument_utils_clazz_local = env->FindClass("com/tencent/mtt/hippy/utils/ArgumentUtils"); argument_utils_clazz = - (jclass)(env->NewGlobalRef(argument_utils_clazz_local)); + (jclass) (env->NewGlobalRef(argument_utils_clazz_local)); get_methods_signature = env->GetStaticMethodID(argument_utils_clazz, "getMethodsSignature", "(Ljava/lang/Object;)Ljava/lang/String;"); env->DeleteLocalRef(argument_utils_clazz_local); } -void JavaTurboModule::Destory() { +void JavaTurboModule::Destroy() { JNIEnv *env = JNIEnvironment::GetInstance()->AttachCurrentThread(); if (argument_utils_clazz) { diff --git a/android/sdk/src/main/jni/src/jni/jni_env.cc b/android/sdk/src/main/jni/src/jni/jni_env.cc index e4c21a9ea49..090f38b3e44 100644 --- a/android/sdk/src/main/jni/src/jni/jni_env.cc +++ b/android/sdk/src/main/jni/src/jni/jni_env.cc @@ -29,6 +29,17 @@ std::shared_ptr JNIEnvironment::instance_ = nullptr; std::mutex JNIEnvironment::mutex_; +struct JNIEnvAutoRelease { + JavaVM* j_vm; + + explicit JNIEnvAutoRelease(JavaVM* j_vm): j_vm(j_vm) {} + ~JNIEnvAutoRelease() { + if (j_vm) { + j_vm->DetachCurrentThread(); + } + } +}; + void JNIEnvironment::init(JavaVM* j_vm, JNIEnv* j_env) { j_vm_ = j_vm; @@ -105,15 +116,8 @@ JNIEnv* JNIEnvironment::AttachCurrentThread() { ret = j_vm_->AttachCurrentThread(&j_env, &args); TDF_BASE_DCHECK(JNI_OK == ret); + thread_local JNIEnvAutoRelease env_auto_release(j_vm_); } return j_env; } - -void JNIEnvironment::DetachCurrentThread() { - TDF_BASE_CHECK(j_vm_); - - if (j_vm_) { - j_vm_->DetachCurrentThread(); - } -} diff --git a/android/sdk/src/main/jni/src/jni/jni_register.cc b/android/sdk/src/main/jni/src/jni/jni_register.cc index 62e42bae965..667c9a62b22 100644 --- a/android/sdk/src/main/jni/src/jni/jni_register.cc +++ b/android/sdk/src/main/jni/src/jni/jni_register.cc @@ -23,7 +23,7 @@ #include "jni/jni_register.h" #include "base/unicode_string_view.h" -#include "core/base/string_view_utils.h" +#include "core/core.h" #include "jni/jni_env.h" #include "jni/uri.h" @@ -43,10 +43,10 @@ bool JNIRegister::RegisterMethods(JNIEnv* j_env) { const std::unordered_map>& jni_modules = JNIRegister::GetInstance()->GetJniModules(); - for (auto it = jni_modules.begin(); it != jni_modules.end(); ++it) { + for (const auto & jni_module : jni_modules) { std::vector methods; jclass j_class; - const char* class_name = it->first.c_str(); + const char* class_name = jni_module.first.c_str(); j_class = j_env->FindClass(class_name); if (!j_class) { TDF_BASE_DLOG(ERROR) @@ -55,11 +55,11 @@ bool JNIRegister::RegisterMethods(JNIEnv* j_env) { << "not found"; return false; } - std::vector datas = it->second; - for (auto data_it = datas.begin(); data_it != datas.end(); ++data_it) { - JNINativeMethod method = data_it->ToJNINativeMethod(); + std::vector jni_register_data = jni_module.second; + for (auto & data : jni_register_data) { + JNINativeMethod method = data.ToJNINativeMethod(); jmethodID id; - bool is_static = data_it->IsStaticMethod(); + bool is_static = data.IsStaticMethod(); if (is_static) { id = j_env->GetStaticMethodID(j_class, method.name, method.signature); } else { @@ -80,7 +80,9 @@ bool JNIRegister::RegisterMethods(JNIEnv* j_env) { methods.push_back(method); } - j_env->RegisterNatives(j_class, methods.data(), methods.size()); + j_env->RegisterNatives(j_class, + methods.data(), + hippy::base::checked_numeric_cast(methods.size())); } return true; } diff --git a/android/sdk/src/main/jni/src/jni/jni_utils.cc b/android/sdk/src/main/jni/src/jni/jni_utils.cc index b28b7a5f2c6..518eb2cfb93 100644 --- a/android/sdk/src/main/jni/src/jni/jni_utils.cc +++ b/android/sdk/src/main/jni/src/jni/jni_utils.cc @@ -23,10 +23,9 @@ #include "jni/jni_utils.h" // NOLINT(build/include_subdir) #include -#include -#include +#include +#include -#include "core/base/string_view_utils.h" #include "core/core.h" using unicode_string_view = tdf::base::unicode_string_view; @@ -57,7 +56,7 @@ JniUtils::bytes JniUtils::AppendJavaByteArrayToBytes(JNIEnv* j_env, } bytes ret; - ret.resize(j_length); + ret.resize(hippy::base::checked_numeric_cast(j_length)); j_env->GetByteArrayRegion(j_byte_array, j_offset, j_len, reinterpret_cast(&ret[0])); return ret; @@ -82,11 +81,11 @@ unicode_string_view JniUtils::JByteArrayToStrView(JNIEnv* j_env, } std::string ret; - ret.resize(j_len); + ret.resize(hippy::base::checked_numeric_cast(j_len)); j_env->GetByteArrayRegion(j_byte_array, j_offset, j_len, reinterpret_cast(&ret[0])); - const char16_t* ptr = reinterpret_cast(ret.c_str()); + const auto* ptr = reinterpret_cast(ret.c_str()); return unicode_string_view(ptr, ret.length() / sizeof(char16_t)); } @@ -96,7 +95,7 @@ jstring JniUtils::StrViewToJString(JNIEnv* j_env, StringViewUtils::Convert(str_view, unicode_string_view::Encoding::Utf16) .utf16_value(); return j_env->NewString(reinterpret_cast(str.c_str()), - str.length()); + hippy::base::checked_numeric_cast(str.length())); } unicode_string_view::u8string JniUtils::ToU8String(JNIEnv* j_env, @@ -104,9 +103,10 @@ unicode_string_view::u8string JniUtils::ToU8String(JNIEnv* j_env, TDF_BASE_DCHECK(j_str); const char* c_str = j_env->GetStringUTFChars(j_str, nullptr); - const int32_t len = j_env->GetStringLength(j_str); + auto len = j_env->GetStringLength(j_str); unicode_string_view::u8string ret( - reinterpret_cast(c_str), len); + reinterpret_cast(c_str), + hippy::base::checked_numeric_cast(len)); j_env->ReleaseStringUTFChars(j_str, c_str); return ret; } @@ -115,13 +115,9 @@ unicode_string_view JniUtils::ToStrView(JNIEnv* j_env, jstring j_str) { TDF_BASE_DCHECK(j_str); const jchar* j_char = j_env->GetStringChars(j_str, nullptr); - int32_t len = j_env->GetStringLength(j_str); - unicode_string_view ret(reinterpret_cast(j_char), len); + auto len = j_env->GetStringLength(j_str); + unicode_string_view ret(reinterpret_cast(j_char), + hippy::base::checked_numeric_cast(len)); j_env->ReleaseStringChars(j_str, j_char); return ret; } - -void JniUtils::printCurrentThreadID() { -#define LOG_DEBUG(FORMAT, ...) \ - __android_log_print(ANDROID_LOG_DEBUG, "Debug", FORMAT, ##__VA_ARGS__); -} diff --git a/android/sdk/src/main/jni/src/jni/turbo_module_manager.cc b/android/sdk/src/main/jni/src/jni/turbo_module_manager.cc index e1697aff356..6eec7121276 100644 --- a/android/sdk/src/main/jni/src/jni/turbo_module_manager.cc +++ b/android/sdk/src/main/jni/src/jni/turbo_module_manager.cc @@ -22,31 +22,26 @@ #include "jni/turbo_module_manager.h" -#include - #include #include "bridge/runtime.h" -#include "core/core.h" -#include "core/napi/v8/js_native_api_v8.h" +#include "core/vm/v8/snapshot_collector.h" #include "jni/java_turbo_module.h" +#include "jni/jni_env.h" +#include "jni/jni_register.h" #include "jni/jni_utils.h" -#include "jni/scoped_java_ref.h" -REGISTER_JNI("com/tencent/mtt/hippy/bridge/jsi/TurboModuleManager", +REGISTER_JNI("com/tencent/mtt/hippy/bridge/jsi/TurboModuleManager", // NOLINT(cert-err58-cpp) "install", "(J)I", Install) -REGISTER_JNI("com/tencent/mtt/hippy/bridge/jsi/TurboModuleManager", - "uninstall", - "(J)V", - Uninstall) - using namespace hippy::napi; using unicode_string_view = tdf::base::unicode_string_view; using StringViewUtils = hippy::base::StringViewUtils; +constexpr char kTurboKey[] = "getTurboModule"; + jclass turbo_module_manager_clazz; jmethodID get_method_id; @@ -55,119 +50,84 @@ jmethodID get_method_id; */ std::shared_ptr QueryTurboModuleImpl(std::shared_ptr &runtime, const std::string &module_name) { - TDF_BASE_DLOG(INFO) << "enter QueryTurboModuleImpl %s", module_name.c_str(); + TDF_BASE_DLOG(INFO) << "enter QueryTurboModuleImpl " << module_name.c_str(); JNIEnv *env = JNIEnvironment::GetInstance()->AttachCurrentThread(); jstring name = env->NewStringUTF(module_name.c_str()); - jobject module_impl = env->CallObjectMethod( - runtime->GetTurboModuleRuntime()->turbo_module_manager_obj_, - get_method_id, name); + jobject module_impl = env->CallObjectMethod(runtime->GetTurboManager()->GetObj(),get_method_id, name); auto result = std::make_shared(env, module_impl); env->DeleteLocalRef(name); env->DeleteLocalRef(module_impl); return result; } -void GetTurboModule(const v8::FunctionCallbackInfo &info) { +void GetTurboModule(const CallbackInfo& info, void* data) { TDF_BASE_DLOG(INFO) << "[turbo-perf] enter getTurboModule"; - auto data = info.Data().As(); - int64_t runtime_key = *(reinterpret_cast(data->Value())); - - std::shared_ptr runtime = Runtime::Find(runtime_key); - std::shared_ptr ctx = - std::static_pointer_cast(runtime->GetScope()->GetContext()); - std::shared_ptr v8_ctx = std::static_pointer_cast(ctx); + auto scope_wrapper = reinterpret_cast(std::any_cast(info.GetSlot())); + auto scope = scope_wrapper->scope.lock(); + TDF_BASE_CHECK(scope); + auto v8_ctx = std::static_pointer_cast(scope->GetContext()); + TDF_BASE_CHECK(v8_ctx->HasFuncExternalData(data)); + auto runtime_id = static_cast(reinterpret_cast(v8_ctx->GetFuncExternalData(data))); + auto runtime = Runtime::Find(runtime_id); + if (!runtime) { + return; + } + auto ctx = scope->GetContext(); v8::HandleScope handle_scope(v8_ctx->isolate_); - v8::Local context = - v8_ctx->context_persistent_.Get(v8_ctx->isolate_); + auto context = v8_ctx->context_persistent_.Get(v8_ctx->isolate_); v8::Context::Scope context_scope(context); - if (info.Length() == 1 && !info[0].IsEmpty() && info[0]->IsString()) { - // 1. moduleName - v8::String::Utf8Value module_name(info.GetIsolate(), info[0]); - std::string name = module_name.operator*(); + if (!info[0] || !ctx->IsString(info[0])) { + TDF_BASE_LOG(ERROR) << "cannot find TurboModule as param is invalid"; + info.GetReturnValue()->SetUndefined(); + } + + unicode_string_view name; + ctx->GetValueString(info[0], &name); + auto turbo_manager = runtime->GetTurboManager(); + if (!turbo_manager) { + TDF_BASE_LOG(ERROR) << "turbo_manager error"; + info.GetReturnValue()->SetUndefined(); + return; + } - std::shared_ptr turbo_module_runtime = - runtime->GetTurboModuleRuntime(); - if (!turbo_module_runtime) { - TDF_BASE_LOG(ERROR) << "getTurboModule but turboModuleRuntime is null"; - info.GetReturnValue().SetUndefined(); - return; + auto u8_name = StringViewUtils::ToU8StdStr(name); + std::shared_ptr result; + auto has_instance = scope->HasTurboInstance(u8_name); + if (!has_instance) { + // 2. if not cached, query from Java + auto module_impl = QueryTurboModuleImpl(runtime, u8_name); + if (!module_impl->GetObj()) { + TDF_BASE_LOG(ERROR) << "cannot find TurboModule = " << name; + ctx->ThrowException("Cannot find TurboModule: " + name); + return info.GetReturnValue()->SetUndefined(); } - std::shared_ptr result = - turbo_module_runtime->module_cache_[name]; - if (!result) { - // 2. if not cached, query from Java - std::shared_ptr module_impl = - QueryTurboModuleImpl(runtime, name); - if (!module_impl->GetObj()) { - std::string exception_info = - std::string("Cannot find TurboModule: ").append(name); - TDF_BASE_LOG(ERROR) << "cannot find TurboModule = %s", name.c_str(); - ConvertUtils::ThrowException(ctx, exception_info); - return info.GetReturnValue().SetUndefined(); - } - - // 3. constructor c++ JavaTurboModule - std::shared_ptr java_turbo_module = - std::make_shared(name, module_impl); - - // 4. init v8TurboEnv - if (!turbo_module_runtime->turbo_env_) { - turbo_module_runtime->turbo_env_ = std::make_shared(ctx); - } - - // 5. bind c++ JavaTurboModule to js - result = - turbo_module_runtime->turbo_env_->CreateObject(java_turbo_module); - - // 6. add To Cache - turbo_module_runtime->module_cache_[name] = result; - TDF_BASE_DLOG(INFO) << "return module=%s", name.c_str(); - } else { - TDF_BASE_DLOG(INFO) << "return cached module=%s", name.c_str(); - } + // 3. constructor c++ JavaTurboModule + auto java_turbo_module = std::make_shared(u8_name, module_impl, ctx); + + // 4. bind c++ JavaTurboModule to js + result = ctx->NewInstance(java_turbo_module->constructor, 0, nullptr, java_turbo_module.get()); + + // 5. add To Cache + scope->SetTurboInstance(u8_name, result); + scope->SetTurboHostObject(u8_name, java_turbo_module); - std::shared_ptr v8_result = - std::static_pointer_cast(result); - info.GetReturnValue().Set(v8_result->global_value_); + TDF_BASE_DLOG(INFO) << "return module = " << name; } else { - TDF_BASE_LOG(ERROR) << "cannot find TurboModule as param is invalid"; - info.GetReturnValue().SetUndefined(); + result = scope->GetTurboInstance(u8_name); + TDF_BASE_DLOG(INFO) << "return cached module = " << name; } - TDF_BASE_DLOG(INFO) << "[turbo-perf] exit getTurboModule"; -} - -void BindNativeFunction(std::shared_ptr runtime, - const unicode_string_view &name, - v8::FunctionCallback function_callback) { - TDF_BASE_DLOG(INFO) << "enter bindNativeFunction name " - << StringViewUtils::ToU8StdStr(name); - std::shared_ptr v8_ctx = - std::static_pointer_cast(runtime->GetScope()->GetContext()); - v8::HandleScope handle_scope(v8_ctx->isolate_); - v8::Local context = - v8_ctx->context_persistent_.Get(v8_ctx->isolate_); - v8::Context::Scope context_scope(context); - v8::Local function_template = v8::FunctionTemplate::New( - v8_ctx->isolate_, function_callback, - v8::External::New(v8_ctx->isolate_, reinterpret_cast(runtime->GetId()))); - function_template->RemovePrototype(); - - v8::Local function_name = v8_ctx->CreateV8String(name); - v8::Local function = - function_template->GetFunction(context).ToLocalChecked(); - context->Global()->Set(context, function_name, function).ToChecked(); - TDF_BASE_DLOG(INFO) << "exit bindNativeFunction name " - << StringViewUtils::ToU8StdStr(name); + info.GetReturnValue()->Set(result); + TDF_BASE_DLOG(INFO) << "[turbo-perf] exit getTurboModule"; } void TurboModuleManager::Init() { JNIEnv *env = JNIEnvironment::GetInstance()->AttachCurrentThread(); jclass clazz = env->FindClass("com/tencent/mtt/hippy/bridge/jsi/TurboModuleManager"); - turbo_module_manager_clazz = static_cast(env->NewGlobalRef(clazz)); + turbo_module_manager_clazz = reinterpret_cast(env->NewGlobalRef(clazz)); env->DeleteLocalRef(clazz); get_method_id = @@ -176,7 +136,7 @@ void TurboModuleManager::Init() { "nativemodules/HippyNativeModuleBase;"); } -void TurboModuleManager::Destory() { +void TurboModuleManager::Destroy() { JNIEnv *env = JNIEnvironment::GetInstance()->AttachCurrentThread(); if (turbo_module_manager_clazz) { env->DeleteGlobalRef(turbo_module_manager_clazz); @@ -185,40 +145,39 @@ void TurboModuleManager::Destory() { get_method_id = nullptr; } -int Install(JNIEnv *, jobject j_obj, jlong j_runtime_id) { +int Install(JNIEnv* j_env, jobject j_obj, jlong j_runtime_id) { TDF_BASE_LOG(INFO) << "install TurboModuleManager"; - std::shared_ptr runtime = Runtime::Find(j_runtime_id); + auto runtime = Runtime::Find(hippy::base::checked_numeric_cast(j_runtime_id)); if (!runtime) { - TDF_BASE_LOG(ERROR) << "TurboModuleManager install, v8RuntimePtr invalid"; + TDF_BASE_LOG(ERROR) << "TurboModuleManager install, j_runtime_id invalid"; return -1; } - runtime->SetTurboModuleRuntime(std::make_shared(j_obj)); + runtime->SetTurboModuleManager(std::make_shared(j_env, j_obj)); // v8的操作放到js线程 - std::shared_ptr runner = - runtime->GetEngine()->GetJSRunner(); + auto runner = runtime->GetEngine()->GetJSRunner(); if (!runner) { TDF_BASE_LOG(WARNING) << "TurboModuleManager install, runner invalid"; return -1; } - std::shared_ptr task = std::make_shared(); - task->callback = [runtime] { - BindNativeFunction(runtime, "getTurboModule", GetTurboModule); + auto task = std::make_shared(); + std::weak_ptr weak_scope = runtime->GetScope(); + auto runtime_id = runtime->GetId(); + task->callback = [weak_scope, runtime_id] { + auto scope = weak_scope.lock(); + if (!scope) { + return; + } + auto context = scope->GetContext(); + auto wrapper = std::make_unique(GetTurboModule, reinterpret_cast(runtime_id)); + auto func = context->CreateFunction(wrapper); + scope->SaveFuncWrapper(std::move(wrapper)); + auto global_object = context->GetGlobalObject(); + auto key = context->CreateString(kTurboKey); + context->SetProperty(global_object, key, func); }; runner->PostTask(task); return 0; } - -void Uninstall(JNIEnv *, jobject, jlong j_runtime_id) { - TDF_BASE_LOG(INFO) << "uninstall install TurboModuleManager"; - std::shared_ptr runtime = Runtime::Find(j_runtime_id); - if (!runtime) { - TDF_BASE_LOG(ERROR) << "TurboModuleManager install, v8RuntimePtr invalid"; - return; - } - if (runtime->GetTurboModuleRuntime()) { - runtime->GetTurboModuleRuntime().reset(); - } -} diff --git a/android/sdk/src/main/jni/src/jni/uri.cc b/android/sdk/src/main/jni/src/jni/uri.cc index 9c64c25332d..2519963e3d7 100644 --- a/android/sdk/src/main/jni/src/jni/uri.cc +++ b/android/sdk/src/main/jni/src/jni/uri.cc @@ -22,8 +22,7 @@ #include "jni/uri.h" -#include "base/unicode_string_view.h" -#include "core/base/string_view_utils.h" +#include "core/core.h" #include "jni/jni_env.h" #include "jni/jni_utils.h" @@ -62,7 +61,7 @@ bool Uri::Init() { return true; } -bool Uri::Destory() { +bool Uri::Destroy() { JNIEnv* env = JNIEnvironment::GetInstance()->AttachCurrentThread(); j_get_path_method_id = nullptr; @@ -77,7 +76,7 @@ bool Uri::Destory() { } Uri::Uri(const unicode_string_view& uri) { - TDF_BASE_DCHECK(uri.encoding() != unicode_string_view::Encoding::Unkown); + TDF_BASE_DCHECK(uri.encoding() != unicode_string_view::Encoding::Unknown); JNIEnv* j_env = JNIEnvironment::GetInstance()->AttachCurrentThread(); jstring j_str_uri = JniUtils::StrViewToJString(j_env, uri); j_obj_uri_ = @@ -101,7 +100,7 @@ unicode_string_view Uri::Normalize() { JNIEnv* j_env = JNIEnvironment::GetInstance()->AttachCurrentThread(); jobject j_normalize_uri = (jstring)j_env->CallObjectMethod(j_obj_uri_, j_normalize_method_id); - jstring j_parsed_uri = + auto j_parsed_uri = (jstring)j_env->CallObjectMethod(j_normalize_uri, j_to_string_method_id); if (!j_parsed_uri) { return u""; @@ -115,7 +114,7 @@ unicode_string_view Uri::Normalize() { unicode_string_view Uri::GetScheme() { TDF_BASE_DCHECK(j_obj_uri_); JNIEnv* j_env = JNIEnvironment::GetInstance()->AttachCurrentThread(); - jstring j_scheme = + auto j_scheme = (jstring)j_env->CallObjectMethod(j_obj_uri_, j_get_scheme_method_id); if (!j_scheme) { return u""; @@ -128,7 +127,7 @@ unicode_string_view Uri::GetScheme() { unicode_string_view Uri::GetPath() { TDF_BASE_DCHECK(j_obj_uri_); JNIEnv* j_env = JNIEnvironment::GetInstance()->AttachCurrentThread(); - jstring j_path = + auto j_path = (jstring)j_env->CallObjectMethod(j_obj_uri_, j_get_path_method_id); if (!j_path) { return u""; diff --git a/android/sdk/src/main/jni/src/loader/adr_loader.cc b/android/sdk/src/main/jni/src/loader/adr_loader.cc index b6d61414c3e..ad677d1c1ce 100644 --- a/android/sdk/src/main/jni/src/loader/adr_loader.cc +++ b/android/sdk/src/main/jni/src/loader/adr_loader.cc @@ -22,11 +22,10 @@ #include "loader/adr_loader.h" +#include #include #include "bridge/runtime.h" -#include "core/base/string_view_utils.h" -#include "core/core.h" #include "jni/jni_env.h" #include "jni/jni_register.h" #include "jni/jni_utils.h" @@ -39,12 +38,12 @@ using u8string = unicode_string_view::u8string; using char8_t_ = unicode_string_view::char8_t_; static std::atomic global_request_id{0}; - -ADRLoader::ADRLoader() : aasset_manager_(nullptr) {} +static jclass j_context_holder_class; +static jmethodID j_get_app_context_method_id; bool ADRLoader::RequestUntrustedContent(const unicode_string_view& uri, std::function cb) { - std::shared_ptr uri_obj = Uri::Create(uri); + auto uri_obj = Uri::Create(uri); if (!uri_obj) { TDF_BASE_DLOG(ERROR) << "uri error, uri = " << uri; cb(u8string()); @@ -66,20 +65,16 @@ bool ADRLoader::RequestUntrustedContent(const unicode_string_view& uri, std::u16string schema_str = schema.utf16_value(); if (schema_str == u"file") { return LoadByFile(path, cb); - } else if (schema_str == u"http" || schema_str == u"https" || - schema_str == u"debug") { - return LoadByHttp(uri, cb); } else if (schema_str == u"asset") { - if (aasset_manager_) { + auto aasset_manager = GetAAssetManager(); + if (aasset_manager) { return LoadByAsset(path, cb, false); } TDF_BASE_DLOG(ERROR) << "aasset_manager error, uri = " << uri; cb(u8string()); return false; } else { - TDF_BASE_DLOG(ERROR) << "schema error, schema = " << schema; - cb(u8string()); - return false; + return LoadByJni(uri, cb); } } @@ -112,12 +107,13 @@ bool ADRLoader::RequestUntrustedContent(const unicode_string_view& uri, [p = std::move(promise)](u8string bytes) mutable { p.set_value(std::move(bytes)); }); - bool ret = LoadByHttp(uri, cb); + bool ret = LoadByJni(uri, cb); content = read_file_future.get(); return ret; } else if (schema_str == u"asset") { - if (aasset_manager_) { - return ReadAsset(path, aasset_manager_, content, false); + auto aasset_manager = ADRLoader::GetAAssetManager(); + if (aasset_manager) { + return ReadAsset(path, aasset_manager, content, false); } TDF_BASE_DLOG(ERROR) << "aasset_manager error, uri = " << uri; @@ -128,8 +124,17 @@ bool ADRLoader::RequestUntrustedContent(const unicode_string_view& uri, } } +AAssetManager* ADRLoader::GetAAssetManager() { + auto j_env = JNIEnvironment::GetInstance()->AttachCurrentThread(); + auto j_context = j_env->CallStaticObjectMethod(j_context_holder_class, j_get_app_context_method_id); + auto j_context_class = j_env->GetObjectClass(j_context); + auto j_get_assets_method_id = j_env->GetMethodID(j_context_class, "getAssets", "()Landroid/content/res/AssetManager;"); + auto j_asset_manager = j_env->CallObjectMethod(j_context, j_get_assets_method_id); + return AAssetManager_fromJava(j_env, j_asset_manager); +} + bool ADRLoader::LoadByFile(const unicode_string_view& path, - std::function cb) { + const std::function& cb) { std::shared_ptr runner = runner_.lock(); if (!runner) { return false; @@ -146,7 +151,7 @@ bool ADRLoader::LoadByFile(const unicode_string_view& path, } bool ADRLoader::LoadByAsset(const unicode_string_view& path, - std::function cb, + const std::function& cb, bool is_auto_fill) { TDF_BASE_DLOG(INFO) << "ReadAssetFile file_path = " << path; std::shared_ptr runner = runner_.lock(); @@ -154,8 +159,9 @@ bool ADRLoader::LoadByAsset(const unicode_string_view& path, return false; } std::unique_ptr task = std::make_unique(); - task->func_ = [path, aasset_manager = aasset_manager_, is_auto_fill, cb] { + task->func_ = [path, is_auto_fill, cb] { u8string ret; + auto aasset_manager = GetAAssetManager(); ReadAsset(path, aasset_manager, ret, is_auto_fill); cb(std::move(ret)); }; @@ -164,8 +170,8 @@ bool ADRLoader::LoadByAsset(const unicode_string_view& path, return true; } -bool ADRLoader::LoadByHttp(const unicode_string_view& uri, - std::function cb) { +bool ADRLoader::LoadByJni(const unicode_string_view& uri, + const std::function& cb) { std::shared_ptr instance = JNIEnvironment::GetInstance(); JNIEnv* j_env = instance->AttachCurrentThread(); @@ -175,6 +181,7 @@ bool ADRLoader::LoadByHttp(const unicode_string_view& uri, j_env->CallVoidMethod(bridge_->GetObj(), instance->GetMethods().j_fetch_resource_method_id, j_relative_path, id); + JNIEnvironment::ClearJEnvException(j_env); j_env->DeleteLocalRef(j_relative_path); return true; } @@ -184,69 +191,91 @@ bool ADRLoader::LoadByHttp(const unicode_string_view& uri, } void OnResourceReady(JNIEnv* j_env, - jobject j_object, - jobject j_byte_buffer, + __unused jobject j_object, + jobject j_buffer, jlong j_runtime_id, jlong j_request_id) { - TDF_BASE_DLOG(INFO) << "HippyBridgeImpl onResourceReady j_runtime_id = " - << j_runtime_id; - std::shared_ptr runtime = Runtime::Find(j_runtime_id); + TDF_BASE_DLOG(INFO) << "HippyBridgeImpl onResourceReady j_runtime_id = " << j_runtime_id; + auto runtime = Runtime::Find(hippy::base::checked_numeric_cast(j_runtime_id)); if (!runtime) { - TDF_BASE_DLOG(WARNING) - << "HippyBridgeImpl onResourceReady, j_runtime_id invalid"; + TDF_BASE_DLOG(WARNING) << "HippyBridgeImpl onResourceReady, j_runtime_id invalid"; return; } - std::shared_ptr scope = runtime->GetScope(); - if (!scope) { - TDF_BASE_DLOG(WARNING) << "HippyBridgeImpl onResourceReady, scope invalid"; + auto runner = runtime->GetEngine()->GetWorkerTaskRunner(); + if (!runner) { return; } - - std::shared_ptr loader = - std::static_pointer_cast(scope->GetUriLoader()); + std::weak_ptr weak_scope = runtime->GetScope(); int64_t request_id = j_request_id; - TDF_BASE_DLOG(INFO) << "request_id = " << request_id; - auto cb = loader->GetRequestCB(request_id); - if (!cb) { - TDF_BASE_DLOG(WARNING) << "cb not found" << request_id; - return; - } - if (!j_byte_buffer) { - TDF_BASE_DLOG(INFO) << "HippyBridgeImpl onResourceReady, buff null"; - cb(u8string()); - return; - } - int64_t len = (j_env)->GetDirectBufferCapacity(j_byte_buffer); - TDF_BASE_DLOG(INFO) << "len = " << len; - if (len == -1) { - TDF_BASE_DLOG(ERROR) - << "HippyBridgeImpl onResourceReady, BufferCapacity error"; - cb(u8string()); - return; - } - void* buff = (j_env)->GetDirectBufferAddress(j_byte_buffer); - if (!buff) { - TDF_BASE_DLOG(INFO) << "HippyBridgeImpl onResourceReady, buff null"; - cb(u8string()); - return; - } + auto buffer = std::make_shared(j_env, j_buffer); + auto task = std::make_unique(); + task->func_ = [weak_scope, request_id, buffer_ = std::move(buffer)] { + auto scope = weak_scope.lock(); + if (!scope) { + return; + } + auto j_env = JNIEnvironment::GetInstance()->AttachCurrentThread(); + std::shared_ptr loader = std::static_pointer_cast(scope->GetUriLoader()); + TDF_BASE_DLOG(INFO) << "request_id = " << request_id; + auto cb = loader->GetRequestCB(request_id); + if (!cb) { + TDF_BASE_DLOG(WARNING) << "cb not found" << request_id; + return; + } + auto j_buffer = buffer_->GetObj(); + if (!j_buffer) { + TDF_BASE_DLOG(INFO) << "HippyBridgeImpl onResourceReady, buff null"; + cb(u8string()); + return; + } + auto len = j_env->GetDirectBufferCapacity(j_buffer); + TDF_BASE_DLOG(INFO) << "len = " << len; + if (len == -1) { + TDF_BASE_DLOG(ERROR) << "HippyBridgeImpl onResourceReady, BufferCapacity error"; + cb(u8string()); + return; + } + auto buff = j_env->GetDirectBufferAddress(j_buffer); + if (!buff) { + TDF_BASE_DLOG(INFO) << "HippyBridgeImpl onResourceReady, buff null"; + cb(u8string()); + return; + } - u8string str(reinterpret_cast(buff), len); - cb(std::move(str)); + u8string str(reinterpret_cast(buff), + hippy::base::checked_numeric_cast(len)); + cb(std::move(str)); + }; + runner->PostTask(std::move(task)); } -REGISTER_JNI("com/tencent/mtt/hippy/bridge/HippyBridgeImpl", +REGISTER_JNI("com/tencent/mtt/hippy/bridge/HippyBridgeImpl", // NOLINT(cert-err58-cpp) "onResourceReady", "(Ljava/nio/ByteBuffer;JJ)V", OnResourceReady) std::function ADRLoader::GetRequestCB(int64_t request_id) { + std::lock_guard lock(mutex_); auto it = request_map_.find(request_id); return it != request_map_.end() ? it->second : nullptr; } -int64_t ADRLoader::SetRequestCB(std::function cb) { +int64_t ADRLoader::SetRequestCB(const std::function& cb) { + std::lock_guard lock(mutex_); int64_t id = global_request_id.fetch_add(1); request_map_.insert({id, cb}); return id; } + +void ADRLoader::Init() { + auto j_env = JNIEnvironment::GetInstance()->AttachCurrentThread(); + j_context_holder_class = reinterpret_cast(j_env->NewGlobalRef( + j_env->FindClass("com/tencent/mtt/hippy/utils/ContextHolder"))); + j_get_app_context_method_id = j_env->GetStaticMethodID( + j_context_holder_class, "getAppContext","()Landroid/content/Context;"); +} + +void ADRLoader::Destroy() { + auto j_env = JNIEnvironment::GetInstance()->AttachCurrentThread(); + j_env->DeleteGlobalRef(j_context_holder_class); +} diff --git a/android/sdk/src/main/jni/src/performance/memory.cc b/android/sdk/src/main/jni/src/performance/memory.cc new file mode 100644 index 00000000000..c13326f49c3 --- /dev/null +++ b/android/sdk/src/main/jni/src/performance/memory.cc @@ -0,0 +1,393 @@ +/* + * + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2022 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "performance/memory.h" + +#include "bridge/runtime.h" +#include "jni/jni_env.h" +#include "jni/jni_register.h" +#include "jni/jni_utils.h" + +namespace hippy { +namespace bridge { + +// [Heap] +REGISTER_JNI("com/tencent/mtt/hippy/v8/V8", // NOLINT(cert-err58-cpp) + "getHeapStatistics", + "(JLcom/tencent/mtt/hippy/common/Callback;)Z", + GetHeapStatistics) +REGISTER_JNI("com/tencent/mtt/hippy/v8/V8", // NOLINT(cert-err58-cpp) + "getHeapCodeStatistics", + "(JLcom/tencent/mtt/hippy/common/Callback;)Z", + GetHeapCodeStatistics) +REGISTER_JNI("com/tencent/mtt/hippy/v8/V8", // NOLINT(cert-err58-cpp) + "getHeapSpaceStatistics", + "(JLcom/tencent/mtt/hippy/common/Callback;)Z", + GetHeapSpaceStatistics) +REGISTER_JNI("com/tencent/mtt/hippy/v8/V8", // NOLINT(cert-err58-cpp) + "writeHeapSnapshot", + "(JLjava/lang/String;Lcom/tencent/mtt/hippy/common/Callback;)Z", + WriteHeapSnapshot) + +jint ThrowNoSuchMethodError(JNIEnv* j_env, const char* msg){ + auto j_class = j_env->FindClass("java/lang/NoSuchMethodException" ); + TDF_BASE_CHECK(j_class); + return j_env->ThrowNew(j_class, msg); +} + +using unicode_string_view = tdf::base::unicode_string_view; +using V8VM = hippy::vm::V8VM; + +#ifndef V8_X5_LITE + +// [Heap] write result code +enum HEAP_WRITE : int8_t { + HEAP_WRITE_OK = 0, + HEAP_WRITE_ERR_RUN = -1, + HEAP_WRITE_ERR_FILE = -2, + HEAP_WRITE_ERR_SAVE = -3 +}; + +// [Heap] HeapSnapshot OutputStream +class HeapSnapshotOutputStreamAdapter : public v8::OutputStream { + private: + std::ofstream file_; + public: + static constexpr uint16_t kChunkSize = 1024; + enum SaveResult : int8_t { + kInit = 0, + kOk = 1, + kError = 2 + }; + SaveResult save_res_ = kInit; + void EndOfStream() override { + save_res_ = kOk; + file_.close(); + } + int GetChunkSize() override { + return kChunkSize; + } + WriteResult WriteAsciiChunk(char *data, int size) override { + if (file_.is_open()) { + file_.write(data, size); + } + return kContinue; + } + WriteResult WriteHeapStatsChunk(v8::HeapStatsUpdate *data, int count) override { + save_res_ = kError; + return kAbort; + } + + int SetFilePath(const tdf::base::unicode_string_view &snapshot_file_path) { + TDF_BASE_DLOG(INFO) << "HeapSnapshotOutputStreamAdapter SetFilePath heap_snapshot_file_path = " + << snapshot_file_path; + int result_code = 0; + size_t pos = base::StringViewUtils::FindLastOf(snapshot_file_path, EXTEND_LITERAL('/')); + tdf::base::unicode_string_view + snapshot_parent_dir = base::StringViewUtils::SubStr(snapshot_file_path, 0, pos); + int check_parent_dir_code = base::HippyFile::CheckDir(snapshot_parent_dir, F_OK); + // no file or no permission + if (check_parent_dir_code) { + TDF_BASE_DLOG(INFO) + << "HeapSnapshotOutputStreamAdapter SetFilePath check_heap_snapshot_parent_dir_code = " + << check_parent_dir_code; + result_code = base::HippyFile::CreateDir(snapshot_parent_dir, S_IRWXU); + } + if (result_code) { + save_res_ = kError; + } else { + save_res_ = kInit; + TDF_BASE_DLOG(INFO) << "HeapSnapshotOutputStreamAdapter open file " << snapshot_file_path; + tdf::base::unicode_string_view owner(""_u8s); + const char *path = base::StringViewUtils::ToConstCharPointer(snapshot_file_path, owner); + file_.open(path, std::ios::out | std::ios::ate | std::ios::binary); + } + TDF_BASE_DLOG(INFO) << "HeapSnapshotOutputStreamAdapter SetFilePath result " << result_code; + return result_code; + } +}; +#endif + +// [Heap] GetHeapStatistics +jboolean GetHeapStatistics(__unused JNIEnv *j_env, + __unused jobject j_object, + jlong j_runtime_id, + jobject j_callback) { + TDF_BASE_DLOG(INFO) << "GetHeapStatistics begin, j_runtime_id = " << j_runtime_id; + auto runtime = Runtime::Find(hippy::base::checked_numeric_cast(j_runtime_id)); + // callback + jclass j_cb_class = j_env->GetObjectClass(j_callback); + jmethodID j_cb_method = + j_env->GetMethodID(j_cb_class, "callback", "(Ljava/lang/Object;Ljava/lang/Throwable;)V"); + std::shared_ptr cb = std::make_shared(j_env, j_callback); + j_env->DeleteLocalRef(j_cb_class); + // j_runtime_id invalid + if (!runtime) { + TDF_BASE_DLOG(WARNING) << "GetHeapStatistics, j_runtime_id invalid"; + j_env->CallVoidMethod(cb->GetObj(), j_cb_method, nullptr, nullptr); + JNIEnvironment::ClearJEnvException(j_env); + return JNI_FALSE; + } + // prepare jni class + jclass j_hs_class = j_env->FindClass("com/tencent/mtt/hippy/v8/memory/V8HeapStatistics"); + std::shared_ptr hs_class = std::make_shared(j_env, j_hs_class); + j_env->DeleteLocalRef(j_hs_class); + + TDF_BASE_DLOG(INFO) << "GetHeapStatistics thread begin"; + // v8 GetHeapStatistics + auto heap_statistics = std::make_shared(); + v8::Isolate *isolate = std::static_pointer_cast(runtime->GetEngine()->GetVM())->isolate_; + v8::HandleScope handle_scope(isolate); + isolate->GetHeapStatistics(heap_statistics.get()); + // set data + jmethodID j_hs_constructor = + j_env->GetMethodID(reinterpret_cast(hs_class->GetObj()), "", "(JJJJJJJJJJJJJ)V"); + std::shared_ptr hs_obj = std::make_shared(j_env, + j_env->NewObject( + reinterpret_cast(hs_class->GetObj()), + j_hs_constructor, + heap_statistics->total_heap_size(), + heap_statistics->total_heap_size_executable(), + heap_statistics->total_physical_size(), + heap_statistics->total_available_size(), +#if (V8_MAJOR_VERSION == 9 && V8_MINOR_VERSION == 8 && V8_BUILD_NUMBER >= 124) || (V8_MAJOR_VERSION == 9 && V8_MINOR_VERSION > 8) || (V8_MAJOR_VERSION > 9) + heap_statistics->total_global_handles_size(), + heap_statistics->used_global_handles_size(), +#else + -1, + -1, +#endif + heap_statistics->used_heap_size(), + heap_statistics->heap_size_limit(), + heap_statistics->malloced_memory(), + heap_statistics->external_memory(), + heap_statistics->peak_malloced_memory(), + heap_statistics->number_of_native_contexts(), + heap_statistics->number_of_detached_contexts())); + j_env->CallVoidMethod(cb->GetObj(), j_cb_method, hs_obj->GetObj(), nullptr); + JNIEnvironment::ClearJEnvException(j_env); + TDF_BASE_DLOG(INFO) << "GetHeapStatistics thread end"; + return JNI_TRUE; +} +// [Heap] GetHeapCodeStatistics +jboolean GetHeapCodeStatistics(__unused JNIEnv *j_env, + __unused jobject j_object, + jlong j_runtime_id, + jobject j_callback) { + TDF_BASE_DLOG(INFO) << "GetHeapCodeStatistics begin, j_runtime_id = " << j_runtime_id; + auto runtime = Runtime::Find(hippy::base::checked_numeric_cast(j_runtime_id)); + // callback + jclass j_cb_class = j_env->GetObjectClass(j_callback); + jmethodID j_cb_method = + j_env->GetMethodID(j_cb_class, "callback", "(Ljava/lang/Object;Ljava/lang/Throwable;)V"); + std::shared_ptr cb = std::make_shared(j_env, j_callback); + j_env->DeleteLocalRef(j_cb_class); + // j_runtime_id invalid + if (!runtime) { + TDF_BASE_DLOG(WARNING) << "GetHeapCodeStatistics, j_runtime_id invalid"; + j_env->CallVoidMethod(cb->GetObj(), j_cb_method, nullptr, nullptr); + JNIEnvironment::ClearJEnvException(j_env); + return JNI_FALSE; + } + // prepare jni class + jclass j_hcs_class = j_env->FindClass("com/tencent/mtt/hippy/v8/memory/V8HeapCodeStatistics"); + std::shared_ptr hcs_class = std::make_shared(j_env, j_hcs_class); + j_env->DeleteLocalRef(j_hcs_class); + + TDF_BASE_DLOG(INFO) << "GetHeapCodeStatistics thread begin"; + // v8 GetHeapCodeAndMetadataStatistics + auto heap_code_statistics = std::make_shared(); + v8::Isolate *isolate = std::static_pointer_cast(runtime->GetEngine()->GetVM())->isolate_; + v8::HandleScope handle_scope(isolate); + isolate->GetHeapCodeAndMetadataStatistics(heap_code_statistics.get()); + // set data + jmethodID j_hcs_constructor = + j_env->GetMethodID(reinterpret_cast(hcs_class->GetObj()), "", "(JJJ)V"); + std::shared_ptr hcs_obj = std::make_shared(j_env, + j_env->NewObject( + reinterpret_cast(hcs_class->GetObj()), + j_hcs_constructor, + heap_code_statistics->code_and_metadata_size(), + heap_code_statistics->bytecode_and_metadata_size(), + heap_code_statistics->external_script_source_size())); + j_env->CallVoidMethod(cb->GetObj(), j_cb_method, hcs_obj->GetObj(), nullptr); + JNIEnvironment::ClearJEnvException(j_env); + TDF_BASE_DLOG(INFO) << "GetHeapCodeStatistics thread end"; + return JNI_TRUE; +} +// [Heap] GetHeapSpaceStatistics +jboolean GetHeapSpaceStatistics(__unused JNIEnv *j_env, + __unused jobject j_object, + jlong j_runtime_id, + jobject j_callback) { + TDF_BASE_DLOG(INFO) << "GetHeapSpaceStatistics begin, j_runtime_id = " << j_runtime_id; + auto runtime = Runtime::Find(hippy::base::checked_numeric_cast(j_runtime_id)); + // callback + jclass j_cb_class = j_env->GetObjectClass(j_callback); + jmethodID j_cb_method = + j_env->GetMethodID(j_cb_class, "callback", "(Ljava/lang/Object;Ljava/lang/Throwable;)V"); + std::shared_ptr cb = std::make_shared(j_env, j_callback); + j_env->DeleteLocalRef(j_cb_class); + // j_runtime_id invalid + if (!runtime) { + TDF_BASE_DLOG(WARNING) << "GetHeapSpaceStatistics, j_runtime_id invalid"; + j_env->CallVoidMethod(cb->GetObj(), j_cb_method, nullptr, nullptr); + JNIEnvironment::ClearJEnvException(j_env); + return JNI_FALSE; + } + // prepare jni class + jclass j_hss_class = j_env->FindClass("com/tencent/mtt/hippy/v8/memory/V8HeapSpaceStatistics"); + std::shared_ptr hss_class = std::make_shared(j_env, j_hss_class); + j_env->DeleteLocalRef(j_hss_class); + jclass j_list_class = j_env->FindClass("java/util/ArrayList"); + std::shared_ptr list_class = std::make_shared(j_env, j_list_class); + j_env->DeleteLocalRef(j_list_class); + + TDF_BASE_DLOG(INFO) << "GetHeapSpaceStatistics thread begin"; + // init ArrayList + jmethodID j_list_constructor = + j_env->GetMethodID(reinterpret_cast(list_class->GetObj()), "", "()V"); + std::shared_ptr list_obj = std::make_shared(j_env, + j_env->NewObject( + reinterpret_cast(list_class->GetObj()), + j_list_constructor)); + jmethodID j_list_add = j_env->GetMethodID(reinterpret_cast(list_class->GetObj()), + "add", + "(Ljava/lang/Object;)Z"); + jmethodID j_hss_constructor = j_env->GetMethodID(reinterpret_cast(hss_class->GetObj()), + "", + "(Ljava/lang/String;JJJJ)V"); + // v8 NumberOfHeapSpaces + v8::Isolate *isolate = std::static_pointer_cast(runtime->GetEngine()->GetVM())->isolate_; + v8::HandleScope handle_scope(isolate); + size_t space_count = isolate->NumberOfHeapSpaces(); + TDF_BASE_DLOG(INFO) << "GetHeapSpaceStatistics thread, space_count = " << space_count; + std::shared_ptr heap_space_statistics; + // set data + for (size_t i = 0; i < space_count; i++) { + // v8 getHeapSpaceStatistics + heap_space_statistics = std::make_shared(); + isolate->GetHeapSpaceStatistics(heap_space_statistics.get(), i); + // set HeapSpaceStatistics data + jstring j_space_name = j_env->NewStringUTF(heap_space_statistics->space_name()); + std::shared_ptr hss_obj = std::make_shared(j_env, + j_env->NewObject( + reinterpret_cast(hss_class->GetObj()), + j_hss_constructor, + j_space_name, + heap_space_statistics->space_size(), + heap_space_statistics->space_used_size(), + heap_space_statistics->space_available_size(), + heap_space_statistics->physical_space_size())); + j_env->CallBooleanMethod(list_obj->GetObj(), j_list_add, hss_obj->GetObj()); + JNIEnvironment::ClearJEnvException(j_env); + j_env->DeleteLocalRef(j_space_name); + } + j_env->CallVoidMethod(cb->GetObj(), j_cb_method, list_obj->GetObj(), nullptr); + JNIEnvironment::ClearJEnvException(j_env); + TDF_BASE_DLOG(INFO) << "GetHeapSpaceStatistics thread end"; + return JNI_TRUE; +} +// [Heap] WriteHeapSnapshot +jboolean WriteHeapSnapshot(__unused JNIEnv *j_env, + __unused jobject j_object, + jlong j_runtime_id, + jstring j_heap_snapshot_path, + jobject j_callback) { +#ifndef V8_X5_LITE + TDF_BASE_DLOG(INFO) << "WriteHeapSnapshot begin, j_runtime_id = " << j_runtime_id; + auto runtime = Runtime::Find(hippy::base::checked_numeric_cast(j_runtime_id)); + // callback + jclass j_cb_class = j_env->GetObjectClass(j_callback); + jmethodID j_cb_method = + j_env->GetMethodID(j_cb_class, "callback", "(Ljava/lang/Object;Ljava/lang/Throwable;)V"); + std::shared_ptr cb = std::make_shared(j_env, j_callback); + j_env->DeleteLocalRef(j_cb_class); + // callback code + jclass j_int_class = j_env->FindClass("java/lang/Integer"); + jmethodID j_int_constructor = j_env->GetMethodID(j_int_class, "", "(I)V"); + std::shared_ptr + int_obj = std::make_shared(j_env, j_env->AllocObject(j_int_class)); + std::shared_ptr ret_code_obj = std::make_shared(j_env, int_obj->GetObj()); + j_env->DeleteLocalRef(j_int_class); + // error: runtime_id invalid + if (!runtime) { + TDF_BASE_DLOG(WARNING) << "WriteHeapSnapshot, j_runtime_id invalid"; + j_env->CallVoidMethod(ret_code_obj->GetObj(), + j_int_constructor, + static_cast(HEAP_WRITE_ERR_RUN)); + JNIEnvironment::ClearJEnvException(j_env); + j_env->CallVoidMethod(cb->GetObj(), j_cb_method, ret_code_obj->GetObj(), nullptr); + JNIEnvironment::ClearJEnvException(j_env); + return JNI_FALSE; + } + const unicode_string_view heap_snapshot_path = JniUtils::ToStrView(j_env, j_heap_snapshot_path); + TDF_BASE_DLOG(INFO) << "WriteHeapSnapshot thread start"; + auto heap_snapshot_stream = std::make_shared(); + int set_file_err = heap_snapshot_stream->SetFilePath(heap_snapshot_path); + // error: file_path invalid + if (set_file_err) { + TDF_BASE_DLOG(WARNING) << "WriteHeapSnapshot thread, set_file_err = " << set_file_err; + j_env->CallVoidMethod(ret_code_obj->GetObj(), + j_int_constructor, + static_cast(HEAP_WRITE_ERR_FILE)); + JNIEnvironment::ClearJEnvException(j_env); + j_env->CallVoidMethod(cb->GetObj(), j_cb_method, ret_code_obj->GetObj(), nullptr); + JNIEnvironment::ClearJEnvException(j_env); + return JNI_FALSE; + } + v8::Isolate *isolate = std::static_pointer_cast(runtime->GetEngine()->GetVM())->isolate_; + v8::HandleScope handle_scope(isolate); + // v8 TakeHeapSnapshot and Serialize + auto heap_snapshot = isolate->GetHeapProfiler()->TakeHeapSnapshot(); + heap_snapshot->Serialize(heap_snapshot_stream.get()); + const_cast(heap_snapshot)->Delete(); + // error: save heapSnapshots error + if (heap_snapshot_stream->save_res_ != HeapSnapshotOutputStreamAdapter::kOk) { + TDF_BASE_DLOG(WARNING) << "WriteHeapSnapshot RequestInterrupt, save heapSnapshots = " + << heap_snapshot_stream->save_res_; + j_env->CallVoidMethod(ret_code_obj->GetObj(), + j_int_constructor, + static_cast(HEAP_WRITE_ERR_SAVE)); + JNIEnvironment::ClearJEnvException(j_env); + j_env->CallVoidMethod(cb->GetObj(), j_cb_method, ret_code_obj->GetObj(), nullptr); + JNIEnvironment::ClearJEnvException(j_env); + return JNI_FALSE; + } + j_env->CallVoidMethod(ret_code_obj->GetObj(), + j_int_constructor, + static_cast(HEAP_WRITE_OK)); + JNIEnvironment::ClearJEnvException(j_env); + j_env->CallVoidMethod(cb->GetObj(), j_cb_method, ret_code_obj->GetObj(), nullptr); + JNIEnvironment::ClearJEnvException(j_env); + TDF_BASE_DLOG(INFO) << "WriteHeapSnapshot thread end"; + return JNI_TRUE; +#else + ThrowNoSuchMethodError(j_env, "X5 lite has no WriteHeapSnapshot method"); + return JNI_FALSE; +#endif +} + +//#endif + +} // namespace bridge +} // namespace hippy diff --git a/android/sdk/src/main/jni/src/v8/heap_limit.cc b/android/sdk/src/main/jni/src/v8/heap_limit.cc new file mode 100644 index 00000000000..dcdd5b52c28 --- /dev/null +++ b/android/sdk/src/main/jni/src/v8/heap_limit.cc @@ -0,0 +1,73 @@ +/* + * + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2022 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "v8/heap_limit.h" + +#include "bridge/runtime.h" +#include "jni/jni_env.h" +#include "v8/v8.h" + +namespace hippy { +inline namespace driver { +inline namespace v8_engine { + +using V8VM = hippy::vm::V8VM; + +REGISTER_JNI("com/tencent/mtt/hippy/v8/V8", // NOLINT(cert-err58-cpp) + "addNearHeapLimitCallback", + "(JLcom/tencent/mtt/hippy/v8/V8$NearHeapLimitCallback;)V", + AddNearHeapLimitCallback) + + +void AddNearHeapLimitCallback(JNIEnv *j_env, + jobject j_object, + jlong j_runtime_id, + jobject j_callback) { + auto runtime_id = hippy::base::checked_numeric_cast(j_runtime_id); + auto runtime = Runtime::Find(runtime_id); + TDF_BASE_CHECK(runtime); + auto cb = std::make_shared(j_env, j_callback); + runtime->SetNearHeapLimitCallback([cb] ( + void*, size_t current_heap_limit, size_t initial_heap_limit) -> size_t { + auto j_env = JNIEnvironment::GetInstance()->AttachCurrentThread(); + auto j_callback = cb->GetObj(); + auto j_cb_class = j_env->GetObjectClass(j_callback); + auto j_cb_method_id = j_env->GetMethodID(j_cb_class, "callback","(JJ)J"); + auto j_current_heap_limit = hippy::base::checked_numeric_cast(current_heap_limit); + auto j_initial_heap_limit = hippy::base::checked_numeric_cast(initial_heap_limit); + auto j_new_limit = j_env->CallLongMethod(j_callback, j_cb_method_id, j_current_heap_limit, j_initial_heap_limit); + JNIEnvironment::ClearJEnvException(j_env); + return hippy::base::checked_numeric_cast(j_new_limit); + }); + v8::Isolate *isolate = std::static_pointer_cast(runtime->GetEngine()->GetVM())->isolate_; + isolate->AddNearHeapLimitCallback([](void* data, size_t current_heap_limit, + size_t initial_heap_limit) -> size_t { + auto runtime = Runtime::Find(static_cast(reinterpret_cast(data))); + auto cb = runtime->GetNearHeapLimitCallback(); + TDF_BASE_CHECK(cb); + return cb(data, current_heap_limit, initial_heap_limit); + }, reinterpret_cast(runtime_id)); +} + +} +} +} diff --git a/android/sdk/src/main/jni/src/v8/interrupt_queue.cc b/android/sdk/src/main/jni/src/v8/interrupt_queue.cc new file mode 100644 index 00000000000..f61b24563e4 --- /dev/null +++ b/android/sdk/src/main/jni/src/v8/interrupt_queue.cc @@ -0,0 +1,86 @@ +/* + * + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "v8/interrupt_queue.h" +#include "core/task/javascript_task.h" + +namespace hippy { +inline namespace driver { +inline namespace runtime { + +std::atomic InterruptQueue::g_id = 1; + +InterruptQueue::PersistentObjectMap InterruptQueue::persistent_map_; + +InterruptQueue::InterruptQueue(v8::Isolate* isolate): isolate_(isolate), task_queue_(), queue_mutex_() { + id_ = g_id.fetch_add(1); +} + +void InterruptQueue::PostTask(std::unique_ptr task) { + { + std::lock_guard lock_guard(queue_mutex_); + task_queue_.push(std::move(task)); + } + + if (task_runner_) { + auto weak_self = weak_from_this(); + auto js_task = std::make_unique(); + js_task->callback = [weak_self]() { + auto self = weak_self.lock(); + if (self) { + self->Run(); + } + }; + task_runner_->PostTask(std::move(js_task)); + } + + isolate_->RequestInterrupt([](v8::Isolate* isolate, void* data) { + auto& map = InterruptQueue::GetPersistentMap(); + auto index = static_cast(reinterpret_cast(data)); + std::shared_ptr queue; + auto flag = map.Find(index, queue); + if (flag && queue) { + queue->Run(); + } + }, reinterpret_cast(id_)); +} + +void InterruptQueue::Run() { + std::queue> queue; + { + std::lock_guard lock_guard(queue_mutex_); + + if (task_queue_.empty()) { + return; + } + std::swap(queue, task_queue_); + } + while (!queue.empty()) { + auto task = std::move(queue.front()); + queue.pop(); + task->Run(); + } +} + +} +} +} diff --git a/android/sdk/src/main/jni/src/v8/request_interrupt.cc b/android/sdk/src/main/jni/src/v8/request_interrupt.cc new file mode 100644 index 00000000000..d447df5f5df --- /dev/null +++ b/android/sdk/src/main/jni/src/v8/request_interrupt.cc @@ -0,0 +1,65 @@ +/* + * + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2022 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "v8/request_interrupt.h" + +#include "bridge/runtime.h" +#include "jni/jni_env.h" +#include "v8/v8.h" + +namespace hippy { +inline namespace driver { +inline namespace v8_engine { + +using V8VM = hippy::vm::V8VM; + +REGISTER_JNI("com/tencent/mtt/hippy/v8/V8", // NOLINT(cert-err58-cpp) + "requestInterrupt", + "(JLcom/tencent/mtt/hippy/common/Callback;)V", + RequestInterrupt) + + +void RequestInterrupt(JNIEnv *j_env, + jobject j_object, + jlong j_runtime_id, + jobject j_callback) { + auto runtime = Runtime::Find(hippy::base::checked_numeric_cast(j_runtime_id)); + TDF_BASE_CHECK(runtime); + auto cb = std::make_shared(j_env, j_callback); + auto interrupt_queue = runtime->GetInterruptQueue(); + auto task = std::make_unique(); + task->callback = [cb]() { + auto j_env = JNIEnvironment::GetInstance()->AttachCurrentThread(); + auto j_callback = cb->GetObj(); + auto j_cb_class = j_env->GetObjectClass(j_callback); + auto j_cb_method_id = j_env->GetMethodID(j_cb_class, "callback", + "(Ljava/lang/Object;Ljava/lang/Throwable;)V"); + j_env->CallVoidMethod(cb->GetObj(), j_cb_method_id, nullptr, nullptr); + JNIEnvironment::ClearJEnvException(j_env); + }; + interrupt_queue->PostTask(std::move(task)); +} + + +} +} +} diff --git a/android/sdk/src/main/jni/src/v8/stack_trace.cc b/android/sdk/src/main/jni/src/v8/stack_trace.cc new file mode 100644 index 00000000000..39a229dcecb --- /dev/null +++ b/android/sdk/src/main/jni/src/v8/stack_trace.cc @@ -0,0 +1,78 @@ +/* + * + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2022 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "v8/stack_trace.h" + +#include "bridge/runtime.h" +#include "jni/jni_utils.h" +#include "jni/jni_env.h" +#include "v8/v8.h" + +namespace hippy { +inline namespace driver { +inline namespace v8_engine { + +using V8VM = hippy::vm::V8VM; + +#if V8_MAJOR_VERSION < 9 +constexpr int kFrameLimit = 20; +#endif + +REGISTER_JNI("com/tencent/mtt/hippy/v8/V8", // NOLINT(cert-err58-cpp) + "printCurrentStackTrace", + "(JLcom/tencent/mtt/hippy/common/Callback;)V", + GetCurrentStackTrace) + + +void GetCurrentStackTrace(JNIEnv *j_env, + jobject j_object, + jlong j_runtime_id, + jobject j_callback) { + auto runtime = Runtime::Find(hippy::base::checked_numeric_cast(j_runtime_id)); + TDF_BASE_CHECK(runtime); + + auto ctx = std::static_pointer_cast( runtime->GetScope()->GetContext()); + v8::Isolate *isolate = ctx->isolate_; + v8::HandleScope handle_scope(isolate); +#if V8_MAJOR_VERSION >= 9 + std::ostringstream trace; + v8::Message::PrintCurrentStackTrace(isolate, trace); + auto trace_str = trace.str(); + auto trace_info = tdf::base::unicode_string_view::new_from_utf8(trace_str.c_str(), trace_str.length()); +#else + auto trace = v8::StackTrace::CurrentStackTrace(isolate, kFrameLimit); + auto trace_info = ctx->GetStackTrace(trace); +#endif + + auto j_cb_class = j_env->GetObjectClass(j_callback); + auto j_cb_method_id = j_env->GetMethodID(j_cb_class, "callback", + "(Ljava/lang/Object;Ljava/lang/Throwable;)V"); + auto j_trace_info = JniUtils::StrViewToJString(j_env, trace_info); + j_env->CallVoidMethod(j_callback, j_cb_method_id, j_trace_info, nullptr); + JNIEnvironment::ClearJEnvException(j_env); + j_env->DeleteLocalRef(j_trace_info); +} + + +} +} +} diff --git a/android/sdk/src/main/jni/third_party/layout/arm64-v8a/lib.unstripped/libflexbox.so b/android/sdk/src/main/jni/third_party/layout/arm64-v8a/lib.unstripped/libflexbox.so deleted file mode 100644 index 75f14425054..00000000000 --- a/android/sdk/src/main/jni/third_party/layout/arm64-v8a/lib.unstripped/libflexbox.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8b7cf3921281e59286584f6b6c5692cb51574a67bb68e80994d24a40c43e842a -size 2014456 diff --git a/android/sdk/src/main/jni/third_party/layout/arm64-v8a/libflexbox.so b/android/sdk/src/main/jni/third_party/layout/arm64-v8a/libflexbox.so deleted file mode 100644 index 324c93b3b34..00000000000 --- a/android/sdk/src/main/jni/third_party/layout/arm64-v8a/libflexbox.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dab82ba5341b38ce86efc6728b799482f9b34d7c58f53c66f7f81783d66a39ac -size 260152 diff --git a/android/sdk/src/main/jni/third_party/layout/armeabi-v7a/lib.unstripped/libflexbox.so b/android/sdk/src/main/jni/third_party/layout/armeabi-v7a/lib.unstripped/libflexbox.so deleted file mode 100644 index 4c13aaa9cba..00000000000 --- a/android/sdk/src/main/jni/third_party/layout/armeabi-v7a/lib.unstripped/libflexbox.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0627859d3d64971e16e3e311d2cbf6eb4a7c4faa192e0bb9b59d3e46f11fa291 -size 1615040 diff --git a/android/sdk/src/main/jni/third_party/layout/armeabi-v7a/libflexbox.so b/android/sdk/src/main/jni/third_party/layout/armeabi-v7a/libflexbox.so deleted file mode 100644 index b2a7c414b18..00000000000 --- a/android/sdk/src/main/jni/third_party/layout/armeabi-v7a/libflexbox.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:39998c94d16155c36b5e95e272bf360e04e91ddfe0d256d08ddba9453a4e08df -size 136796 diff --git a/android/sdk/src/main/jni/third_party/layout/x86/lib.unstripped/libflexbox.so b/android/sdk/src/main/jni/third_party/layout/x86/lib.unstripped/libflexbox.so deleted file mode 100644 index e0327302ad7..00000000000 --- a/android/sdk/src/main/jni/third_party/layout/x86/lib.unstripped/libflexbox.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:981db55c3461ca18c20eca1026165b21746a874b98df235f49507ae4114ee13c -size 1921984 diff --git a/android/sdk/src/main/jni/third_party/layout/x86/libflexbox.so b/android/sdk/src/main/jni/third_party/layout/x86/libflexbox.so deleted file mode 100644 index 0def6999a04..00000000000 --- a/android/sdk/src/main/jni/third_party/layout/x86/libflexbox.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:01c8cfc1651040ad91cc2c3251fb02bac5f68df8cb8dbb778d8329e3729a039d -size 272016 diff --git a/android/sdk/src/main/jni/third_party/layout/x86_64/lib.unstripped/libflexbox.so b/android/sdk/src/main/jni/third_party/layout/x86_64/lib.unstripped/libflexbox.so deleted file mode 100644 index ec367be33a8..00000000000 --- a/android/sdk/src/main/jni/third_party/layout/x86_64/lib.unstripped/libflexbox.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd53c88519a7f085cef1b996a6456e4f958465d64857d659a20b2ce72733ac8d -size 2086600 diff --git a/android/sdk/src/main/jni/third_party/layout/x86_64/libflexbox.so b/android/sdk/src/main/jni/third_party/layout/x86_64/libflexbox.so deleted file mode 100644 index 561396722c7..00000000000 --- a/android/sdk/src/main/jni/third_party/layout/x86_64/libflexbox.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c70ef99c2603527ee3dad89e756aa7d11379d6e83db024550b08bbe206ec7ead -size 289096 diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/CMakeLists.txt b/android/sdk/src/main/jni/third_party/v8/latest/official-release/CMakeLists.txt deleted file mode 100644 index ca93711ceed..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -cmake_minimum_required(VERSION 3.4.1) - -project(v8) - -set(V8_LIBRARY_PATH ${CMAKE_CURRENT_SOURCE_DIR}/libs/${ANDROID_ABI} PARENT_SCOPE) -set(V8_LIBRARY_NAME libv8_monolith.a PARENT_SCOPE) - -set(V8_DEFINITIONS) -list(APPEND V8_DEFINITIONS "-DV8_IMMINENT_DEPRECATION_WARNINGS;") -list(APPEND V8_DEFINITIONS "-DV8_DEPRECATION_WARNINGS;") -if ((${ANDROID_ABI} STREQUAL "arm64-v8a") OR (${ANDROID_ABI} STREQUAL "x86_64")) - list(APPEND V8_DEFINITIONS "-DV8_COMPRESS_POINTERS;") -endif() -set(V8_DEFINITIONS ${V8_DEFINITIONS} PARENT_SCOPE) - -set(V8_INCLUDE_DIRECTORIES) -string(APPEND V8_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/include;") -string(APPEND V8_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/include/v8;") -set(V8_INCLUDE_DIRECTORIES ${V8_INCLUDE_DIRECTORIES} PARENT_SCOPE) - -set(V8_LINKING_MODE static PARENT_SCOPE) diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/README.md b/android/sdk/src/main/jni/third_party/v8/latest/official-release/README.md deleted file mode 100644 index d336fdb7df8..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/README.md +++ /dev/null @@ -1,8 +0,0 @@ -This v8 release is auto generated by Github Action([985784432][2]). - -_Do NOT modify this release manually._ - -Refs: [9.1.75][1] - -[1]: https://github.com/v8/v8/tree/9.1.75 -[2]: https://github.com/Tencent/Hippy/actions/runs/985784432 \ No newline at end of file diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/allocation.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/allocation.h deleted file mode 100644 index a3112dd61fb..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/allocation.h +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_ALLOCATION_H_ -#define INCLUDE_CPPGC_ALLOCATION_H_ - -#include -#include -#include -#include -#include -#include - -#include "cppgc/custom-space.h" -#include "cppgc/internal/api-constants.h" -#include "cppgc/internal/gc-info.h" -#include "cppgc/type-traits.h" -#include "v8config.h" // NOLINT(build/include_directory) - -namespace cppgc { - -/** - * AllocationHandle is used to allocate garbage-collected objects. - */ -class AllocationHandle; - -namespace internal { - -class V8_EXPORT MakeGarbageCollectedTraitInternal { - protected: - static inline void MarkObjectAsFullyConstructed(const void* payload) { - // See api_constants for an explanation of the constants. - std::atomic* atomic_mutable_bitfield = - reinterpret_cast*>( - const_cast(reinterpret_cast( - reinterpret_cast(payload) - - api_constants::kFullyConstructedBitFieldOffsetFromPayload))); - // It's safe to split use load+store here (instead of a read-modify-write - // operation), since it's guaranteed that this 16-bit bitfield is only - // modified by a single thread. This is cheaper in terms of code bloat (on - // ARM) and performance. - uint16_t value = atomic_mutable_bitfield->load(std::memory_order_relaxed); - value |= api_constants::kFullyConstructedBitMask; - atomic_mutable_bitfield->store(value, std::memory_order_release); - } - - template - struct SpacePolicy { - static void* Allocate(AllocationHandle& handle, size_t size) { - // Custom space. - static_assert(std::is_base_of::value, - "Custom space must inherit from CustomSpaceBase."); - return MakeGarbageCollectedTraitInternal::Allocate( - handle, size, internal::GCInfoTrait::Index(), - CustomSpace::kSpaceIndex); - } - }; - - template - struct SpacePolicy { - static void* Allocate(AllocationHandle& handle, size_t size) { - // Default space. - return MakeGarbageCollectedTraitInternal::Allocate( - handle, size, internal::GCInfoTrait::Index()); - } - }; - - private: - static void* Allocate(cppgc::AllocationHandle& handle, size_t size, - GCInfoIndex index); - static void* Allocate(cppgc::AllocationHandle& handle, size_t size, - GCInfoIndex index, CustomSpaceIndex space_index); - - friend class HeapObjectHeader; -}; - -} // namespace internal - -/** - * Base trait that provides utilities for advancers users that have custom - * allocation needs (e.g., overriding size). It's expected that users override - * MakeGarbageCollectedTrait (see below) and inherit from - * MakeGarbageCollectedTraitBase and make use of the low-level primitives - * offered to allocate and construct an object. - */ -template -class MakeGarbageCollectedTraitBase - : private internal::MakeGarbageCollectedTraitInternal { - private: - static_assert(internal::IsGarbageCollectedType::value, - "T needs to be a garbage collected object"); - static_assert(!IsGarbageCollectedWithMixinTypeV || - sizeof(T) <= - internal::api_constants::kLargeObjectSizeThreshold, - "GarbageCollectedMixin may not be a large object"); - - protected: - /** - * Allocates memory for an object of type T. - * - * \param handle AllocationHandle identifying the heap to allocate the object - * on. - * \param size The size that should be reserved for the object. - * \returns the memory to construct an object of type T on. - */ - V8_INLINE static void* Allocate(AllocationHandle& handle, size_t size) { - static_assert( - std::is_base_of::value, - "U of GarbageCollected must be a base of T. Check " - "GarbageCollected base class inheritance."); - return SpacePolicy< - typename internal::GCInfoFolding< - T, typename T::ParentMostGarbageCollectedType>::ResultType, - typename SpaceTrait::Space>::Allocate(handle, size); - } - - /** - * Marks an object as fully constructed, resulting in precise handling by the - * garbage collector. - * - * \param payload The base pointer the object is allocated at. - */ - V8_INLINE static void MarkObjectAsFullyConstructed(const void* payload) { - internal::MakeGarbageCollectedTraitInternal::MarkObjectAsFullyConstructed( - payload); - } -}; - -/** - * Passed to MakeGarbageCollected to specify how many bytes should be appended - * to the allocated object. - * - * Example: - * \code - * class InlinedArray final : public GarbageCollected { - * public: - * explicit InlinedArray(size_t bytes) : size(bytes), byte_array(this + 1) {} - * void Trace(Visitor*) const {} - - * size_t size; - * char* byte_array; - * }; - * - * auto* inlined_array = MakeGarbageCollectedbyte_array[i]); - * } - * \endcode - */ -struct AdditionalBytes { - constexpr explicit AdditionalBytes(size_t bytes) : value(bytes) {} - const size_t value; -}; - -/** - * Default trait class that specifies how to construct an object of type T. - * Advanced users may override how an object is constructed using the utilities - * that are provided through MakeGarbageCollectedTraitBase. - * - * Any trait overriding construction must - * - allocate through `MakeGarbageCollectedTraitBase::Allocate`; - * - mark the object as fully constructed using - * `MakeGarbageCollectedTraitBase::MarkObjectAsFullyConstructed`; - */ -template -class MakeGarbageCollectedTrait : public MakeGarbageCollectedTraitBase { - public: - template - static T* Call(AllocationHandle& handle, Args&&... args) { - void* memory = - MakeGarbageCollectedTraitBase::Allocate(handle, sizeof(T)); - T* object = ::new (memory) T(std::forward(args)...); - MakeGarbageCollectedTraitBase::MarkObjectAsFullyConstructed(object); - return object; - } - - template - static T* Call(AllocationHandle& handle, AdditionalBytes additional_bytes, - Args&&... args) { - void* memory = MakeGarbageCollectedTraitBase::Allocate( - handle, sizeof(T) + additional_bytes.value); - T* object = ::new (memory) T(std::forward(args)...); - MakeGarbageCollectedTraitBase::MarkObjectAsFullyConstructed(object); - return object; - } -}; - -/** - * Allows users to specify a post-construction callback for specific types. The - * callback is invoked on the instance of type T right after it has been - * constructed. This can be useful when the callback requires a - * fully-constructed object to be able to dispatch to virtual methods. - */ -template -struct PostConstructionCallbackTrait { - static void Call(T*) {} -}; - -/** - * Constructs a managed object of type T where T transitively inherits from - * GarbageCollected. - * - * \param args List of arguments with which an instance of T will be - * constructed. - * \returns an instance of type T. - */ -template -V8_INLINE T* MakeGarbageCollected(AllocationHandle& handle, Args&&... args) { - T* object = - MakeGarbageCollectedTrait::Call(handle, std::forward(args)...); - PostConstructionCallbackTrait::Call(object); - return object; -} - -/** - * Constructs a managed object of type T where T transitively inherits from - * GarbageCollected. Created objects will have additional bytes appended to - * it. Allocated memory would suffice for `sizeof(T) + additional_bytes`. - * - * \param additional_bytes Denotes how many bytes to append to T. - * \param args List of arguments with which an instance of T will be - * constructed. - * \returns an instance of type T. - */ -template -V8_INLINE T* MakeGarbageCollected(AllocationHandle& handle, - AdditionalBytes additional_bytes, - Args&&... args) { - T* object = MakeGarbageCollectedTrait::Call(handle, additional_bytes, - std::forward(args)...); - PostConstructionCallbackTrait::Call(object); - return object; -} - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_ALLOCATION_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/common.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/common.h deleted file mode 100644 index b6dbff3dd6f..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/common.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_COMMON_H_ -#define INCLUDE_CPPGC_COMMON_H_ - -// TODO(chromium:1056170): Remove dependency on v8. -#include "v8config.h" // NOLINT(build/include_directory) - -namespace cppgc { - -/** - * Indicator for the stack state of the embedder. - */ -enum class EmbedderStackState { - /** - * Stack may contain interesting heap pointers. - */ - kMayContainHeapPointers, - /** - * Stack does not contain any interesting heap pointers. - */ - kNoHeapPointers, -}; - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_COMMON_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/cross-thread-persistent.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/cross-thread-persistent.h deleted file mode 100644 index c8751e1d641..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/cross-thread-persistent.h +++ /dev/null @@ -1,465 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_ -#define INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_ - -#include - -#include "cppgc/internal/persistent-node.h" -#include "cppgc/internal/pointer-policies.h" -#include "cppgc/persistent.h" -#include "cppgc/visitor.h" - -namespace cppgc { -namespace internal { - -// Wrapper around PersistentBase that allows accessing poisoned memory when -// using ASAN. This is needed as the GC of the heap that owns the value -// of a CTP, may clear it (heap termination, weakness) while the object -// holding the CTP may be poisoned as itself may be deemed dead. -class CrossThreadPersistentBase : public PersistentBase { - public: - CrossThreadPersistentBase() = default; - explicit CrossThreadPersistentBase(const void* raw) : PersistentBase(raw) {} - - V8_CLANG_NO_SANITIZE("address") const void* GetValueFromGC() const { - return raw_; - } - - V8_CLANG_NO_SANITIZE("address") - PersistentNode* GetNodeFromGC() const { return node_; } - - V8_CLANG_NO_SANITIZE("address") - void ClearFromGC() const { - raw_ = nullptr; - SetNodeSafe(nullptr); - } - - // GetNodeSafe() can be used for a thread-safe IsValid() check in a - // double-checked locking pattern. See ~BasicCrossThreadPersistent. - PersistentNode* GetNodeSafe() const { - return reinterpret_cast*>(&node_)->load( - std::memory_order_acquire); - } - - // The GC writes using SetNodeSafe() while holding the lock. - V8_CLANG_NO_SANITIZE("address") - void SetNodeSafe(PersistentNode* value) const { -#if defined(__has_feature) -#if __has_feature(address_sanitizer) -#define V8_IS_ASAN 1 -#endif -#endif - -#ifdef V8_IS_ASAN - __atomic_store(&node_, &value, __ATOMIC_RELEASE); -#else // !V8_IS_ASAN - // Non-ASAN builds can use atomics. This also covers MSVC which does not - // have the __atomic_store intrinsic. - reinterpret_cast*>(&node_)->store( - value, std::memory_order_release); -#endif // !V8_IS_ASAN - -#undef V8_IS_ASAN - } -}; - -template -class BasicCrossThreadPersistent final : public CrossThreadPersistentBase, - public LocationPolicy, - private WeaknessPolicy, - private CheckingPolicy { - public: - using typename WeaknessPolicy::IsStrongPersistent; - using PointeeType = T; - - ~BasicCrossThreadPersistent() { - // This implements fast path for destroying empty/sentinel. - // - // Simplified version of `AssignUnsafe()` to allow calling without a - // complete type `T`. Uses double-checked locking with a simple thread-safe - // check for a valid handle based on a node. - if (GetNodeSafe()) { - PersistentRegionLock guard; - const void* old_value = GetValue(); - // The fast path check (GetNodeSafe()) does not acquire the lock. Recheck - // validity while holding the lock to ensure the reference has not been - // cleared. - if (IsValid(old_value)) { - CrossThreadPersistentRegion& region = - this->GetPersistentRegion(old_value); - region.FreeNode(GetNode()); - SetNode(nullptr); - } else { - CPPGC_DCHECK(!GetNode()); - } - } - // No need to call SetValue() as the handle is not used anymore. This can - // leave behind stale sentinel values but will always destroy the underlying - // node. - } - - BasicCrossThreadPersistent( - const SourceLocation& loc = SourceLocation::Current()) - : LocationPolicy(loc) {} - - BasicCrossThreadPersistent( - std::nullptr_t, const SourceLocation& loc = SourceLocation::Current()) - : LocationPolicy(loc) {} - - BasicCrossThreadPersistent( - SentinelPointer s, const SourceLocation& loc = SourceLocation::Current()) - : CrossThreadPersistentBase(s), LocationPolicy(loc) {} - - BasicCrossThreadPersistent( - T* raw, const SourceLocation& loc = SourceLocation::Current()) - : CrossThreadPersistentBase(raw), LocationPolicy(loc) { - if (!IsValid(raw)) return; - PersistentRegionLock guard; - CrossThreadPersistentRegion& region = this->GetPersistentRegion(raw); - SetNode(region.AllocateNode(this, &Trace)); - this->CheckPointer(raw); - } - - class UnsafeCtorTag { - private: - UnsafeCtorTag() = default; - template - friend class BasicCrossThreadPersistent; - }; - - BasicCrossThreadPersistent( - UnsafeCtorTag, T* raw, - const SourceLocation& loc = SourceLocation::Current()) - : CrossThreadPersistentBase(raw), LocationPolicy(loc) { - if (!IsValid(raw)) return; - CrossThreadPersistentRegion& region = this->GetPersistentRegion(raw); - SetNode(region.AllocateNode(this, &Trace)); - this->CheckPointer(raw); - } - - BasicCrossThreadPersistent( - T& raw, const SourceLocation& loc = SourceLocation::Current()) - : BasicCrossThreadPersistent(&raw, loc) {} - - template ::value>> - BasicCrossThreadPersistent( - internal::BasicMember - member, - const SourceLocation& loc = SourceLocation::Current()) - : BasicCrossThreadPersistent(member.Get(), loc) {} - - BasicCrossThreadPersistent( - const BasicCrossThreadPersistent& other, - const SourceLocation& loc = SourceLocation::Current()) - : BasicCrossThreadPersistent(loc) { - // Invoke operator=. - *this = other; - } - - // Heterogeneous ctor. - template ::value>> - BasicCrossThreadPersistent( - const BasicCrossThreadPersistent& other, - const SourceLocation& loc = SourceLocation::Current()) - : BasicCrossThreadPersistent(loc) { - *this = other; - } - - BasicCrossThreadPersistent( - BasicCrossThreadPersistent&& other, - const SourceLocation& loc = SourceLocation::Current()) noexcept { - // Invoke operator=. - *this = std::move(other); - } - - BasicCrossThreadPersistent& operator=( - const BasicCrossThreadPersistent& other) { - PersistentRegionLock guard; - AssignSafe(guard, other.Get()); - return *this; - } - - template ::value>> - BasicCrossThreadPersistent& operator=( - const BasicCrossThreadPersistent& other) { - PersistentRegionLock guard; - AssignSafe(guard, other.Get()); - return *this; - } - - BasicCrossThreadPersistent& operator=(BasicCrossThreadPersistent&& other) { - if (this == &other) return *this; - Clear(); - PersistentRegionLock guard; - PersistentBase::operator=(std::move(other)); - LocationPolicy::operator=(std::move(other)); - if (!IsValid(GetValue())) return *this; - GetNode()->UpdateOwner(this); - other.SetValue(nullptr); - other.SetNode(nullptr); - this->CheckPointer(Get()); - return *this; - } - - /** - * Assigns a raw pointer. - * - * Note: **Not thread-safe.** - */ - BasicCrossThreadPersistent& operator=(T* other) { - AssignUnsafe(other); - return *this; - } - - // Assignment from member. - template ::value>> - BasicCrossThreadPersistent& operator=( - internal::BasicMember - member) { - return operator=(member.Get()); - } - - /** - * Assigns a nullptr. - * - * \returns the handle. - */ - BasicCrossThreadPersistent& operator=(std::nullptr_t) { - Clear(); - return *this; - } - - /** - * Assigns the sentinel pointer. - * - * \returns the handle. - */ - BasicCrossThreadPersistent& operator=(SentinelPointer s) { - PersistentRegionLock guard; - AssignSafe(guard, s); - return *this; - } - - /** - * Returns a pointer to the stored object. - * - * Note: **Not thread-safe.** - * - * \returns a pointer to the stored object. - */ - // CFI cast exemption to allow passing SentinelPointer through T* and support - // heterogeneous assignments between different Member and Persistent handles - // based on their actual types. - V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") T* Get() const { - return static_cast(const_cast(GetValue())); - } - - /** - * Clears the stored object. - */ - void Clear() { - PersistentRegionLock guard; - AssignSafe(guard, nullptr); - } - - /** - * Returns a pointer to the stored object and releases it. - * - * Note: **Not thread-safe.** - * - * \returns a pointer to the stored object. - */ - T* Release() { - T* result = Get(); - Clear(); - return result; - } - - /** - * Conversio to boolean. - * - * Note: **Not thread-safe.** - * - * \returns true if an actual object has been stored and false otherwise. - */ - explicit operator bool() const { return Get(); } - - /** - * Conversion to object of type T. - * - * Note: **Not thread-safe.** - * - * \returns the object. - */ - operator T*() const { return Get(); } - - /** - * Dereferences the stored object. - * - * Note: **Not thread-safe.** - */ - T* operator->() const { return Get(); } - T& operator*() const { return *Get(); } - - template - BasicCrossThreadPersistent - To() const { - using OtherBasicCrossThreadPersistent = - BasicCrossThreadPersistent; - PersistentRegionLock guard; - return OtherBasicCrossThreadPersistent( - typename OtherBasicCrossThreadPersistent::UnsafeCtorTag(), - static_cast(Get())); - } - - template ::IsStrongPersistent::value>::type> - BasicCrossThreadPersistent - Lock() const { - return BasicCrossThreadPersistent< - U, internal::StrongCrossThreadPersistentPolicy>(*this); - } - - private: - static bool IsValid(const void* ptr) { - return ptr && ptr != kSentinelPointer; - } - - static void Trace(Visitor* v, const void* ptr) { - const auto* handle = static_cast(ptr); - v->TraceRoot(*handle, handle->Location()); - } - - void AssignUnsafe(T* ptr) { - const void* old_value = GetValue(); - if (IsValid(old_value)) { - PersistentRegionLock guard; - old_value = GetValue(); - // The fast path check (IsValid()) does not acquire the lock. Reload - // the value to ensure the reference has not been cleared. - if (IsValid(old_value)) { - CrossThreadPersistentRegion& region = - this->GetPersistentRegion(old_value); - if (IsValid(ptr) && (®ion == &this->GetPersistentRegion(ptr))) { - SetValue(ptr); - this->CheckPointer(ptr); - return; - } - region.FreeNode(GetNode()); - SetNode(nullptr); - } else { - CPPGC_DCHECK(!GetNode()); - } - } - SetValue(ptr); - if (!IsValid(ptr)) return; - PersistentRegionLock guard; - SetNode(this->GetPersistentRegion(ptr).AllocateNode(this, &Trace)); - this->CheckPointer(ptr); - } - - void AssignSafe(PersistentRegionLock&, T* ptr) { - PersistentRegionLock::AssertLocked(); - const void* old_value = GetValue(); - if (IsValid(old_value)) { - CrossThreadPersistentRegion& region = - this->GetPersistentRegion(old_value); - if (IsValid(ptr) && (®ion == &this->GetPersistentRegion(ptr))) { - SetValue(ptr); - this->CheckPointer(ptr); - return; - } - region.FreeNode(GetNode()); - SetNode(nullptr); - } - SetValue(ptr); - if (!IsValid(ptr)) return; - SetNode(this->GetPersistentRegion(ptr).AllocateNode(this, &Trace)); - this->CheckPointer(ptr); - } - - void ClearFromGC() const { - if (IsValid(GetValueFromGC())) { - WeaknessPolicy::GetPersistentRegion(GetValueFromGC()) - .FreeNode(GetNodeFromGC()); - CrossThreadPersistentBase::ClearFromGC(); - } - } - - // See Get() for details. - V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") - T* GetFromGC() const { - return static_cast(const_cast(GetValueFromGC())); - } - - friend class cppgc::Visitor; -}; - -template -struct IsWeak< - BasicCrossThreadPersistent> - : std::true_type {}; - -} // namespace internal - -namespace subtle { - -/** - * **DO NOT USE: Has known caveats, see below.** - * - * CrossThreadPersistent allows retaining objects from threads other than the - * thread the owning heap is operating on. - * - * Known caveats: - * - Does not protect the heap owning an object from terminating. - * - Reaching transitively through the graph is unsupported as objects may be - * moved concurrently on the thread owning the object. - */ -template -using CrossThreadPersistent = internal::BasicCrossThreadPersistent< - T, internal::StrongCrossThreadPersistentPolicy>; - -/** - * **DO NOT USE: Has known caveats, see below.** - * - * CrossThreadPersistent allows weakly retaining objects from threads other than - * the thread the owning heap is operating on. - * - * Known caveats: - * - Does not protect the heap owning an object from terminating. - * - Reaching transitively through the graph is unsupported as objects may be - * moved concurrently on the thread owning the object. - */ -template -using WeakCrossThreadPersistent = internal::BasicCrossThreadPersistent< - T, internal::WeakCrossThreadPersistentPolicy>; - -} // namespace subtle -} // namespace cppgc - -#endif // INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/custom-space.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/custom-space.h deleted file mode 100644 index 757c4fde15e..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/custom-space.h +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_CUSTOM_SPACE_H_ -#define INCLUDE_CPPGC_CUSTOM_SPACE_H_ - -#include - -namespace cppgc { - -/** - * Index identifying a custom space. - */ -struct CustomSpaceIndex { - constexpr CustomSpaceIndex(size_t value) : value(value) {} // NOLINT - size_t value; -}; - -/** - * Top-level base class for custom spaces. Users must inherit from CustomSpace - * below. - */ -class CustomSpaceBase { - public: - virtual ~CustomSpaceBase() = default; - virtual CustomSpaceIndex GetCustomSpaceIndex() const = 0; - virtual bool IsCompactable() const = 0; -}; - -/** - * Base class custom spaces should directly inherit from. The class inheriting - * from `CustomSpace` must define `kSpaceIndex` as unique space index. These - * indices need for form a sequence starting at 0. - * - * Example: - * \code - * class CustomSpace1 : public CustomSpace { - * public: - * static constexpr CustomSpaceIndex kSpaceIndex = 0; - * }; - * class CustomSpace2 : public CustomSpace { - * public: - * static constexpr CustomSpaceIndex kSpaceIndex = 1; - * }; - * \endcode - */ -template -class CustomSpace : public CustomSpaceBase { - public: - /** - * Compaction is only supported on spaces that manually manage slots - * recording. - */ - static constexpr bool kSupportsCompaction = false; - - CustomSpaceIndex GetCustomSpaceIndex() const final { - return ConcreteCustomSpace::kSpaceIndex; - } - bool IsCompactable() const final { - return ConcreteCustomSpace::kSupportsCompaction; - } -}; - -/** - * User-overridable trait that allows pinning types to custom spaces. - */ -template -struct SpaceTrait { - using Space = void; -}; - -namespace internal { - -template -struct IsAllocatedOnCompactableSpaceImpl { - static constexpr bool value = CustomSpace::kSupportsCompaction; -}; - -template <> -struct IsAllocatedOnCompactableSpaceImpl { - // Non-custom spaces are by default not compactable. - static constexpr bool value = false; -}; - -template -struct IsAllocatedOnCompactableSpace { - public: - static constexpr bool value = - IsAllocatedOnCompactableSpaceImpl::Space>::value; -}; - -} // namespace internal - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_CUSTOM_SPACE_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/default-platform.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/default-platform.h deleted file mode 100644 index 2ccdeddd837..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/default-platform.h +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_DEFAULT_PLATFORM_H_ -#define INCLUDE_CPPGC_DEFAULT_PLATFORM_H_ - -#include -#include - -#include "cppgc/platform.h" -#include "libplatform/libplatform.h" -#include "v8config.h" // NOLINT(build/include_directory) - -namespace cppgc { - -/** - * Platform provided by cppgc. Uses V8's DefaultPlatform provided by - * libplatform internally. Exception: `GetForegroundTaskRunner()`, see below. - */ -class V8_EXPORT DefaultPlatform : public Platform { - public: - /** - * Use this method instead of 'cppgc::InitializeProcess' when using - * 'cppgc::DefaultPlatform'. 'cppgc::DefaultPlatform::InitializeProcess' - * will initialize cppgc and v8 if needed (for non-standalone builds). - * - * \param platform DefaultPlatform instance used to initialize cppgc/v8. - */ - static void InitializeProcess(DefaultPlatform* platform); - - using IdleTaskSupport = v8::platform::IdleTaskSupport; - explicit DefaultPlatform( - int thread_pool_size = 0, - IdleTaskSupport idle_task_support = IdleTaskSupport::kDisabled, - std::unique_ptr tracing_controller = {}) - : v8_platform_(v8::platform::NewDefaultPlatform( - thread_pool_size, idle_task_support, - v8::platform::InProcessStackDumping::kDisabled, - std::move(tracing_controller))) {} - - cppgc::PageAllocator* GetPageAllocator() override { - return v8_platform_->GetPageAllocator(); - } - - double MonotonicallyIncreasingTime() override { - return v8_platform_->MonotonicallyIncreasingTime(); - } - - std::shared_ptr GetForegroundTaskRunner() override { - // V8's default platform creates a new task runner when passed the - // `v8::Isolate` pointer the first time. For non-default platforms this will - // require getting the appropriate task runner. - return v8_platform_->GetForegroundTaskRunner(kNoIsolate); - } - - std::unique_ptr PostJob( - cppgc::TaskPriority priority, - std::unique_ptr job_task) override { - return v8_platform_->PostJob(priority, std::move(job_task)); - } - - TracingController* GetTracingController() override { - return v8_platform_->GetTracingController(); - } - - protected: - static constexpr v8::Isolate* kNoIsolate = nullptr; - - std::unique_ptr v8_platform_; -}; - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_DEFAULT_PLATFORM_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/ephemeron-pair.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/ephemeron-pair.h deleted file mode 100644 index e16cf1f0aa2..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/ephemeron-pair.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_EPHEMERON_PAIR_H_ -#define INCLUDE_CPPGC_EPHEMERON_PAIR_H_ - -#include "cppgc/liveness-broker.h" -#include "cppgc/member.h" - -namespace cppgc { - -/** - * An ephemeron pair is used to conditionally retain an object. - * The `value` will be kept alive only if the `key` is alive. - */ -template -struct EphemeronPair { - EphemeronPair(K* k, V* v) : key(k), value(v) {} - WeakMember key; - Member value; - - void ClearValueIfKeyIsDead(const LivenessBroker& broker) { - if (!broker.IsHeapObjectAlive(key)) value = nullptr; - } -}; - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_EPHEMERON_PAIR_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/explicit-management.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/explicit-management.h deleted file mode 100644 index cdb6af48586..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/explicit-management.h +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_EXPLICIT_MANAGEMENT_H_ -#define INCLUDE_CPPGC_EXPLICIT_MANAGEMENT_H_ - -#include - -#include "cppgc/allocation.h" -#include "cppgc/internal/logging.h" -#include "cppgc/type-traits.h" - -namespace cppgc { - -class HeapHandle; - -namespace internal { - -V8_EXPORT void FreeUnreferencedObject(HeapHandle&, void*); -V8_EXPORT bool Resize(void*, size_t); - -} // namespace internal - -namespace subtle { - -/** - * Informs the garbage collector that `object` can be immediately reclaimed. The - * destructor may not be invoked immediately but only on next garbage - * collection. - * - * It is up to the embedder to guarantee that no other object holds a reference - * to `object` after calling `FreeUnreferencedObject()`. In case such a - * reference exists, it's use results in a use-after-free. - * - * To aid in using the API, `FreeUnreferencedObject()` may be called from - * destructors on objects that would be reclaimed in the same garbage collection - * cycle. - * - * \param heap_handle The corresponding heap. - * \param object Reference to an object that is of type `GarbageCollected` and - * should be immediately reclaimed. - */ -template -void FreeUnreferencedObject(HeapHandle& heap_handle, T& object) { - static_assert(IsGarbageCollectedTypeV, - "Object must be of type GarbageCollected."); - internal::FreeUnreferencedObject(heap_handle, &object); -} - -/** - * Tries to resize `object` of type `T` with additional bytes on top of - * sizeof(T). Resizing is only useful with trailing inlined storage, see e.g. - * `MakeGarbageCollected(AllocationHandle&, AdditionalBytes)`. - * - * `Resize()` performs growing or shrinking as needed and may skip the operation - * for internal reasons, see return value. - * - * It is up to the embedder to guarantee that in case of shrinking a larger - * object down, the reclaimed area is not used anymore. Any subsequent use - * results in a use-after-free. - * - * The `object` must be live when calling `Resize()`. - * - * \param object Reference to an object that is of type `GarbageCollected` and - * should be resized. - * \param additional_bytes Bytes in addition to sizeof(T) that the object should - * provide. - * \returns true when the operation was successful and the result can be relied - * on, and false otherwise. - */ -template -bool Resize(T& object, AdditionalBytes additional_bytes) { - static_assert(IsGarbageCollectedTypeV, - "Object must be of type GarbageCollected."); - return internal::Resize(&object, sizeof(T) + additional_bytes.value); -} - -} // namespace subtle -} // namespace cppgc - -#endif // INCLUDE_CPPGC_EXPLICIT_MANAGEMENT_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/garbage-collected.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/garbage-collected.h deleted file mode 100644 index a3839e1baa5..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/garbage-collected.h +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_GARBAGE_COLLECTED_H_ -#define INCLUDE_CPPGC_GARBAGE_COLLECTED_H_ - -#include - -#include "cppgc/internal/api-constants.h" -#include "cppgc/platform.h" -#include "cppgc/trace-trait.h" -#include "cppgc/type-traits.h" - -namespace cppgc { - -class Visitor; - -namespace internal { - -class GarbageCollectedBase { - public: - // Must use MakeGarbageCollected. - void* operator new(size_t) = delete; - void* operator new[](size_t) = delete; - // The garbage collector is taking care of reclaiming the object. Also, - // virtual destructor requires an unambiguous, accessible 'operator delete'. - void operator delete(void*) { -#ifdef V8_ENABLE_CHECKS - internal::Abort(); -#endif // V8_ENABLE_CHECKS - } - void operator delete[](void*) = delete; - - protected: - GarbageCollectedBase() = default; -}; - -} // namespace internal - -/** - * Base class for managed objects. Only descendent types of `GarbageCollected` - * can be constructed using `MakeGarbageCollected()`. Must be inherited from as - * left-most base class. - * - * Types inheriting from GarbageCollected must provide a method of - * signature `void Trace(cppgc::Visitor*) const` that dispatchs all managed - * pointers to the visitor and delegates to garbage-collected base classes. - * The method must be virtual if the type is not directly a child of - * GarbageCollected and marked as final. - * - * \code - * // Example using final class. - * class FinalType final : public GarbageCollected { - * public: - * void Trace(cppgc::Visitor* visitor) const { - * // Dispatch using visitor->Trace(...); - * } - * }; - * - * // Example using non-final base class. - * class NonFinalBase : public GarbageCollected { - * public: - * virtual void Trace(cppgc::Visitor*) const {} - * }; - * - * class FinalChild final : public NonFinalBase { - * public: - * void Trace(cppgc::Visitor* visitor) const final { - * // Dispatch using visitor->Trace(...); - * NonFinalBase::Trace(visitor); - * } - * }; - * \endcode - */ -template -class GarbageCollected : public internal::GarbageCollectedBase { - public: - using IsGarbageCollectedTypeMarker = void; - using ParentMostGarbageCollectedType = T; - - protected: - GarbageCollected() = default; -}; - -/** - * Base class for managed mixin objects. Such objects cannot be constructed - * directly but must be mixed into the inheritance hierarchy of a - * GarbageCollected object. - * - * Types inheriting from GarbageCollectedMixin must override a virtual method - * of signature `void Trace(cppgc::Visitor*) const` that dispatchs all managed - * pointers to the visitor and delegates to base classes. - * - * \code - * class Mixin : public GarbageCollectedMixin { - * public: - * void Trace(cppgc::Visitor* visitor) const override { - * // Dispatch using visitor->Trace(...); - * } - * }; - * \endcode - */ -class GarbageCollectedMixin : public internal::GarbageCollectedBase { - public: - using IsGarbageCollectedMixinTypeMarker = void; - - /** - * This Trace method must be overriden by objects inheriting from - * GarbageCollectedMixin. - */ - virtual void Trace(cppgc::Visitor*) const {} -}; - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_GARBAGE_COLLECTED_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/heap-consistency.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/heap-consistency.h deleted file mode 100644 index 8e603d5d8af..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/heap-consistency.h +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_HEAP_CONSISTENCY_H_ -#define INCLUDE_CPPGC_HEAP_CONSISTENCY_H_ - -#include - -#include "cppgc/internal/write-barrier.h" -#include "cppgc/macros.h" -#include "cppgc/trace-trait.h" -#include "v8config.h" // NOLINT(build/include_directory) - -namespace cppgc { - -class HeapHandle; - -namespace subtle { - -/** - * **DO NOT USE: Use the appropriate managed types.** - * - * Consistency helpers that aid in maintaining a consistent internal state of - * the garbage collector. - */ -class HeapConsistency final { - public: - using WriteBarrierParams = internal::WriteBarrier::Params; - using WriteBarrierType = internal::WriteBarrier::Type; - - /** - * Gets the required write barrier type for a specific write. - * - * \param slot Slot containing the pointer to the object. The slot itself - * must reside in an object that has been allocated using - * `MakeGarbageCollected()`. - * \param value The pointer to the object. May be an interior pointer to an - * interface of the actual object. - * \param params Parameters that may be used for actual write barrier calls. - * Only filled if return value indicates that a write barrier is needed. The - * contents of the `params` are an implementation detail. - * \returns whether a write barrier is needed and which barrier to invoke. - */ - static V8_INLINE WriteBarrierType GetWriteBarrierType( - const void* slot, const void* value, WriteBarrierParams& params) { - return internal::WriteBarrier::GetWriteBarrierType(slot, value, params); - } - - /** - * Gets the required write barrier type for a specific write. - * - * \param slot Slot to some part of an object. The object must not necessarily - have been allocated using `MakeGarbageCollected()` but can also live - off-heap or on stack. - * \param params Parameters that may be used for actual write barrier calls. - * Only filled if return value indicates that a write barrier is needed. The - * contents of the `params` are an implementation detail. - * \param callback Callback returning the corresponding heap handle. The - * callback is only invoked if the heap cannot otherwise be figured out. The - * callback must not allocate. - * \returns whether a write barrier is needed and which barrier to invoke. - */ - template - static V8_INLINE WriteBarrierType - GetWriteBarrierType(const void* slot, WriteBarrierParams& params, - HeapHandleCallback callback) { - return internal::WriteBarrier::GetWriteBarrierType(slot, params, callback); - } - - /** - * Gets the required write barrier type for a specific write. - * This version is meant to be used in conjunction with with a marking write - * barrier barrier which doesn't consider the slot. - * - * \param value The pointer to the object. May be an interior pointer to an - * interface of the actual object. - * \param params Parameters that may be used for actual write barrier calls. - * Only filled if return value indicates that a write barrier is needed. The - * contents of the `params` are an implementation detail. - * \returns whether a write barrier is needed and which barrier to invoke. - */ - static V8_INLINE WriteBarrierType - GetWriteBarrierType(const void* value, WriteBarrierParams& params) { - return internal::WriteBarrier::GetWriteBarrierType(value, params); - } - - /** - * Conservative Dijkstra-style write barrier that processes an object if it - * has not yet been processed. - * - * \param params The parameters retrieved from `GetWriteBarrierType()`. - * \param object The pointer to the object. May be an interior pointer to a - * an interface of the actual object. - */ - static V8_INLINE void DijkstraWriteBarrier(const WriteBarrierParams& params, - const void* object) { - internal::WriteBarrier::DijkstraMarkingBarrier(params, object); - } - - /** - * Conservative Dijkstra-style write barrier that processes a range of - * elements if they have not yet been processed. - * - * \param params The parameters retrieved from `GetWriteBarrierType()`. - * \param first_element Pointer to the first element that should be processed. - * The slot itself must reside in an object that has been allocated using - * `MakeGarbageCollected()`. - * \param element_size Size of the element in bytes. - * \param number_of_elements Number of elements that should be processed, - * starting with `first_element`. - * \param trace_callback The trace callback that should be invoked for each - * element if necessary. - */ - static V8_INLINE void DijkstraWriteBarrierRange( - const WriteBarrierParams& params, const void* first_element, - size_t element_size, size_t number_of_elements, - TraceCallback trace_callback) { - internal::WriteBarrier::DijkstraMarkingBarrierRange( - params, first_element, element_size, number_of_elements, - trace_callback); - } - - /** - * Steele-style write barrier that re-processes an object if it has already - * been processed. - * - * \param params The parameters retrieved from `GetWriteBarrierType()`. - * \param object The pointer to the object which must point to an object that - * has been allocated using `MakeGarbageCollected()`. Interior pointers are - * not supported. - */ - static V8_INLINE void SteeleWriteBarrier(const WriteBarrierParams& params, - const void* object) { - internal::WriteBarrier::SteeleMarkingBarrier(params, object); - } - - /** - * Generational barrier for maintaining consistency when running with multiple - * generations. - * - * \param params The parameters retrieved from `GetWriteBarrierType()`. - * \param slot Slot containing the pointer to the object. The slot itself - * must reside in an object that has been allocated using - * `MakeGarbageCollected()`. - */ - static V8_INLINE void GenerationalBarrier(const WriteBarrierParams& params, - const void* slot) { - internal::WriteBarrier::GenerationalBarrier(params, slot); - } - - private: - HeapConsistency() = delete; -}; - -/** - * Disallows garbage collection finalizations. Any garbage collection triggers - * result in a crash when in this scope. - * - * Note that the garbage collector already covers paths that can lead to garbage - * collections, so user code does not require checking - * `IsGarbageCollectionAllowed()` before allocations. - */ -class V8_EXPORT V8_NODISCARD DisallowGarbageCollectionScope final { - CPPGC_STACK_ALLOCATED(); - - public: - /** - * \returns whether garbage collections are currently allowed. - */ - static bool IsGarbageCollectionAllowed(HeapHandle& heap_handle); - - /** - * Enters a disallow garbage collection scope. Must be paired with `Leave()`. - * Prefer a scope instance of `DisallowGarbageCollectionScope`. - * - * \param heap_handle The corresponding heap. - */ - static void Enter(HeapHandle& heap_handle); - - /** - * Leaves a disallow garbage collection scope. Must be paired with `Enter()`. - * Prefer a scope instance of `DisallowGarbageCollectionScope`. - * - * \param heap_handle The corresponding heap. - */ - static void Leave(HeapHandle& heap_handle); - - /** - * Constructs a scoped object that automatically enters and leaves a disallow - * garbage collection scope based on its lifetime. - * - * \param heap_handle The corresponding heap. - */ - explicit DisallowGarbageCollectionScope(HeapHandle& heap_handle); - ~DisallowGarbageCollectionScope(); - - DisallowGarbageCollectionScope(const DisallowGarbageCollectionScope&) = - delete; - DisallowGarbageCollectionScope& operator=( - const DisallowGarbageCollectionScope&) = delete; - - private: - HeapHandle& heap_handle_; -}; - -/** - * Avoids invoking garbage collection finalizations. Already running garbage - * collection phase are unaffected by this scope. - * - * Should only be used temporarily as the scope has an impact on memory usage - * and follow up garbage collections. - */ -class V8_EXPORT V8_NODISCARD NoGarbageCollectionScope final { - CPPGC_STACK_ALLOCATED(); - - public: - /** - * Enters a no garbage collection scope. Must be paired with `Leave()`. Prefer - * a scope instance of `NoGarbageCollectionScope`. - * - * \param heap_handle The corresponding heap. - */ - static void Enter(HeapHandle& heap_handle); - - /** - * Leaves a no garbage collection scope. Must be paired with `Enter()`. Prefer - * a scope instance of `NoGarbageCollectionScope`. - * - * \param heap_handle The corresponding heap. - */ - static void Leave(HeapHandle& heap_handle); - - /** - * Constructs a scoped object that automatically enters and leaves a no - * garbage collection scope based on its lifetime. - * - * \param heap_handle The corresponding heap. - */ - explicit NoGarbageCollectionScope(HeapHandle& heap_handle); - ~NoGarbageCollectionScope(); - - NoGarbageCollectionScope(const NoGarbageCollectionScope&) = delete; - NoGarbageCollectionScope& operator=(const NoGarbageCollectionScope&) = delete; - - private: - HeapHandle& heap_handle_; -}; - -} // namespace subtle -} // namespace cppgc - -#endif // INCLUDE_CPPGC_HEAP_CONSISTENCY_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/heap-state.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/heap-state.h deleted file mode 100644 index 3fd6b54a8a2..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/heap-state.h +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_HEAP_STATE_H_ -#define INCLUDE_CPPGC_HEAP_STATE_H_ - -#include "v8config.h" // NOLINT(build/include_directory) - -namespace cppgc { - -class HeapHandle; - -namespace subtle { - -/** - * Helpers to peek into heap-internal state. - */ -class V8_EXPORT HeapState final { - public: - /** - * Returns whether the garbage collector is marking. This API is experimental - * and is expected to be removed in future. - * - * \param heap_handle The corresponding heap. - * \returns true if the garbage collector is currently marking, and false - * otherwise. - */ - static bool IsMarking(const HeapHandle& heap_handle); - - /* - * Returns whether the garbage collector is sweeping. This API is experimental - * and is expected to be removed in future. - * - * \param heap_handle The corresponding heap. - * \returns true if the garbage collector is currently sweeping, and false - * otherwise. - */ - static bool IsSweeping(const HeapHandle& heap_handle); - - /** - * Returns whether the garbage collector is in the atomic pause, i.e., the - * mutator is stopped from running. This API is experimental and is expected - * to be removed in future. - * - * \param heap_handle The corresponding heap. - * \returns true if the garbage collector is currently in the atomic pause, - * and false otherwise. - */ - static bool IsInAtomicPause(const HeapHandle& heap_handle); - - /** - * Returns whether the last garbage collection was finalized conservatively - * (i.e., with a non-empty stack). This API is experimental and is expected to - * be removed in future. - * - * \param heap_handle The corresponding heap. - * \returns true if the last garbage collection was finalized conservatively, - * and false otherwise. - */ - static bool PreviousGCWasConservative(const HeapHandle& heap_handle); - - private: - HeapState() = delete; -}; - -} // namespace subtle -} // namespace cppgc - -#endif // INCLUDE_CPPGC_HEAP_STATE_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/heap-statistics.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/heap-statistics.h deleted file mode 100644 index 8e626596e5b..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/heap-statistics.h +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_HEAP_STATISTICS_H_ -#define INCLUDE_CPPGC_HEAP_STATISTICS_H_ - -#include -#include -#include -#include - -namespace cppgc { - -/** - * `HeapStatistics` contains memory consumption and utilization statistics for a - * cppgc heap. - */ -struct HeapStatistics final { - /** - * Specifies the detail level of the heap statistics. Brief statistics contain - * only the top-level allocated and used memory statistics for the entire - * heap. Detailed statistics also contain a break down per space and page, as - * well as freelist statistics and object type histograms. Note that used - * memory reported by brief statistics and detailed statistics might differ - * slightly. - */ - enum DetailLevel : uint8_t { - kBrief, - kDetailed, - }; - - /** - * Object statistics for a single type. - */ - struct ObjectStatsEntry { - /** - * Number of allocated bytes. - */ - size_t allocated_bytes; - /** - * Number of allocated objects. - */ - size_t object_count; - }; - - /** - * Page granularity statistics. For each page the statistics record the - * allocated memory size and overall used memory size for the page. - */ - struct PageStatistics { - /** Overall committed amount of memory for the page. */ - size_t committed_size_bytes = 0; - /** Resident amount of memory held by the page. */ - size_t resident_size_bytes = 0; - /** Amount of memory actually used on the page. */ - size_t used_size_bytes = 0; - /** Statistics for object allocated on the page. Filled only when - * NameProvider::HideInternalNames() is false. */ - std::vector object_statistics; - }; - - /** - * Statistics of the freelist (used only in non-large object spaces). For - * each bucket in the freelist the statistics record the bucket size, the - * number of freelist entries in the bucket, and the overall allocated memory - * consumed by these freelist entries. - */ - struct FreeListStatistics { - /** bucket sizes in the freelist. */ - std::vector bucket_size; - /** number of freelist entries per bucket. */ - std::vector free_count; - /** memory size consumed by freelist entries per size. */ - std::vector free_size; - }; - - /** - * Space granularity statistics. For each space the statistics record the - * space name, the amount of allocated memory and overall used memory for the - * space. The statistics also contain statistics for each of the space's - * pages, its freelist and the objects allocated on the space. - */ - struct SpaceStatistics { - /** The space name */ - std::string name; - /** Overall committed amount of memory for the heap. */ - size_t committed_size_bytes = 0; - /** Resident amount of memory held by the heap. */ - size_t resident_size_bytes = 0; - /** Amount of memory actually used on the space. */ - size_t used_size_bytes = 0; - /** Statistics for each of the pages in the space. */ - std::vector page_stats; - /** Statistics for the freelist of the space. */ - FreeListStatistics free_list_stats; - }; - - /** Overall committed amount of memory for the heap. */ - size_t committed_size_bytes = 0; - /** Resident amount of memory help by the heap. */ - size_t resident_size_bytes = 0; - /** Amount of memory actually used on the heap. */ - size_t used_size_bytes = 0; - /** Detail level of this HeapStatistics. */ - DetailLevel detail_level; - - /** Statistics for each of the spaces in the heap. Filled only when - * `detail_level` is `DetailLevel::kDetailed`. */ - std::vector space_stats; - - /** - * Vector of `cppgc::GarbageCollected` type names. - */ - std::vector type_names; -}; - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_HEAP_STATISTICS_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/heap.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/heap.h deleted file mode 100644 index 136c4fb44d0..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/heap.h +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_HEAP_H_ -#define INCLUDE_CPPGC_HEAP_H_ - -#include -#include -#include -#include - -#include "cppgc/common.h" -#include "cppgc/custom-space.h" -#include "cppgc/platform.h" -#include "v8config.h" // NOLINT(build/include_directory) - -/** - * cppgc - A C++ garbage collection library. - */ -namespace cppgc { - -class AllocationHandle; - -/** - * Implementation details of cppgc. Those details are considered internal and - * may change at any point in time without notice. Users should never rely on - * the contents of this namespace. - */ -namespace internal { -class Heap; -} // namespace internal - -/** - * Used for additional heap APIs. - */ -class HeapHandle; - -class V8_EXPORT Heap { - public: - /** - * Specifies the stack state the embedder is in. - */ - using StackState = EmbedderStackState; - - /** - * Specifies whether conservative stack scanning is supported. - */ - enum class StackSupport : uint8_t { - /** - * Conservative stack scan is supported. - */ - kSupportsConservativeStackScan, - /** - * Conservative stack scan is not supported. Embedders may use this option - * when using custom infrastructure that is unsupported by the library. - */ - kNoConservativeStackScan, - }; - - /** - * Specifies supported marking types - */ - enum class MarkingType : uint8_t { - /** - * Atomic stop-the-world marking. This option does not require any write - * barriers but is the most intrusive in terms of jank. - */ - kAtomic, - /** - * Incremental marking, i.e. interleave marking is the rest of the - * application on the same thread. - */ - kIncremental, - /** - * Incremental and concurrent marking. - */ - kIncrementalAndConcurrent - }; - - /** - * Specifies supported sweeping types - */ - enum class SweepingType : uint8_t { - /** - * Atomic stop-the-world sweeping. All of sweeping is performed at once. - */ - kAtomic, - /** - * Incremental and concurrent sweeping. Sweeping is split and interleaved - * with the rest of the application. - */ - kIncrementalAndConcurrent - }; - - /** - * Constraints for a Heap setup. - */ - struct ResourceConstraints { - /** - * Allows the heap to grow to some initial size in bytes before triggering - * garbage collections. This is useful when it is known that applications - * need a certain minimum heap to run to avoid repeatedly invoking the - * garbage collector when growing the heap. - */ - size_t initial_heap_size_bytes = 0; - }; - - /** - * Options specifying Heap properties (e.g. custom spaces) when initializing a - * heap through `Heap::Create()`. - */ - struct HeapOptions { - /** - * Creates reasonable defaults for instantiating a Heap. - * - * \returns the HeapOptions that can be passed to `Heap::Create()`. - */ - static HeapOptions Default() { return {}; } - - /** - * Custom spaces added to heap are required to have indices forming a - * numbered sequence starting at 0, i.e., their `kSpaceIndex` must - * correspond to the index they reside in the vector. - */ - std::vector> custom_spaces; - - /** - * Specifies whether conservative stack scan is supported. When conservative - * stack scan is not supported, the collector may try to invoke - * garbage collections using non-nestable task, which are guaranteed to have - * no interesting stack, through the provided Platform. If such tasks are - * not supported by the Platform, the embedder must take care of invoking - * the GC through `ForceGarbageCollectionSlow()`. - */ - StackSupport stack_support = StackSupport::kSupportsConservativeStackScan; - - /** - * Specifies which types of marking are supported by the heap. - */ - MarkingType marking_support = MarkingType::kIncrementalAndConcurrent; - - /** - * Specifies which types of sweeping are supported by the heap. - */ - SweepingType sweeping_support = SweepingType::kIncrementalAndConcurrent; - - /** - * Resource constraints specifying various properties that the internal - * GC scheduler follows. - */ - ResourceConstraints resource_constraints; - }; - - /** - * Creates a new heap that can be used for object allocation. - * - * \param platform implemented and provided by the embedder. - * \param options HeapOptions specifying various properties for the Heap. - * \returns a new Heap instance. - */ - static std::unique_ptr Create( - std::shared_ptr platform, - HeapOptions options = HeapOptions::Default()); - - virtual ~Heap() = default; - - /** - * Forces garbage collection. - * - * \param source String specifying the source (or caller) triggering a - * forced garbage collection. - * \param reason String specifying the reason for the forced garbage - * collection. - * \param stack_state The embedder stack state, see StackState. - */ - void ForceGarbageCollectionSlow( - const char* source, const char* reason, - StackState stack_state = StackState::kMayContainHeapPointers); - - /** - * \returns the opaque handle for allocating objects using - * `MakeGarbageCollected()`. - */ - AllocationHandle& GetAllocationHandle(); - - /** - * \returns the opaque heap handle which may be used to refer to this heap in - * other APIs. Valid as long as the underlying `Heap` is alive. - */ - HeapHandle& GetHeapHandle(); - - private: - Heap() = default; - - friend class internal::Heap; -}; - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_HEAP_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/api-constants.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/api-constants.h deleted file mode 100644 index 7253a470893..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/api-constants.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_INTERNAL_API_CONSTANTS_H_ -#define INCLUDE_CPPGC_INTERNAL_API_CONSTANTS_H_ - -#include -#include - -#include "v8config.h" // NOLINT(build/include_directory) - -namespace cppgc { -namespace internal { - -// Embedders should not rely on this code! - -// Internal constants to avoid exposing internal types on the API surface. -namespace api_constants { - -constexpr size_t kKB = 1024; -constexpr size_t kMB = kKB * 1024; -constexpr size_t kGB = kMB * 1024; - -// Offset of the uint16_t bitfield from the payload contaning the -// in-construction bit. This is subtracted from the payload pointer to get -// to the right bitfield. -static constexpr size_t kFullyConstructedBitFieldOffsetFromPayload = - 2 * sizeof(uint16_t); -// Mask for in-construction bit. -static constexpr uint16_t kFullyConstructedBitMask = uint16_t{1}; - -static constexpr size_t kPageSize = size_t{1} << 17; - -static constexpr size_t kLargeObjectSizeThreshold = kPageSize / 2; - -#if defined(CPPGC_CAGED_HEAP) -constexpr size_t kCagedHeapReservationSize = static_cast(4) * kGB; -constexpr size_t kCagedHeapReservationAlignment = kCagedHeapReservationSize; -#endif - -} // namespace api_constants - -} // namespace internal -} // namespace cppgc - -#endif // INCLUDE_CPPGC_INTERNAL_API_CONSTANTS_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/atomic-entry-flag.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/atomic-entry-flag.h deleted file mode 100644 index 5a7d3b8f8ac..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/atomic-entry-flag.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_INTERNAL_ATOMIC_ENTRY_FLAG_H_ -#define INCLUDE_CPPGC_INTERNAL_ATOMIC_ENTRY_FLAG_H_ - -#include - -namespace cppgc { -namespace internal { - -// A flag which provides a fast check whether a scope may be entered on the -// current thread, without needing to access thread-local storage or mutex. Can -// have false positives (i.e., spuriously report that it might be entered), so -// it is expected that this will be used in tandem with a precise check that the -// scope is in fact entered on that thread. -// -// Example: -// g_frobnicating_flag.MightBeEntered() && -// ThreadLocalFrobnicator().IsFrobnicating() -// -// Relaxed atomic operations are sufficient, since: -// - all accesses remain atomic -// - each thread must observe its own operations in order -// - no thread ever exits the flag more times than it enters (if used correctly) -// And so if a thread observes zero, it must be because it has observed an equal -// number of exits as entries. -class AtomicEntryFlag final { - public: - void Enter() { entries_.fetch_add(1, std::memory_order_relaxed); } - void Exit() { entries_.fetch_sub(1, std::memory_order_relaxed); } - - // Returns false only if the current thread is not between a call to Enter - // and a call to Exit. Returns true if this thread or another thread may - // currently be in the scope guarded by this flag. - bool MightBeEntered() const { - return entries_.load(std::memory_order_relaxed) != 0; - } - - private: - std::atomic_int entries_{0}; -}; - -} // namespace internal -} // namespace cppgc - -#endif // INCLUDE_CPPGC_INTERNAL_ATOMIC_ENTRY_FLAG_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/caged-heap-local-data.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/caged-heap-local-data.h deleted file mode 100644 index 5b30d670292..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/caged-heap-local-data.h +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_INTERNAL_CAGED_HEAP_LOCAL_DATA_H_ -#define INCLUDE_CPPGC_INTERNAL_CAGED_HEAP_LOCAL_DATA_H_ - -#include - -#include "cppgc/internal/api-constants.h" -#include "cppgc/internal/logging.h" -#include "cppgc/platform.h" -#include "v8config.h" // NOLINT(build/include_directory) - -namespace cppgc { -namespace internal { - -class HeapBase; - -#if defined(CPPGC_YOUNG_GENERATION) - -// AgeTable contains entries that correspond to 4KB memory regions. Each entry -// can be in one of three states: kOld, kYoung or kUnknown. -class AgeTable final { - static constexpr size_t kGranularityBits = 12; // 4KiB per byte. - - public: - enum class Age : uint8_t { kOld, kYoung, kUnknown }; - - static constexpr size_t kEntrySizeInBytes = 1 << kGranularityBits; - - Age& operator[](uintptr_t offset) { return table_[entry(offset)]; } - Age operator[](uintptr_t offset) const { return table_[entry(offset)]; } - - void Reset(PageAllocator* allocator); - - private: - static constexpr size_t kAgeTableSize = - api_constants::kCagedHeapReservationSize >> kGranularityBits; - - size_t entry(uintptr_t offset) const { - const size_t entry = offset >> kGranularityBits; - CPPGC_DCHECK(table_.size() > entry); - return entry; - } - - std::array table_; -}; - -static_assert(sizeof(AgeTable) == 1 * api_constants::kMB, - "Size of AgeTable is 1MB"); - -#endif // CPPGC_YOUNG_GENERATION - -struct CagedHeapLocalData final { - CagedHeapLocalData(HeapBase&, PageAllocator&); - - bool is_incremental_marking_in_progress = false; - HeapBase& heap_base; -#if defined(CPPGC_YOUNG_GENERATION) - AgeTable age_table; -#endif -}; - -} // namespace internal -} // namespace cppgc - -#endif // INCLUDE_CPPGC_INTERNAL_CAGED_HEAP_LOCAL_DATA_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/compiler-specific.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/compiler-specific.h deleted file mode 100644 index 595b6398cb7..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/compiler-specific.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_INTERNAL_COMPILER_SPECIFIC_H_ -#define INCLUDE_CPPGC_INTERNAL_COMPILER_SPECIFIC_H_ - -namespace cppgc { - -#if defined(__has_attribute) -#define CPPGC_HAS_ATTRIBUTE(FEATURE) __has_attribute(FEATURE) -#else -#define CPPGC_HAS_ATTRIBUTE(FEATURE) 0 -#endif - -#if defined(__has_cpp_attribute) -#define CPPGC_HAS_CPP_ATTRIBUTE(FEATURE) __has_cpp_attribute(FEATURE) -#else -#define CPPGC_HAS_CPP_ATTRIBUTE(FEATURE) 0 -#endif - -// [[no_unique_address]] comes in C++20 but supported in clang with -std >= -// c++11. -#if CPPGC_HAS_CPP_ATTRIBUTE(no_unique_address) -#define CPPGC_NO_UNIQUE_ADDRESS [[no_unique_address]] -#else -#define CPPGC_NO_UNIQUE_ADDRESS -#endif - -#if CPPGC_HAS_ATTRIBUTE(unused) -#define CPPGC_UNUSED __attribute__((unused)) -#else -#define CPPGC_UNUSED -#endif - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_INTERNAL_COMPILER_SPECIFIC_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/finalizer-trait.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/finalizer-trait.h deleted file mode 100644 index 7bd6f83bf60..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/finalizer-trait.h +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_INTERNAL_FINALIZER_TRAIT_H_ -#define INCLUDE_CPPGC_INTERNAL_FINALIZER_TRAIT_H_ - -#include - -#include "cppgc/type-traits.h" - -namespace cppgc { -namespace internal { - -using FinalizationCallback = void (*)(void*); - -template -struct HasFinalizeGarbageCollectedObject : std::false_type {}; - -template -struct HasFinalizeGarbageCollectedObject< - T, void_t().FinalizeGarbageCollectedObject())>> - : std::true_type {}; - -// The FinalizerTraitImpl specifies how to finalize objects. -template -struct FinalizerTraitImpl; - -template -struct FinalizerTraitImpl { - private: - // Dispatch to custom FinalizeGarbageCollectedObject(). - struct Custom { - static void Call(void* obj) { - static_cast(obj)->FinalizeGarbageCollectedObject(); - } - }; - - // Dispatch to regular destructor. - struct Destructor { - static void Call(void* obj) { static_cast(obj)->~T(); } - }; - - using FinalizeImpl = - std::conditional_t::value, Custom, - Destructor>; - - public: - static void Finalize(void* obj) { - static_assert(sizeof(T), "T must be fully defined"); - FinalizeImpl::Call(obj); - } -}; - -template -struct FinalizerTraitImpl { - static void Finalize(void* obj) { - static_assert(sizeof(T), "T must be fully defined"); - } -}; - -// The FinalizerTrait is used to determine if a type requires finalization and -// what finalization means. -template -struct FinalizerTrait { - private: - // Object has a finalizer if it has - // - a custom FinalizeGarbageCollectedObject method, or - // - a destructor. - static constexpr bool kNonTrivialFinalizer = - internal::HasFinalizeGarbageCollectedObject::value || - !std::is_trivially_destructible::type>::value; - - static void Finalize(void* obj) { - internal::FinalizerTraitImpl::Finalize(obj); - } - - public: - static constexpr bool HasFinalizer() { return kNonTrivialFinalizer; } - - // The callback used to finalize an object of type T. - static constexpr FinalizationCallback kCallback = - kNonTrivialFinalizer ? Finalize : nullptr; -}; - -template -constexpr FinalizationCallback FinalizerTrait::kCallback; - -} // namespace internal -} // namespace cppgc - -#endif // INCLUDE_CPPGC_INTERNAL_FINALIZER_TRAIT_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/gc-info.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/gc-info.h deleted file mode 100644 index 82a0d053431..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/gc-info.h +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_INTERNAL_GC_INFO_H_ -#define INCLUDE_CPPGC_INTERNAL_GC_INFO_H_ - -#include -#include -#include - -#include "cppgc/internal/finalizer-trait.h" -#include "cppgc/internal/name-trait.h" -#include "cppgc/trace-trait.h" -#include "v8config.h" // NOLINT(build/include_directory) - -namespace cppgc { -namespace internal { - -using GCInfoIndex = uint16_t; - -struct V8_EXPORT EnsureGCInfoIndexTrait final { - // Acquires a new GC info object and returns the index. In addition, also - // updates `registered_index` atomically. - template - V8_INLINE static GCInfoIndex EnsureIndex( - std::atomic& registered_index) { - return EnsureGCInfoIndexTraitDispatch{}(registered_index); - } - - private: - template ::value, - bool = FinalizerTrait::HasFinalizer(), - bool = NameTrait::HasNonHiddenName()> - struct EnsureGCInfoIndexTraitDispatch; - - static GCInfoIndex EnsureGCInfoIndexPolymorphic(std::atomic&, - TraceCallback, - FinalizationCallback, - NameCallback); - static GCInfoIndex EnsureGCInfoIndexPolymorphic(std::atomic&, - TraceCallback, - FinalizationCallback); - static GCInfoIndex EnsureGCInfoIndexPolymorphic(std::atomic&, - TraceCallback, NameCallback); - static GCInfoIndex EnsureGCInfoIndexPolymorphic(std::atomic&, - TraceCallback); - static GCInfoIndex EnsureGCInfoIndexNonPolymorphic(std::atomic&, - TraceCallback, - FinalizationCallback, - - NameCallback); - static GCInfoIndex EnsureGCInfoIndexNonPolymorphic(std::atomic&, - TraceCallback, - FinalizationCallback); - static GCInfoIndex EnsureGCInfoIndexNonPolymorphic(std::atomic&, - TraceCallback, - NameCallback); - static GCInfoIndex EnsureGCInfoIndexNonPolymorphic(std::atomic&, - TraceCallback); -}; - -#define DISPATCH(is_polymorphic, has_finalizer, has_non_hidden_name, function) \ - template \ - struct EnsureGCInfoIndexTrait::EnsureGCInfoIndexTraitDispatch< \ - T, is_polymorphic, has_finalizer, has_non_hidden_name> { \ - V8_INLINE GCInfoIndex \ - operator()(std::atomic& registered_index) { \ - return function; \ - } \ - }; - -// --------------------------------------------------------------------- // -// DISPATCH(is_polymorphic, has_finalizer, has_non_hidden_name, function) -// --------------------------------------------------------------------- // -DISPATCH(true, true, true, // - EnsureGCInfoIndexPolymorphic(registered_index, // - TraceTrait::Trace, // - FinalizerTrait::kCallback, // - NameTrait::GetName)) // -DISPATCH(true, true, false, // - EnsureGCInfoIndexPolymorphic(registered_index, // - TraceTrait::Trace, // - FinalizerTrait::kCallback)) // -DISPATCH(true, false, true, // - EnsureGCInfoIndexPolymorphic(registered_index, // - TraceTrait::Trace, // - NameTrait::GetName)) // -DISPATCH(true, false, false, // - EnsureGCInfoIndexPolymorphic(registered_index, // - TraceTrait::Trace)) // -DISPATCH(false, true, true, // - EnsureGCInfoIndexNonPolymorphic(registered_index, // - TraceTrait::Trace, // - FinalizerTrait::kCallback, // - NameTrait::GetName)) // -DISPATCH(false, true, false, // - EnsureGCInfoIndexNonPolymorphic(registered_index, // - TraceTrait::Trace, // - FinalizerTrait::kCallback)) // -DISPATCH(false, false, true, // - EnsureGCInfoIndexNonPolymorphic(registered_index, // - TraceTrait::Trace, // - NameTrait::GetName)) // -DISPATCH(false, false, false, // - EnsureGCInfoIndexNonPolymorphic(registered_index, // - TraceTrait::Trace)) // - -#undef DISPATCH - -// Fold types based on finalizer behavior. Note that finalizer characteristics -// align with trace behavior, i.e., destructors are virtual when trace methods -// are and vice versa. -template -struct GCInfoFolding { - static constexpr bool kHasVirtualDestructorAtBase = - std::has_virtual_destructor::value; - static constexpr bool kBothTypesAreTriviallyDestructible = - std::is_trivially_destructible::value && - std::is_trivially_destructible::value; - static constexpr bool kHasCustomFinalizerDispatchAtBase = - internal::HasFinalizeGarbageCollectedObject< - ParentMostGarbageCollectedType>::value; -#ifdef CPPGC_SUPPORTS_OBJECT_NAMES - static constexpr bool kWantsDetailedObjectNames = true; -#else // !CPPGC_SUPPORTS_OBJECT_NAMES - static constexpr bool kWantsDetailedObjectNames = false; -#endif // !CPPGC_SUPPORTS_OBJECT_NAMES - - // Folding would regresses name resolution when deriving names from C++ - // class names as it would just folds a name to the base class name. - using ResultType = std::conditional_t<(kHasVirtualDestructorAtBase || - kBothTypesAreTriviallyDestructible || - kHasCustomFinalizerDispatchAtBase) && - !kWantsDetailedObjectNames, - ParentMostGarbageCollectedType, T>; -}; - -// Trait determines how the garbage collector treats objects wrt. to traversing, -// finalization, and naming. -template -struct GCInfoTrait final { - V8_INLINE static GCInfoIndex Index() { - static_assert(sizeof(T), "T must be fully defined"); - static std::atomic - registered_index; // Uses zero initialization. - const GCInfoIndex index = registered_index.load(std::memory_order_acquire); - return index ? index - : EnsureGCInfoIndexTrait::EnsureIndex(registered_index); - } -}; - -} // namespace internal -} // namespace cppgc - -#endif // INCLUDE_CPPGC_INTERNAL_GC_INFO_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/logging.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/logging.h deleted file mode 100644 index 79beaef7d4f..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/logging.h +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_INTERNAL_LOGGING_H_ -#define INCLUDE_CPPGC_INTERNAL_LOGGING_H_ - -#include "cppgc/source-location.h" -#include "v8config.h" // NOLINT(build/include_directory) - -namespace cppgc { -namespace internal { - -void V8_EXPORT DCheckImpl(const char*, - const SourceLocation& = SourceLocation::Current()); -[[noreturn]] void V8_EXPORT -FatalImpl(const char*, const SourceLocation& = SourceLocation::Current()); - -// Used to ignore -Wunused-variable. -template -struct EatParams {}; - -#if DEBUG -#define CPPGC_DCHECK_MSG(condition, message) \ - do { \ - if (V8_UNLIKELY(!(condition))) { \ - ::cppgc::internal::DCheckImpl(message); \ - } \ - } while (false) -#else -#define CPPGC_DCHECK_MSG(condition, message) \ - (static_cast(::cppgc::internal::EatParams(condition), message)>{})) -#endif - -#define CPPGC_DCHECK(condition) CPPGC_DCHECK_MSG(condition, #condition) - -#define CPPGC_CHECK_MSG(condition, message) \ - do { \ - if (V8_UNLIKELY(!(condition))) { \ - ::cppgc::internal::FatalImpl(message); \ - } \ - } while (false) - -#define CPPGC_CHECK(condition) CPPGC_CHECK_MSG(condition, #condition) - -} // namespace internal -} // namespace cppgc - -#endif // INCLUDE_CPPGC_INTERNAL_LOGGING_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/name-trait.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/name-trait.h deleted file mode 100644 index 32a33478592..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/name-trait.h +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_INTERNAL_NAME_TRAIT_H_ -#define INCLUDE_CPPGC_INTERNAL_NAME_TRAIT_H_ - -#include -#include - -#include "cppgc/name-provider.h" -#include "v8config.h" // NOLINT(build/include_directory) - -namespace cppgc { -namespace internal { - -#if CPPGC_SUPPORTS_OBJECT_NAMES && defined(__clang__) -#define CPPGC_SUPPORTS_COMPILE_TIME_TYPENAME 1 - -// Provides constexpr c-string storage for a name of fixed |Size| characters. -// Automatically appends terminating 0 byte. -template -struct NameBuffer { - char name[Size + 1]{}; - - static constexpr NameBuffer FromCString(const char* str) { - NameBuffer result; - for (size_t i = 0; i < Size; ++i) result.name[i] = str[i]; - result.name[Size] = 0; - return result; - } -}; - -template -const char* GetTypename() { - static constexpr char kSelfPrefix[] = - "const char *cppgc::internal::GetTypename() [T ="; - static_assert(__builtin_strncmp(__PRETTY_FUNCTION__, kSelfPrefix, - sizeof(kSelfPrefix) - 1) == 0, - "The prefix must match"); - static constexpr const char* kTypenameStart = - __PRETTY_FUNCTION__ + sizeof(kSelfPrefix); - static constexpr size_t kTypenameSize = - __builtin_strlen(__PRETTY_FUNCTION__) - sizeof(kSelfPrefix) - 1; - // NameBuffer is an indirection that is needed to make sure that only a - // substring of __PRETTY_FUNCTION__ gets materialized in the binary. - static constexpr auto buffer = - NameBuffer::FromCString(kTypenameStart); - return buffer.name; -} - -#else -#define CPPGC_SUPPORTS_COMPILE_TIME_TYPENAME 0 -#endif - -struct HeapObjectName { - const char* value; - bool name_was_hidden; -}; - -class V8_EXPORT NameTraitBase { - protected: - static HeapObjectName GetNameFromTypeSignature(const char*); -}; - -// Trait that specifies how the garbage collector retrieves the name for a -// given object. -template -class NameTrait final : public NameTraitBase { - public: - static constexpr bool HasNonHiddenName() { -#if CPPGC_SUPPORTS_COMPILE_TIME_TYPENAME - return true; -#elif CPPGC_SUPPORTS_OBJECT_NAMES - return true; -#else // !CPPGC_SUPPORTS_OBJECT_NAMES - return std::is_base_of::value; -#endif // !CPPGC_SUPPORTS_OBJECT_NAMES - } - - static HeapObjectName GetName(const void* obj) { - return GetNameFor(static_cast(obj)); - } - - private: - static HeapObjectName GetNameFor(const NameProvider* name_provider) { - return {name_provider->GetHumanReadableName(), false}; - } - - static HeapObjectName GetNameFor(...) { -#if CPPGC_SUPPORTS_COMPILE_TIME_TYPENAME - return {GetTypename(), false}; -#elif CPPGC_SUPPORTS_OBJECT_NAMES - -#if defined(V8_CC_GNU) -#define PRETTY_FUNCTION_VALUE __PRETTY_FUNCTION__ -#elif defined(V8_CC_MSVC) -#define PRETTY_FUNCTION_VALUE __FUNCSIG__ -#else -#define PRETTY_FUNCTION_VALUE nullptr -#endif - - static const HeapObjectName leaky_name = - GetNameFromTypeSignature(PRETTY_FUNCTION_VALUE); - return {leaky_name, false}; - -#undef PRETTY_FUNCTION_VALUE - -#else // !CPPGC_SUPPORTS_OBJECT_NAMES - return {NameProvider::kHiddenName, true}; -#endif // !CPPGC_SUPPORTS_OBJECT_NAMES - } -}; - -using NameCallback = HeapObjectName (*)(const void*); - -} // namespace internal -} // namespace cppgc - -#undef CPPGC_SUPPORTS_COMPILE_TIME_TYPENAME - -#endif // INCLUDE_CPPGC_INTERNAL_NAME_TRAIT_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/persistent-node.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/persistent-node.h deleted file mode 100644 index b5dba476a47..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/persistent-node.h +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_INTERNAL_PERSISTENT_NODE_H_ -#define INCLUDE_CPPGC_INTERNAL_PERSISTENT_NODE_H_ - -#include -#include -#include - -#include "cppgc/internal/logging.h" -#include "cppgc/trace-trait.h" -#include "v8config.h" // NOLINT(build/include_directory) - -namespace cppgc { - -class Visitor; - -namespace internal { - -class CrossThreadPersistentRegion; - -// PersistentNode represents a variant of two states: -// 1) traceable node with a back pointer to the Persistent object; -// 2) freelist entry. -class PersistentNode final { - public: - PersistentNode() = default; - - PersistentNode(const PersistentNode&) = delete; - PersistentNode& operator=(const PersistentNode&) = delete; - - void InitializeAsUsedNode(void* owner, TraceCallback trace) { - CPPGC_DCHECK(trace); - owner_ = owner; - trace_ = trace; - } - - void InitializeAsFreeNode(PersistentNode* next) { - next_ = next; - trace_ = nullptr; - } - - void UpdateOwner(void* owner) { - CPPGC_DCHECK(IsUsed()); - owner_ = owner; - } - - PersistentNode* FreeListNext() const { - CPPGC_DCHECK(!IsUsed()); - return next_; - } - - void Trace(Visitor* visitor) const { - CPPGC_DCHECK(IsUsed()); - trace_(visitor, owner_); - } - - bool IsUsed() const { return trace_; } - - void* owner() const { - CPPGC_DCHECK(IsUsed()); - return owner_; - } - - private: - // PersistentNode acts as a designated union: - // If trace_ != nullptr, owner_ points to the corresponding Persistent handle. - // Otherwise, next_ points to the next freed PersistentNode. - union { - void* owner_ = nullptr; - PersistentNode* next_; - }; - TraceCallback trace_ = nullptr; -}; - -class V8_EXPORT PersistentRegion { - using PersistentNodeSlots = std::array; - - public: - PersistentRegion() = default; - // Clears Persistent fields to avoid stale pointers after heap teardown. - ~PersistentRegion(); - - PersistentRegion(const PersistentRegion&) = delete; - PersistentRegion& operator=(const PersistentRegion&) = delete; - - PersistentNode* AllocateNode(void* owner, TraceCallback trace) { - if (!free_list_head_) { - EnsureNodeSlots(); - } - PersistentNode* node = free_list_head_; - free_list_head_ = free_list_head_->FreeListNext(); - CPPGC_DCHECK(!node->IsUsed()); - node->InitializeAsUsedNode(owner, trace); - nodes_in_use_++; - return node; - } - - void FreeNode(PersistentNode* node) { - CPPGC_DCHECK(node); - CPPGC_DCHECK(node->IsUsed()); - node->InitializeAsFreeNode(free_list_head_); - free_list_head_ = node; - CPPGC_DCHECK(nodes_in_use_ > 0); - nodes_in_use_--; - } - - void Trace(Visitor*); - - size_t NodesInUse() const; - - void ClearAllUsedNodes(); - - private: - void EnsureNodeSlots(); - - template - void ClearAllUsedNodes(); - - std::vector> nodes_; - PersistentNode* free_list_head_ = nullptr; - size_t nodes_in_use_ = 0; - - friend class CrossThreadPersistentRegion; -}; - -// CrossThreadPersistent uses PersistentRegion but protects it using this lock -// when needed. -class V8_EXPORT PersistentRegionLock final { - public: - PersistentRegionLock(); - ~PersistentRegionLock(); - - static void AssertLocked(); -}; - -// Variant of PersistentRegion that checks whether the PersistentRegionLock is -// locked. -class V8_EXPORT CrossThreadPersistentRegion final : protected PersistentRegion { - public: - CrossThreadPersistentRegion() = default; - // Clears Persistent fields to avoid stale pointers after heap teardown. - ~CrossThreadPersistentRegion(); - - CrossThreadPersistentRegion(const CrossThreadPersistentRegion&) = delete; - CrossThreadPersistentRegion& operator=(const CrossThreadPersistentRegion&) = - delete; - - V8_INLINE PersistentNode* AllocateNode(void* owner, TraceCallback trace) { - PersistentRegionLock::AssertLocked(); - return PersistentRegion::AllocateNode(owner, trace); - } - - V8_INLINE void FreeNode(PersistentNode* node) { - PersistentRegionLock::AssertLocked(); - PersistentRegion::FreeNode(node); - } - - void Trace(Visitor*); - - size_t NodesInUse() const; - - void ClearAllUsedNodes(); -}; - -} // namespace internal - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_INTERNAL_PERSISTENT_NODE_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/pointer-policies.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/pointer-policies.h deleted file mode 100644 index cdf0bb693d6..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/pointer-policies.h +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_INTERNAL_POINTER_POLICIES_H_ -#define INCLUDE_CPPGC_INTERNAL_POINTER_POLICIES_H_ - -#include -#include - -#include "cppgc/internal/write-barrier.h" -#include "cppgc/sentinel-pointer.h" -#include "cppgc/source-location.h" -#include "cppgc/type-traits.h" -#include "v8config.h" // NOLINT(build/include_directory) - -namespace cppgc { -namespace internal { - -class HeapBase; -class PersistentRegion; -class CrossThreadPersistentRegion; - -// Tags to distinguish between strong and weak member types. -class StrongMemberTag; -class WeakMemberTag; -class UntracedMemberTag; - -struct DijkstraWriteBarrierPolicy { - static void InitializingBarrier(const void*, const void*) { - // Since in initializing writes the source object is always white, having no - // barrier doesn't break the tri-color invariant. - } - static void AssigningBarrier(const void* slot, const void* value) { - WriteBarrier::Params params; - switch (WriteBarrier::GetWriteBarrierType(slot, value, params)) { - case WriteBarrier::Type::kGenerational: - WriteBarrier::GenerationalBarrier(params, slot); - break; - case WriteBarrier::Type::kMarking: - WriteBarrier::DijkstraMarkingBarrier(params, value); - break; - case WriteBarrier::Type::kNone: - break; - } - } -}; - -struct NoWriteBarrierPolicy { - static void InitializingBarrier(const void*, const void*) {} - static void AssigningBarrier(const void*, const void*) {} -}; - -class V8_EXPORT EnabledCheckingPolicy { - protected: - template - void CheckPointer(const T* ptr) { - if (!ptr || (kSentinelPointer == ptr)) return; - - CheckPointersImplTrampoline::Call(this, ptr); - } - - private: - void CheckPointerImpl(const void* ptr, bool points_to_payload); - - template > - struct CheckPointersImplTrampoline { - static void Call(EnabledCheckingPolicy* policy, const T* ptr) { - policy->CheckPointerImpl(ptr, false); - } - }; - - template - struct CheckPointersImplTrampoline { - static void Call(EnabledCheckingPolicy* policy, const T* ptr) { - policy->CheckPointerImpl(ptr, IsGarbageCollectedTypeV); - } - }; - - const HeapBase* heap_ = nullptr; -}; - -class DisabledCheckingPolicy { - protected: - void CheckPointer(const void*) {} -}; - -#if V8_ENABLE_CHECKS -using DefaultMemberCheckingPolicy = EnabledCheckingPolicy; -using DefaultPersistentCheckingPolicy = EnabledCheckingPolicy; -#else -using DefaultMemberCheckingPolicy = DisabledCheckingPolicy; -using DefaultPersistentCheckingPolicy = DisabledCheckingPolicy; -#endif -// For CT(W)P neither marking information (for value), nor objectstart bitmap -// (for slot) are guaranteed to be present because there's no synchonization -// between heaps after marking. -using DefaultCrossThreadPersistentCheckingPolicy = DisabledCheckingPolicy; - -class KeepLocationPolicy { - public: - constexpr const SourceLocation& Location() const { return location_; } - - protected: - constexpr KeepLocationPolicy() = default; - constexpr explicit KeepLocationPolicy(const SourceLocation& location) - : location_(location) {} - - // KeepLocationPolicy must not copy underlying source locations. - KeepLocationPolicy(const KeepLocationPolicy&) = delete; - KeepLocationPolicy& operator=(const KeepLocationPolicy&) = delete; - - // Location of the original moved from object should be preserved. - KeepLocationPolicy(KeepLocationPolicy&&) = default; - KeepLocationPolicy& operator=(KeepLocationPolicy&&) = default; - - private: - SourceLocation location_; -}; - -class IgnoreLocationPolicy { - public: - constexpr SourceLocation Location() const { return {}; } - - protected: - constexpr IgnoreLocationPolicy() = default; - constexpr explicit IgnoreLocationPolicy(const SourceLocation&) {} -}; - -#if CPPGC_SUPPORTS_OBJECT_NAMES -using DefaultLocationPolicy = KeepLocationPolicy; -#else -using DefaultLocationPolicy = IgnoreLocationPolicy; -#endif - -struct StrongPersistentPolicy { - using IsStrongPersistent = std::true_type; - static V8_EXPORT PersistentRegion& GetPersistentRegion(const void* object); -}; - -struct WeakPersistentPolicy { - using IsStrongPersistent = std::false_type; - static V8_EXPORT PersistentRegion& GetPersistentRegion(const void* object); -}; - -struct StrongCrossThreadPersistentPolicy { - using IsStrongPersistent = std::true_type; - static V8_EXPORT CrossThreadPersistentRegion& GetPersistentRegion( - const void* object); -}; - -struct WeakCrossThreadPersistentPolicy { - using IsStrongPersistent = std::false_type; - static V8_EXPORT CrossThreadPersistentRegion& GetPersistentRegion( - const void* object); -}; - -// Forward declarations setting up the default policies. -template -class BasicCrossThreadPersistent; -template -class BasicPersistent; -template -class BasicMember; - -} // namespace internal - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_INTERNAL_POINTER_POLICIES_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/prefinalizer-handler.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/prefinalizer-handler.h deleted file mode 100644 index 64b07ec9112..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/prefinalizer-handler.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_INTERNAL_PREFINALIZER_HANDLER_H_ -#define INCLUDE_CPPGC_INTERNAL_PREFINALIZER_HANDLER_H_ - -#include "cppgc/heap.h" -#include "cppgc/liveness-broker.h" - -namespace cppgc { -namespace internal { - -class V8_EXPORT PreFinalizerRegistrationDispatcher final { - public: - using PreFinalizerCallback = bool (*)(const LivenessBroker&, void*); - struct PreFinalizer { - void* object; - PreFinalizerCallback callback; - - bool operator==(const PreFinalizer& other) const; - }; - - static void RegisterPrefinalizer(PreFinalizer pre_finalizer); -}; - -} // namespace internal -} // namespace cppgc - -#endif // INCLUDE_CPPGC_INTERNAL_PREFINALIZER_HANDLER_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/write-barrier.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/write-barrier.h deleted file mode 100644 index 28184dc9c83..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/internal/write-barrier.h +++ /dev/null @@ -1,433 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_INTERNAL_WRITE_BARRIER_H_ -#define INCLUDE_CPPGC_INTERNAL_WRITE_BARRIER_H_ - -#include -#include - -#include "cppgc/heap-state.h" -#include "cppgc/internal/api-constants.h" -#include "cppgc/internal/atomic-entry-flag.h" -#include "cppgc/platform.h" -#include "cppgc/sentinel-pointer.h" -#include "cppgc/trace-trait.h" -#include "v8config.h" // NOLINT(build/include_directory) - -#if defined(CPPGC_CAGED_HEAP) -#include "cppgc/internal/caged-heap-local-data.h" -#endif - -namespace cppgc { - -class HeapHandle; - -namespace internal { - -#if defined(CPPGC_CAGED_HEAP) -class WriteBarrierTypeForCagedHeapPolicy; -#else // !CPPGC_CAGED_HEAP -class WriteBarrierTypeForNonCagedHeapPolicy; -#endif // !CPPGC_CAGED_HEAP - -class V8_EXPORT WriteBarrier final { - public: - enum class Type : uint8_t { - kNone, - kMarking, - kGenerational, - }; - - struct Params { - HeapHandle* heap = nullptr; -#if V8_ENABLE_CHECKS - Type type = Type::kNone; -#endif // !V8_ENABLE_CHECKS -#if defined(CPPGC_CAGED_HEAP) - uintptr_t start = 0; - CagedHeapLocalData& caged_heap() const { - return *reinterpret_cast(start); - } - uintptr_t slot_offset = 0; - uintptr_t value_offset = 0; -#endif // CPPGC_CAGED_HEAP - }; - - enum class ValueMode { - kValuePresent, - kNoValuePresent, - }; - - // Returns the required write barrier for a given `slot` and `value`. - static V8_INLINE Type GetWriteBarrierType(const void* slot, const void* value, - Params& params); - // Returns the required write barrier for a given `slot`. - template - static V8_INLINE Type GetWriteBarrierType(const void* slot, Params& params, - HeapHandleCallback callback); - // Returns the required write barrier for a given `value`. - static V8_INLINE Type GetWriteBarrierType(const void* value, Params& params); - - template - static V8_INLINE Type GetWriteBarrierTypeForExternallyReferencedObject( - const void* value, Params& params, HeapHandleCallback callback); - - static V8_INLINE void DijkstraMarkingBarrier(const Params& params, - const void* object); - static V8_INLINE void DijkstraMarkingBarrierRange( - const Params& params, const void* first_element, size_t element_size, - size_t number_of_elements, TraceCallback trace_callback); - static V8_INLINE void SteeleMarkingBarrier(const Params& params, - const void* object); -#if defined(CPPGC_YOUNG_GENERATION) - static V8_INLINE void GenerationalBarrier(const Params& params, - const void* slot); -#else // !CPPGC_YOUNG_GENERATION - static V8_INLINE void GenerationalBarrier(const Params& params, - const void* slot) {} -#endif // CPPGC_YOUNG_GENERATION - -#if V8_ENABLE_CHECKS - static void CheckParams(Type expected_type, const Params& params); -#else // !V8_ENABLE_CHECKS - static void CheckParams(Type expected_type, const Params& params) {} -#endif // !V8_ENABLE_CHECKS - - // The IncrementalOrConcurrentUpdater class allows cppgc internal to update - // |incremental_or_concurrent_marking_flag_|. - class IncrementalOrConcurrentMarkingFlagUpdater; - static bool IsAnyIncrementalOrConcurrentMarking() { - return incremental_or_concurrent_marking_flag_.MightBeEntered(); - } - - private: - WriteBarrier() = delete; - -#if defined(CPPGC_CAGED_HEAP) - using WriteBarrierTypePolicy = WriteBarrierTypeForCagedHeapPolicy; -#else // !CPPGC_CAGED_HEAP - using WriteBarrierTypePolicy = WriteBarrierTypeForNonCagedHeapPolicy; -#endif // !CPPGC_CAGED_HEAP - - static void DijkstraMarkingBarrierSlow(const void* value); - static void DijkstraMarkingBarrierSlowWithSentinelCheck(const void* value); - static void DijkstraMarkingBarrierRangeSlow(HeapHandle& heap_handle, - const void* first_element, - size_t element_size, - size_t number_of_elements, - TraceCallback trace_callback); - static void SteeleMarkingBarrierSlow(const void* value); - static void SteeleMarkingBarrierSlowWithSentinelCheck(const void* value); - -#if defined(CPPGC_YOUNG_GENERATION) - static CagedHeapLocalData& GetLocalData(HeapHandle&); - static void GenerationalBarrierSlow(const CagedHeapLocalData& local_data, - const AgeTable& ageTable, - const void* slot, uintptr_t value_offset); -#endif // CPPGC_YOUNG_GENERATION - - static AtomicEntryFlag incremental_or_concurrent_marking_flag_; -}; - -template -V8_INLINE WriteBarrier::Type SetAndReturnType(WriteBarrier::Params& params) { - if (type == WriteBarrier::Type::kNone) return WriteBarrier::Type::kNone; -#if V8_ENABLE_CHECKS - params.type = type; -#endif // !V8_ENABLE_CHECKS - return type; -} - -#if defined(CPPGC_CAGED_HEAP) -class V8_EXPORT WriteBarrierTypeForCagedHeapPolicy final { - public: - template - static V8_INLINE WriteBarrier::Type Get(const void* slot, const void* value, - WriteBarrier::Params& params, - HeapHandleCallback callback) { - return ValueModeDispatch::Get(slot, value, params, callback); - } - - template - static V8_INLINE WriteBarrier::Type Get(const void* value, - WriteBarrier::Params& params, - HeapHandleCallback callback) { - return GetNoSlot(value, params, callback); - } - - template - static V8_INLINE WriteBarrier::Type GetForExternallyReferenced( - const void* value, WriteBarrier::Params& params, - HeapHandleCallback callback) { - return GetNoSlot(value, params, callback); - } - - private: - WriteBarrierTypeForCagedHeapPolicy() = delete; - - template - static V8_INLINE WriteBarrier::Type GetNoSlot(const void* value, - WriteBarrier::Params& params, - HeapHandleCallback) { - if (!TryGetCagedHeap(value, value, params)) { - return WriteBarrier::Type::kNone; - } - if (V8_UNLIKELY(params.caged_heap().is_incremental_marking_in_progress)) { - return SetAndReturnType(params); - } - return SetAndReturnType(params); - } - - template - struct ValueModeDispatch; - - static V8_INLINE bool TryGetCagedHeap(const void* slot, const void* value, - WriteBarrier::Params& params) { - // TODO(chromium:1056170): Check if the null check can be folded in with - // the rest of the write barrier. - if (!value) return false; - params.start = reinterpret_cast(value) & - ~(api_constants::kCagedHeapReservationAlignment - 1); - const uintptr_t slot_offset = - reinterpret_cast(slot) - params.start; - if (slot_offset > api_constants::kCagedHeapReservationSize) { - // Check if slot is on stack or value is sentinel or nullptr. This relies - // on the fact that kSentinelPointer is encoded as 0x1. - return false; - } - return true; - } - - // Returns whether marking is in progress. If marking is not in progress - // sets the start of the cage accordingly. - // - // TODO(chromium:1056170): Create fast path on API. - static bool IsMarking(const HeapHandle&, WriteBarrier::Params&); -}; - -template <> -struct WriteBarrierTypeForCagedHeapPolicy::ValueModeDispatch< - WriteBarrier::ValueMode::kValuePresent> { - template - static V8_INLINE WriteBarrier::Type Get(const void* slot, const void* value, - WriteBarrier::Params& params, - HeapHandleCallback) { - bool within_cage = TryGetCagedHeap(slot, value, params); - if (!within_cage) { - return WriteBarrier::Type::kNone; - } - if (V8_LIKELY(!params.caged_heap().is_incremental_marking_in_progress)) { -#if defined(CPPGC_YOUNG_GENERATION) - params.heap = reinterpret_cast(params.start); - params.slot_offset = reinterpret_cast(slot) - params.start; - params.value_offset = reinterpret_cast(value) - params.start; - return SetAndReturnType(params); -#else // !CPPGC_YOUNG_GENERATION - return SetAndReturnType(params); -#endif // !CPPGC_YOUNG_GENERATION - } - params.heap = reinterpret_cast(params.start); - return SetAndReturnType(params); - } -}; - -template <> -struct WriteBarrierTypeForCagedHeapPolicy::ValueModeDispatch< - WriteBarrier::ValueMode::kNoValuePresent> { - template - static V8_INLINE WriteBarrier::Type Get(const void* slot, const void*, - WriteBarrier::Params& params, - HeapHandleCallback callback) { -#if defined(CPPGC_YOUNG_GENERATION) - HeapHandle& handle = callback(); - if (V8_LIKELY(!IsMarking(handle, params))) { - // params.start is populated by IsMarking(). - params.heap = &handle; - params.slot_offset = reinterpret_cast(slot) - params.start; - // params.value_offset stays 0. - if (params.slot_offset > api_constants::kCagedHeapReservationSize) { - // Check if slot is on stack. - return SetAndReturnType(params); - } - return SetAndReturnType(params); - } -#else // !CPPGC_YOUNG_GENERATION - if (V8_LIKELY(!WriteBarrier::IsAnyIncrementalOrConcurrentMarking())) { - return SetAndReturnType(params); - } - HeapHandle& handle = callback(); - if (V8_UNLIKELY(!subtle::HeapState::IsMarking(handle))) { - return SetAndReturnType(params); - } -#endif // !CPPGC_YOUNG_GENERATION - params.heap = &handle; - return SetAndReturnType(params); - } -}; - -#endif // CPPGC_CAGED_HEAP - -class V8_EXPORT WriteBarrierTypeForNonCagedHeapPolicy final { - public: - template - static V8_INLINE WriteBarrier::Type Get(const void* slot, const void* value, - WriteBarrier::Params& params, - HeapHandleCallback callback) { - return ValueModeDispatch::Get(slot, value, params, callback); - } - - template - static V8_INLINE WriteBarrier::Type Get(const void* value, - WriteBarrier::Params& params, - HeapHandleCallback callback) { - // The slot will never be used in `Get()` below. - return Get(nullptr, value, params, - callback); - } - - template - static V8_INLINE WriteBarrier::Type GetForExternallyReferenced( - const void* value, WriteBarrier::Params& params, - HeapHandleCallback callback) { - // The slot will never be used in `Get()` below. - return Get(nullptr, value, params, - callback); - } - - private: - template - struct ValueModeDispatch; - - // TODO(chromium:1056170): Create fast path on API. - static bool IsMarking(const void*, HeapHandle**); - // TODO(chromium:1056170): Create fast path on API. - static bool IsMarking(HeapHandle&); - - WriteBarrierTypeForNonCagedHeapPolicy() = delete; -}; - -template <> -struct WriteBarrierTypeForNonCagedHeapPolicy::ValueModeDispatch< - WriteBarrier::ValueMode::kValuePresent> { - template - static V8_INLINE WriteBarrier::Type Get(const void*, const void* object, - WriteBarrier::Params& params, - HeapHandleCallback callback) { - // The following check covers nullptr as well as sentinel pointer. - if (object <= static_cast(kSentinelPointer)) { - return WriteBarrier::Type::kNone; - } - if (IsMarking(object, ¶ms.heap)) { - return SetAndReturnType(params); - } - return SetAndReturnType(params); - } -}; - -template <> -struct WriteBarrierTypeForNonCagedHeapPolicy::ValueModeDispatch< - WriteBarrier::ValueMode::kNoValuePresent> { - template - static V8_INLINE WriteBarrier::Type Get(const void*, const void*, - WriteBarrier::Params& params, - HeapHandleCallback callback) { - if (V8_UNLIKELY(WriteBarrier::IsAnyIncrementalOrConcurrentMarking())) { - HeapHandle& handle = callback(); - if (IsMarking(handle)) { - params.heap = &handle; - return SetAndReturnType(params); - } - } - return WriteBarrier::Type::kNone; - } -}; - -// static -WriteBarrier::Type WriteBarrier::GetWriteBarrierType( - const void* slot, const void* value, WriteBarrier::Params& params) { - return WriteBarrierTypePolicy::Get(slot, value, - params, []() {}); -} - -// static -template -WriteBarrier::Type WriteBarrier::GetWriteBarrierType( - const void* slot, WriteBarrier::Params& params, - HeapHandleCallback callback) { - return WriteBarrierTypePolicy::Get( - slot, nullptr, params, callback); -} - -// static -WriteBarrier::Type WriteBarrier::GetWriteBarrierType( - const void* value, WriteBarrier::Params& params) { - return WriteBarrierTypePolicy::Get(value, params, - []() {}); -} - -// static -template -WriteBarrier::Type -WriteBarrier::GetWriteBarrierTypeForExternallyReferencedObject( - const void* value, Params& params, HeapHandleCallback callback) { - return WriteBarrierTypePolicy::GetForExternallyReferenced(value, params, - callback); -} - -// static -void WriteBarrier::DijkstraMarkingBarrier(const Params& params, - const void* object) { - CheckParams(Type::kMarking, params); -#if defined(CPPGC_CAGED_HEAP) - // Caged heap already filters out sentinels. - DijkstraMarkingBarrierSlow(object); -#else // !CPPGC_CAGED_HEAP - DijkstraMarkingBarrierSlowWithSentinelCheck(object); -#endif // !CPPGC_CAGED_HEAP -} - -// static -void WriteBarrier::DijkstraMarkingBarrierRange(const Params& params, - const void* first_element, - size_t element_size, - size_t number_of_elements, - TraceCallback trace_callback) { - CheckParams(Type::kMarking, params); - DijkstraMarkingBarrierRangeSlow(*params.heap, first_element, element_size, - number_of_elements, trace_callback); -} - -// static -void WriteBarrier::SteeleMarkingBarrier(const Params& params, - const void* object) { - CheckParams(Type::kMarking, params); -#if defined(CPPGC_CAGED_HEAP) - // Caged heap already filters out sentinels. - SteeleMarkingBarrierSlow(object); -#else // !CPPGC_CAGED_HEAP - SteeleMarkingBarrierSlowWithSentinelCheck(object); -#endif // !CPPGC_CAGED_HEAP -} - -#if defined(CPPGC_YOUNG_GENERATION) -// static -void WriteBarrier::GenerationalBarrier(const Params& params, const void* slot) { - CheckParams(Type::kGenerational, params); - - const CagedHeapLocalData& local_data = params.caged_heap(); - const AgeTable& age_table = local_data.age_table; - - // Bail out if the slot is in young generation. - if (V8_LIKELY(age_table[params.slot_offset] == AgeTable::Age::kYoung)) return; - - GenerationalBarrierSlow(local_data, age_table, slot, params.value_offset); -} - -#endif // !CPPGC_YOUNG_GENERATION - -} // namespace internal -} // namespace cppgc - -#endif // INCLUDE_CPPGC_INTERNAL_WRITE_BARRIER_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/liveness-broker.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/liveness-broker.h deleted file mode 100644 index c94eef0d4ac..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/liveness-broker.h +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_LIVENESS_BROKER_H_ -#define INCLUDE_CPPGC_LIVENESS_BROKER_H_ - -#include "cppgc/heap.h" -#include "cppgc/member.h" -#include "cppgc/trace-trait.h" -#include "v8config.h" // NOLINT(build/include_directory) - -namespace cppgc { - -namespace internal { -class LivenessBrokerFactory; -} // namespace internal - -/** - * The broker is passed to weak callbacks to allow (temporarily) querying - * the liveness state of an object. References to non-live objects must be - * cleared when `IsHeapObjectAlive()` returns false. - * - * \code - * class GCedWithCustomWeakCallback final - * : public GarbageCollected { - * public: - * UntracedMember bar; - * - * void CustomWeakCallbackMethod(const LivenessBroker& broker) { - * if (!broker.IsHeapObjectAlive(bar)) - * bar = nullptr; - * } - * - * void Trace(cppgc::Visitor* visitor) const { - * visitor->RegisterWeakCallbackMethod< - * GCedWithCustomWeakCallback, - * &GCedWithCustomWeakCallback::CustomWeakCallbackMethod>(this); - * } - * }; - * \endcode - */ -class V8_EXPORT LivenessBroker final { - public: - template - bool IsHeapObjectAlive(const T* object) const { - // nullptr objects are considered alive to allow weakness to be used from - // stack while running into a conservative GC. Treating nullptr as dead - // would mean that e.g. custom collectins could not be strongified on stack. - return !object || - IsHeapObjectAliveImpl( - TraceTrait::GetTraceDescriptor(object).base_object_payload); - } - - template - bool IsHeapObjectAlive(const WeakMember& weak_member) const { - return (weak_member != kSentinelPointer) && - IsHeapObjectAlive(weak_member.Get()); - } - - template - bool IsHeapObjectAlive(const UntracedMember& untraced_member) const { - return (untraced_member != kSentinelPointer) && - IsHeapObjectAlive(untraced_member.Get()); - } - - private: - LivenessBroker() = default; - - bool IsHeapObjectAliveImpl(const void*) const; - - friend class internal::LivenessBrokerFactory; -}; - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_LIVENESS_BROKER_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/macros.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/macros.h deleted file mode 100644 index 030f397e3df..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/macros.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_MACROS_H_ -#define INCLUDE_CPPGC_MACROS_H_ - -#include - -#include "cppgc/internal/compiler-specific.h" - -namespace cppgc { - -// Use if the object is only stack allocated. -#define CPPGC_STACK_ALLOCATED() \ - public: \ - using IsStackAllocatedTypeMarker CPPGC_UNUSED = int; \ - \ - private: \ - void* operator new(size_t) = delete; \ - void* operator new(size_t, void*) = delete; \ - static_assert(true, "Force semicolon.") - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_MACROS_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/member.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/member.h deleted file mode 100644 index 38105b8e432..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/member.h +++ /dev/null @@ -1,288 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_MEMBER_H_ -#define INCLUDE_CPPGC_MEMBER_H_ - -#include -#include -#include - -#include "cppgc/internal/pointer-policies.h" -#include "cppgc/sentinel-pointer.h" -#include "cppgc/type-traits.h" -#include "v8config.h" // NOLINT(build/include_directory) - -namespace cppgc { - -class Visitor; - -namespace internal { - -// MemberBase always refers to the object as const object and defers to -// BasicMember on casting to the right type as needed. -class MemberBase { - protected: - struct AtomicInitializerTag {}; - - MemberBase() = default; - explicit MemberBase(const void* value) : raw_(value) {} - MemberBase(const void* value, AtomicInitializerTag) { SetRawAtomic(value); } - - const void** GetRawSlot() const { return &raw_; } - const void* GetRaw() const { return raw_; } - void SetRaw(void* value) { raw_ = value; } - - const void* GetRawAtomic() const { - return reinterpret_cast*>(&raw_)->load( - std::memory_order_relaxed); - } - void SetRawAtomic(const void* value) { - reinterpret_cast*>(&raw_)->store( - value, std::memory_order_relaxed); - } - - void ClearFromGC() const { raw_ = nullptr; } - - private: - mutable const void* raw_ = nullptr; -}; - -// The basic class from which all Member classes are 'generated'. -template -class BasicMember final : private MemberBase, private CheckingPolicy { - public: - using PointeeType = T; - - constexpr BasicMember() = default; - constexpr BasicMember(std::nullptr_t) {} // NOLINT - BasicMember(SentinelPointer s) : MemberBase(s) {} // NOLINT - BasicMember(T* raw) : MemberBase(raw) { // NOLINT - InitializingWriteBarrier(); - this->CheckPointer(Get()); - } - BasicMember(T& raw) : BasicMember(&raw) {} // NOLINT - // Atomic ctor. Using the AtomicInitializerTag forces BasicMember to - // initialize using atomic assignments. This is required for preventing - // data races with concurrent marking. - using AtomicInitializerTag = MemberBase::AtomicInitializerTag; - BasicMember(std::nullptr_t, AtomicInitializerTag atomic) - : MemberBase(nullptr, atomic) {} - BasicMember(SentinelPointer s, AtomicInitializerTag atomic) - : MemberBase(s, atomic) {} - BasicMember(T* raw, AtomicInitializerTag atomic) : MemberBase(raw, atomic) { - InitializingWriteBarrier(); - this->CheckPointer(Get()); - } - BasicMember(T& raw, AtomicInitializerTag atomic) - : BasicMember(&raw, atomic) {} - // Copy ctor. - BasicMember(const BasicMember& other) : BasicMember(other.Get()) {} - // Allow heterogeneous construction. - template ::value>> - BasicMember( // NOLINT - const BasicMember& other) - : BasicMember(other.Get()) {} - // Move ctor. - BasicMember(BasicMember&& other) noexcept : BasicMember(other.Get()) { - other.Clear(); - } - // Allow heterogeneous move construction. - template ::value>> - BasicMember(BasicMember&& other) noexcept - : BasicMember(other.Get()) { - other.Clear(); - } - // Construction from Persistent. - template ::value>> - BasicMember(const BasicPersistent& p) - : BasicMember(p.Get()) {} - - // Copy assignment. - BasicMember& operator=(const BasicMember& other) { - return operator=(other.Get()); - } - // Allow heterogeneous copy assignment. - template ::value>> - BasicMember& operator=( - const BasicMember& other) { - return operator=(other.Get()); - } - // Move assignment. - BasicMember& operator=(BasicMember&& other) noexcept { - operator=(other.Get()); - other.Clear(); - return *this; - } - // Heterogeneous move assignment. - template ::value>> - BasicMember& operator=(BasicMember&& other) noexcept { - operator=(other.Get()); - other.Clear(); - return *this; - } - // Assignment from Persistent. - template ::value>> - BasicMember& operator=( - const BasicPersistent& - other) { - return operator=(other.Get()); - } - BasicMember& operator=(T* other) { - SetRawAtomic(other); - AssigningWriteBarrier(); - this->CheckPointer(Get()); - return *this; - } - BasicMember& operator=(std::nullptr_t) { - Clear(); - return *this; - } - BasicMember& operator=(SentinelPointer s) { - SetRawAtomic(s); - return *this; - } - - template - void Swap(BasicMember& other) { - T* tmp = Get(); - *this = other; - other = tmp; - } - - explicit operator bool() const { return Get(); } - operator T*() const { return Get(); } - T* operator->() const { return Get(); } - T& operator*() const { return *Get(); } - - // CFI cast exemption to allow passing SentinelPointer through T* and support - // heterogeneous assignments between different Member and Persistent handles - // based on their actual types. - V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") T* Get() const { - // Executed by the mutator, hence non atomic load. - // - // The const_cast below removes the constness from MemberBase storage. The - // following static_cast re-adds any constness if specified through the - // user-visible template parameter T. - return static_cast(const_cast(MemberBase::GetRaw())); - } - - void Clear() { SetRawAtomic(nullptr); } - - T* Release() { - T* result = Get(); - Clear(); - return result; - } - - const T** GetSlotForTesting() const { - return reinterpret_cast(GetRawSlot()); - } - - private: - const T* GetRawAtomic() const { - return static_cast(MemberBase::GetRawAtomic()); - } - - void InitializingWriteBarrier() const { - WriteBarrierPolicy::InitializingBarrier(GetRawSlot(), GetRaw()); - } - void AssigningWriteBarrier() const { - WriteBarrierPolicy::AssigningBarrier(GetRawSlot(), GetRaw()); - } - - void ClearFromGC() const { MemberBase::ClearFromGC(); } - - T* GetFromGC() const { return Get(); } - - friend class cppgc::Visitor; - template - friend struct cppgc::TraceTrait; -}; - -template -bool operator==(const BasicMember& member1, - const BasicMember& member2) { - return member1.Get() == member2.Get(); -} - -template -bool operator!=(const BasicMember& member1, - const BasicMember& member2) { - return !(member1 == member2); -} - -template -struct IsWeak< - internal::BasicMember> - : std::true_type {}; - -} // namespace internal - -/** - * Members are used in classes to contain strong pointers to other garbage - * collected objects. All Member fields of a class must be traced in the class' - * trace method. - */ -template -using Member = internal::BasicMember; - -/** - * WeakMember is similar to Member in that it is used to point to other garbage - * collected objects. However instead of creating a strong pointer to the - * object, the WeakMember creates a weak pointer, which does not keep the - * pointee alive. Hence if all pointers to to a heap allocated object are weak - * the object will be garbage collected. At the time of GC the weak pointers - * will automatically be set to null. - */ -template -using WeakMember = internal::BasicMember; - -/** - * UntracedMember is a pointer to an on-heap object that is not traced for some - * reason. Do not use this unless you know what you are doing. Keeping raw - * pointers to on-heap objects is prohibited unless used from stack. Pointee - * must be kept alive through other means. - */ -template -using UntracedMember = internal::BasicMember; - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_MEMBER_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/name-provider.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/name-provider.h deleted file mode 100644 index 224dd4b5d67..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/name-provider.h +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_NAME_PROVIDER_H_ -#define INCLUDE_CPPGC_NAME_PROVIDER_H_ - -#include "v8config.h" // NOLINT(build/include_directory) - -namespace cppgc { - -/** - * NameProvider allows for providing a human-readable name for garbage-collected - * objects. - * - * There's two cases of names to distinguish: - * a. Explicitly specified names via using NameProvider. Such names are always - * preserved in the system. - * b. Internal names that Oilpan infers from a C++ type on the class hierarchy - * of the object. This is not necessarily the type of the actually - * instantiated object. - * - * Depending on the build configuration, Oilpan may hide names, i.e., represent - * them with kHiddenName, of case b. to avoid exposing internal details. - */ -class V8_EXPORT NameProvider { - public: - /** - * Name that is used when hiding internals. - */ - static constexpr const char kHiddenName[] = "InternalNode"; - - /** - * Name that is used in case compiler support is missing for composing a name - * from C++ types. - */ - static constexpr const char kNoNameDeducible[] = ""; - - /** - * Indicating whether internal names are hidden or not. - * - * @returns true if C++ names should be hidden and represented by kHiddenName. - */ - static constexpr bool HideInternalNames() { -#if CPPGC_SUPPORTS_OBJECT_NAMES - return false; -#else // !CPPGC_SUPPORTS_OBJECT_NAMES - return true; -#endif // !CPPGC_SUPPORTS_OBJECT_NAMES - } - - virtual ~NameProvider() = default; - - /** - * Specifies a name for the garbage-collected object. Such names will never - * be hidden, as they are explicitly specified by the user of this API. - * - * @returns a human readable name for the object. - */ - virtual const char* GetHumanReadableName() const = 0; -}; - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_NAME_PROVIDER_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/object-size-trait.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/object-size-trait.h deleted file mode 100644 index 35795596d36..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/object-size-trait.h +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_OBJECT_SIZE_TRAIT_H_ -#define INCLUDE_CPPGC_OBJECT_SIZE_TRAIT_H_ - -#include - -#include "cppgc/type-traits.h" -#include "v8config.h" // NOLINT(build/include_directory) - -namespace cppgc { - -namespace internal { - -struct V8_EXPORT BaseObjectSizeTrait { - protected: - static size_t GetObjectSizeForGarbageCollected(const void*); - static size_t GetObjectSizeForGarbageCollectedMixin(const void*); -}; - -} // namespace internal - -namespace subtle { - -/** - * Trait specifying how to get the size of an object that was allocated using - * `MakeGarbageCollected()`. Also supports querying the size with an inner - * pointer to a mixin. - */ -template > -struct ObjectSizeTrait; - -template -struct ObjectSizeTrait : cppgc::internal::BaseObjectSizeTrait { - static_assert(sizeof(T), "T must be fully defined"); - static_assert(IsGarbageCollectedTypeV, - "T must be of type GarbageCollected or GarbageCollectedMixin"); - - static size_t GetSize(const T& object) { - return GetObjectSizeForGarbageCollected(&object); - } -}; - -template -struct ObjectSizeTrait : cppgc::internal::BaseObjectSizeTrait { - static_assert(sizeof(T), "T must be fully defined"); - - static size_t GetSize(const T& object) { - return GetObjectSizeForGarbageCollectedMixin(&object); - } -}; - -} // namespace subtle -} // namespace cppgc - -#endif // INCLUDE_CPPGC_OBJECT_SIZE_TRAIT_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/persistent.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/persistent.h deleted file mode 100644 index b83a464576e..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/persistent.h +++ /dev/null @@ -1,371 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_PERSISTENT_H_ -#define INCLUDE_CPPGC_PERSISTENT_H_ - -#include - -#include "cppgc/internal/persistent-node.h" -#include "cppgc/internal/pointer-policies.h" -#include "cppgc/sentinel-pointer.h" -#include "cppgc/source-location.h" -#include "cppgc/type-traits.h" -#include "cppgc/visitor.h" -#include "v8config.h" // NOLINT(build/include_directory) - -namespace cppgc { - -class Visitor; - -namespace internal { - -// PersistentBase always refers to the object as const object and defers to -// BasicPersistent on casting to the right type as needed. -class PersistentBase { - protected: - PersistentBase() = default; - explicit PersistentBase(const void* raw) : raw_(raw) {} - - const void* GetValue() const { return raw_; } - void SetValue(const void* value) { raw_ = value; } - - PersistentNode* GetNode() const { return node_; } - void SetNode(PersistentNode* node) { node_ = node; } - - // Performs a shallow clear which assumes that internal persistent nodes are - // destroyed elsewhere. - void ClearFromGC() const { - raw_ = nullptr; - node_ = nullptr; - } - - protected: - mutable const void* raw_ = nullptr; - mutable PersistentNode* node_ = nullptr; - - friend class PersistentRegion; -}; - -// The basic class from which all Persistent classes are generated. -template -class BasicPersistent final : public PersistentBase, - public LocationPolicy, - private WeaknessPolicy, - private CheckingPolicy { - public: - using typename WeaknessPolicy::IsStrongPersistent; - using PointeeType = T; - - // Null-state/sentinel constructors. - BasicPersistent( // NOLINT - const SourceLocation& loc = SourceLocation::Current()) - : LocationPolicy(loc) {} - - BasicPersistent(std::nullptr_t, // NOLINT - const SourceLocation& loc = SourceLocation::Current()) - : LocationPolicy(loc) {} - - BasicPersistent( // NOLINT - SentinelPointer s, const SourceLocation& loc = SourceLocation::Current()) - : PersistentBase(s), LocationPolicy(loc) {} - - // Raw value constructors. - BasicPersistent(T* raw, // NOLINT - const SourceLocation& loc = SourceLocation::Current()) - : PersistentBase(raw), LocationPolicy(loc) { - if (!IsValid()) return; - SetNode(WeaknessPolicy::GetPersistentRegion(GetValue()) - .AllocateNode(this, &BasicPersistent::Trace)); - this->CheckPointer(Get()); - } - - BasicPersistent(T& raw, // NOLINT - const SourceLocation& loc = SourceLocation::Current()) - : BasicPersistent(&raw, loc) {} - - // Copy ctor. - BasicPersistent(const BasicPersistent& other, - const SourceLocation& loc = SourceLocation::Current()) - : BasicPersistent(other.Get(), loc) {} - - // Heterogeneous ctor. - template ::value>> - BasicPersistent( - const BasicPersistent& other, - const SourceLocation& loc = SourceLocation::Current()) - : BasicPersistent(other.Get(), loc) {} - - // Move ctor. The heterogeneous move ctor is not supported since e.g. - // persistent can't reuse persistent node from weak persistent. - BasicPersistent( - BasicPersistent&& other, - const SourceLocation& loc = SourceLocation::Current()) noexcept - : PersistentBase(std::move(other)), LocationPolicy(std::move(other)) { - if (!IsValid()) return; - GetNode()->UpdateOwner(this); - other.SetValue(nullptr); - other.SetNode(nullptr); - this->CheckPointer(Get()); - } - - // Constructor from member. - template ::value>> - BasicPersistent(internal::BasicMember - member, - const SourceLocation& loc = SourceLocation::Current()) - : BasicPersistent(member.Get(), loc) {} - - ~BasicPersistent() { Clear(); } - - // Copy assignment. - BasicPersistent& operator=(const BasicPersistent& other) { - return operator=(other.Get()); - } - - template ::value>> - BasicPersistent& operator=( - const BasicPersistent& other) { - return operator=(other.Get()); - } - - // Move assignment. - BasicPersistent& operator=(BasicPersistent&& other) noexcept { - if (this == &other) return *this; - Clear(); - PersistentBase::operator=(std::move(other)); - LocationPolicy::operator=(std::move(other)); - if (!IsValid()) return *this; - GetNode()->UpdateOwner(this); - other.SetValue(nullptr); - other.SetNode(nullptr); - this->CheckPointer(Get()); - return *this; - } - - // Assignment from member. - template ::value>> - BasicPersistent& operator=( - internal::BasicMember - member) { - return operator=(member.Get()); - } - - BasicPersistent& operator=(T* other) { - Assign(other); - return *this; - } - - BasicPersistent& operator=(std::nullptr_t) { - Clear(); - return *this; - } - - BasicPersistent& operator=(SentinelPointer s) { - Assign(s); - return *this; - } - - explicit operator bool() const { return Get(); } - operator T*() const { return Get(); } - T* operator->() const { return Get(); } - T& operator*() const { return *Get(); } - - // CFI cast exemption to allow passing SentinelPointer through T* and support - // heterogeneous assignments between different Member and Persistent handles - // based on their actual types. - V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") T* Get() const { - // The const_cast below removes the constness from PersistentBase storage. - // The following static_cast re-adds any constness if specified through the - // user-visible template parameter T. - return static_cast(const_cast(GetValue())); - } - - void Clear() { - // Simplified version of `Assign()` to allow calling without a complete type - // `T`. - if (IsValid()) { - WeaknessPolicy::GetPersistentRegion(GetValue()).FreeNode(GetNode()); - SetNode(nullptr); - } - SetValue(nullptr); - } - - T* Release() { - T* result = Get(); - Clear(); - return result; - } - - template - BasicPersistent - To() const { - return BasicPersistent(static_cast(Get())); - } - - private: - static void Trace(Visitor* v, const void* ptr) { - const auto* persistent = static_cast(ptr); - v->TraceRoot(*persistent, persistent->Location()); - } - - bool IsValid() const { - // Ideally, handling kSentinelPointer would be done by the embedder. On the - // other hand, having Persistent aware of it is beneficial since no node - // gets wasted. - return GetValue() != nullptr && GetValue() != kSentinelPointer; - } - - void Assign(T* ptr) { - if (IsValid()) { - if (ptr && ptr != kSentinelPointer) { - // Simply assign the pointer reusing the existing node. - SetValue(ptr); - this->CheckPointer(ptr); - return; - } - WeaknessPolicy::GetPersistentRegion(GetValue()).FreeNode(GetNode()); - SetNode(nullptr); - } - SetValue(ptr); - if (!IsValid()) return; - SetNode(WeaknessPolicy::GetPersistentRegion(GetValue()) - .AllocateNode(this, &BasicPersistent::Trace)); - this->CheckPointer(Get()); - } - - void ClearFromGC() const { - if (IsValid()) { - WeaknessPolicy::GetPersistentRegion(GetValue()).FreeNode(GetNode()); - PersistentBase::ClearFromGC(); - } - } - - // Set Get() for details. - V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") - T* GetFromGC() const { - return static_cast(const_cast(GetValue())); - } - - friend class cppgc::Visitor; -}; - -template -bool operator==(const BasicPersistent& p1, - const BasicPersistent& p2) { - return p1.Get() == p2.Get(); -} - -template -bool operator!=(const BasicPersistent& p1, - const BasicPersistent& p2) { - return !(p1 == p2); -} - -template -bool operator==(const BasicPersistent& p, - BasicMember - m) { - return p.Get() == m.Get(); -} - -template -bool operator!=(const BasicPersistent& p, - BasicMember - m) { - return !(p == m); -} - -template -bool operator==(BasicMember - m, - const BasicPersistent& p) { - return m.Get() == p.Get(); -} - -template -bool operator!=(BasicMember - m, - const BasicPersistent& p) { - return !(m == p); -} - -template -struct IsWeak> : std::true_type {}; -} // namespace internal - -/** - * Persistent is a way to create a strong pointer from an off-heap object to - * another on-heap object. As long as the Persistent handle is alive the GC will - * keep the object pointed to alive. The Persistent handle is always a GC root - * from the point of view of the GC. Persistent must be constructed and - * destructed in the same thread. - */ -template -using Persistent = - internal::BasicPersistent; - -/** - * WeakPersistent is a way to create a weak pointer from an off-heap object to - * an on-heap object. The pointer is automatically cleared when the pointee gets - * collected. WeakPersistent must be constructed and destructed in the same - * thread. - */ -template -using WeakPersistent = - internal::BasicPersistent; - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_PERSISTENT_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/platform.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/platform.h deleted file mode 100644 index 3276a26b652..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/platform.h +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_PLATFORM_H_ -#define INCLUDE_CPPGC_PLATFORM_H_ - -#include - -#include "v8-platform.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace cppgc { - -// TODO(v8:10346): Create separate includes for concepts that are not -// V8-specific. -using IdleTask = v8::IdleTask; -using JobHandle = v8::JobHandle; -using JobDelegate = v8::JobDelegate; -using JobTask = v8::JobTask; -using PageAllocator = v8::PageAllocator; -using Task = v8::Task; -using TaskPriority = v8::TaskPriority; -using TaskRunner = v8::TaskRunner; -using TracingController = v8::TracingController; - -/** - * Platform interface used by Heap. Contains allocators and executors. - */ -class V8_EXPORT Platform { - public: - virtual ~Platform() = default; - - /** - * Returns the allocator used by cppgc to allocate its heap and various - * support structures. - */ - virtual PageAllocator* GetPageAllocator() = 0; - - /** - * Monotonically increasing time in seconds from an arbitrary fixed point in - * the past. This function is expected to return at least - * millisecond-precision values. For this reason, - * it is recommended that the fixed point be no further in the past than - * the epoch. - **/ - virtual double MonotonicallyIncreasingTime() = 0; - - /** - * Foreground task runner that should be used by a Heap. - */ - virtual std::shared_ptr GetForegroundTaskRunner() { - return nullptr; - } - - /** - * Posts `job_task` to run in parallel. Returns a `JobHandle` associated with - * the `Job`, which can be joined or canceled. - * This avoids degenerate cases: - * - Calling `CallOnWorkerThread()` for each work item, causing significant - * overhead. - * - Fixed number of `CallOnWorkerThread()` calls that split the work and - * might run for a long time. This is problematic when many components post - * "num cores" tasks and all expect to use all the cores. In these cases, - * the scheduler lacks context to be fair to multiple same-priority requests - * and/or ability to request lower priority work to yield when high priority - * work comes in. - * A canonical implementation of `job_task` looks like: - * \code - * class MyJobTask : public JobTask { - * public: - * MyJobTask(...) : worker_queue_(...) {} - * // JobTask implementation. - * void Run(JobDelegate* delegate) override { - * while (!delegate->ShouldYield()) { - * // Smallest unit of work. - * auto work_item = worker_queue_.TakeWorkItem(); // Thread safe. - * if (!work_item) return; - * ProcessWork(work_item); - * } - * } - * - * size_t GetMaxConcurrency() const override { - * return worker_queue_.GetSize(); // Thread safe. - * } - * }; - * - * // ... - * auto handle = PostJob(TaskPriority::kUserVisible, - * std::make_unique(...)); - * handle->Join(); - * \endcode - * - * `PostJob()` and methods of the returned JobHandle/JobDelegate, must never - * be called while holding a lock that could be acquired by `JobTask::Run()` - * or `JobTask::GetMaxConcurrency()` -- that could result in a deadlock. This - * is because (1) `JobTask::GetMaxConcurrency()` may be invoked while holding - * internal lock (A), hence `JobTask::GetMaxConcurrency()` can only use a lock - * (B) if that lock is *never* held while calling back into `JobHandle` from - * any thread (A=>B/B=>A deadlock) and (2) `JobTask::Run()` or - * `JobTask::GetMaxConcurrency()` may be invoked synchronously from - * `JobHandle` (B=>JobHandle::foo=>B deadlock). - * - * A sufficient `PostJob()` implementation that uses the default Job provided - * in libplatform looks like: - * \code - * std::unique_ptr PostJob( - * TaskPriority priority, std::unique_ptr job_task) override { - * return std::make_unique( - * std::make_shared( - * this, std::move(job_task), kNumThreads)); - * } - * \endcode - */ - virtual std::unique_ptr PostJob( - TaskPriority priority, std::unique_ptr job_task) { - return nullptr; - } - - /** - * Returns an instance of a `TracingController`. This must be non-nullptr. The - * default implementation returns an empty `TracingController` that consumes - * trace data without effect. - */ - virtual TracingController* GetTracingController(); -}; - -/** - * Process-global initialization of the garbage collector. Must be called before - * creating a Heap. - * - * Can be called multiple times when paired with `ShutdownProcess()`. - * - * \param page_allocator The allocator used for maintaining meta data. Must not - * change between multiple calls to InitializeProcess. - */ -V8_EXPORT void InitializeProcess(PageAllocator* page_allocator); - -/** - * Must be called after destroying the last used heap. Some process-global - * metadata may not be returned and reused upon a subsequent - * `InitializeProcess()` call. - */ -V8_EXPORT void ShutdownProcess(); - -namespace internal { - -V8_EXPORT void Abort(); - -} // namespace internal - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_PLATFORM_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/prefinalizer.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/prefinalizer.h deleted file mode 100644 index 6153b37ff5d..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/prefinalizer.h +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_PREFINALIZER_H_ -#define INCLUDE_CPPGC_PREFINALIZER_H_ - -#include "cppgc/internal/compiler-specific.h" -#include "cppgc/internal/prefinalizer-handler.h" -#include "cppgc/liveness-broker.h" - -namespace cppgc { - -namespace internal { - -template -class PrefinalizerRegistration final { - public: - explicit PrefinalizerRegistration(T* self) { - static_assert(sizeof(&T::InvokePreFinalizer) > 0, - "USING_PRE_FINALIZER(T) must be defined."); - - cppgc::internal::PreFinalizerRegistrationDispatcher::RegisterPrefinalizer( - {self, T::InvokePreFinalizer}); - } - - void* operator new(size_t, void* location) = delete; - void* operator new(size_t) = delete; -}; - -} // namespace internal - -#define CPPGC_USING_PRE_FINALIZER(Class, PreFinalizer) \ - public: \ - static bool InvokePreFinalizer(const cppgc::LivenessBroker& liveness_broker, \ - void* object) { \ - static_assert(cppgc::IsGarbageCollectedOrMixinTypeV, \ - "Only garbage collected objects can have prefinalizers"); \ - Class* self = static_cast(object); \ - if (liveness_broker.IsHeapObjectAlive(self)) return false; \ - self->PreFinalizer(); \ - return true; \ - } \ - \ - private: \ - CPPGC_NO_UNIQUE_ADDRESS cppgc::internal::PrefinalizerRegistration \ - prefinalizer_dummy_{this}; \ - static_assert(true, "Force semicolon.") - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_PREFINALIZER_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/process-heap-statistics.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/process-heap-statistics.h deleted file mode 100644 index 774cc92f46c..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/process-heap-statistics.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_PROCESS_HEAP_STATISTICS_H_ -#define INCLUDE_CPPGC_PROCESS_HEAP_STATISTICS_H_ - -#include -#include - -#include "v8config.h" // NOLINT(build/include_directory) - -namespace cppgc { -namespace internal { -class ProcessHeapStatisticsUpdater; -} // namespace internal - -class V8_EXPORT ProcessHeapStatistics final { - public: - static size_t TotalAllocatedObjectSize() { - return total_allocated_object_size_.load(std::memory_order_relaxed); - } - static size_t TotalAllocatedSpace() { - return total_allocated_space_.load(std::memory_order_relaxed); - } - - private: - static std::atomic_size_t total_allocated_space_; - static std::atomic_size_t total_allocated_object_size_; - - friend class internal::ProcessHeapStatisticsUpdater; -}; - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_PROCESS_HEAP_STATISTICS_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/sentinel-pointer.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/sentinel-pointer.h deleted file mode 100644 index b049d1a2b34..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/sentinel-pointer.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_SENTINEL_POINTER_H_ -#define INCLUDE_CPPGC_SENTINEL_POINTER_H_ - -#include - -namespace cppgc { -namespace internal { - -// Special tag type used to denote some sentinel member. The semantics of the -// sentinel is defined by the embedder. -struct SentinelPointer { - template - operator T*() const { - static constexpr intptr_t kSentinelValue = 1; - return reinterpret_cast(kSentinelValue); - } - // Hidden friends. - friend bool operator==(SentinelPointer, SentinelPointer) { return true; } - friend bool operator!=(SentinelPointer, SentinelPointer) { return false; } -}; - -} // namespace internal - -constexpr internal::SentinelPointer kSentinelPointer; - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_SENTINEL_POINTER_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/source-location.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/source-location.h deleted file mode 100644 index da5a5ede520..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/source-location.h +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_SOURCE_LOCATION_H_ -#define INCLUDE_CPPGC_SOURCE_LOCATION_H_ - -#include -#include - -#include "v8config.h" // NOLINT(build/include_directory) - -#if defined(__has_builtin) -#define CPPGC_SUPPORTS_SOURCE_LOCATION \ - (__has_builtin(__builtin_FUNCTION) && __has_builtin(__builtin_FILE) && \ - __has_builtin(__builtin_LINE)) // NOLINT -#elif defined(V8_CC_GNU) && __GNUC__ >= 7 -#define CPPGC_SUPPORTS_SOURCE_LOCATION 1 -#elif defined(V8_CC_INTEL) && __ICC >= 1800 -#define CPPGC_SUPPORTS_SOURCE_LOCATION 1 -#else -#define CPPGC_SUPPORTS_SOURCE_LOCATION 0 -#endif - -namespace cppgc { - -/** - * Encapsulates source location information. Mimics C++20's - * `std::source_location`. - */ -class V8_EXPORT SourceLocation final { - public: - /** - * Construct source location information corresponding to the location of the - * call site. - */ -#if CPPGC_SUPPORTS_SOURCE_LOCATION - static constexpr SourceLocation Current( - const char* function = __builtin_FUNCTION(), - const char* file = __builtin_FILE(), size_t line = __builtin_LINE()) { - return SourceLocation(function, file, line); - } -#else - static constexpr SourceLocation Current() { return SourceLocation(); } -#endif // CPPGC_SUPPORTS_SOURCE_LOCATION - - /** - * Constructs unspecified source location information. - */ - constexpr SourceLocation() = default; - - /** - * Returns the name of the function associated with the position represented - * by this object, if any. - * - * \returns the function name as cstring. - */ - constexpr const char* Function() const { return function_; } - - /** - * Returns the name of the current source file represented by this object. - * - * \returns the file name as cstring. - */ - constexpr const char* FileName() const { return file_; } - - /** - * Returns the line number represented by this object. - * - * \returns the line number. - */ - constexpr size_t Line() const { return line_; } - - /** - * Returns a human-readable string representing this object. - * - * \returns a human-readable string representing source location information. - */ - std::string ToString() const; - - private: - constexpr SourceLocation(const char* function, const char* file, size_t line) - : function_(function), file_(file), line_(line) {} - - const char* function_ = nullptr; - const char* file_ = nullptr; - size_t line_ = 0u; -}; - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_SOURCE_LOCATION_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/testing.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/testing.h deleted file mode 100644 index 229ce140f94..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/testing.h +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_TESTING_H_ -#define INCLUDE_CPPGC_TESTING_H_ - -#include "cppgc/common.h" -#include "cppgc/macros.h" -#include "v8config.h" // NOLINT(build/include_directory) - -namespace cppgc { - -class HeapHandle; - -/** - * Namespace contains testing helpers. - */ -namespace testing { - -/** - * Overrides the state of the stack with the provided value. Takes precedence - * over other parameters that set the stack state. Must no be nested. - */ -class V8_EXPORT V8_NODISCARD OverrideEmbedderStackStateScope final { - CPPGC_STACK_ALLOCATED(); - - public: - /** - * Constructs a scoped object that automatically enters and leaves the scope. - * - * \param heap_handle The corresponding heap. - */ - explicit OverrideEmbedderStackStateScope(HeapHandle& heap_handle, - EmbedderStackState state); - ~OverrideEmbedderStackStateScope(); - - OverrideEmbedderStackStateScope(const OverrideEmbedderStackStateScope&) = - delete; - OverrideEmbedderStackStateScope& operator=( - const OverrideEmbedderStackStateScope&) = delete; - - private: - HeapHandle& heap_handle_; -}; - -/** - * Testing interface for managed heaps that allows for controlling garbage - * collection timings. Embedders should use this class when testing the - * interaction of their code with incremental/concurrent garbage collection. - */ -class V8_EXPORT StandaloneTestingHeap final { - public: - explicit StandaloneTestingHeap(HeapHandle&); - - /** - * Start an incremental garbage collection. - */ - void StartGarbageCollection(); - - /** - * Perform an incremental step. This will also schedule concurrent steps if - * needed. - * - * \param stack_state The state of the stack during the step. - */ - bool PerformMarkingStep(EmbedderStackState stack_state); - - /** - * Finalize the current garbage collection cycle atomically. - * Assumes that garbage collection is in progress. - * - * \param stack_state The state of the stack for finalizing the garbage - * collection cycle. - */ - void FinalizeGarbageCollection(EmbedderStackState stack_state); - - /** - * Toggle main thread marking on/off. Allows to stress concurrent marking - * (e.g. to better detect data races). - * - * \param should_mark Denotes whether the main thread should contribute to - * marking. Defaults to true. - */ - void ToggleMainThreadMarking(bool should_mark); - - /** - * Force enable compaction for the next garbage collection cycle. - */ - void ForceCompactionForNextGarbageCollection(); - - private: - HeapHandle& heap_handle_; -}; - -} // namespace testing -} // namespace cppgc - -#endif // INCLUDE_CPPGC_TESTING_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/trace-trait.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/trace-trait.h deleted file mode 100644 index 83619b1d518..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/trace-trait.h +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_TRACE_TRAIT_H_ -#define INCLUDE_CPPGC_TRACE_TRAIT_H_ - -#include - -#include "cppgc/type-traits.h" -#include "v8config.h" // NOLINT(build/include_directory) - -namespace cppgc { - -class Visitor; - -namespace internal { - -// Implementation of the default TraceTrait handling GarbageCollected and -// GarbageCollectedMixin. -template ::type>> -struct TraceTraitImpl; - -} // namespace internal - -/** - * Callback for invoking tracing on a given object. - * - * \param visitor The visitor to dispatch to. - * \param object The object to invoke tracing on. - */ -using TraceCallback = void (*)(Visitor* visitor, const void* object); - -/** - * Describes how to trace an object, i.e., how to visit all Oilpan-relevant - * fields of an object. - */ -struct TraceDescriptor { - /** - * Adjusted base pointer, i.e., the pointer to the class inheriting directly - * from GarbageCollected, of the object that is being traced. - */ - const void* base_object_payload; - /** - * Callback for tracing the object. - */ - TraceCallback callback; -}; - -namespace internal { - -struct V8_EXPORT TraceTraitFromInnerAddressImpl { - static TraceDescriptor GetTraceDescriptor(const void* address); -}; - -/** - * Trait specifying how the garbage collector processes an object of type T. - * - * Advanced users may override handling by creating a specialization for their - * type. - */ -template -struct TraceTraitBase { - static_assert(internal::IsTraceableV, "T must have a Trace() method"); - - /** - * Accessor for retrieving a TraceDescriptor to process an object of type T. - * - * \param self The object to be processed. - * \returns a TraceDescriptor to process the object. - */ - static TraceDescriptor GetTraceDescriptor(const void* self) { - return internal::TraceTraitImpl::GetTraceDescriptor( - static_cast(self)); - } - - /** - * Function invoking the tracing for an object of type T. - * - * \param visitor The visitor to dispatch to. - * \param self The object to invoke tracing on. - */ - static void Trace(Visitor* visitor, const void* self) { - static_cast(self)->Trace(visitor); - } -}; - -} // namespace internal - -template -struct TraceTrait : public internal::TraceTraitBase {}; - -namespace internal { - -template -struct TraceTraitImpl { - static_assert(IsGarbageCollectedTypeV, - "T must be of type GarbageCollected or GarbageCollectedMixin"); - static TraceDescriptor GetTraceDescriptor(const void* self) { - return {self, TraceTrait::Trace}; - } -}; - -template -struct TraceTraitImpl { - static TraceDescriptor GetTraceDescriptor(const void* self) { - return internal::TraceTraitFromInnerAddressImpl::GetTraceDescriptor(self); - } -}; - -} // namespace internal -} // namespace cppgc - -#endif // INCLUDE_CPPGC_TRACE_TRAIT_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/type-traits.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/type-traits.h deleted file mode 100644 index 56cd55d61e2..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/type-traits.h +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_TYPE_TRAITS_H_ -#define INCLUDE_CPPGC_TYPE_TRAITS_H_ - -// This file should stay with minimal dependencies to allow embedder to check -// against Oilpan types without including any other parts. -#include -#include - -namespace cppgc { - -class Visitor; - -namespace internal { -template -class BasicMember; -struct DijkstraWriteBarrierPolicy; -struct NoWriteBarrierPolicy; -class StrongMemberTag; -class UntracedMemberTag; -class WeakMemberTag; - -// Pre-C++17 custom implementation of std::void_t. -template -struct make_void { - typedef void type; -}; -template -using void_t = typename make_void::type; - -// Not supposed to be specialized by the user. -template -struct IsWeak : std::false_type {}; - -// IsTraceMethodConst is used to verify that all Trace methods are marked as -// const. It is equivalent to IsTraceable but for a non-const object. -template -struct IsTraceMethodConst : std::false_type {}; - -template -struct IsTraceMethodConst().Trace( - std::declval()))>> : std::true_type { -}; - -template -struct IsTraceable : std::false_type { - static_assert(sizeof(T), "T must be fully defined"); -}; - -template -struct IsTraceable< - T, void_t().Trace(std::declval()))>> - : std::true_type { - // All Trace methods should be marked as const. If an object of type - // 'T' is traceable then any object of type 'const T' should also - // be traceable. - static_assert(IsTraceMethodConst(), - "Trace methods should be marked as const."); -}; - -template -constexpr bool IsTraceableV = IsTraceable::value; - -template -struct HasGarbageCollectedMixinTypeMarker : std::false_type { - static_assert(sizeof(T), "T must be fully defined"); -}; - -template -struct HasGarbageCollectedMixinTypeMarker< - T, - void_t::IsGarbageCollectedMixinTypeMarker>> - : std::true_type { - static_assert(sizeof(T), "T must be fully defined"); -}; - -template -struct HasGarbageCollectedTypeMarker : std::false_type { - static_assert(sizeof(T), "T must be fully defined"); -}; - -template -struct HasGarbageCollectedTypeMarker< - T, void_t::IsGarbageCollectedTypeMarker>> - : std::true_type { - static_assert(sizeof(T), "T must be fully defined"); -}; - -template ::value, - bool = HasGarbageCollectedMixinTypeMarker::value> -struct IsGarbageCollectedMixinType : std::false_type { - static_assert(sizeof(T), "T must be fully defined"); -}; - -template -struct IsGarbageCollectedMixinType : std::true_type { - static_assert(sizeof(T), "T must be fully defined"); -}; - -template ::value> -struct IsGarbageCollectedType : std::false_type { - static_assert(sizeof(T), "T must be fully defined"); -}; - -template -struct IsGarbageCollectedType : std::true_type { - static_assert(sizeof(T), "T must be fully defined"); -}; - -template -struct IsGarbageCollectedOrMixinType - : std::integral_constant::value || - IsGarbageCollectedMixinType::value> { - static_assert(sizeof(T), "T must be fully defined"); -}; - -template ::value && - HasGarbageCollectedMixinTypeMarker::value)> -struct IsGarbageCollectedWithMixinType : std::false_type { - static_assert(sizeof(T), "T must be fully defined"); -}; - -template -struct IsGarbageCollectedWithMixinType : std::true_type { - static_assert(sizeof(T), "T must be fully defined"); -}; - -template -struct IsSubclassOfBasicMemberTemplate { - private: - template - static std::true_type SubclassCheck( - BasicMember*); - static std::false_type SubclassCheck(...); - - public: - static constexpr bool value = - decltype(SubclassCheck(std::declval()))::value; -}; - -template ::value> -struct IsMemberType : std::false_type {}; - -template -struct IsMemberType : std::true_type {}; - -template ::value> -struct IsWeakMemberType : std::false_type {}; - -template -struct IsWeakMemberType : std::true_type {}; - -template ::value> -struct IsUntracedMemberType : std::false_type {}; - -template -struct IsUntracedMemberType : std::true_type {}; - -template -struct IsComplete { - private: - template - static std::true_type IsSizeOfKnown(U*); - static std::false_type IsSizeOfKnown(...); - - public: - static constexpr bool value = - decltype(IsSizeOfKnown(std::declval()))::value; -}; - -} // namespace internal - -/** - * Value is true for types that inherit from `GarbageCollectedMixin` but not - * `GarbageCollected` (i.e., they are free mixins), and false otherwise. - */ -template -constexpr bool IsGarbageCollectedMixinTypeV = - internal::IsGarbageCollectedMixinType::value; - -/** - * Value is true for types that inherit from `GarbageCollected`, and false - * otherwise. - */ -template -constexpr bool IsGarbageCollectedTypeV = - internal::IsGarbageCollectedType::value; - -/** - * Value is true for types that inherit from either `GarbageCollected` or - * `GarbageCollectedMixin`, and false otherwise. - */ -template -constexpr bool IsGarbageCollectedOrMixinTypeV = - internal::IsGarbageCollectedOrMixinType::value; - -/** - * Value is true for types that inherit from `GarbageCollected` and - * `GarbageCollectedMixin`, and false otherwise. - */ -template -constexpr bool IsGarbageCollectedWithMixinTypeV = - internal::IsGarbageCollectedWithMixinType::value; - -/** - * Value is true for types of type `Member`, and false otherwise. - */ -template -constexpr bool IsMemberTypeV = internal::IsMemberType::value; - -/** - * Value is true for types of type `UntracedMember`, and false otherwise. - */ -template -constexpr bool IsUntracedMemberTypeV = internal::IsUntracedMemberType::value; - -/** - * Value is true for types of type `WeakMember`, and false otherwise. - */ -template -constexpr bool IsWeakMemberTypeV = internal::IsWeakMemberType::value; - -/** - * Value is true for types that are considered weak references, and false - * otherwise. - */ -template -constexpr bool IsWeakV = internal::IsWeak::value; - -/** - * Value is true for types that are complete, and false otherwise. - */ -template -constexpr bool IsCompleteV = internal::IsComplete::value; - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_TYPE_TRAITS_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/visitor.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/visitor.h deleted file mode 100644 index 57e2ce3963a..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/cppgc/visitor.h +++ /dev/null @@ -1,379 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_CPPGC_VISITOR_H_ -#define INCLUDE_CPPGC_VISITOR_H_ - -#include "cppgc/custom-space.h" -#include "cppgc/ephemeron-pair.h" -#include "cppgc/garbage-collected.h" -#include "cppgc/internal/logging.h" -#include "cppgc/internal/pointer-policies.h" -#include "cppgc/liveness-broker.h" -#include "cppgc/member.h" -#include "cppgc/sentinel-pointer.h" -#include "cppgc/source-location.h" -#include "cppgc/trace-trait.h" -#include "cppgc/type-traits.h" - -namespace cppgc { - -namespace internal { -template -class BasicCrossThreadPersistent; -template -class BasicPersistent; -class ConservativeTracingVisitor; -class VisitorBase; -class VisitorFactory; -} // namespace internal - -using WeakCallback = void (*)(const LivenessBroker&, const void*); - -/** - * Visitor passed to trace methods. All managed pointers must have called the - * Visitor's trace method on them. - * - * \code - * class Foo final : public GarbageCollected { - * public: - * void Trace(Visitor* visitor) const { - * visitor->Trace(foo_); - * visitor->Trace(weak_foo_); - * } - * private: - * Member foo_; - * WeakMember weak_foo_; - * }; - * \endcode - */ -class V8_EXPORT Visitor { - public: - class Key { - private: - Key() = default; - friend class internal::VisitorFactory; - }; - - explicit Visitor(Key) {} - - virtual ~Visitor() = default; - - /** - * Trace method for raw pointers. Prefer the versions for managed pointers. - * - * \param member Reference retaining an object. - */ - template - void Trace(const T* t) { - static_assert(sizeof(T), "Pointee type must be fully defined."); - static_assert(internal::IsGarbageCollectedOrMixinType::value, - "T must be GarbageCollected or GarbageCollectedMixin type"); - if (!t) { - return; - } - Visit(t, TraceTrait::GetTraceDescriptor(t)); - } - - /** - * Trace method for Member. - * - * \param member Member reference retaining an object. - */ - template - void Trace(const Member& member) { - const T* value = member.GetRawAtomic(); - CPPGC_DCHECK(value != kSentinelPointer); - Trace(value); - } - - /** - * Trace method for WeakMember. - * - * \param weak_member WeakMember reference weakly retaining an object. - */ - template - void Trace(const WeakMember& weak_member) { - static_assert(sizeof(T), "Pointee type must be fully defined."); - static_assert(internal::IsGarbageCollectedOrMixinType::value, - "T must be GarbageCollected or GarbageCollectedMixin type"); - static_assert(!internal::IsAllocatedOnCompactableSpace::value, - "Weak references to compactable objects are not allowed"); - - const T* value = weak_member.GetRawAtomic(); - - // Bailout assumes that WeakMember emits write barrier. - if (!value) { - return; - } - - CPPGC_DCHECK(value != kSentinelPointer); - VisitWeak(value, TraceTrait::GetTraceDescriptor(value), - &HandleWeak>, &weak_member); - } - - /** - * Trace method for inlined objects that are not allocated themselves but - * otherwise follow managed heap layout and have a Trace() method. - * - * \param object reference of the inlined object. - */ - template - void Trace(const T& object) { -#if V8_ENABLE_CHECKS - // This object is embedded in potentially multiple nested objects. The - // outermost object must not be in construction as such objects are (a) not - // processed immediately, and (b) only processed conservatively if not - // otherwise possible. - CheckObjectNotInConstruction(&object); -#endif // V8_ENABLE_CHECKS - TraceTrait::Trace(this, &object); - } - - /** - * Registers a weak callback method on the object of type T. See - * LivenessBroker for an usage example. - * - * \param object of type T specifying a weak callback method. - */ - template - void RegisterWeakCallbackMethod(const T* object) { - RegisterWeakCallback(&WeakCallbackMethodDelegate, object); - } - - /** - * Trace method for EphemeronPair. - * - * \param ephemeron_pair EphemeronPair reference weakly retaining a key object - * and strongly retaining a value object in case the key object is alive. - */ - template - void Trace(const EphemeronPair& ephemeron_pair) { - TraceEphemeron(ephemeron_pair.key, &ephemeron_pair.value); - RegisterWeakCallbackMethod, - &EphemeronPair::ClearValueIfKeyIsDead>( - &ephemeron_pair); - } - - /** - * Trace method for a single ephemeron. Used for tracing a raw ephemeron in - * which the `key` and `value` are kept separately. - * - * \param weak_member_key WeakMember reference weakly retaining a key object. - * \param member_value Member reference with ephemeron semantics. - */ - template - void TraceEphemeron(const WeakMember& weak_member_key, - const Member* member_value) { - const KeyType* key = weak_member_key.GetRawAtomic(); - if (!key) return; - - // `value` must always be non-null. - CPPGC_DCHECK(member_value); - const ValueType* value = member_value->GetRawAtomic(); - if (!value) return; - - // KeyType and ValueType may refer to GarbageCollectedMixin. - TraceDescriptor value_desc = - TraceTrait::GetTraceDescriptor(value); - CPPGC_DCHECK(value_desc.base_object_payload); - const void* key_base_object_payload = - TraceTrait::GetTraceDescriptor(key).base_object_payload; - CPPGC_DCHECK(key_base_object_payload); - - VisitEphemeron(key_base_object_payload, value, value_desc); - } - - /** - * Trace method for a single ephemeron. Used for tracing a raw ephemeron in - * which the `key` and `value` are kept separately. Note that this overload - * is for non-GarbageCollected `value`s that can be traced though. - * - * \param key `WeakMember` reference weakly retaining a key object. - * \param value Reference weakly retaining a value object. Note that - * `ValueType` here should not be `Member`. It is expected that - * `TraceTrait::GetTraceDescriptor(value)` returns a - * `TraceDescriptor` with a null base pointer but a valid trace method. - */ - template - void TraceEphemeron(const WeakMember& weak_member_key, - const ValueType* value) { - static_assert(!IsGarbageCollectedOrMixinTypeV, - "garbage-collected types must use WeakMember and Member"); - const KeyType* key = weak_member_key.GetRawAtomic(); - if (!key) return; - - // `value` must always be non-null. - CPPGC_DCHECK(value); - TraceDescriptor value_desc = - TraceTrait::GetTraceDescriptor(value); - // `value_desc.base_object_payload` must be null as this override is only - // taken for non-garbage-collected values. - CPPGC_DCHECK(!value_desc.base_object_payload); - - // KeyType might be a GarbageCollectedMixin. - const void* key_base_object_payload = - TraceTrait::GetTraceDescriptor(key).base_object_payload; - CPPGC_DCHECK(key_base_object_payload); - - VisitEphemeron(key_base_object_payload, value, value_desc); - } - - /** - * Trace method that strongifies a WeakMember. - * - * \param weak_member WeakMember reference retaining an object. - */ - template - void TraceStrongly(const WeakMember& weak_member) { - const T* value = weak_member.GetRawAtomic(); - CPPGC_DCHECK(value != kSentinelPointer); - Trace(value); - } - - /** - * Trace method for weak containers. - * - * \param object reference of the weak container. - * \param callback to be invoked. - * \param data custom data that is passed to the callback. - */ - template - void TraceWeakContainer(const T* object, WeakCallback callback, - const void* data) { - if (!object) return; - VisitWeakContainer(object, TraceTrait::GetTraceDescriptor(object), - TraceTrait::GetWeakTraceDescriptor(object), callback, - data); - } - - /** - * Registers a slot containing a reference to an object allocated on a - * compactable space. Such references maybe be arbitrarily moved by the GC. - * - * \param slot location of reference to object that might be moved by the GC. - */ - template - void RegisterMovableReference(const T** slot) { - static_assert(internal::IsAllocatedOnCompactableSpace::value, - "Only references to objects allocated on compactable spaces " - "should be registered as movable slots."); - static_assert(!IsGarbageCollectedMixinTypeV, - "Mixin types do not support compaction."); - HandleMovableReference(reinterpret_cast(slot)); - } - - /** - * Registers a weak callback that is invoked during garbage collection. - * - * \param callback to be invoked. - * \param data custom data that is passed to the callback. - */ - virtual void RegisterWeakCallback(WeakCallback callback, const void* data) {} - - /** - * Defers tracing an object from a concurrent thread to the mutator thread. - * Should be called by Trace methods of types that are not safe to trace - * concurrently. - * - * \param parameter tells the trace callback which object was deferred. - * \param callback to be invoked for tracing on the mutator thread. - * \param deferred_size size of deferred object. - * - * \returns false if the object does not need to be deferred (i.e. currently - * traced on the mutator thread) and true otherwise (i.e. currently traced on - * a concurrent thread). - */ - virtual V8_WARN_UNUSED_RESULT bool DeferTraceToMutatorThreadIfConcurrent( - const void* parameter, TraceCallback callback, size_t deferred_size) { - // By default tracing is not deferred. - return false; - } - - protected: - virtual void Visit(const void* self, TraceDescriptor) {} - virtual void VisitWeak(const void* self, TraceDescriptor, WeakCallback, - const void* weak_member) {} - virtual void VisitRoot(const void*, TraceDescriptor, const SourceLocation&) {} - virtual void VisitWeakRoot(const void* self, TraceDescriptor, WeakCallback, - const void* weak_root, const SourceLocation&) {} - virtual void VisitEphemeron(const void* key, const void* value, - TraceDescriptor value_desc) {} - virtual void VisitWeakContainer(const void* self, TraceDescriptor strong_desc, - TraceDescriptor weak_desc, - WeakCallback callback, const void* data) {} - virtual void HandleMovableReference(const void**) {} - - private: - template - static void WeakCallbackMethodDelegate(const LivenessBroker& info, - const void* self) { - // Callback is registered through a potential const Trace method but needs - // to be able to modify fields. See HandleWeak. - (const_cast(static_cast(self))->*method)(info); - } - - template - static void HandleWeak(const LivenessBroker& info, const void* object) { - const PointerType* weak = static_cast(object); - auto* raw_ptr = weak->GetFromGC(); - // Sentinel values are preserved for weak pointers. - if (raw_ptr == kSentinelPointer) return; - if (!info.IsHeapObjectAlive(raw_ptr)) { - weak->ClearFromGC(); - } - } - - template * = nullptr> - void TraceRoot(const Persistent& p, const SourceLocation& loc) { - using PointeeType = typename Persistent::PointeeType; - static_assert(sizeof(PointeeType), - "Persistent's pointee type must be fully defined"); - static_assert(internal::IsGarbageCollectedOrMixinType::value, - "Persistent's pointee type must be GarbageCollected or " - "GarbageCollectedMixin"); - auto* ptr = p.GetFromGC(); - if (!ptr) { - return; - } - VisitRoot(ptr, TraceTrait::GetTraceDescriptor(ptr), loc); - } - - template < - typename WeakPersistent, - std::enable_if_t* = nullptr> - void TraceRoot(const WeakPersistent& p, const SourceLocation& loc) { - using PointeeType = typename WeakPersistent::PointeeType; - static_assert(sizeof(PointeeType), - "Persistent's pointee type must be fully defined"); - static_assert(internal::IsGarbageCollectedOrMixinType::value, - "Persistent's pointee type must be GarbageCollected or " - "GarbageCollectedMixin"); - static_assert(!internal::IsAllocatedOnCompactableSpace::value, - "Weak references to compactable objects are not allowed"); - auto* ptr = p.GetFromGC(); - VisitWeakRoot(ptr, TraceTrait::GetTraceDescriptor(ptr), - &HandleWeak, &p, loc); - } - -#if V8_ENABLE_CHECKS - void CheckObjectNotInConstruction(const void* address); -#endif // V8_ENABLE_CHECKS - - template - friend class internal::BasicCrossThreadPersistent; - template - friend class internal::BasicPersistent; - friend class internal::ConservativeTracingVisitor; - friend class internal::VisitorBase; -}; - -} // namespace cppgc - -#endif // INCLUDE_CPPGC_VISITOR_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/inspector/Debugger.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/inspector/Debugger.h deleted file mode 100644 index d984c6a4ad0..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/inspector/Debugger.h +++ /dev/null @@ -1,59 +0,0 @@ -// This file is generated by Exported_h.template. - -// Copyright (c) 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef v8_inspector_protocol_Debugger_api_h -#define v8_inspector_protocol_Debugger_api_h - -#include "v8-inspector.h" - -namespace v8_inspector { -namespace protocol { - -#ifndef v8_inspector_protocol_exported_api_h -#define v8_inspector_protocol_exported_api_h -class V8_EXPORT Exported { -public: - virtual void AppendSerialized(std::vector* out) const = 0; - - virtual ~Exported() { } -}; -#endif // !defined(v8_inspector_protocol_exported_api_h) - -namespace Debugger { -namespace API { - -// ------------- Enums. - -namespace Paused { -namespace ReasonEnum { -V8_EXPORT extern const char* Ambiguous; -V8_EXPORT extern const char* Assert; -V8_EXPORT extern const char* CSPViolation; -V8_EXPORT extern const char* DebugCommand; -V8_EXPORT extern const char* DOM; -V8_EXPORT extern const char* EventListener; -V8_EXPORT extern const char* Exception; -V8_EXPORT extern const char* Instrumentation; -V8_EXPORT extern const char* OOM; -V8_EXPORT extern const char* Other; -V8_EXPORT extern const char* PromiseRejection; -V8_EXPORT extern const char* XHR; -} // ReasonEnum -} // Paused - -// ------------- Types. - -class V8_EXPORT SearchMatch : public Exported { -public: - static std::unique_ptr fromBinary(const uint8_t* data, size_t length); -}; - -} // namespace API -} // namespace Debugger -} // namespace v8_inspector -} // namespace protocol - -#endif // !defined(v8_inspector_protocol_Debugger_api_h) diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/inspector/Runtime.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/inspector/Runtime.h deleted file mode 100644 index f9d515ba74e..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/inspector/Runtime.h +++ /dev/null @@ -1,52 +0,0 @@ -// This file is generated by Exported_h.template. - -// Copyright (c) 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef v8_inspector_protocol_Runtime_api_h -#define v8_inspector_protocol_Runtime_api_h - -#include "v8-inspector.h" - -namespace v8_inspector { -namespace protocol { - -#ifndef v8_inspector_protocol_exported_api_h -#define v8_inspector_protocol_exported_api_h -class V8_EXPORT Exported { -public: - virtual void AppendSerialized(std::vector* out) const = 0; - - virtual ~Exported() { } -}; -#endif // !defined(v8_inspector_protocol_exported_api_h) - -namespace Runtime { -namespace API { - -// ------------- Enums. - -// ------------- Types. - -class V8_EXPORT RemoteObject : public Exported { -public: - static std::unique_ptr fromBinary(const uint8_t* data, size_t length); -}; - -class V8_EXPORT StackTrace : public Exported { -public: - static std::unique_ptr fromBinary(const uint8_t* data, size_t length); -}; - -class V8_EXPORT StackTraceId : public Exported { -public: - static std::unique_ptr fromBinary(const uint8_t* data, size_t length); -}; - -} // namespace API -} // namespace Runtime -} // namespace v8_inspector -} // namespace protocol - -#endif // !defined(v8_inspector_protocol_Runtime_api_h) diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/inspector/Schema.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/inspector/Schema.h deleted file mode 100644 index 03a76fa9c56..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/inspector/Schema.h +++ /dev/null @@ -1,42 +0,0 @@ -// This file is generated by Exported_h.template. - -// Copyright (c) 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef v8_inspector_protocol_Schema_api_h -#define v8_inspector_protocol_Schema_api_h - -#include "v8-inspector.h" - -namespace v8_inspector { -namespace protocol { - -#ifndef v8_inspector_protocol_exported_api_h -#define v8_inspector_protocol_exported_api_h -class V8_EXPORT Exported { -public: - virtual void AppendSerialized(std::vector* out) const = 0; - - virtual ~Exported() { } -}; -#endif // !defined(v8_inspector_protocol_exported_api_h) - -namespace Schema { -namespace API { - -// ------------- Enums. - -// ------------- Types. - -class V8_EXPORT Domain : public Exported { -public: - static std::unique_ptr fromBinary(const uint8_t* data, size_t length); -}; - -} // namespace API -} // namespace Schema -} // namespace v8_inspector -} // namespace protocol - -#endif // !defined(v8_inspector_protocol_Schema_api_h) diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/libplatform/libplatform-export.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/libplatform/libplatform-export.h deleted file mode 100644 index 15618434977..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/libplatform/libplatform-export.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2016 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef V8_LIBPLATFORM_LIBPLATFORM_EXPORT_H_ -#define V8_LIBPLATFORM_LIBPLATFORM_EXPORT_H_ - -#if defined(_WIN32) - -#ifdef BUILDING_V8_PLATFORM_SHARED -#define V8_PLATFORM_EXPORT __declspec(dllexport) -#elif USING_V8_PLATFORM_SHARED -#define V8_PLATFORM_EXPORT __declspec(dllimport) -#else -#define V8_PLATFORM_EXPORT -#endif // BUILDING_V8_PLATFORM_SHARED - -#else // defined(_WIN32) - -// Setup for Linux shared library export. -#ifdef BUILDING_V8_PLATFORM_SHARED -#define V8_PLATFORM_EXPORT __attribute__((visibility("default"))) -#else -#define V8_PLATFORM_EXPORT -#endif - -#endif // defined(_WIN32) - -#endif // V8_LIBPLATFORM_LIBPLATFORM_EXPORT_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/libplatform/libplatform.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/libplatform/libplatform.h deleted file mode 100644 index 00de81df887..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/libplatform/libplatform.h +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2014 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef V8_LIBPLATFORM_LIBPLATFORM_H_ -#define V8_LIBPLATFORM_LIBPLATFORM_H_ - -#include - -#include "libplatform/libplatform-export.h" -#include "libplatform/v8-tracing.h" -#include "v8-platform.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { -namespace platform { - -enum class IdleTaskSupport { kDisabled, kEnabled }; -enum class InProcessStackDumping { kDisabled, kEnabled }; - -enum class MessageLoopBehavior : bool { - kDoNotWait = false, - kWaitForWork = true -}; - -/** - * Returns a new instance of the default v8::Platform implementation. - * - * The caller will take ownership of the returned pointer. |thread_pool_size| - * is the number of worker threads to allocate for background jobs. If a value - * of zero is passed, a suitable default based on the current number of - * processors online will be chosen. - * If |idle_task_support| is enabled then the platform will accept idle - * tasks (IdleTasksEnabled will return true) and will rely on the embedder - * calling v8::platform::RunIdleTasks to process the idle tasks. - * If |tracing_controller| is nullptr, the default platform will create a - * v8::platform::TracingController instance and use it. - */ -V8_PLATFORM_EXPORT std::unique_ptr NewDefaultPlatform( - int thread_pool_size = 0, - IdleTaskSupport idle_task_support = IdleTaskSupport::kDisabled, - InProcessStackDumping in_process_stack_dumping = - InProcessStackDumping::kDisabled, - std::unique_ptr tracing_controller = {}); - -/** - * The same as NewDefaultPlatform but disables the worker thread pool. - * It must be used with the --single-threaded V8 flag. - */ -V8_PLATFORM_EXPORT std::unique_ptr -NewSingleThreadedDefaultPlatform( - IdleTaskSupport idle_task_support = IdleTaskSupport::kDisabled, - InProcessStackDumping in_process_stack_dumping = - InProcessStackDumping::kDisabled, - std::unique_ptr tracing_controller = {}); - -/** - * Returns a new instance of the default v8::JobHandle implementation. - * - * The job will be executed by spawning up to |num_worker_threads| many worker - * threads on the provided |platform| with the given |priority|. - */ -V8_PLATFORM_EXPORT std::unique_ptr NewDefaultJobHandle( - v8::Platform* platform, v8::TaskPriority priority, - std::unique_ptr job_task, size_t num_worker_threads); - -/** - * Pumps the message loop for the given isolate. - * - * The caller has to make sure that this is called from the right thread. - * Returns true if a task was executed, and false otherwise. If the call to - * PumpMessageLoop is nested within another call to PumpMessageLoop, only - * nestable tasks may run. Otherwise, any task may run. Unless requested through - * the |behavior| parameter, this call does not block if no task is pending. The - * |platform| has to be created using |NewDefaultPlatform|. - */ -V8_PLATFORM_EXPORT bool PumpMessageLoop( - v8::Platform* platform, v8::Isolate* isolate, - MessageLoopBehavior behavior = MessageLoopBehavior::kDoNotWait); - -/** - * Runs pending idle tasks for at most |idle_time_in_seconds| seconds. - * - * The caller has to make sure that this is called from the right thread. - * This call does not block if no task is pending. The |platform| has to be - * created using |NewDefaultPlatform|. - */ -V8_PLATFORM_EXPORT void RunIdleTasks(v8::Platform* platform, - v8::Isolate* isolate, - double idle_time_in_seconds); - -/** - * Attempts to set the tracing controller for the given platform. - * - * The |platform| has to be created using |NewDefaultPlatform|. - * - */ -V8_DEPRECATE_SOON("Access the DefaultPlatform directly") -V8_PLATFORM_EXPORT void SetTracingController( - v8::Platform* platform, - v8::platform::tracing::TracingController* tracing_controller); - -/** - * Notifies the given platform about the Isolate getting deleted soon. Has to be - * called for all Isolates which are deleted - unless we're shutting down the - * platform. - * - * The |platform| has to be created using |NewDefaultPlatform|. - * - */ -V8_PLATFORM_EXPORT void NotifyIsolateShutdown(v8::Platform* platform, - Isolate* isolate); - -} // namespace platform -} // namespace v8 - -#endif // V8_LIBPLATFORM_LIBPLATFORM_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/libplatform/v8-tracing.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/libplatform/v8-tracing.h deleted file mode 100644 index c7a5c4f9f5f..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/libplatform/v8-tracing.h +++ /dev/null @@ -1,334 +0,0 @@ -// Copyright 2016 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef V8_LIBPLATFORM_V8_TRACING_H_ -#define V8_LIBPLATFORM_V8_TRACING_H_ - -#include -#include -#include -#include -#include - -#include "libplatform/libplatform-export.h" -#include "v8-platform.h" // NOLINT(build/include_directory) - -namespace perfetto { -namespace trace_processor { -class TraceProcessorStorage; -} -class TracingSession; -} - -namespace v8 { - -namespace base { -class Mutex; -} // namespace base - -namespace platform { -namespace tracing { - -class TraceEventListener; - -const int kTraceMaxNumArgs = 2; - -class V8_PLATFORM_EXPORT TraceObject { - public: - union ArgValue { - V8_DEPRECATED("use as_uint ? true : false") bool as_bool; - uint64_t as_uint; - int64_t as_int; - double as_double; - const void* as_pointer; - const char* as_string; - }; - - TraceObject() = default; - ~TraceObject(); - void Initialize( - char phase, const uint8_t* category_enabled_flag, const char* name, - const char* scope, uint64_t id, uint64_t bind_id, int num_args, - const char** arg_names, const uint8_t* arg_types, - const uint64_t* arg_values, - std::unique_ptr* arg_convertables, - unsigned int flags, int64_t timestamp, int64_t cpu_timestamp); - void UpdateDuration(int64_t timestamp, int64_t cpu_timestamp); - void InitializeForTesting( - char phase, const uint8_t* category_enabled_flag, const char* name, - const char* scope, uint64_t id, uint64_t bind_id, int num_args, - const char** arg_names, const uint8_t* arg_types, - const uint64_t* arg_values, - std::unique_ptr* arg_convertables, - unsigned int flags, int pid, int tid, int64_t ts, int64_t tts, - uint64_t duration, uint64_t cpu_duration); - - int pid() const { return pid_; } - int tid() const { return tid_; } - char phase() const { return phase_; } - const uint8_t* category_enabled_flag() const { - return category_enabled_flag_; - } - const char* name() const { return name_; } - const char* scope() const { return scope_; } - uint64_t id() const { return id_; } - uint64_t bind_id() const { return bind_id_; } - int num_args() const { return num_args_; } - const char** arg_names() { return arg_names_; } - uint8_t* arg_types() { return arg_types_; } - ArgValue* arg_values() { return arg_values_; } - std::unique_ptr* arg_convertables() { - return arg_convertables_; - } - unsigned int flags() const { return flags_; } - int64_t ts() { return ts_; } - int64_t tts() { return tts_; } - uint64_t duration() { return duration_; } - uint64_t cpu_duration() { return cpu_duration_; } - - private: - int pid_; - int tid_; - char phase_; - const char* name_; - const char* scope_; - const uint8_t* category_enabled_flag_; - uint64_t id_; - uint64_t bind_id_; - int num_args_ = 0; - const char* arg_names_[kTraceMaxNumArgs]; - uint8_t arg_types_[kTraceMaxNumArgs]; - ArgValue arg_values_[kTraceMaxNumArgs]; - std::unique_ptr - arg_convertables_[kTraceMaxNumArgs]; - char* parameter_copy_storage_ = nullptr; - unsigned int flags_; - int64_t ts_; - int64_t tts_; - uint64_t duration_; - uint64_t cpu_duration_; - - // Disallow copy and assign - TraceObject(const TraceObject&) = delete; - void operator=(const TraceObject&) = delete; -}; - -class V8_PLATFORM_EXPORT TraceWriter { - public: - TraceWriter() = default; - virtual ~TraceWriter() = default; - virtual void AppendTraceEvent(TraceObject* trace_event) = 0; - virtual void Flush() = 0; - - static TraceWriter* CreateJSONTraceWriter(std::ostream& stream); - static TraceWriter* CreateJSONTraceWriter(std::ostream& stream, - const std::string& tag); - - static TraceWriter* CreateSystemInstrumentationTraceWriter(); - - private: - // Disallow copy and assign - TraceWriter(const TraceWriter&) = delete; - void operator=(const TraceWriter&) = delete; -}; - -class V8_PLATFORM_EXPORT TraceBufferChunk { - public: - explicit TraceBufferChunk(uint32_t seq); - - void Reset(uint32_t new_seq); - bool IsFull() const { return next_free_ == kChunkSize; } - TraceObject* AddTraceEvent(size_t* event_index); - TraceObject* GetEventAt(size_t index) { return &chunk_[index]; } - - uint32_t seq() const { return seq_; } - size_t size() const { return next_free_; } - - static const size_t kChunkSize = 64; - - private: - size_t next_free_ = 0; - TraceObject chunk_[kChunkSize]; - uint32_t seq_; - - // Disallow copy and assign - TraceBufferChunk(const TraceBufferChunk&) = delete; - void operator=(const TraceBufferChunk&) = delete; -}; - -class V8_PLATFORM_EXPORT TraceBuffer { - public: - TraceBuffer() = default; - virtual ~TraceBuffer() = default; - - virtual TraceObject* AddTraceEvent(uint64_t* handle) = 0; - virtual TraceObject* GetEventByHandle(uint64_t handle) = 0; - virtual bool Flush() = 0; - - static const size_t kRingBufferChunks = 1024; - - static TraceBuffer* CreateTraceBufferRingBuffer(size_t max_chunks, - TraceWriter* trace_writer); - - private: - // Disallow copy and assign - TraceBuffer(const TraceBuffer&) = delete; - void operator=(const TraceBuffer&) = delete; -}; - -// Options determines how the trace buffer stores data. -enum TraceRecordMode { - // Record until the trace buffer is full. - RECORD_UNTIL_FULL, - - // Record until the user ends the trace. The trace buffer is a fixed size - // and we use it as a ring buffer during recording. - RECORD_CONTINUOUSLY, - - // Record until the trace buffer is full, but with a huge buffer size. - RECORD_AS_MUCH_AS_POSSIBLE, - - // Echo to console. Events are discarded. - ECHO_TO_CONSOLE, -}; - -class V8_PLATFORM_EXPORT TraceConfig { - public: - typedef std::vector StringList; - - static TraceConfig* CreateDefaultTraceConfig(); - - TraceConfig() : enable_systrace_(false), enable_argument_filter_(false) {} - TraceRecordMode GetTraceRecordMode() const { return record_mode_; } - const StringList& GetEnabledCategories() const { - return included_categories_; - } - bool IsSystraceEnabled() const { return enable_systrace_; } - bool IsArgumentFilterEnabled() const { return enable_argument_filter_; } - - void SetTraceRecordMode(TraceRecordMode mode) { record_mode_ = mode; } - void EnableSystrace() { enable_systrace_ = true; } - void EnableArgumentFilter() { enable_argument_filter_ = true; } - - void AddIncludedCategory(const char* included_category); - - bool IsCategoryGroupEnabled(const char* category_group) const; - - private: - TraceRecordMode record_mode_; - bool enable_systrace_ : 1; - bool enable_argument_filter_ : 1; - StringList included_categories_; - - // Disallow copy and assign - TraceConfig(const TraceConfig&) = delete; - void operator=(const TraceConfig&) = delete; -}; - -#if defined(_MSC_VER) -#define V8_PLATFORM_NON_EXPORTED_BASE(code) \ - __pragma(warning(suppress : 4275)) code -#else -#define V8_PLATFORM_NON_EXPORTED_BASE(code) code -#endif // defined(_MSC_VER) - -class V8_PLATFORM_EXPORT TracingController - : public V8_PLATFORM_NON_EXPORTED_BASE(v8::TracingController) { - public: - TracingController(); - ~TracingController() override; - -#if defined(V8_USE_PERFETTO) - // Must be called before StartTracing() if V8_USE_PERFETTO is true. Provides - // the output stream for the JSON trace data. - void InitializeForPerfetto(std::ostream* output_stream); - // Provide an optional listener for testing that will receive trace events. - // Must be called before StartTracing(). - void SetTraceEventListenerForTesting(TraceEventListener* listener); -#else // defined(V8_USE_PERFETTO) - // The pointer returned from GetCategoryGroupEnabled() points to a value with - // zero or more of the following bits. Used in this class only. The - // TRACE_EVENT macros should only use the value as a bool. These values must - // be in sync with macro values in TraceEvent.h in Blink. - enum CategoryGroupEnabledFlags { - // Category group enabled for the recording mode. - ENABLED_FOR_RECORDING = 1 << 0, - // Category group enabled by SetEventCallbackEnabled(). - ENABLED_FOR_EVENT_CALLBACK = 1 << 2, - // Category group enabled to export events to ETW. - ENABLED_FOR_ETW_EXPORT = 1 << 3 - }; - - // Takes ownership of |trace_buffer|. - void Initialize(TraceBuffer* trace_buffer); - - // v8::TracingController implementation. - const uint8_t* GetCategoryGroupEnabled(const char* category_group) override; - uint64_t AddTraceEvent( - char phase, const uint8_t* category_enabled_flag, const char* name, - const char* scope, uint64_t id, uint64_t bind_id, int32_t num_args, - const char** arg_names, const uint8_t* arg_types, - const uint64_t* arg_values, - std::unique_ptr* arg_convertables, - unsigned int flags) override; - uint64_t AddTraceEventWithTimestamp( - char phase, const uint8_t* category_enabled_flag, const char* name, - const char* scope, uint64_t id, uint64_t bind_id, int32_t num_args, - const char** arg_names, const uint8_t* arg_types, - const uint64_t* arg_values, - std::unique_ptr* arg_convertables, - unsigned int flags, int64_t timestamp) override; - void UpdateTraceEventDuration(const uint8_t* category_enabled_flag, - const char* name, uint64_t handle) override; - - static const char* GetCategoryGroupName(const uint8_t* category_enabled_flag); -#endif // !defined(V8_USE_PERFETTO) - - void AddTraceStateObserver( - v8::TracingController::TraceStateObserver* observer) override; - void RemoveTraceStateObserver( - v8::TracingController::TraceStateObserver* observer) override; - - void StartTracing(TraceConfig* trace_config); - void StopTracing(); - - protected: -#if !defined(V8_USE_PERFETTO) - virtual int64_t CurrentTimestampMicroseconds(); - virtual int64_t CurrentCpuTimestampMicroseconds(); -#endif // !defined(V8_USE_PERFETTO) - - private: -#if !defined(V8_USE_PERFETTO) - void UpdateCategoryGroupEnabledFlag(size_t category_index); - void UpdateCategoryGroupEnabledFlags(); -#endif // !defined(V8_USE_PERFETTO) - - std::unique_ptr mutex_; - std::unique_ptr trace_config_; - std::atomic_bool recording_{false}; - std::unordered_set observers_; - -#if defined(V8_USE_PERFETTO) - std::ostream* output_stream_ = nullptr; - std::unique_ptr - trace_processor_; - TraceEventListener* listener_for_testing_ = nullptr; - std::unique_ptr tracing_session_; -#else // !defined(V8_USE_PERFETTO) - std::unique_ptr trace_buffer_; -#endif // !defined(V8_USE_PERFETTO) - - // Disallow copy and assign - TracingController(const TracingController&) = delete; - void operator=(const TracingController&) = delete; -}; - -#undef V8_PLATFORM_NON_EXPORTED_BASE - -} // namespace tracing -} // namespace platform -} // namespace v8 - -#endif // V8_LIBPLATFORM_V8_TRACING_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-array-buffer.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-array-buffer.h deleted file mode 100644 index 0ce2b653684..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-array-buffer.h +++ /dev/null @@ -1,433 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_ARRAY_BUFFER_H_ -#define INCLUDE_V8_ARRAY_BUFFER_H_ - -#include - -#include - -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8-object.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class SharedArrayBuffer; - -#ifndef V8_ARRAY_BUFFER_INTERNAL_FIELD_COUNT -// The number of required internal fields can be defined by embedder. -#define V8_ARRAY_BUFFER_INTERNAL_FIELD_COUNT 2 -#endif - -enum class ArrayBufferCreationMode { kInternalized, kExternalized }; - -/** - * A wrapper around the backing store (i.e. the raw memory) of an array buffer. - * See a document linked in http://crbug.com/v8/9908 for more information. - * - * The allocation and destruction of backing stores is generally managed by - * V8. Clients should always use standard C++ memory ownership types (i.e. - * std::unique_ptr and std::shared_ptr) to manage lifetimes of backing stores - * properly, since V8 internal objects may alias backing stores. - * - * This object does not keep the underlying |ArrayBuffer::Allocator| alive by - * default. Use Isolate::CreateParams::array_buffer_allocator_shared when - * creating the Isolate to make it hold a reference to the allocator itself. - */ -class V8_EXPORT BackingStore : public v8::internal::BackingStoreBase { - public: - ~BackingStore(); - - /** - * Return a pointer to the beginning of the memory block for this backing - * store. The pointer is only valid as long as this backing store object - * lives. - */ - void* Data() const; - - /** - * The length (in bytes) of this backing store. - */ - size_t ByteLength() const; - - /** - * Indicates whether the backing store was created for an ArrayBuffer or - * a SharedArrayBuffer. - */ - bool IsShared() const; - - /** - * Prevent implicit instantiation of operator delete with size_t argument. - * The size_t argument would be incorrect because ptr points to the - * internal BackingStore object. - */ - void operator delete(void* ptr) { ::operator delete(ptr); } - - /** - * Wrapper around ArrayBuffer::Allocator::Reallocate that preserves IsShared. - * Assumes that the backing_store was allocated by the ArrayBuffer allocator - * of the given isolate. - */ - static std::unique_ptr Reallocate( - v8::Isolate* isolate, std::unique_ptr backing_store, - size_t byte_length); - - /** - * This callback is used only if the memory block for a BackingStore cannot be - * allocated with an ArrayBuffer::Allocator. In such cases the destructor of - * the BackingStore invokes the callback to free the memory block. - */ - using DeleterCallback = void (*)(void* data, size_t length, - void* deleter_data); - - /** - * If the memory block of a BackingStore is static or is managed manually, - * then this empty deleter along with nullptr deleter_data can be passed to - * ArrayBuffer::NewBackingStore to indicate that. - * - * The manually managed case should be used with caution and only when it - * is guaranteed that the memory block freeing happens after detaching its - * ArrayBuffer. - */ - static void EmptyDeleter(void* data, size_t length, void* deleter_data); - - private: - /** - * See [Shared]ArrayBuffer::GetBackingStore and - * [Shared]ArrayBuffer::NewBackingStore. - */ - BackingStore(); -}; - -#if !defined(V8_IMMINENT_DEPRECATION_WARNINGS) -// Use v8::BackingStore::DeleterCallback instead. -using BackingStoreDeleterCallback = void (*)(void* data, size_t length, - void* deleter_data); - -#endif - -/** - * An instance of the built-in ArrayBuffer constructor (ES6 draft 15.13.5). - */ -class V8_EXPORT ArrayBuffer : public Object { - public: - /** - * A thread-safe allocator that V8 uses to allocate |ArrayBuffer|'s memory. - * The allocator is a global V8 setting. It has to be set via - * Isolate::CreateParams. - * - * Memory allocated through this allocator by V8 is accounted for as external - * memory by V8. Note that V8 keeps track of the memory for all internalized - * |ArrayBuffer|s. Responsibility for tracking external memory (using - * Isolate::AdjustAmountOfExternalAllocatedMemory) is handed over to the - * embedder upon externalization and taken over upon internalization (creating - * an internalized buffer from an existing buffer). - * - * Note that it is unsafe to call back into V8 from any of the allocator - * functions. - */ - class V8_EXPORT Allocator { - public: - virtual ~Allocator() = default; - - /** - * Allocate |length| bytes. Return nullptr if allocation is not successful. - * Memory should be initialized to zeroes. - */ - virtual void* Allocate(size_t length) = 0; - - /** - * Allocate |length| bytes. Return nullptr if allocation is not successful. - * Memory does not have to be initialized. - */ - virtual void* AllocateUninitialized(size_t length) = 0; - - /** - * Free the memory block of size |length|, pointed to by |data|. - * That memory is guaranteed to be previously allocated by |Allocate|. - */ - virtual void Free(void* data, size_t length) = 0; - - /** - * Reallocate the memory block of size |old_length| to a memory block of - * size |new_length| by expanding, contracting, or copying the existing - * memory block. If |new_length| > |old_length|, then the new part of - * the memory must be initialized to zeros. Return nullptr if reallocation - * is not successful. - * - * The caller guarantees that the memory block was previously allocated - * using Allocate or AllocateUninitialized. - * - * The default implementation allocates a new block and copies data. - */ - virtual void* Reallocate(void* data, size_t old_length, size_t new_length); - - /** - * ArrayBuffer allocation mode. kNormal is a malloc/free style allocation, - * while kReservation is for larger allocations with the ability to set - * access permissions. - */ - enum class AllocationMode { kNormal, kReservation }; - - /** - * Convenience allocator. - * - * When the virtual memory cage is enabled, this allocator will allocate its - * backing memory inside the cage. Otherwise, it will rely on malloc/free. - * - * Caller takes ownership, i.e. the returned object needs to be freed using - * |delete allocator| once it is no longer in use. - */ - static Allocator* NewDefaultAllocator(); - }; - - /** - * Data length in bytes. - */ - size_t ByteLength() const; - - /** - * Create a new ArrayBuffer. Allocate |byte_length| bytes. - * Allocated memory will be owned by a created ArrayBuffer and - * will be deallocated when it is garbage-collected, - * unless the object is externalized. - */ - static Local New(Isolate* isolate, size_t byte_length); - - /** - * Create a new ArrayBuffer with an existing backing store. - * The created array keeps a reference to the backing store until the array - * is garbage collected. Note that the IsExternal bit does not affect this - * reference from the array to the backing store. - * - * In future IsExternal bit will be removed. Until then the bit is set as - * follows. If the backing store does not own the underlying buffer, then - * the array is created in externalized state. Otherwise, the array is created - * in internalized state. In the latter case the array can be transitioned - * to the externalized state using Externalize(backing_store). - */ - static Local New(Isolate* isolate, - std::shared_ptr backing_store); - - /** - * Returns a new standalone BackingStore that is allocated using the array - * buffer allocator of the isolate. The result can be later passed to - * ArrayBuffer::New. - * - * If the allocator returns nullptr, then the function may cause GCs in the - * given isolate and re-try the allocation. If GCs do not help, then the - * function will crash with an out-of-memory error. - */ - static std::unique_ptr NewBackingStore(Isolate* isolate, - size_t byte_length); - /** - * Returns a new standalone BackingStore that takes over the ownership of - * the given buffer. The destructor of the BackingStore invokes the given - * deleter callback. - * - * The result can be later passed to ArrayBuffer::New. The raw pointer - * to the buffer must not be passed again to any V8 API function. - */ - static std::unique_ptr NewBackingStore( - void* data, size_t byte_length, v8::BackingStore::DeleterCallback deleter, - void* deleter_data); - - /** - * Returns true if this ArrayBuffer may be detached. - */ - bool IsDetachable() const; - - /** - * Detaches this ArrayBuffer and all its views (typed arrays). - * Detaching sets the byte length of the buffer and all typed arrays to zero, - * preventing JavaScript from ever accessing underlying backing store. - * ArrayBuffer should have been externalized and must be detachable. - */ - void Detach(); - - /** - * Get a shared pointer to the backing store of this array buffer. This - * pointer coordinates the lifetime management of the internal storage - * with any live ArrayBuffers on the heap, even across isolates. The embedder - * should not attempt to manage lifetime of the storage through other means. - */ - std::shared_ptr GetBackingStore(); - - V8_INLINE static ArrayBuffer* Cast(Value* value) { -#ifdef V8_ENABLE_CHECKS - CheckCast(value); -#endif - return static_cast(value); - } - - static const int kInternalFieldCount = V8_ARRAY_BUFFER_INTERNAL_FIELD_COUNT; - static const int kEmbedderFieldCount = V8_ARRAY_BUFFER_INTERNAL_FIELD_COUNT; - - private: - ArrayBuffer(); - static void CheckCast(Value* obj); -}; - -#ifndef V8_ARRAY_BUFFER_VIEW_INTERNAL_FIELD_COUNT -// The number of required internal fields can be defined by embedder. -#define V8_ARRAY_BUFFER_VIEW_INTERNAL_FIELD_COUNT 2 -#endif - -/** - * A base class for an instance of one of "views" over ArrayBuffer, - * including TypedArrays and DataView (ES6 draft 15.13). - */ -class V8_EXPORT ArrayBufferView : public Object { - public: - /** - * Returns underlying ArrayBuffer. - */ - Local Buffer(); - /** - * Byte offset in |Buffer|. - */ - size_t ByteOffset(); - /** - * Size of a view in bytes. - */ - size_t ByteLength(); - - /** - * Copy the contents of the ArrayBufferView's buffer to an embedder defined - * memory without additional overhead that calling ArrayBufferView::Buffer - * might incur. - * - * Will write at most min(|byte_length|, ByteLength) bytes starting at - * ByteOffset of the underlying buffer to the memory starting at |dest|. - * Returns the number of bytes actually written. - */ - size_t CopyContents(void* dest, size_t byte_length); - - /** - * Returns true if ArrayBufferView's backing ArrayBuffer has already been - * allocated. - */ - bool HasBuffer() const; - - V8_INLINE static ArrayBufferView* Cast(Value* value) { -#ifdef V8_ENABLE_CHECKS - CheckCast(value); -#endif - return static_cast(value); - } - - static const int kInternalFieldCount = - V8_ARRAY_BUFFER_VIEW_INTERNAL_FIELD_COUNT; - static const int kEmbedderFieldCount = - V8_ARRAY_BUFFER_VIEW_INTERNAL_FIELD_COUNT; - - private: - ArrayBufferView(); - static void CheckCast(Value* obj); -}; - -/** - * An instance of DataView constructor (ES6 draft 15.13.7). - */ -class V8_EXPORT DataView : public ArrayBufferView { - public: - static Local New(Local array_buffer, - size_t byte_offset, size_t length); - static Local New(Local shared_array_buffer, - size_t byte_offset, size_t length); - V8_INLINE static DataView* Cast(Value* value) { -#ifdef V8_ENABLE_CHECKS - CheckCast(value); -#endif - return static_cast(value); - } - - private: - DataView(); - static void CheckCast(Value* obj); -}; - -/** - * An instance of the built-in SharedArrayBuffer constructor. - */ -class V8_EXPORT SharedArrayBuffer : public Object { - public: - /** - * Data length in bytes. - */ - size_t ByteLength() const; - - /** - * Create a new SharedArrayBuffer. Allocate |byte_length| bytes. - * Allocated memory will be owned by a created SharedArrayBuffer and - * will be deallocated when it is garbage-collected, - * unless the object is externalized. - */ - static Local New(Isolate* isolate, size_t byte_length); - - /** - * Create a new SharedArrayBuffer with an existing backing store. - * The created array keeps a reference to the backing store until the array - * is garbage collected. Note that the IsExternal bit does not affect this - * reference from the array to the backing store. - * - * In future IsExternal bit will be removed. Until then the bit is set as - * follows. If the backing store does not own the underlying buffer, then - * the array is created in externalized state. Otherwise, the array is created - * in internalized state. In the latter case the array can be transitioned - * to the externalized state using Externalize(backing_store). - */ - static Local New( - Isolate* isolate, std::shared_ptr backing_store); - - /** - * Returns a new standalone BackingStore that is allocated using the array - * buffer allocator of the isolate. The result can be later passed to - * SharedArrayBuffer::New. - * - * If the allocator returns nullptr, then the function may cause GCs in the - * given isolate and re-try the allocation. If GCs do not help, then the - * function will crash with an out-of-memory error. - */ - static std::unique_ptr NewBackingStore(Isolate* isolate, - size_t byte_length); - /** - * Returns a new standalone BackingStore that takes over the ownership of - * the given buffer. The destructor of the BackingStore invokes the given - * deleter callback. - * - * The result can be later passed to SharedArrayBuffer::New. The raw pointer - * to the buffer must not be passed again to any V8 functions. - */ - static std::unique_ptr NewBackingStore( - void* data, size_t byte_length, v8::BackingStore::DeleterCallback deleter, - void* deleter_data); - - /** - * Get a shared pointer to the backing store of this array buffer. This - * pointer coordinates the lifetime management of the internal storage - * with any live ArrayBuffers on the heap, even across isolates. The embedder - * should not attempt to manage lifetime of the storage through other means. - */ - std::shared_ptr GetBackingStore(); - - V8_INLINE static SharedArrayBuffer* Cast(Value* value) { -#ifdef V8_ENABLE_CHECKS - CheckCast(value); -#endif - return static_cast(value); - } - - static const int kInternalFieldCount = V8_ARRAY_BUFFER_INTERNAL_FIELD_COUNT; - - private: - SharedArrayBuffer(); - static void CheckCast(Value* obj); -}; - -} // namespace v8 - -#endif // INCLUDE_V8_ARRAY_BUFFER_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-callbacks.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-callbacks.h deleted file mode 100644 index ff894161f42..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-callbacks.h +++ /dev/null @@ -1,400 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_ISOLATE_CALLBACKS_H_ -#define INCLUDE_V8_ISOLATE_CALLBACKS_H_ - -#include - -#include - -#include "cppgc/common.h" -#include "v8-data.h" // NOLINT(build/include_directory) -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -#if defined(V8_OS_WIN) -struct _EXCEPTION_POINTERS; -#endif - -namespace v8 { - -template -class FunctionCallbackInfo; -class Isolate; -class Message; -class Module; -class Object; -class Promise; -class ScriptOrModule; -class String; -class UnboundScript; -class Value; - -/** - * A JIT code event is issued each time code is added, moved or removed. - * - * \note removal events are not currently issued. - */ -struct JitCodeEvent { - enum EventType { - CODE_ADDED, - CODE_MOVED, - CODE_REMOVED, - CODE_ADD_LINE_POS_INFO, - CODE_START_LINE_INFO_RECORDING, - CODE_END_LINE_INFO_RECORDING - }; - // Definition of the code position type. The "POSITION" type means the place - // in the source code which are of interest when making stack traces to - // pin-point the source location of a stack frame as close as possible. - // The "STATEMENT_POSITION" means the place at the beginning of each - // statement, and is used to indicate possible break locations. - enum PositionType { POSITION, STATEMENT_POSITION }; - - // There are three different kinds of CodeType, one for JIT code generated - // by the optimizing compiler, one for byte code generated for the - // interpreter, and one for code generated from Wasm. For JIT_CODE and - // WASM_CODE, |code_start| points to the beginning of jitted assembly code, - // while for BYTE_CODE events, |code_start| points to the first bytecode of - // the interpreted function. - enum CodeType { BYTE_CODE, JIT_CODE, WASM_CODE }; - - // Type of event. - EventType type; - CodeType code_type; - // Start of the instructions. - void* code_start; - // Size of the instructions. - size_t code_len; - // Script info for CODE_ADDED event. - Local script; - // User-defined data for *_LINE_INFO_* event. It's used to hold the source - // code line information which is returned from the - // CODE_START_LINE_INFO_RECORDING event. And it's passed to subsequent - // CODE_ADD_LINE_POS_INFO and CODE_END_LINE_INFO_RECORDING events. - void* user_data; - - struct name_t { - // Name of the object associated with the code, note that the string is not - // zero-terminated. - const char* str; - // Number of chars in str. - size_t len; - }; - - struct line_info_t { - // PC offset - size_t offset; - // Code position - size_t pos; - // The position type. - PositionType position_type; - }; - - struct wasm_source_info_t { - // Source file name. - const char* filename; - // Length of filename. - size_t filename_size; - // Line number table, which maps offsets of JITted code to line numbers of - // source file. - const line_info_t* line_number_table; - // Number of entries in the line number table. - size_t line_number_table_size; - }; - - wasm_source_info_t* wasm_source_info; - - union { - // Only valid for CODE_ADDED. - struct name_t name; - - // Only valid for CODE_ADD_LINE_POS_INFO - struct line_info_t line_info; - - // New location of instructions. Only valid for CODE_MOVED. - void* new_code_start; - }; - - Isolate* isolate; -}; - -/** - * Option flags passed to the SetJitCodeEventHandler function. - */ -enum JitCodeEventOptions { - kJitCodeEventDefault = 0, - // Generate callbacks for already existent code. - kJitCodeEventEnumExisting = 1 -}; - -/** - * Callback function passed to SetJitCodeEventHandler. - * - * \param event code add, move or removal event. - */ -using JitCodeEventHandler = void (*)(const JitCodeEvent* event); - -// --- Garbage Collection Callbacks --- - -/** - * Applications can register callback functions which will be called before and - * after certain garbage collection operations. Allocations are not allowed in - * the callback functions, you therefore cannot manipulate objects (set or - * delete properties for example) since it is possible such operations will - * result in the allocation of objects. - */ -enum GCType { - kGCTypeScavenge = 1 << 0, - kGCTypeMarkSweepCompact = 1 << 1, - kGCTypeIncrementalMarking = 1 << 2, - kGCTypeProcessWeakCallbacks = 1 << 3, - kGCTypeAll = kGCTypeScavenge | kGCTypeMarkSweepCompact | - kGCTypeIncrementalMarking | kGCTypeProcessWeakCallbacks -}; - -/** - * GCCallbackFlags is used to notify additional information about the GC - * callback. - * - kGCCallbackFlagConstructRetainedObjectInfos: The GC callback is for - * constructing retained object infos. - * - kGCCallbackFlagForced: The GC callback is for a forced GC for testing. - * - kGCCallbackFlagSynchronousPhantomCallbackProcessing: The GC callback - * is called synchronously without getting posted to an idle task. - * - kGCCallbackFlagCollectAllAvailableGarbage: The GC callback is called - * in a phase where V8 is trying to collect all available garbage - * (e.g., handling a low memory notification). - * - kGCCallbackScheduleIdleGarbageCollection: The GC callback is called to - * trigger an idle garbage collection. - */ -enum GCCallbackFlags { - kNoGCCallbackFlags = 0, - kGCCallbackFlagConstructRetainedObjectInfos = 1 << 1, - kGCCallbackFlagForced = 1 << 2, - kGCCallbackFlagSynchronousPhantomCallbackProcessing = 1 << 3, - kGCCallbackFlagCollectAllAvailableGarbage = 1 << 4, - kGCCallbackFlagCollectAllExternalMemory = 1 << 5, - kGCCallbackScheduleIdleGarbageCollection = 1 << 6, -}; - -using GCCallback = void (*)(GCType type, GCCallbackFlags flags); - -using InterruptCallback = void (*)(Isolate* isolate, void* data); - -/** - * This callback is invoked when the heap size is close to the heap limit and - * V8 is likely to abort with out-of-memory error. - * The callback can extend the heap limit by returning a value that is greater - * than the current_heap_limit. The initial heap limit is the limit that was - * set after heap setup. - */ -using NearHeapLimitCallback = size_t (*)(void* data, size_t current_heap_limit, - size_t initial_heap_limit); - -/** - * Callback function passed to SetUnhandledExceptionCallback. - */ -#if defined(V8_OS_WIN) -using UnhandledExceptionCallback = - int (*)(_EXCEPTION_POINTERS* exception_pointers); -#endif - -// --- Counters Callbacks --- - -using CounterLookupCallback = int* (*)(const char* name); - -using CreateHistogramCallback = void* (*)(const char* name, int min, int max, - size_t buckets); - -using AddHistogramSampleCallback = void (*)(void* histogram, int sample); - -/** - * HostImportModuleDynamicallyCallback is called when we require the - * embedder to load a module. This is used as part of the dynamic - * import syntax. - * - * The referrer contains metadata about the script/module that calls - * import. - * - * The specifier is the name of the module that should be imported. - * - * The embedder must compile, instantiate, evaluate the Module, and - * obtain its namespace object. - * - * The Promise returned from this function is forwarded to userland - * JavaScript. The embedder must resolve this promise with the module - * namespace object. In case of an exception, the embedder must reject - * this promise with the exception. If the promise creation itself - * fails (e.g. due to stack overflow), the embedder must propagate - * that exception by returning an empty MaybeLocal. - */ -using HostImportModuleDynamicallyCallback V8_DEPRECATED( - "Use HostImportModuleDynamicallyWithImportAssertionsCallback instead") = - MaybeLocal (*)(Local context, - Local referrer, - Local specifier); - -// --- Exceptions --- - -using FatalErrorCallback = void (*)(const char* location, const char* message); - -using OOMErrorCallback = void (*)(const char* location, bool is_heap_oom); - -using MessageCallback = void (*)(Local message, Local data); - -// --- Tracing --- - -enum LogEventStatus : int { kStart = 0, kEnd = 1, kStamp = 2 }; -using LogEventCallback = void (*)(const char* name, - int /* LogEventStatus */ status); - -// --- Crashkeys Callback --- -enum class CrashKeyId { - kIsolateAddress, - kReadonlySpaceFirstPageAddress, - kMapSpaceFirstPageAddress, - kCodeSpaceFirstPageAddress, - kDumpType, -}; - -using AddCrashKeyCallback = void (*)(CrashKeyId id, const std::string& value); - -// --- Enter/Leave Script Callback --- -using BeforeCallEnteredCallback = void (*)(Isolate*); -using CallCompletedCallback = void (*)(Isolate*); - -// --- AllowCodeGenerationFromStrings callbacks --- - -/** - * Callback to check if code generation from strings is allowed. See - * Context::AllowCodeGenerationFromStrings. - */ -using AllowCodeGenerationFromStringsCallback = bool (*)(Local context, - Local source); - -struct ModifyCodeGenerationFromStringsResult { - // If true, proceed with the codegen algorithm. Otherwise, block it. - bool codegen_allowed = false; - // Overwrite the original source with this string, if present. - // Use the original source if empty. - // This field is considered only if codegen_allowed is true. - MaybeLocal modified_source; -}; - -/** - * Access type specification. - */ -enum AccessType { - ACCESS_GET, - ACCESS_SET, - ACCESS_HAS, - ACCESS_DELETE, - ACCESS_KEYS -}; - -// --- Failed Access Check Callback --- - -using FailedAccessCheckCallback = void (*)(Local target, - AccessType type, Local data); - -/** - * Callback to check if codegen is allowed from a source object, and convert - * the source to string if necessary. See: ModifyCodeGenerationFromStrings. - */ -using ModifyCodeGenerationFromStringsCallback = - ModifyCodeGenerationFromStringsResult (*)(Local context, - Local source); -using ModifyCodeGenerationFromStringsCallback2 = - ModifyCodeGenerationFromStringsResult (*)(Local context, - Local source, - bool is_code_like); - -// --- WebAssembly compilation callbacks --- -using ExtensionCallback = bool (*)(const FunctionCallbackInfo&); - -using AllowWasmCodeGenerationCallback = bool (*)(Local context, - Local source); - -// --- Callback for APIs defined on v8-supported objects, but implemented -// by the embedder. Example: WebAssembly.{compile|instantiate}Streaming --- -using ApiImplementationCallback = void (*)(const FunctionCallbackInfo&); - -// --- Callback for WebAssembly.compileStreaming --- -using WasmStreamingCallback = void (*)(const FunctionCallbackInfo&); - -// --- Callback for loading source map file for Wasm profiling support -using WasmLoadSourceMapCallback = Local (*)(Isolate* isolate, - const char* name); - -// --- Callback for checking if WebAssembly Simd is enabled --- -using WasmSimdEnabledCallback = bool (*)(Local context); - -// --- Callback for checking if WebAssembly exceptions are enabled --- -using WasmExceptionsEnabledCallback = bool (*)(Local context); - -// --- Callback for checking if the SharedArrayBuffer constructor is enabled --- -using SharedArrayBufferConstructorEnabledCallback = - bool (*)(Local context); - -/** - * HostImportModuleDynamicallyWithImportAssertionsCallback is called when we - * require the embedder to load a module. This is used as part of the dynamic - * import syntax. - * - * The referrer contains metadata about the script/module that calls - * import. - * - * The specifier is the name of the module that should be imported. - * - * The import_assertions are import assertions for this request in the form: - * [key1, value1, key2, value2, ...] where the keys and values are of type - * v8::String. Note, unlike the FixedArray passed to ResolveModuleCallback and - * returned from ModuleRequest::GetImportAssertions(), this array does not - * contain the source Locations of the assertions. - * - * The embedder must compile, instantiate, evaluate the Module, and - * obtain its namespace object. - * - * The Promise returned from this function is forwarded to userland - * JavaScript. The embedder must resolve this promise with the module - * namespace object. In case of an exception, the embedder must reject - * this promise with the exception. If the promise creation itself - * fails (e.g. due to stack overflow), the embedder must propagate - * that exception by returning an empty MaybeLocal. - */ -using HostImportModuleDynamicallyWithImportAssertionsCallback = - MaybeLocal (*)(Local context, - Local referrer, - Local specifier, - Local import_assertions); - -/** - * HostInitializeImportMetaObjectCallback is called the first time import.meta - * is accessed for a module. Subsequent access will reuse the same value. - * - * The method combines two implementation-defined abstract operations into one: - * HostGetImportMetaProperties and HostFinalizeImportMeta. - * - * The embedder should use v8::Object::CreateDataProperty to add properties on - * the meta object. - */ -using HostInitializeImportMetaObjectCallback = void (*)(Local context, - Local module, - Local meta); - -/** - * PrepareStackTraceCallback is called when the stack property of an error is - * first accessed. The return value will be used as the stack value. If this - * callback is registed, the |Error.prepareStackTrace| API will be disabled. - * |sites| is an array of call sites, specified in - * https://v8.dev/docs/stack-trace-api - */ -using PrepareStackTraceCallback = MaybeLocal (*)(Local context, - Local error, - Local sites); - -} // namespace v8 - -#endif // INCLUDE_V8_ISOLATE_CALLBACKS_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-container.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-container.h deleted file mode 100644 index ce068603649..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-container.h +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_CONTAINER_H_ -#define INCLUDE_V8_CONTAINER_H_ - -#include -#include - -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8-object.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Context; -class Isolate; - -/** - * An instance of the built-in array constructor (ECMA-262, 15.4.2). - */ -class V8_EXPORT Array : public Object { - public: - uint32_t Length() const; - - /** - * Creates a JavaScript array with the given length. If the length - * is negative the returned array will have length 0. - */ - static Local New(Isolate* isolate, int length = 0); - - /** - * Creates a JavaScript array out of a Local array in C++ - * with a known length. - */ - static Local New(Isolate* isolate, Local* elements, - size_t length); - V8_INLINE static Array* Cast(Value* value) { -#ifdef V8_ENABLE_CHECKS - CheckCast(value); -#endif - return static_cast(value); - } - - private: - Array(); - static void CheckCast(Value* obj); -}; - -/** - * An instance of the built-in Map constructor (ECMA-262, 6th Edition, 23.1.1). - */ -class V8_EXPORT Map : public Object { - public: - size_t Size() const; - void Clear(); - V8_WARN_UNUSED_RESULT MaybeLocal Get(Local context, - Local key); - V8_WARN_UNUSED_RESULT MaybeLocal Set(Local context, - Local key, - Local value); - V8_WARN_UNUSED_RESULT Maybe Has(Local context, - Local key); - V8_WARN_UNUSED_RESULT Maybe Delete(Local context, - Local key); - - /** - * Returns an array of length Size() * 2, where index N is the Nth key and - * index N + 1 is the Nth value. - */ - Local AsArray() const; - - /** - * Creates a new empty Map. - */ - static Local New(Isolate* isolate); - - V8_INLINE static Map* Cast(Value* value) { -#ifdef V8_ENABLE_CHECKS - CheckCast(value); -#endif - return static_cast(value); - } - - private: - Map(); - static void CheckCast(Value* obj); -}; - -/** - * An instance of the built-in Set constructor (ECMA-262, 6th Edition, 23.2.1). - */ -class V8_EXPORT Set : public Object { - public: - size_t Size() const; - void Clear(); - V8_WARN_UNUSED_RESULT MaybeLocal Add(Local context, - Local key); - V8_WARN_UNUSED_RESULT Maybe Has(Local context, - Local key); - V8_WARN_UNUSED_RESULT Maybe Delete(Local context, - Local key); - - /** - * Returns an array of the keys in this Set. - */ - Local AsArray() const; - - /** - * Creates a new empty Set. - */ - static Local New(Isolate* isolate); - - V8_INLINE static Set* Cast(Value* value) { -#ifdef V8_ENABLE_CHECKS - CheckCast(value); -#endif - return static_cast(value); - } - - private: - Set(); - static void CheckCast(Value* obj); -}; - -} // namespace v8 - -#endif // INCLUDE_V8_CONTAINER_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-context.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-context.h deleted file mode 100644 index bd28c6c9c93..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-context.h +++ /dev/null @@ -1,418 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_CONTEXT_H_ -#define INCLUDE_V8_CONTEXT_H_ - -#include - -#include "v8-data.h" // NOLINT(build/include_directory) -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8-snapshot.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Function; -class MicrotaskQueue; -class Object; -class ObjectTemplate; -class Value; -class String; - -/** - * A container for extension names. - */ -class V8_EXPORT ExtensionConfiguration { - public: - ExtensionConfiguration() : name_count_(0), names_(nullptr) {} - ExtensionConfiguration(int name_count, const char* names[]) - : name_count_(name_count), names_(names) {} - - const char** begin() const { return &names_[0]; } - const char** end() const { return &names_[name_count_]; } - - private: - const int name_count_; - const char** names_; -}; - -/** - * A sandboxed execution context with its own set of built-in objects - * and functions. - */ -class V8_EXPORT Context : public Data { - public: - /** - * Returns the global proxy object. - * - * Global proxy object is a thin wrapper whose prototype points to actual - * context's global object with the properties like Object, etc. This is done - * that way for security reasons (for more details see - * https://wiki.mozilla.org/Gecko:SplitWindow). - * - * Please note that changes to global proxy object prototype most probably - * would break VM---v8 expects only global object as a prototype of global - * proxy object. - */ - Local Global(); - - /** - * Detaches the global object from its context before - * the global object can be reused to create a new context. - */ - void DetachGlobal(); - - /** - * Creates a new context and returns a handle to the newly allocated - * context. - * - * \param isolate The isolate in which to create the context. - * - * \param extensions An optional extension configuration containing - * the extensions to be installed in the newly created context. - * - * \param global_template An optional object template from which the - * global object for the newly created context will be created. - * - * \param global_object An optional global object to be reused for - * the newly created context. This global object must have been - * created by a previous call to Context::New with the same global - * template. The state of the global object will be completely reset - * and only object identify will remain. - */ - static Local New( - Isolate* isolate, ExtensionConfiguration* extensions = nullptr, - MaybeLocal global_template = MaybeLocal(), - MaybeLocal global_object = MaybeLocal(), - DeserializeInternalFieldsCallback internal_fields_deserializer = - DeserializeInternalFieldsCallback(), - MicrotaskQueue* microtask_queue = nullptr); - - /** - * Create a new context from a (non-default) context snapshot. There - * is no way to provide a global object template since we do not create - * a new global object from template, but we can reuse a global object. - * - * \param isolate See v8::Context::New. - * - * \param context_snapshot_index The index of the context snapshot to - * deserialize from. Use v8::Context::New for the default snapshot. - * - * \param embedder_fields_deserializer Optional callback to deserialize - * internal fields. It should match the SerializeInternalFieldCallback used - * to serialize. - * - * \param extensions See v8::Context::New. - * - * \param global_object See v8::Context::New. - */ - static MaybeLocal FromSnapshot( - Isolate* isolate, size_t context_snapshot_index, - DeserializeInternalFieldsCallback embedder_fields_deserializer = - DeserializeInternalFieldsCallback(), - ExtensionConfiguration* extensions = nullptr, - MaybeLocal global_object = MaybeLocal(), - MicrotaskQueue* microtask_queue = nullptr); - - /** - * Returns an global object that isn't backed by an actual context. - * - * The global template needs to have access checks with handlers installed. - * If an existing global object is passed in, the global object is detached - * from its context. - * - * Note that this is different from a detached context where all accesses to - * the global proxy will fail. Instead, the access check handlers are invoked. - * - * It is also not possible to detach an object returned by this method. - * Instead, the access check handlers need to return nothing to achieve the - * same effect. - * - * It is possible, however, to create a new context from the global object - * returned by this method. - */ - static MaybeLocal NewRemoteContext( - Isolate* isolate, Local global_template, - MaybeLocal global_object = MaybeLocal()); - - /** - * Sets the security token for the context. To access an object in - * another context, the security tokens must match. - */ - void SetSecurityToken(Local token); - - /** Restores the security token to the default value. */ - void UseDefaultSecurityToken(); - - /** Returns the security token of this context.*/ - Local GetSecurityToken(); - - /** - * Enter this context. After entering a context, all code compiled - * and run is compiled and run in this context. If another context - * is already entered, this old context is saved so it can be - * restored when the new context is exited. - */ - void Enter(); - - /** - * Exit this context. Exiting the current context restores the - * context that was in place when entering the current context. - */ - void Exit(); - - /** Returns the isolate associated with a current context. */ - Isolate* GetIsolate(); - - /** Returns the microtask queue associated with a current context. */ - MicrotaskQueue* GetMicrotaskQueue(); - - /** - * The field at kDebugIdIndex used to be reserved for the inspector. - * It now serves no purpose. - */ - enum EmbedderDataFields { kDebugIdIndex = 0 }; - - /** - * Return the number of fields allocated for embedder data. - */ - uint32_t GetNumberOfEmbedderDataFields(); - - /** - * Gets the embedder data with the given index, which must have been set by a - * previous call to SetEmbedderData with the same index. - */ - V8_INLINE Local GetEmbedderData(int index); - - /** - * Gets the binding object used by V8 extras. Extra natives get a reference - * to this object and can use it to "export" functionality by adding - * properties. Extra natives can also "import" functionality by accessing - * properties added by the embedder using the V8 API. - */ - Local GetExtrasBindingObject(); - - /** - * Sets the embedder data with the given index, growing the data as - * needed. Note that index 0 currently has a special meaning for Chrome's - * debugger. - */ - void SetEmbedderData(int index, Local value); - - /** - * Gets a 2-byte-aligned native pointer from the embedder data with the given - * index, which must have been set by a previous call to - * SetAlignedPointerInEmbedderData with the same index. Note that index 0 - * currently has a special meaning for Chrome's debugger. - */ - V8_INLINE void* GetAlignedPointerFromEmbedderData(int index); - - /** - * Sets a 2-byte-aligned native pointer in the embedder data with the given - * index, growing the data as needed. Note that index 0 currently has a - * special meaning for Chrome's debugger. - */ - void SetAlignedPointerInEmbedderData(int index, void* value); - - /** - * Control whether code generation from strings is allowed. Calling - * this method with false will disable 'eval' and the 'Function' - * constructor for code running in this context. If 'eval' or the - * 'Function' constructor are used an exception will be thrown. - * - * If code generation from strings is not allowed the - * V8::AllowCodeGenerationFromStrings callback will be invoked if - * set before blocking the call to 'eval' or the 'Function' - * constructor. If that callback returns true, the call will be - * allowed, otherwise an exception will be thrown. If no callback is - * set an exception will be thrown. - */ - void AllowCodeGenerationFromStrings(bool allow); - - /** - * Returns true if code generation from strings is allowed for the context. - * For more details see AllowCodeGenerationFromStrings(bool) documentation. - */ - bool IsCodeGenerationFromStringsAllowed() const; - - /** - * Sets the error description for the exception that is thrown when - * code generation from strings is not allowed and 'eval' or the 'Function' - * constructor are called. - */ - void SetErrorMessageForCodeGenerationFromStrings(Local message); - - /** - * Return data that was previously attached to the context snapshot via - * SnapshotCreator, and removes the reference to it. - * Repeated call with the same index returns an empty MaybeLocal. - */ - template - V8_INLINE MaybeLocal GetDataFromSnapshotOnce(size_t index); - - /** - * If callback is set, abort any attempt to execute JavaScript in this - * context, call the specified callback, and throw an exception. - * To unset abort, pass nullptr as callback. - */ - using AbortScriptExecutionCallback = void (*)(Isolate* isolate, - Local context); - void SetAbortScriptExecution(AbortScriptExecutionCallback callback); - - /** - * Returns the value that was set or restored by - * SetContinuationPreservedEmbedderData(), if any. - */ - Local GetContinuationPreservedEmbedderData() const; - - /** - * Sets a value that will be stored on continuations and reset while the - * continuation runs. - */ - void SetContinuationPreservedEmbedderData(Local context); - - /** - * Set or clear hooks to be invoked for promise lifecycle operations. - * To clear a hook, set it to an empty v8::Function. Each function will - * receive the observed promise as the first argument. If a chaining - * operation is used on a promise, the init will additionally receive - * the parent promise as the second argument. - */ - void SetPromiseHooks(Local init_hook, Local before_hook, - Local after_hook, - Local resolve_hook); - - /** - * Stack-allocated class which sets the execution context for all - * operations executed within a local scope. - */ - class V8_NODISCARD Scope { - public: - explicit V8_INLINE Scope(Local context) : context_(context) { - context_->Enter(); - } - V8_INLINE ~Scope() { context_->Exit(); } - - private: - Local context_; - }; - - /** - * Stack-allocated class to support the backup incumbent settings object - * stack. - * https://html.spec.whatwg.org/multipage/webappapis.html#backup-incumbent-settings-object-stack - */ - class V8_EXPORT V8_NODISCARD BackupIncumbentScope final { - public: - /** - * |backup_incumbent_context| is pushed onto the backup incumbent settings - * object stack. - */ - explicit BackupIncumbentScope(Local backup_incumbent_context); - ~BackupIncumbentScope(); - - /** - * Returns address that is comparable with JS stack address. Note that JS - * stack may be allocated separately from the native stack. See also - * |TryCatch::JSStackComparableAddressPrivate| for details. - */ - V8_DEPRECATE_SOON( - "This is private V8 information that should not be exposed in the API.") - uintptr_t JSStackComparableAddress() const { - return JSStackComparableAddressPrivate(); - } - - private: - friend class internal::Isolate; - - uintptr_t JSStackComparableAddressPrivate() const { - return js_stack_comparable_address_; - } - - Local backup_incumbent_context_; - uintptr_t js_stack_comparable_address_ = 0; - const BackupIncumbentScope* prev_ = nullptr; - }; - - V8_INLINE static Context* Cast(Data* data); - - private: - friend class Value; - friend class Script; - friend class Object; - friend class Function; - - static void CheckCast(Data* obj); - - internal::Address* GetDataFromSnapshotOnce(size_t index); - Local SlowGetEmbedderData(int index); - void* SlowGetAlignedPointerFromEmbedderData(int index); -}; - -// --- Implementation --- - -Local Context::GetEmbedderData(int index) { -#ifndef V8_ENABLE_CHECKS - using A = internal::Address; - using I = internal::Internals; - A ctx = *reinterpret_cast(this); - A embedder_data = - I::ReadTaggedPointerField(ctx, I::kNativeContextEmbedderDataOffset); - int value_offset = - I::kEmbedderDataArrayHeaderSize + (I::kEmbedderDataSlotSize * index); - A value = I::ReadRawField(embedder_data, value_offset); -#ifdef V8_COMPRESS_POINTERS - // We read the full pointer value and then decompress it in order to avoid - // dealing with potential endiannes issues. - value = - I::DecompressTaggedAnyField(embedder_data, static_cast(value)); -#endif - internal::Isolate* isolate = internal::IsolateFromNeverReadOnlySpaceObject( - *reinterpret_cast(this)); - A* result = HandleScope::CreateHandle(isolate, value); - return Local(reinterpret_cast(result)); -#else - return SlowGetEmbedderData(index); -#endif -} - -void* Context::GetAlignedPointerFromEmbedderData(int index) { -#ifndef V8_ENABLE_CHECKS - using A = internal::Address; - using I = internal::Internals; - A ctx = *reinterpret_cast(this); - A embedder_data = - I::ReadTaggedPointerField(ctx, I::kNativeContextEmbedderDataOffset); - int value_offset = - I::kEmbedderDataArrayHeaderSize + (I::kEmbedderDataSlotSize * index); -#ifdef V8_HEAP_SANDBOX - value_offset += I::kEmbedderDataSlotRawPayloadOffset; -#endif - internal::Isolate* isolate = I::GetIsolateForHeapSandbox(ctx); - return reinterpret_cast( - I::ReadExternalPointerField(isolate, embedder_data, value_offset, - internal::kEmbedderDataSlotPayloadTag)); -#else - return SlowGetAlignedPointerFromEmbedderData(index); -#endif -} - -template -MaybeLocal Context::GetDataFromSnapshotOnce(size_t index) { - T* data = reinterpret_cast(GetDataFromSnapshotOnce(index)); - if (data) internal::PerformCastCheck(data); - return Local(data); -} - -Context* Context::Cast(v8::Data* data) { -#ifdef V8_ENABLE_CHECKS - CheckCast(data); -#endif - return static_cast(data); -} - -} // namespace v8 - -#endif // INCLUDE_V8_CONTEXT_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-cppgc.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-cppgc.h deleted file mode 100644 index 813e0842fa7..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-cppgc.h +++ /dev/null @@ -1,328 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_CPPGC_H_ -#define INCLUDE_V8_CPPGC_H_ - -#include -#include -#include - -#include "cppgc/common.h" -#include "cppgc/custom-space.h" -#include "cppgc/heap-statistics.h" -#include "cppgc/internal/write-barrier.h" -#include "cppgc/visitor.h" -#include "v8-internal.h" // NOLINT(build/include_directory) -#include "v8-platform.h" // NOLINT(build/include_directory) -#include "v8-traced-handle.h" // NOLINT(build/include_directory) - -namespace cppgc { -class AllocationHandle; -class HeapHandle; -} // namespace cppgc - -namespace v8 { - -class Object; - -namespace internal { -class CppHeap; -} // namespace internal - -class CustomSpaceStatisticsReceiver; - -/** - * Describes how V8 wrapper objects maintain references to garbage-collected C++ - * objects. - */ -struct WrapperDescriptor final { - /** - * The index used on `v8::Ojbect::SetAlignedPointerFromInternalField()` and - * related APIs to add additional data to an object which is used to identify - * JS->C++ references. - */ - using InternalFieldIndex = int; - - /** - * Unknown embedder id. The value is reserved for internal usages and must not - * be used with `CppHeap`. - */ - static constexpr uint16_t kUnknownEmbedderId = UINT16_MAX; - - constexpr WrapperDescriptor(InternalFieldIndex wrappable_type_index, - InternalFieldIndex wrappable_instance_index, - uint16_t embedder_id_for_garbage_collected) - : wrappable_type_index(wrappable_type_index), - wrappable_instance_index(wrappable_instance_index), - embedder_id_for_garbage_collected(embedder_id_for_garbage_collected) {} - - /** - * Index of the wrappable type. - */ - InternalFieldIndex wrappable_type_index; - - /** - * Index of the wrappable instance. - */ - InternalFieldIndex wrappable_instance_index; - - /** - * Embedder id identifying instances of garbage-collected objects. It is - * expected that the first field of the wrappable type is a uint16_t holding - * the id. Only references to instances of wrappables types with an id of - * `embedder_id_for_garbage_collected` will be considered by CppHeap. - */ - uint16_t embedder_id_for_garbage_collected; -}; - -struct V8_EXPORT CppHeapCreateParams { - CppHeapCreateParams(const CppHeapCreateParams&) = delete; - CppHeapCreateParams& operator=(const CppHeapCreateParams&) = delete; - - std::vector> custom_spaces; - WrapperDescriptor wrapper_descriptor; -}; - -/** - * A heap for allocating managed C++ objects. - */ -class V8_EXPORT CppHeap { - public: - static std::unique_ptr Create(v8::Platform* platform, - const CppHeapCreateParams& params); - - virtual ~CppHeap() = default; - - /** - * \returns the opaque handle for allocating objects using - * `MakeGarbageCollected()`. - */ - cppgc::AllocationHandle& GetAllocationHandle(); - - /** - * \returns the opaque heap handle which may be used to refer to this heap in - * other APIs. Valid as long as the underlying `CppHeap` is alive. - */ - cppgc::HeapHandle& GetHeapHandle(); - - /** - * Terminate clears all roots and performs multiple garbage collections to - * reclaim potentially newly created objects in destructors. - * - * After this call, object allocation is prohibited. - */ - void Terminate(); - - /** - * \param detail_level specifies whether should return detailed - * statistics or only brief summary statistics. - * \returns current CppHeap statistics regarding memory consumption - * and utilization. - */ - cppgc::HeapStatistics CollectStatistics( - cppgc::HeapStatistics::DetailLevel detail_level); - - /** - * Collects statistics for the given spaces and reports them to the receiver. - * - * \param custom_spaces a collection of custom space indicies. - * \param receiver an object that gets the results. - */ - void CollectCustomSpaceStatisticsAtLastGC( - std::vector custom_spaces, - std::unique_ptr receiver); - - /** - * Enables a detached mode that allows testing garbage collection using - * `cppgc::testing` APIs. Once used, the heap cannot be attached to an - * `Isolate` anymore. - */ - void EnableDetachedGarbageCollectionsForTesting(); - - /** - * Performs a stop-the-world garbage collection for testing purposes. - * - * \param stack_state The stack state to assume for the garbage collection. - */ - void CollectGarbageForTesting(cppgc::EmbedderStackState stack_state); - - private: - CppHeap() = default; - - friend class internal::CppHeap; -}; - -class JSVisitor : public cppgc::Visitor { - public: - explicit JSVisitor(cppgc::Visitor::Key key) : cppgc::Visitor(key) {} - - void Trace(const TracedReferenceBase& ref) { - if (ref.IsEmptyThreadSafe()) return; - Visit(ref); - } - - protected: - using cppgc::Visitor::Visit; - - virtual void Visit(const TracedReferenceBase& ref) {} -}; - -/** - * **DO NOT USE: Use the appropriate managed types.** - * - * Consistency helpers that aid in maintaining a consistent internal state of - * the garbage collector. - */ -class V8_EXPORT JSHeapConsistency final { - public: - using WriteBarrierParams = cppgc::internal::WriteBarrier::Params; - using WriteBarrierType = cppgc::internal::WriteBarrier::Type; - - /** - * Gets the required write barrier type for a specific write. - * - * Note: Handling for C++ to JS references. - * - * \param ref The reference being written to. - * \param params Parameters that may be used for actual write barrier calls. - * Only filled if return value indicates that a write barrier is needed. The - * contents of the `params` are an implementation detail. - * \param callback Callback returning the corresponding heap handle. The - * callback is only invoked if the heap cannot otherwise be figured out. The - * callback must not allocate. - * \returns whether a write barrier is needed and which barrier to invoke. - */ - template - static V8_INLINE WriteBarrierType - GetWriteBarrierType(const TracedReferenceBase& ref, - WriteBarrierParams& params, HeapHandleCallback callback) { - if (ref.IsEmpty()) return WriteBarrierType::kNone; - - if (V8_LIKELY(!cppgc::internal::WriteBarrier:: - IsAnyIncrementalOrConcurrentMarking())) { - return cppgc::internal::WriteBarrier::Type::kNone; - } - cppgc::HeapHandle& handle = callback(); - if (!cppgc::subtle::HeapState::IsMarking(handle)) { - return cppgc::internal::WriteBarrier::Type::kNone; - } - params.heap = &handle; -#if V8_ENABLE_CHECKS - params.type = cppgc::internal::WriteBarrier::Type::kMarking; -#endif // !V8_ENABLE_CHECKS - return cppgc::internal::WriteBarrier::Type::kMarking; - } - - /** - * Gets the required write barrier type for a specific write. - * - * Note: Handling for JS to C++ references. - * - * \param wrapper The wrapper that has been written into. - * \param wrapper_index The wrapper index in `wrapper` that has been written - * into. - * \param wrappable The value that was written. - * \param params Parameters that may be used for actual write barrier calls. - * Only filled if return value indicates that a write barrier is needed. The - * contents of the `params` are an implementation detail. - * \param callback Callback returning the corresponding heap handle. The - * callback is only invoked if the heap cannot otherwise be figured out. The - * callback must not allocate. - * \returns whether a write barrier is needed and which barrier to invoke. - */ - template - static V8_INLINE WriteBarrierType GetWriteBarrierType( - v8::Local& wrapper, int wrapper_index, const void* wrappable, - WriteBarrierParams& params, HeapHandleCallback callback) { -#if V8_ENABLE_CHECKS - CheckWrapper(wrapper, wrapper_index, wrappable); -#endif // V8_ENABLE_CHECKS - return cppgc::internal::WriteBarrier:: - GetWriteBarrierTypeForExternallyReferencedObject(wrappable, params, - callback); - } - - /** - * Conservative Dijkstra-style write barrier that processes an object if it - * has not yet been processed. - * - * \param params The parameters retrieved from `GetWriteBarrierType()`. - * \param ref The reference being written to. - */ - static V8_INLINE void DijkstraMarkingBarrier(const WriteBarrierParams& params, - cppgc::HeapHandle& heap_handle, - const TracedReferenceBase& ref) { - cppgc::internal::WriteBarrier::CheckParams(WriteBarrierType::kMarking, - params); - DijkstraMarkingBarrierSlow(heap_handle, ref); - } - - /** - * Conservative Dijkstra-style write barrier that processes an object if it - * has not yet been processed. - * - * \param params The parameters retrieved from `GetWriteBarrierType()`. - * \param object The pointer to the object. May be an interior pointer to a - * an interface of the actual object. - */ - static V8_INLINE void DijkstraMarkingBarrier(const WriteBarrierParams& params, - cppgc::HeapHandle& heap_handle, - const void* object) { - cppgc::internal::WriteBarrier::DijkstraMarkingBarrier(params, object); - } - - /** - * Generational barrier for maintaining consistency when running with multiple - * generations. - * - * \param params The parameters retrieved from `GetWriteBarrierType()`. - * \param ref The reference being written to. - */ - static V8_INLINE void GenerationalBarrier(const WriteBarrierParams& params, - const TracedReferenceBase& ref) {} - - private: - JSHeapConsistency() = delete; - - static void CheckWrapper(v8::Local&, int, const void*); - - static void DijkstraMarkingBarrierSlow(cppgc::HeapHandle&, - const TracedReferenceBase& ref); -}; - -/** - * Provided as input to `CppHeap::CollectCustomSpaceStatisticsAtLastGC()`. - * - * Its method is invoked with the results of the statistic collection. - */ -class CustomSpaceStatisticsReceiver { - public: - virtual ~CustomSpaceStatisticsReceiver() = default; - /** - * Reports the size of a space at the last GC. It is called for each space - * that was requested in `CollectCustomSpaceStatisticsAtLastGC()`. - * - * \param space_index The index of the space. - * \param bytes The total size of live objects in the space at the last GC. - * It is zero if there was no GC yet. - */ - virtual void AllocatedBytes(cppgc::CustomSpaceIndex space_index, - size_t bytes) = 0; -}; - -} // namespace v8 - -namespace cppgc { - -template -struct TraceTrait> { - static void Trace(Visitor* visitor, const v8::TracedReference* self) { - static_cast(visitor)->Trace(*self); - } -}; - -} // namespace cppgc - -#endif // INCLUDE_V8_CPPGC_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-data.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-data.h deleted file mode 100644 index dbd36c9a035..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-data.h +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_DATA_H_ -#define INCLUDE_V8_DATA_H_ - -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Context; - -/** - * The superclass of objects that can reside on V8's heap. - */ -class V8_EXPORT Data { - public: - /** - * Returns true if this data is a |v8::Value|. - */ - bool IsValue() const; - - /** - * Returns true if this data is a |v8::Module|. - */ - bool IsModule() const; - - /** - * Returns true if this data is a |v8::Private|. - */ - bool IsPrivate() const; - - /** - * Returns true if this data is a |v8::ObjectTemplate|. - */ - bool IsObjectTemplate() const; - - /** - * Returns true if this data is a |v8::FunctionTemplate|. - */ - bool IsFunctionTemplate() const; - - /** - * Returns true if this data is a |v8::Context|. - */ - bool IsContext() const; - - private: - Data(); -}; - -/** - * A fixed-sized array with elements of type Data. - */ -class V8_EXPORT FixedArray : public Data { - public: - int Length() const; - Local Get(Local context, int i) const; -}; - -} // namespace v8 - -#endif // INCLUDE_V8_DATA_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-date.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-date.h deleted file mode 100644 index e7a01f29b2d..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-date.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_DATE_H_ -#define INCLUDE_V8_DATE_H_ - -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8-object.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Context; - -/** - * An instance of the built-in Date constructor (ECMA-262, 15.9). - */ -class V8_EXPORT Date : public Object { - public: - static V8_WARN_UNUSED_RESULT MaybeLocal New(Local context, - double time); - - /** - * A specialization of Value::NumberValue that is more efficient - * because we know the structure of this object. - */ - double ValueOf() const; - - V8_INLINE static Date* Cast(Value* value) { -#ifdef V8_ENABLE_CHECKS - CheckCast(value); -#endif - return static_cast(value); - } - - private: - static void CheckCast(Value* obj); -}; - -} // namespace v8 - -#endif // INCLUDE_V8_DATE_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-debug.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-debug.h deleted file mode 100644 index a13ae3f6d6c..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-debug.h +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_DEBUG_H_ -#define INCLUDE_V8_DEBUG_H_ - -#include - -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Isolate; -class String; - -/** - * A single JavaScript stack frame. - */ -class V8_EXPORT StackFrame { - public: - /** - * Returns the number, 1-based, of the line for the associate function call. - * This method will return Message::kNoLineNumberInfo if it is unable to - * retrieve the line number, or if kLineNumber was not passed as an option - * when capturing the StackTrace. - */ - int GetLineNumber() const; - - /** - * Returns the 1-based column offset on the line for the associated function - * call. - * This method will return Message::kNoColumnInfo if it is unable to retrieve - * the column number, or if kColumnOffset was not passed as an option when - * capturing the StackTrace. - */ - int GetColumn() const; - - /** - * Returns the id of the script for the function for this StackFrame. - * This method will return Message::kNoScriptIdInfo if it is unable to - * retrieve the script id, or if kScriptId was not passed as an option when - * capturing the StackTrace. - */ - int GetScriptId() const; - - /** - * Returns the name of the resource that contains the script for the - * function for this StackFrame. - */ - Local GetScriptName() const; - - /** - * Returns the name of the resource that contains the script for the - * function for this StackFrame or sourceURL value if the script name - * is undefined and its source ends with //# sourceURL=... string or - * deprecated //@ sourceURL=... string. - */ - Local GetScriptNameOrSourceURL() const; - - /** - * Returns the source of the script for the function for this StackFrame. - */ - Local GetScriptSource() const; - - /** - * Returns the source mapping URL (if one is present) of the script for - * the function for this StackFrame. - */ - Local GetScriptSourceMappingURL() const; - - /** - * Returns the name of the function associated with this stack frame. - */ - Local GetFunctionName() const; - - /** - * Returns whether or not the associated function is compiled via a call to - * eval(). - */ - bool IsEval() const; - - /** - * Returns whether or not the associated function is called as a - * constructor via "new". - */ - bool IsConstructor() const; - - /** - * Returns whether or not the associated functions is defined in wasm. - */ - bool IsWasm() const; - - /** - * Returns whether or not the associated function is defined by the user. - */ - bool IsUserJavaScript() const; -}; - -/** - * Representation of a JavaScript stack trace. The information collected is a - * snapshot of the execution stack and the information remains valid after - * execution continues. - */ -class V8_EXPORT StackTrace { - public: - /** - * Flags that determine what information is placed captured for each - * StackFrame when grabbing the current stack trace. - * Note: these options are deprecated and we always collect all available - * information (kDetailed). - */ - enum StackTraceOptions { - kLineNumber = 1, - kColumnOffset = 1 << 1 | kLineNumber, - kScriptName = 1 << 2, - kFunctionName = 1 << 3, - kIsEval = 1 << 4, - kIsConstructor = 1 << 5, - kScriptNameOrSourceURL = 1 << 6, - kScriptId = 1 << 7, - kExposeFramesAcrossSecurityOrigins = 1 << 8, - kOverview = kLineNumber | kColumnOffset | kScriptName | kFunctionName, - kDetailed = kOverview | kIsEval | kIsConstructor | kScriptNameOrSourceURL - }; - - /** - * Returns a StackFrame at a particular index. - */ - Local GetFrame(Isolate* isolate, uint32_t index) const; - - /** - * Returns the number of StackFrames. - */ - int GetFrameCount() const; - - /** - * Grab a snapshot of the current JavaScript execution stack. - * - * \param frame_limit The maximum number of stack frames we want to capture. - * \param options Enumerates the set of things we will capture for each - * StackFrame. - */ - static Local CurrentStackTrace( - Isolate* isolate, int frame_limit, StackTraceOptions options = kDetailed); -}; - -} // namespace v8 - -#endif // INCLUDE_V8_DEBUG_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-embedder-heap.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-embedder-heap.h deleted file mode 100644 index 501a4fc523b..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-embedder-heap.h +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_EMBEDDER_HEAP_H_ -#define INCLUDE_V8_EMBEDDER_HEAP_H_ - -#include -#include - -#include -#include - -#include "cppgc/common.h" -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8-traced-handle.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Data; -class Isolate; -class Value; - -namespace internal { -class LocalEmbedderHeapTracer; -} // namespace internal - -/** - * Handler for embedder roots on non-unified heap garbage collections. - */ -class V8_EXPORT EmbedderRootsHandler { - public: - virtual ~EmbedderRootsHandler() = default; - - /** - * Returns true if the TracedGlobal handle should be considered as root for - * the currently running non-tracing garbage collection and false otherwise. - * The default implementation will keep all TracedGlobal references as roots. - * - * If this returns false, then V8 may decide that the object referred to by - * such a handle is reclaimed. In that case: - * - No action is required if handles are used with destructors, i.e., by just - * using |TracedGlobal|. - * - When run without destructors, i.e., by using |TracedReference|, V8 calls - * |ResetRoot|. - * - * Note that the |handle| is different from the handle that the embedder holds - * for retaining the object. The embedder may use |WrapperClassId()| to - * distinguish cases where it wants handles to be treated as roots from not - * being treated as roots. - */ - virtual bool IsRoot(const v8::TracedReference& handle) = 0; - virtual bool IsRoot(const v8::TracedGlobal& handle) = 0; - - /** - * Used in combination with |IsRoot|. Called by V8 when an - * object that is backed by a handle is reclaimed by a non-tracing garbage - * collection. It is up to the embedder to reset the original handle. - * - * Note that the |handle| is different from the handle that the embedder holds - * for retaining the object. It is up to the embedder to find the original - * handle via the object or class id. - */ - virtual void ResetRoot(const v8::TracedReference& handle) = 0; -}; - -/** - * Interface for tracing through the embedder heap. During a V8 garbage - * collection, V8 collects hidden fields of all potential wrappers, and at the - * end of its marking phase iterates the collection and asks the embedder to - * trace through its heap and use reporter to report each JavaScript object - * reachable from any of the given wrappers. - */ -class V8_EXPORT EmbedderHeapTracer { - public: - using EmbedderStackState = cppgc::EmbedderStackState; - - enum TraceFlags : uint64_t { - kNoFlags = 0, - kReduceMemory = 1 << 0, - kForced = 1 << 2, - }; - - /** - * Interface for iterating through TracedGlobal handles. - */ - class V8_EXPORT TracedGlobalHandleVisitor { - public: - virtual ~TracedGlobalHandleVisitor() = default; - virtual void VisitTracedGlobalHandle(const TracedGlobal& handle) {} - virtual void VisitTracedReference(const TracedReference& handle) {} - }; - - /** - * Summary of a garbage collection cycle. See |TraceEpilogue| on how the - * summary is reported. - */ - struct TraceSummary { - /** - * Time spent managing the retained memory in milliseconds. This can e.g. - * include the time tracing through objects in the embedder. - */ - double time = 0.0; - - /** - * Memory retained by the embedder through the |EmbedderHeapTracer| - * mechanism in bytes. - */ - size_t allocated_size = 0; - }; - - virtual ~EmbedderHeapTracer() = default; - - /** - * Iterates all TracedGlobal handles created for the v8::Isolate the tracer is - * attached to. - */ - void IterateTracedGlobalHandles(TracedGlobalHandleVisitor* visitor); - - /** - * Called by the embedder to set the start of the stack which is e.g. used by - * V8 to determine whether handles are used from stack or heap. - */ - void SetStackStart(void* stack_start); - - /** - * Called by the embedder to notify V8 of an empty execution stack. - */ - V8_DEPRECATE_SOON( - "This call only optimized internal caches which V8 is able to figure out " - "on its own now.") - void NotifyEmptyEmbedderStack(); - - /** - * Called by v8 to register internal fields of found wrappers. - * - * The embedder is expected to store them somewhere and trace reachable - * wrappers from them when called through |AdvanceTracing|. - */ - virtual void RegisterV8References( - const std::vector>& embedder_fields) = 0; - - void RegisterEmbedderReference(const BasicTracedReference& ref); - - /** - * Called at the beginning of a GC cycle. - */ - virtual void TracePrologue(TraceFlags flags) {} - - /** - * Called to advance tracing in the embedder. - * - * The embedder is expected to trace its heap starting from wrappers reported - * by RegisterV8References method, and report back all reachable wrappers. - * Furthermore, the embedder is expected to stop tracing by the given - * deadline. A deadline of infinity means that tracing should be finished. - * - * Returns |true| if tracing is done, and false otherwise. - */ - virtual bool AdvanceTracing(double deadline_in_ms) = 0; - - /* - * Returns true if there no more tracing work to be done (see AdvanceTracing) - * and false otherwise. - */ - virtual bool IsTracingDone() = 0; - - /** - * Called at the end of a GC cycle. - * - * Note that allocation is *not* allowed within |TraceEpilogue|. Can be - * overriden to fill a |TraceSummary| that is used by V8 to schedule future - * garbage collections. - */ - virtual void TraceEpilogue(TraceSummary* trace_summary) {} - - /** - * Called upon entering the final marking pause. No more incremental marking - * steps will follow this call. - */ - virtual void EnterFinalPause(EmbedderStackState stack_state) = 0; - - /* - * Called by the embedder to request immediate finalization of the currently - * running tracing phase that has been started with TracePrologue and not - * yet finished with TraceEpilogue. - * - * Will be a noop when currently not in tracing. - * - * This is an experimental feature. - */ - void FinalizeTracing(); - - /** - * See documentation on EmbedderRootsHandler. - */ - virtual bool IsRootForNonTracingGC( - const v8::TracedReference& handle); - virtual bool IsRootForNonTracingGC(const v8::TracedGlobal& handle); - - /** - * See documentation on EmbedderRootsHandler. - */ - virtual void ResetHandleInNonTracingGC( - const v8::TracedReference& handle); - - /* - * Called by the embedder to immediately perform a full garbage collection. - * - * Should only be used in testing code. - */ - void GarbageCollectionForTesting(EmbedderStackState stack_state); - - /* - * Called by the embedder to signal newly allocated or freed memory. Not bound - * to tracing phases. Embedders should trade off when increments are reported - * as V8 may consult global heuristics on whether to trigger garbage - * collection on this change. - */ - void IncreaseAllocatedSize(size_t bytes); - void DecreaseAllocatedSize(size_t bytes); - - /* - * Returns the v8::Isolate this tracer is attached too and |nullptr| if it - * is not attached to any v8::Isolate. - */ - v8::Isolate* isolate() const { return isolate_; } - - protected: - v8::Isolate* isolate_ = nullptr; - - friend class internal::LocalEmbedderHeapTracer; -}; - -} // namespace v8 - -#endif // INCLUDE_V8_EMBEDDER_HEAP_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-exception.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-exception.h deleted file mode 100644 index add882da4c4..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-exception.h +++ /dev/null @@ -1,224 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_EXCEPTION_H_ -#define INCLUDE_V8_EXCEPTION_H_ - -#include - -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Context; -class Isolate; -class Message; -class StackTrace; -class String; -class Value; - -namespace internal { -class Isolate; -class ThreadLocalTop; -} // namespace internal - -/** - * Create new error objects by calling the corresponding error object - * constructor with the message. - */ -class V8_EXPORT Exception { - public: - static Local RangeError(Local message); - static Local ReferenceError(Local message); - static Local SyntaxError(Local message); - static Local TypeError(Local message); - static Local WasmCompileError(Local message); - static Local WasmLinkError(Local message); - static Local WasmRuntimeError(Local message); - static Local Error(Local message); - - /** - * Creates an error message for the given exception. - * Will try to reconstruct the original stack trace from the exception value, - * or capture the current stack trace if not available. - */ - static Local CreateMessage(Isolate* isolate, Local exception); - - /** - * Returns the original stack trace that was captured at the creation time - * of a given exception, or an empty handle if not available. - */ - static Local GetStackTrace(Local exception); -}; - -/** - * An external exception handler. - */ -class V8_EXPORT TryCatch { - public: - /** - * Creates a new try/catch block and registers it with v8. Note that - * all TryCatch blocks should be stack allocated because the memory - * location itself is compared against JavaScript try/catch blocks. - */ - explicit TryCatch(Isolate* isolate); - - /** - * Unregisters and deletes this try/catch block. - */ - ~TryCatch(); - - /** - * Returns true if an exception has been caught by this try/catch block. - */ - bool HasCaught() const; - - /** - * For certain types of exceptions, it makes no sense to continue execution. - * - * If CanContinue returns false, the correct action is to perform any C++ - * cleanup needed and then return. If CanContinue returns false and - * HasTerminated returns true, it is possible to call - * CancelTerminateExecution in order to continue calling into the engine. - */ - bool CanContinue() const; - - /** - * Returns true if an exception has been caught due to script execution - * being terminated. - * - * There is no JavaScript representation of an execution termination - * exception. Such exceptions are thrown when the TerminateExecution - * methods are called to terminate a long-running script. - * - * If such an exception has been thrown, HasTerminated will return true, - * indicating that it is possible to call CancelTerminateExecution in order - * to continue calling into the engine. - */ - bool HasTerminated() const; - - /** - * Throws the exception caught by this TryCatch in a way that avoids - * it being caught again by this same TryCatch. As with ThrowException - * it is illegal to execute any JavaScript operations after calling - * ReThrow; the caller must return immediately to where the exception - * is caught. - */ - Local ReThrow(); - - /** - * Returns the exception caught by this try/catch block. If no exception has - * been caught an empty handle is returned. - */ - Local Exception() const; - - /** - * Returns the .stack property of an object. If no .stack - * property is present an empty handle is returned. - */ - V8_WARN_UNUSED_RESULT static MaybeLocal StackTrace( - Local context, Local exception); - - /** - * Returns the .stack property of the thrown object. If no .stack property is - * present or if this try/catch block has not caught an exception, an empty - * handle is returned. - */ - V8_WARN_UNUSED_RESULT MaybeLocal StackTrace( - Local context) const; - - /** - * Returns the message associated with this exception. If there is - * no message associated an empty handle is returned. - */ - Local Message() const; - - /** - * Clears any exceptions that may have been caught by this try/catch block. - * After this method has been called, HasCaught() will return false. Cancels - * the scheduled exception if it is caught and ReThrow() is not called before. - * - * It is not necessary to clear a try/catch block before using it again; if - * another exception is thrown the previously caught exception will just be - * overwritten. However, it is often a good idea since it makes it easier - * to determine which operation threw a given exception. - */ - void Reset(); - - /** - * Set verbosity of the external exception handler. - * - * By default, exceptions that are caught by an external exception - * handler are not reported. Call SetVerbose with true on an - * external exception handler to have exceptions caught by the - * handler reported as if they were not caught. - */ - void SetVerbose(bool value); - - /** - * Returns true if verbosity is enabled. - */ - bool IsVerbose() const; - - /** - * Set whether or not this TryCatch should capture a Message object - * which holds source information about where the exception - * occurred. True by default. - */ - void SetCaptureMessage(bool value); - - V8_DEPRECATE_SOON( - "This is private information that should not be exposed by the API") - static void* JSStackComparableAddress(TryCatch* handler) { - if (handler == nullptr) return nullptr; - return reinterpret_cast(handler->JSStackComparableAddressPrivate()); - } - - TryCatch(const TryCatch&) = delete; - void operator=(const TryCatch&) = delete; - - private: - // Declaring operator new and delete as deleted is not spec compliant. - // Therefore declare them private instead to disable dynamic alloc - void* operator new(size_t size); - void* operator new[](size_t size); - void operator delete(void*, size_t); - void operator delete[](void*, size_t); - - /** - * There are cases when the raw address of C++ TryCatch object cannot be - * used for comparisons with addresses into the JS stack. The cases are: - * 1) ARM, ARM64 and MIPS simulators which have separate JS stack. - * 2) Address sanitizer allocates local C++ object in the heap when - * UseAfterReturn mode is enabled. - * This method returns address that can be used for comparisons with - * addresses into the JS stack. When neither simulator nor ASAN's - * UseAfterReturn is enabled, then the address returned will be the address - * of the C++ try catch handler itself. - */ - internal::Address JSStackComparableAddressPrivate() { - return js_stack_comparable_address_; - } - - void ResetInternal(); - - internal::Isolate* isolate_; - TryCatch* next_; - void* exception_; - void* message_obj_; - internal::Address js_stack_comparable_address_; - bool is_verbose_ : 1; - bool can_continue_ : 1; - bool capture_message_ : 1; - bool rethrow_ : 1; - bool has_terminated_ : 1; - - friend class internal::Isolate; - friend class internal::ThreadLocalTop; -}; - -} // namespace v8 - -#endif // INCLUDE_V8_EXCEPTION_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-extension.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-extension.h deleted file mode 100644 index 0705e2afbb8..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-extension.h +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_EXTENSION_H_ -#define INCLUDE_V8_EXTENSION_H_ - -#include - -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8-primitive.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class FunctionTemplate; - -// --- Extensions --- - -/** - * Ignore - */ -class V8_EXPORT Extension { - public: - // Note that the strings passed into this constructor must live as long - // as the Extension itself. - Extension(const char* name, const char* source = nullptr, int dep_count = 0, - const char** deps = nullptr, int source_length = -1); - virtual ~Extension() { delete source_; } - virtual Local GetNativeFunctionTemplate( - Isolate* isolate, Local name) { - return Local(); - } - - const char* name() const { return name_; } - size_t source_length() const { return source_length_; } - const String::ExternalOneByteStringResource* source() const { - return source_; - } - int dependency_count() const { return dep_count_; } - const char** dependencies() const { return deps_; } - void set_auto_enable(bool value) { auto_enable_ = value; } - bool auto_enable() { return auto_enable_; } - - // Disallow copying and assigning. - Extension(const Extension&) = delete; - void operator=(const Extension&) = delete; - - private: - const char* name_; - size_t source_length_; // expected to initialize before source_ - String::ExternalOneByteStringResource* source_; - int dep_count_; - const char** deps_; - bool auto_enable_; -}; - -void V8_EXPORT RegisterExtension(std::unique_ptr); - -} // namespace v8 - -#endif // INCLUDE_V8_EXTENSION_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-external.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-external.h deleted file mode 100644 index 2e245036f42..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-external.h +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_EXTERNAL_H_ -#define INCLUDE_V8_EXTERNAL_H_ - -#include "v8-value.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Isolate; - -/** - * A JavaScript value that wraps a C++ void*. This type of value is mainly used - * to associate C++ data structures with JavaScript objects. - */ -class V8_EXPORT External : public Value { - public: - static Local New(Isolate* isolate, void* value); - V8_INLINE static External* Cast(Value* value) { -#ifdef V8_ENABLE_CHECKS - CheckCast(value); -#endif - return static_cast(value); - } - - void* Value() const; - - private: - static void CheckCast(v8::Value* obj); -}; - -} // namespace v8 - -#endif // INCLUDE_V8_EXTERNAL_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-fast-api-calls.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-fast-api-calls.h deleted file mode 100644 index ca13b1e6266..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-fast-api-calls.h +++ /dev/null @@ -1,838 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -/** - * This file provides additional API on top of the default one for making - * API calls, which come from embedder C++ functions. The functions are being - * called directly from optimized code, doing all the necessary typechecks - * in the compiler itself, instead of on the embedder side. Hence the "fast" - * in the name. Example usage might look like: - * - * \code - * void FastMethod(int param, bool another_param); - * - * v8::FunctionTemplate::New(isolate, SlowCallback, data, - * signature, length, constructor_behavior - * side_effect_type, - * &v8::CFunction::Make(FastMethod)); - * \endcode - * - * By design, fast calls are limited by the following requirements, which - * the embedder should enforce themselves: - * - they should not allocate on the JS heap; - * - they should not trigger JS execution. - * To enforce them, the embedder could use the existing - * v8::Isolate::DisallowJavascriptExecutionScope and a utility similar to - * Blink's NoAllocationScope: - * https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/platform/heap/thread_state_scopes.h;l=16 - * - * Due to these limitations, it's not directly possible to report errors by - * throwing a JS exception or to otherwise do an allocation. There is an - * alternative way of creating fast calls that supports falling back to the - * slow call and then performing the necessary allocation. When one creates - * the fast method by using CFunction::MakeWithFallbackSupport instead of - * CFunction::Make, the fast callback gets as last parameter an output variable, - * through which it can request falling back to the slow call. So one might - * declare their method like: - * - * \code - * void FastMethodWithFallback(int param, FastApiCallbackOptions& options); - * \endcode - * - * If the callback wants to signal an error condition or to perform an - * allocation, it must set options.fallback to true and do an early return from - * the fast method. Then V8 checks the value of options.fallback and if it's - * true, falls back to executing the SlowCallback, which is capable of reporting - * the error (either by throwing a JS exception or logging to the console) or - * doing the allocation. It's the embedder's responsibility to ensure that the - * fast callback is idempotent up to the point where error and fallback - * conditions are checked, because otherwise executing the slow callback might - * produce visible side-effects twice. - * - * An example for custom embedder type support might employ a way to wrap/ - * unwrap various C++ types in JSObject instances, e.g: - * - * \code - * - * // Helper method with a check for field count. - * template - * inline T* GetInternalField(v8::Local wrapper) { - * assert(offset < wrapper->InternalFieldCount()); - * return reinterpret_cast( - * wrapper->GetAlignedPointerFromInternalField(offset)); - * } - * - * class CustomEmbedderType { - * public: - * // Returns the raw C object from a wrapper JS object. - * static CustomEmbedderType* Unwrap(v8::Local wrapper) { - * return GetInternalField(wrapper); - * } - * static void FastMethod(v8::Local receiver_obj, int param) { - * CustomEmbedderType* receiver = static_cast( - * receiver_obj->GetAlignedPointerFromInternalField( - * kV8EmbedderWrapperObjectIndex)); - * - * // Type checks are already done by the optimized code. - * // Then call some performance-critical method like: - * // receiver->Method(param); - * } - * - * static void SlowMethod( - * const v8::FunctionCallbackInfo& info) { - * v8::Local instance = - * v8::Local::Cast(info.Holder()); - * CustomEmbedderType* receiver = Unwrap(instance); - * // TODO: Do type checks and extract {param}. - * receiver->Method(param); - * } - * }; - * - * // TODO(mslekova): Clean-up these constants - * // The constants kV8EmbedderWrapperTypeIndex and - * // kV8EmbedderWrapperObjectIndex describe the offsets for the type info - * // struct and the native object, when expressed as internal field indices - * // within a JSObject. The existance of this helper function assumes that - * // all embedder objects have their JSObject-side type info at the same - * // offset, but this is not a limitation of the API itself. For a detailed - * // use case, see the third example. - * static constexpr int kV8EmbedderWrapperTypeIndex = 0; - * static constexpr int kV8EmbedderWrapperObjectIndex = 1; - * - * // The following setup function can be templatized based on - * // the {embedder_object} argument. - * void SetupCustomEmbedderObject(v8::Isolate* isolate, - * v8::Local context, - * CustomEmbedderType* embedder_object) { - * isolate->set_embedder_wrapper_type_index( - * kV8EmbedderWrapperTypeIndex); - * isolate->set_embedder_wrapper_object_index( - * kV8EmbedderWrapperObjectIndex); - * - * v8::CFunction c_func = - * MakeV8CFunction(CustomEmbedderType::FastMethod); - * - * Local method_template = - * v8::FunctionTemplate::New( - * isolate, CustomEmbedderType::SlowMethod, v8::Local(), - * v8::Local(), 1, v8::ConstructorBehavior::kAllow, - * v8::SideEffectType::kHasSideEffect, &c_func); - * - * v8::Local object_template = - * v8::ObjectTemplate::New(isolate); - * object_template->SetInternalFieldCount( - * kV8EmbedderWrapperObjectIndex + 1); - * object_template->Set(isolate, "method", method_template); - * - * // Instantiate the wrapper JS object. - * v8::Local object = - * object_template->NewInstance(context).ToLocalChecked(); - * object->SetAlignedPointerInInternalField( - * kV8EmbedderWrapperObjectIndex, - * reinterpret_cast(embedder_object)); - * - * // TODO: Expose {object} where it's necessary. - * } - * \endcode - * - * For instance if {object} is exposed via a global "obj" variable, - * one could write in JS: - * function hot_func() { - * obj.method(42); - * } - * and once {hot_func} gets optimized, CustomEmbedderType::FastMethod - * will be called instead of the slow version, with the following arguments: - * receiver := the {embedder_object} from above - * param := 42 - * - * Currently supported return types: - * - void - * - bool - * - int32_t - * - uint32_t - * - float32_t - * - float64_t - * Currently supported argument types: - * - pointer to an embedder type - * - JavaScript array of primitive types - * - bool - * - int32_t - * - uint32_t - * - int64_t - * - uint64_t - * - float32_t - * - float64_t - * - * The 64-bit integer types currently have the IDL (unsigned) long long - * semantics: https://heycam.github.io/webidl/#abstract-opdef-converttoint - * In the future we'll extend the API to also provide conversions from/to - * BigInt to preserve full precision. - * The floating point types currently have the IDL (unrestricted) semantics, - * which is the only one used by WebGL. We plan to add support also for - * restricted floats/doubles, similarly to the BigInt conversion policies. - * We also differ from the specific NaN bit pattern that WebIDL prescribes - * (https://heycam.github.io/webidl/#es-unrestricted-float) in that Blink - * passes NaN values as-is, i.e. doesn't normalize them. - * - * To be supported types: - * - TypedArrays and ArrayBuffers - * - arrays of embedder types - * - * - * The API offers a limited support for function overloads: - * - * \code - * void FastMethod_2Args(int param, bool another_param); - * void FastMethod_3Args(int param, bool another_param, int third_param); - * - * v8::CFunction fast_method_2args_c_func = - * MakeV8CFunction(FastMethod_2Args); - * v8::CFunction fast_method_3args_c_func = - * MakeV8CFunction(FastMethod_3Args); - * const v8::CFunction fast_method_overloads[] = {fast_method_2args_c_func, - * fast_method_3args_c_func}; - * Local method_template = - * v8::FunctionTemplate::NewWithCFunctionOverloads( - * isolate, SlowCallback, data, signature, length, - * constructor_behavior, side_effect_type, - * {fast_method_overloads, 2}); - * \endcode - * - * In this example a single FunctionTemplate is associated to multiple C++ - * functions. The overload resolution is currently only based on the number of - * arguments passed in a call. For example, if this method_template is - * registered with a wrapper JS object as described above, a call with two - * arguments: - * obj.method(42, true); - * will result in a fast call to FastMethod_2Args, while a call with three or - * more arguments: - * obj.method(42, true, 11); - * will result in a fast call to FastMethod_3Args. Instead a call with less than - * two arguments, like: - * obj.method(42); - * would not result in a fast call but would fall back to executing the - * associated SlowCallback. - */ - -#ifndef INCLUDE_V8_FAST_API_CALLS_H_ -#define INCLUDE_V8_FAST_API_CALLS_H_ - -#include -#include - -#include -#include - -#include "v8-internal.h" // NOLINT(build/include_directory) -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8-typed-array.h" // NOLINT(build/include_directory) -#include "v8-value.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Isolate; - -class CTypeInfo { - public: - enum class Type : uint8_t { - kVoid, - kBool, - kInt32, - kUint32, - kInt64, - kUint64, - kFloat32, - kFloat64, - kV8Value, - kApiObject, // This will be deprecated once all users have - // migrated from v8::ApiObject to v8::Local. - }; - - // kCallbackOptionsType is not part of the Type enum - // because it is only used internally. Use value 255 that is larger - // than any valid Type enum. - static constexpr Type kCallbackOptionsType = Type(255); - - enum class SequenceType : uint8_t { - kScalar, - kIsSequence, // sequence - kIsTypedArray, // TypedArray of T or any ArrayBufferView if T - // is void - kIsArrayBuffer // ArrayBuffer - }; - - enum class Flags : uint8_t { - kNone = 0, - kAllowSharedBit = 1 << 0, // Must be an ArrayBuffer or TypedArray - kEnforceRangeBit = 1 << 1, // T must be integral - kClampBit = 1 << 2, // T must be integral - kIsRestrictedBit = 1 << 3, // T must be float or double - }; - - explicit constexpr CTypeInfo( - Type type, SequenceType sequence_type = SequenceType::kScalar, - Flags flags = Flags::kNone) - : type_(type), sequence_type_(sequence_type), flags_(flags) {} - - constexpr Type GetType() const { return type_; } - constexpr SequenceType GetSequenceType() const { return sequence_type_; } - constexpr Flags GetFlags() const { return flags_; } - - static constexpr bool IsIntegralType(Type type) { - return type == Type::kInt32 || type == Type::kUint32 || - type == Type::kInt64 || type == Type::kUint64; - } - - static constexpr bool IsFloatingPointType(Type type) { - return type == Type::kFloat32 || type == Type::kFloat64; - } - - static constexpr bool IsPrimitive(Type type) { - return IsIntegralType(type) || IsFloatingPointType(type) || - type == Type::kBool; - } - - private: - Type type_; - SequenceType sequence_type_; - Flags flags_; -}; - -struct FastApiTypedArrayBase { - public: - // Returns the length in number of elements. - size_t V8_EXPORT length() const { return length_; } - // Checks whether the given index is within the bounds of the collection. - void V8_EXPORT ValidateIndex(size_t index) const; - - protected: - size_t length_ = 0; -}; - -template -struct FastApiTypedArray : public FastApiTypedArrayBase { - public: - V8_INLINE T get(size_t index) const { -#ifdef DEBUG - ValidateIndex(index); -#endif // DEBUG - T tmp; - memcpy(&tmp, reinterpret_cast(data_) + index, sizeof(T)); - return tmp; - } - - private: - // This pointer should include the typed array offset applied. - // It's not guaranteed that it's aligned to sizeof(T), it's only - // guaranteed that it's 4-byte aligned, so for 8-byte types we need to - // provide a special implementation for reading from it, which hides - // the possibly unaligned read in the `get` method. - void* data_; -}; - -// Any TypedArray. It uses kTypedArrayBit with base type void -// Overloaded args of ArrayBufferView and TypedArray are not supported -// (for now) because the generic “any” ArrayBufferView doesn’t have its -// own instance type. It could be supported if we specify that -// TypedArray always has precedence over the generic ArrayBufferView, -// but this complicates overload resolution. -struct FastApiArrayBufferView { - void* data; - size_t byte_length; -}; - -struct FastApiArrayBuffer { - void* data; - size_t byte_length; -}; - -class V8_EXPORT CFunctionInfo { - public: - // Construct a struct to hold a CFunction's type information. - // |return_info| describes the function's return type. - // |arg_info| is an array of |arg_count| CTypeInfos describing the - // arguments. Only the last argument may be of the special type - // CTypeInfo::kCallbackOptionsType. - CFunctionInfo(const CTypeInfo& return_info, unsigned int arg_count, - const CTypeInfo* arg_info); - - const CTypeInfo& ReturnInfo() const { return return_info_; } - - // The argument count, not including the v8::FastApiCallbackOptions - // if present. - unsigned int ArgumentCount() const { - return HasOptions() ? arg_count_ - 1 : arg_count_; - } - - // |index| must be less than ArgumentCount(). - // Note: if the last argument passed on construction of CFunctionInfo - // has type CTypeInfo::kCallbackOptionsType, it is not included in - // ArgumentCount(). - const CTypeInfo& ArgumentInfo(unsigned int index) const; - - bool HasOptions() const { - // The options arg is always the last one. - return arg_count_ > 0 && arg_info_[arg_count_ - 1].GetType() == - CTypeInfo::kCallbackOptionsType; - } - - private: - const CTypeInfo return_info_; - const unsigned int arg_count_; - const CTypeInfo* arg_info_; -}; - -class V8_EXPORT CFunction { - public: - constexpr CFunction() : address_(nullptr), type_info_(nullptr) {} - - const CTypeInfo& ReturnInfo() const { return type_info_->ReturnInfo(); } - - const CTypeInfo& ArgumentInfo(unsigned int index) const { - return type_info_->ArgumentInfo(index); - } - - unsigned int ArgumentCount() const { return type_info_->ArgumentCount(); } - - const void* GetAddress() const { return address_; } - const CFunctionInfo* GetTypeInfo() const { return type_info_; } - - enum class OverloadResolution { kImpossible, kAtRuntime, kAtCompileTime }; - - // Returns whether an overload between this and the given CFunction can - // be resolved at runtime by the RTTI available for the arguments or at - // compile time for functions with different number of arguments. - OverloadResolution GetOverloadResolution(const CFunction* other) { - // Runtime overload resolution can only deal with functions with the - // same number of arguments. Functions with different arity are handled - // by compile time overload resolution though. - if (ArgumentCount() != other->ArgumentCount()) { - return OverloadResolution::kAtCompileTime; - } - - // The functions can only differ by a single argument position. - int diff_index = -1; - for (unsigned int i = 0; i < ArgumentCount(); ++i) { - if (ArgumentInfo(i).GetSequenceType() != - other->ArgumentInfo(i).GetSequenceType()) { - if (diff_index >= 0) { - return OverloadResolution::kImpossible; - } - diff_index = i; - - // We only support overload resolution between sequence types. - if (ArgumentInfo(i).GetSequenceType() == - CTypeInfo::SequenceType::kScalar || - other->ArgumentInfo(i).GetSequenceType() == - CTypeInfo::SequenceType::kScalar) { - return OverloadResolution::kImpossible; - } - } - } - - return OverloadResolution::kAtRuntime; - } - - template - static CFunction Make(F* func) { - return ArgUnwrap::Make(func); - } - - template - V8_DEPRECATED("Use CFunctionBuilder instead.") - static CFunction MakeWithFallbackSupport(F* func) { - return ArgUnwrap::Make(func); - } - - CFunction(const void* address, const CFunctionInfo* type_info); - - private: - const void* address_; - const CFunctionInfo* type_info_; - - template - class ArgUnwrap { - static_assert(sizeof(F) != sizeof(F), - "CFunction must be created from a function pointer."); - }; - - template - class ArgUnwrap { - public: - static CFunction Make(R (*func)(Args...)); - }; -}; - -struct V8_DEPRECATE_SOON("Use v8::Local instead.") ApiObject { - uintptr_t address; -}; - -/** - * A struct which may be passed to a fast call callback, like so: - * \code - * void FastMethodWithOptions(int param, FastApiCallbackOptions& options); - * \endcode - */ -struct FastApiCallbackOptions { - /** - * Creates a new instance of FastApiCallbackOptions for testing purpose. The - * returned instance may be filled with mock data. - */ - static FastApiCallbackOptions CreateForTesting(Isolate* isolate) { - return {false, {0}}; - } - - /** - * If the callback wants to signal an error condition or to perform an - * allocation, it must set options.fallback to true and do an early return - * from the fast method. Then V8 checks the value of options.fallback and if - * it's true, falls back to executing the SlowCallback, which is capable of - * reporting the error (either by throwing a JS exception or logging to the - * console) or doing the allocation. It's the embedder's responsibility to - * ensure that the fast callback is idempotent up to the point where error and - * fallback conditions are checked, because otherwise executing the slow - * callback might produce visible side-effects twice. - */ - bool fallback; - - /** - * The `data` passed to the FunctionTemplate constructor, or `undefined`. - * `data_ptr` allows for default constructing FastApiCallbackOptions. - */ - union { - uintptr_t data_ptr; - v8::Value data; - }; -}; - -namespace internal { - -// Helper to count the number of occurances of `T` in `List` -template -struct count : std::integral_constant {}; -template -struct count - : std::integral_constant::value> {}; -template -struct count : count {}; - -template -class CFunctionInfoImpl : public CFunctionInfo { - static constexpr int kOptionsArgCount = - count(); - static constexpr int kReceiverCount = 1; - - static_assert(kOptionsArgCount == 0 || kOptionsArgCount == 1, - "Only one options parameter is supported."); - - static_assert(sizeof...(ArgBuilders) >= kOptionsArgCount + kReceiverCount, - "The receiver or the options argument is missing."); - - public: - constexpr CFunctionInfoImpl() - : CFunctionInfo(RetBuilder::Build(), sizeof...(ArgBuilders), - arg_info_storage_), - arg_info_storage_{ArgBuilders::Build()...} { - constexpr CTypeInfo::Type kReturnType = RetBuilder::Build().GetType(); - static_assert(kReturnType == CTypeInfo::Type::kVoid || - kReturnType == CTypeInfo::Type::kBool || - kReturnType == CTypeInfo::Type::kInt32 || - kReturnType == CTypeInfo::Type::kUint32 || - kReturnType == CTypeInfo::Type::kFloat32 || - kReturnType == CTypeInfo::Type::kFloat64, - "64-bit int and api object values are not currently " - "supported return types."); - } - - private: - const CTypeInfo arg_info_storage_[sizeof...(ArgBuilders)]; -}; - -template -struct TypeInfoHelper { - static_assert(sizeof(T) != sizeof(T), "This type is not supported"); -}; - -#define SPECIALIZE_GET_TYPE_INFO_HELPER_FOR(T, Enum) \ - template <> \ - struct TypeInfoHelper { \ - static constexpr CTypeInfo::Flags Flags() { \ - return CTypeInfo::Flags::kNone; \ - } \ - \ - static constexpr CTypeInfo::Type Type() { return CTypeInfo::Type::Enum; } \ - static constexpr CTypeInfo::SequenceType SequenceType() { \ - return CTypeInfo::SequenceType::kScalar; \ - } \ - }; - -template -struct CTypeInfoTraits {}; - -#define DEFINE_TYPE_INFO_TRAITS(CType, Enum) \ - template <> \ - struct CTypeInfoTraits { \ - using ctype = CType; \ - }; - -#define PRIMITIVE_C_TYPES(V) \ - V(bool, kBool) \ - V(int32_t, kInt32) \ - V(uint32_t, kUint32) \ - V(int64_t, kInt64) \ - V(uint64_t, kUint64) \ - V(float, kFloat32) \ - V(double, kFloat64) - -// Same as above, but includes deprecated types for compatibility. -#define ALL_C_TYPES(V) \ - PRIMITIVE_C_TYPES(V) \ - V(void, kVoid) \ - V(v8::Local, kV8Value) \ - V(v8::Local, kV8Value) \ - V(ApiObject, kApiObject) - -// ApiObject was a temporary solution to wrap the pointer to the v8::Value. -// Please use v8::Local in new code for the arguments and -// v8::Local for the receiver, as ApiObject will be deprecated. - -ALL_C_TYPES(SPECIALIZE_GET_TYPE_INFO_HELPER_FOR) -PRIMITIVE_C_TYPES(DEFINE_TYPE_INFO_TRAITS) - -#undef PRIMITIVE_C_TYPES -#undef ALL_C_TYPES - -#define SPECIALIZE_GET_TYPE_INFO_HELPER_FOR_TA(T, Enum) \ - template <> \ - struct TypeInfoHelper&> { \ - static constexpr CTypeInfo::Flags Flags() { \ - return CTypeInfo::Flags::kNone; \ - } \ - \ - static constexpr CTypeInfo::Type Type() { return CTypeInfo::Type::Enum; } \ - static constexpr CTypeInfo::SequenceType SequenceType() { \ - return CTypeInfo::SequenceType::kIsTypedArray; \ - } \ - }; - -#define TYPED_ARRAY_C_TYPES(V) \ - V(int32_t, kInt32) \ - V(uint32_t, kUint32) \ - V(int64_t, kInt64) \ - V(uint64_t, kUint64) \ - V(float, kFloat32) \ - V(double, kFloat64) - -TYPED_ARRAY_C_TYPES(SPECIALIZE_GET_TYPE_INFO_HELPER_FOR_TA) - -#undef TYPED_ARRAY_C_TYPES - -template <> -struct TypeInfoHelper> { - static constexpr CTypeInfo::Flags Flags() { return CTypeInfo::Flags::kNone; } - - static constexpr CTypeInfo::Type Type() { return CTypeInfo::Type::kVoid; } - static constexpr CTypeInfo::SequenceType SequenceType() { - return CTypeInfo::SequenceType::kIsSequence; - } -}; - -template <> -struct TypeInfoHelper> { - static constexpr CTypeInfo::Flags Flags() { return CTypeInfo::Flags::kNone; } - - static constexpr CTypeInfo::Type Type() { return CTypeInfo::Type::kUint32; } - static constexpr CTypeInfo::SequenceType SequenceType() { - return CTypeInfo::SequenceType::kIsTypedArray; - } -}; - -template <> -struct TypeInfoHelper { - static constexpr CTypeInfo::Flags Flags() { return CTypeInfo::Flags::kNone; } - - static constexpr CTypeInfo::Type Type() { - return CTypeInfo::kCallbackOptionsType; - } - static constexpr CTypeInfo::SequenceType SequenceType() { - return CTypeInfo::SequenceType::kScalar; - } -}; - -#define STATIC_ASSERT_IMPLIES(COND, ASSERTION, MSG) \ - static_assert(((COND) == 0) || (ASSERTION), MSG) - -template -class CTypeInfoBuilder { - public: - using BaseType = T; - - static constexpr CTypeInfo Build() { - constexpr CTypeInfo::Flags kFlags = - MergeFlags(TypeInfoHelper::Flags(), Flags...); - constexpr CTypeInfo::Type kType = TypeInfoHelper::Type(); - constexpr CTypeInfo::SequenceType kSequenceType = - TypeInfoHelper::SequenceType(); - - STATIC_ASSERT_IMPLIES( - uint8_t(kFlags) & uint8_t(CTypeInfo::Flags::kAllowSharedBit), - (kSequenceType == CTypeInfo::SequenceType::kIsTypedArray || - kSequenceType == CTypeInfo::SequenceType::kIsArrayBuffer), - "kAllowSharedBit is only allowed for TypedArrays and ArrayBuffers."); - STATIC_ASSERT_IMPLIES( - uint8_t(kFlags) & uint8_t(CTypeInfo::Flags::kEnforceRangeBit), - CTypeInfo::IsIntegralType(kType), - "kEnforceRangeBit is only allowed for integral types."); - STATIC_ASSERT_IMPLIES( - uint8_t(kFlags) & uint8_t(CTypeInfo::Flags::kClampBit), - CTypeInfo::IsIntegralType(kType), - "kClampBit is only allowed for integral types."); - STATIC_ASSERT_IMPLIES( - uint8_t(kFlags) & uint8_t(CTypeInfo::Flags::kIsRestrictedBit), - CTypeInfo::IsFloatingPointType(kType), - "kIsRestrictedBit is only allowed for floating point types."); - STATIC_ASSERT_IMPLIES(kSequenceType == CTypeInfo::SequenceType::kIsSequence, - kType == CTypeInfo::Type::kVoid, - "Sequences are only supported from void type."); - STATIC_ASSERT_IMPLIES( - kSequenceType == CTypeInfo::SequenceType::kIsTypedArray, - CTypeInfo::IsPrimitive(kType) || kType == CTypeInfo::Type::kVoid, - "TypedArrays are only supported from primitive types or void."); - - // Return the same type with the merged flags. - return CTypeInfo(TypeInfoHelper::Type(), - TypeInfoHelper::SequenceType(), kFlags); - } - - private: - template - static constexpr CTypeInfo::Flags MergeFlags(CTypeInfo::Flags flags, - Rest... rest) { - return CTypeInfo::Flags(uint8_t(flags) | uint8_t(MergeFlags(rest...))); - } - static constexpr CTypeInfo::Flags MergeFlags() { return CTypeInfo::Flags(0); } -}; - -template -class CFunctionBuilderWithFunction { - public: - explicit constexpr CFunctionBuilderWithFunction(const void* fn) : fn_(fn) {} - - template - constexpr auto Ret() { - return CFunctionBuilderWithFunction< - CTypeInfoBuilder, - ArgBuilders...>(fn_); - } - - template - constexpr auto Arg() { - // Return a copy of the builder with the Nth arg builder merged with - // template parameter pack Flags. - return ArgImpl( - std::make_index_sequence()); - } - - auto Build() { - static CFunctionInfoImpl instance; - return CFunction(fn_, &instance); - } - - private: - template - struct GetArgBuilder; - - // Returns the same ArgBuilder as the one at index N, including its flags. - // Flags in the template parameter pack are ignored. - template - struct GetArgBuilder { - using type = - typename std::tuple_element>::type; - }; - - // Returns an ArgBuilder with the same base type as the one at index N, - // but merges the flags with the flags in the template parameter pack. - template - struct GetArgBuilder { - using type = CTypeInfoBuilder< - typename std::tuple_element>::type::BaseType, - std::tuple_element>::type::Build() - .GetFlags(), - Flags...>; - }; - - // Return a copy of the CFunctionBuilder, but merges the Flags on - // ArgBuilder index N with the new Flags passed in the template parameter - // pack. - template - constexpr auto ArgImpl(std::index_sequence) { - return CFunctionBuilderWithFunction< - RetBuilder, typename GetArgBuilder::type...>(fn_); - } - - const void* fn_; -}; - -class CFunctionBuilder { - public: - constexpr CFunctionBuilder() {} - - template - constexpr auto Fn(R (*fn)(Args...)) { - return CFunctionBuilderWithFunction, - CTypeInfoBuilder...>( - reinterpret_cast(fn)); - } -}; - -} // namespace internal - -// static -template -CFunction CFunction::ArgUnwrap::Make(R (*func)(Args...)) { - return internal::CFunctionBuilder().Fn(func).Build(); -} - -using CFunctionBuilder = internal::CFunctionBuilder; - -static constexpr CTypeInfo kTypeInfoInt32 = CTypeInfo(CTypeInfo::Type::kInt32); -static constexpr CTypeInfo kTypeInfoFloat64 = - CTypeInfo(CTypeInfo::Type::kFloat64); - -/** - * Copies the contents of this JavaScript array to a C++ buffer with - * a given max_length. A CTypeInfo is passed as an argument, - * instructing different rules for conversion (e.g. restricted float/double). - * The element type T of the destination array must match the C type - * corresponding to the CTypeInfo (specified by CTypeInfoTraits). - * If the array length is larger than max_length or the array is of - * unsupported type, the operation will fail, returning false. Generally, an - * array which contains objects, undefined, null or anything not convertible - * to the requested destination type, is considered unsupported. The operation - * returns true on success. `type_info` will be used for conversions. - */ -template -bool V8_EXPORT V8_WARN_UNUSED_RESULT TryCopyAndConvertArrayToCppBuffer( - Local src, T* dst, uint32_t max_length); - -template <> -inline bool V8_WARN_UNUSED_RESULT -TryCopyAndConvertArrayToCppBuffer<&kTypeInfoInt32, int32_t>( - Local src, int32_t* dst, uint32_t max_length) { - return CopyAndConvertArrayToCppBufferInt32(src, dst, max_length); -} - -template <> -inline bool V8_WARN_UNUSED_RESULT -TryCopyAndConvertArrayToCppBuffer<&kTypeInfoFloat64, double>( - Local src, double* dst, uint32_t max_length) { - return CopyAndConvertArrayToCppBufferFloat64(src, dst, max_length); -} - -} // namespace v8 - -#endif // INCLUDE_V8_FAST_API_CALLS_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-forward.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-forward.h deleted file mode 100644 index ae16fe64b21..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-forward.h +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_LOCAL_HANDLES_H_ -#define INCLUDE_V8_LOCAL_HANDLES_H_ - -// This header is intended to be used by headers that pass around V8 types, -// either by pointer or using Local. The full definitions can be included -// either via v8.h or the more fine-grained headers. - -#include "v8-local-handle.h" // NOLINT(build/include_directory) - -namespace v8 { - -class AccessorSignature; -class Array; -class ArrayBuffer; -class ArrayBufferView; -class BigInt; -class BigInt64Array; -class BigIntObject; -class BigUint64Array; -class Boolean; -class BooleanObject; -class Context; -class DataView; -class Data; -class Date; -class External; -class FixedArray; -class Float32Array; -class Float64Array; -class Function; -template -class FunctionCallbackInfo; -class FunctionTemplate; -class Int16Array; -class Int32; -class Int32Array; -class Int8Array; -class Integer; -class Isolate; -class Map; -class Module; -class Name; -class Number; -class NumberObject; -class Object; -class ObjectTemplate; -class Platform; -class Primitive; -class Private; -class Promise; -class Proxy; -class RegExp; -class Script; -class Set; -class SharedArrayBuffer; -class Signature; -class String; -class StringObject; -class Symbol; -class SymbolObject; -class Template; -class TypedArray; -class Uint16Array; -class Uint32; -class Uint32Array; -class Uint8Array; -class Uint8ClampedArray; -class UnboundModuleScript; -class Value; -class WasmMemoryObject; -class WasmModuleObject; - -} // namespace v8 - -#endif // INCLUDE_V8_LOCAL_HANDLES_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-function-callback.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-function-callback.h deleted file mode 100644 index 2adff99b1cb..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-function-callback.h +++ /dev/null @@ -1,475 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_FUNCTION_CALLBACK_H_ -#define INCLUDE_V8_FUNCTION_CALLBACK_H_ - -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8-primitive.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -template -class BasicTracedReference; -template -class Global; -class Object; -class Value; - -namespace internal { -class FunctionCallbackArguments; -class PropertyCallbackArguments; -} // namespace internal - -namespace debug { -class ConsoleCallArguments; -} // namespace debug - -template -class ReturnValue { - public: - template - V8_INLINE ReturnValue(const ReturnValue& that) : value_(that.value_) { - static_assert(std::is_base_of::value, "type check"); - } - // Local setters - template - V8_INLINE void Set(const Global& handle); - template - V8_INLINE void Set(const BasicTracedReference& handle); - template - V8_INLINE void Set(const Local handle); - // Fast primitive setters - V8_INLINE void Set(bool value); - V8_INLINE void Set(double i); - V8_INLINE void Set(int32_t i); - V8_INLINE void Set(uint32_t i); - // Fast JS primitive setters - V8_INLINE void SetNull(); - V8_INLINE void SetUndefined(); - V8_INLINE void SetEmptyString(); - // Convenience getter for Isolate - V8_INLINE Isolate* GetIsolate() const; - - // Pointer setter: Uncompilable to prevent inadvertent misuse. - template - V8_INLINE void Set(S* whatever); - - // Getter. Creates a new Local<> so it comes with a certain performance - // hit. If the ReturnValue was not yet set, this will return the undefined - // value. - V8_INLINE Local Get() const; - - private: - template - friend class ReturnValue; - template - friend class FunctionCallbackInfo; - template - friend class PropertyCallbackInfo; - template - friend class PersistentValueMapBase; - V8_INLINE void SetInternal(internal::Address value) { *value_ = value; } - V8_INLINE internal::Address GetDefaultValue(); - V8_INLINE explicit ReturnValue(internal::Address* slot); - internal::Address* value_; -}; - -/** - * The argument information given to function call callbacks. This - * class provides access to information about the context of the call, - * including the receiver, the number and values of arguments, and - * the holder of the function. - */ -template -class FunctionCallbackInfo { - public: - /** The number of available arguments. */ - V8_INLINE int Length() const; - /** - * Accessor for the available arguments. Returns `undefined` if the index - * is out of bounds. - */ - V8_INLINE Local operator[](int i) const; - /** Returns the receiver. This corresponds to the "this" value. */ - V8_INLINE Local This() const; - /** - * If the callback was created without a Signature, this is the same - * value as This(). If there is a signature, and the signature didn't match - * This() but one of its hidden prototypes, this will be the respective - * hidden prototype. - * - * Note that this is not the prototype of This() on which the accessor - * referencing this callback was found (which in V8 internally is often - * referred to as holder [sic]). - */ - V8_INLINE Local Holder() const; - /** For construct calls, this returns the "new.target" value. */ - V8_INLINE Local NewTarget() const; - /** Indicates whether this is a regular call or a construct call. */ - V8_INLINE bool IsConstructCall() const; - /** The data argument specified when creating the callback. */ - V8_INLINE Local Data() const; - /** The current Isolate. */ - V8_INLINE Isolate* GetIsolate() const; - /** The ReturnValue for the call. */ - V8_INLINE ReturnValue GetReturnValue() const; - // This shouldn't be public, but the arm compiler needs it. - static const int kArgsLength = 6; - - protected: - friend class internal::FunctionCallbackArguments; - friend class internal::CustomArguments; - friend class debug::ConsoleCallArguments; - static const int kHolderIndex = 0; - static const int kIsolateIndex = 1; - static const int kReturnValueDefaultValueIndex = 2; - static const int kReturnValueIndex = 3; - static const int kDataIndex = 4; - static const int kNewTargetIndex = 5; - - V8_INLINE FunctionCallbackInfo(internal::Address* implicit_args, - internal::Address* values, int length); - internal::Address* implicit_args_; - internal::Address* values_; - int length_; -}; - -/** - * The information passed to a property callback about the context - * of the property access. - */ -template -class PropertyCallbackInfo { - public: - /** - * \return The isolate of the property access. - */ - V8_INLINE Isolate* GetIsolate() const; - - /** - * \return The data set in the configuration, i.e., in - * `NamedPropertyHandlerConfiguration` or - * `IndexedPropertyHandlerConfiguration.` - */ - V8_INLINE Local Data() const; - - /** - * \return The receiver. In many cases, this is the object on which the - * property access was intercepted. When using - * `Reflect.get`, `Function.prototype.call`, or similar functions, it is the - * object passed in as receiver or thisArg. - * - * \code - * void GetterCallback(Local name, - * const v8::PropertyCallbackInfo& info) { - * auto context = info.GetIsolate()->GetCurrentContext(); - * - * v8::Local a_this = - * info.This() - * ->GetRealNamedProperty(context, v8_str("a")) - * .ToLocalChecked(); - * v8::Local a_holder = - * info.Holder() - * ->GetRealNamedProperty(context, v8_str("a")) - * .ToLocalChecked(); - * - * CHECK(v8_str("r")->Equals(context, a_this).FromJust()); - * CHECK(v8_str("obj")->Equals(context, a_holder).FromJust()); - * - * info.GetReturnValue().Set(name); - * } - * - * v8::Local templ = - * v8::FunctionTemplate::New(isolate); - * templ->InstanceTemplate()->SetHandler( - * v8::NamedPropertyHandlerConfiguration(GetterCallback)); - * LocalContext env; - * env->Global() - * ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local()) - * .ToLocalChecked() - * ->NewInstance(env.local()) - * .ToLocalChecked()) - * .FromJust(); - * - * CompileRun("obj.a = 'obj'; var r = {a: 'r'}; Reflect.get(obj, 'x', r)"); - * \endcode - */ - V8_INLINE Local This() const; - - /** - * \return The object in the prototype chain of the receiver that has the - * interceptor. Suppose you have `x` and its prototype is `y`, and `y` - * has an interceptor. Then `info.This()` is `x` and `info.Holder()` is `y`. - * The Holder() could be a hidden object (the global object, rather - * than the global proxy). - * - * \note For security reasons, do not pass the object back into the runtime. - */ - V8_INLINE Local Holder() const; - - /** - * \return The return value of the callback. - * Can be changed by calling Set(). - * \code - * info.GetReturnValue().Set(...) - * \endcode - * - */ - V8_INLINE ReturnValue GetReturnValue() const; - - /** - * \return True if the intercepted function should throw if an error occurs. - * Usually, `true` corresponds to `'use strict'`. - * - * \note Always `false` when intercepting `Reflect.set()` - * independent of the language mode. - */ - V8_INLINE bool ShouldThrowOnError() const; - - // This shouldn't be public, but the arm compiler needs it. - static const int kArgsLength = 7; - - protected: - friend class MacroAssembler; - friend class internal::PropertyCallbackArguments; - friend class internal::CustomArguments; - static const int kShouldThrowOnErrorIndex = 0; - static const int kHolderIndex = 1; - static const int kIsolateIndex = 2; - static const int kReturnValueDefaultValueIndex = 3; - static const int kReturnValueIndex = 4; - static const int kDataIndex = 5; - static const int kThisIndex = 6; - - V8_INLINE PropertyCallbackInfo(internal::Address* args) : args_(args) {} - internal::Address* args_; -}; - -using FunctionCallback = void (*)(const FunctionCallbackInfo& info); - -// --- Implementation --- - -template -ReturnValue::ReturnValue(internal::Address* slot) : value_(slot) {} - -template -template -void ReturnValue::Set(const Global& handle) { - static_assert(std::is_base_of::value, "type check"); - if (V8_UNLIKELY(handle.IsEmpty())) { - *value_ = GetDefaultValue(); - } else { - *value_ = *reinterpret_cast(*handle); - } -} - -template -template -void ReturnValue::Set(const BasicTracedReference& handle) { - static_assert(std::is_base_of::value, "type check"); - if (V8_UNLIKELY(handle.IsEmpty())) { - *value_ = GetDefaultValue(); - } else { - *value_ = *reinterpret_cast(handle.val_); - } -} - -template -template -void ReturnValue::Set(const Local handle) { - static_assert(std::is_void::value || std::is_base_of::value, - "type check"); - if (V8_UNLIKELY(handle.IsEmpty())) { - *value_ = GetDefaultValue(); - } else { - *value_ = *reinterpret_cast(*handle); - } -} - -template -void ReturnValue::Set(double i) { - static_assert(std::is_base_of::value, "type check"); - Set(Number::New(GetIsolate(), i)); -} - -template -void ReturnValue::Set(int32_t i) { - static_assert(std::is_base_of::value, "type check"); - using I = internal::Internals; - if (V8_LIKELY(I::IsValidSmi(i))) { - *value_ = I::IntToSmi(i); - return; - } - Set(Integer::New(GetIsolate(), i)); -} - -template -void ReturnValue::Set(uint32_t i) { - static_assert(std::is_base_of::value, "type check"); - // Can't simply use INT32_MAX here for whatever reason. - bool fits_into_int32_t = (i & (1U << 31)) == 0; - if (V8_LIKELY(fits_into_int32_t)) { - Set(static_cast(i)); - return; - } - Set(Integer::NewFromUnsigned(GetIsolate(), i)); -} - -template -void ReturnValue::Set(bool value) { - static_assert(std::is_base_of::value, "type check"); - using I = internal::Internals; - int root_index; - if (value) { - root_index = I::kTrueValueRootIndex; - } else { - root_index = I::kFalseValueRootIndex; - } - *value_ = *I::GetRoot(GetIsolate(), root_index); -} - -template -void ReturnValue::SetNull() { - static_assert(std::is_base_of::value, "type check"); - using I = internal::Internals; - *value_ = *I::GetRoot(GetIsolate(), I::kNullValueRootIndex); -} - -template -void ReturnValue::SetUndefined() { - static_assert(std::is_base_of::value, "type check"); - using I = internal::Internals; - *value_ = *I::GetRoot(GetIsolate(), I::kUndefinedValueRootIndex); -} - -template -void ReturnValue::SetEmptyString() { - static_assert(std::is_base_of::value, "type check"); - using I = internal::Internals; - *value_ = *I::GetRoot(GetIsolate(), I::kEmptyStringRootIndex); -} - -template -Isolate* ReturnValue::GetIsolate() const { - // Isolate is always the pointer below the default value on the stack. - return *reinterpret_cast(&value_[-2]); -} - -template -Local ReturnValue::Get() const { - using I = internal::Internals; - if (*value_ == *I::GetRoot(GetIsolate(), I::kTheHoleValueRootIndex)) - return Local(*Undefined(GetIsolate())); - return Local::New(GetIsolate(), reinterpret_cast(value_)); -} - -template -template -void ReturnValue::Set(S* whatever) { - static_assert(sizeof(S) < 0, "incompilable to prevent inadvertent misuse"); -} - -template -internal::Address ReturnValue::GetDefaultValue() { - // Default value is always the pointer below value_ on the stack. - return value_[-1]; -} - -template -FunctionCallbackInfo::FunctionCallbackInfo(internal::Address* implicit_args, - internal::Address* values, - int length) - : implicit_args_(implicit_args), values_(values), length_(length) {} - -template -Local FunctionCallbackInfo::operator[](int i) const { - // values_ points to the first argument (not the receiver). - if (i < 0 || length_ <= i) return Local(*Undefined(GetIsolate())); - return Local(reinterpret_cast(values_ + i)); -} - -template -Local FunctionCallbackInfo::This() const { - // values_ points to the first argument (not the receiver). - return Local(reinterpret_cast(values_ - 1)); -} - -template -Local FunctionCallbackInfo::Holder() const { - return Local( - reinterpret_cast(&implicit_args_[kHolderIndex])); -} - -template -Local FunctionCallbackInfo::NewTarget() const { - return Local( - reinterpret_cast(&implicit_args_[kNewTargetIndex])); -} - -template -Local FunctionCallbackInfo::Data() const { - return Local(reinterpret_cast(&implicit_args_[kDataIndex])); -} - -template -Isolate* FunctionCallbackInfo::GetIsolate() const { - return *reinterpret_cast(&implicit_args_[kIsolateIndex]); -} - -template -ReturnValue FunctionCallbackInfo::GetReturnValue() const { - return ReturnValue(&implicit_args_[kReturnValueIndex]); -} - -template -bool FunctionCallbackInfo::IsConstructCall() const { - return !NewTarget()->IsUndefined(); -} - -template -int FunctionCallbackInfo::Length() const { - return length_; -} - -template -Isolate* PropertyCallbackInfo::GetIsolate() const { - return *reinterpret_cast(&args_[kIsolateIndex]); -} - -template -Local PropertyCallbackInfo::Data() const { - return Local(reinterpret_cast(&args_[kDataIndex])); -} - -template -Local PropertyCallbackInfo::This() const { - return Local(reinterpret_cast(&args_[kThisIndex])); -} - -template -Local PropertyCallbackInfo::Holder() const { - return Local(reinterpret_cast(&args_[kHolderIndex])); -} - -template -ReturnValue PropertyCallbackInfo::GetReturnValue() const { - return ReturnValue(&args_[kReturnValueIndex]); -} - -template -bool PropertyCallbackInfo::ShouldThrowOnError() const { - using I = internal::Internals; - if (args_[kShouldThrowOnErrorIndex] != - I::IntToSmi(I::kInferShouldThrowMode)) { - return args_[kShouldThrowOnErrorIndex] != I::IntToSmi(I::kDontThrow); - } - return v8::internal::ShouldThrowOnError( - reinterpret_cast(GetIsolate())); -} - -} // namespace v8 - -#endif // INCLUDE_V8_FUNCTION_CALLBACK_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-function.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-function.h deleted file mode 100644 index 9424a86fdaf..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-function.h +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_FUNCTION_H_ -#define INCLUDE_V8_FUNCTION_H_ - -#include -#include - -#include "v8-function-callback.h" // NOLINT(build/include_directory) -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8-message.h" // NOLINT(build/include_directory) -#include "v8-object.h" // NOLINT(build/include_directory) -#include "v8-template.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Context; - -/** - * A JavaScript function object (ECMA-262, 15.3). - */ -class V8_EXPORT Function : public Object { - public: - /** - * Create a function in the current execution context - * for a given FunctionCallback. - */ - static MaybeLocal New( - Local context, FunctionCallback callback, - Local data = Local(), int length = 0, - ConstructorBehavior behavior = ConstructorBehavior::kAllow, - SideEffectType side_effect_type = SideEffectType::kHasSideEffect); - - V8_WARN_UNUSED_RESULT MaybeLocal NewInstance( - Local context, int argc, Local argv[]) const; - - V8_WARN_UNUSED_RESULT MaybeLocal NewInstance( - Local context) const { - return NewInstance(context, 0, nullptr); - } - - /** - * When side effect checks are enabled, passing kHasNoSideEffect allows the - * constructor to be invoked without throwing. Calls made within the - * constructor are still checked. - */ - V8_WARN_UNUSED_RESULT MaybeLocal NewInstanceWithSideEffectType( - Local context, int argc, Local argv[], - SideEffectType side_effect_type = SideEffectType::kHasSideEffect) const; - - V8_WARN_UNUSED_RESULT MaybeLocal Call(Local context, - Local recv, int argc, - Local argv[]); - - void SetName(Local name); - Local GetName() const; - - /** - * Name inferred from variable or property assignment of this function. - * Used to facilitate debugging and profiling of JavaScript code written - * in an OO style, where many functions are anonymous but are assigned - * to object properties. - */ - Local GetInferredName() const; - - /** - * displayName if it is set, otherwise name if it is configured, otherwise - * function name, otherwise inferred name. - */ - Local GetDebugName() const; - - /** - * Returns zero based line number of function body and - * kLineOffsetNotFound if no information available. - */ - int GetScriptLineNumber() const; - /** - * Returns zero based column number of function body and - * kLineOffsetNotFound if no information available. - */ - int GetScriptColumnNumber() const; - - /** - * Returns scriptId. - */ - int ScriptId() const; - - /** - * Returns the original function if this function is bound, else returns - * v8::Undefined. - */ - Local GetBoundFunction() const; - - /** - * Calls builtin Function.prototype.toString on this function. - * This is different from Value::ToString() that may call a user-defined - * toString() function, and different than Object::ObjectProtoToString() which - * always serializes "[object Function]". - */ - V8_WARN_UNUSED_RESULT MaybeLocal FunctionProtoToString( - Local context); - - ScriptOrigin GetScriptOrigin() const; - V8_INLINE static Function* Cast(Value* value) { -#ifdef V8_ENABLE_CHECKS - CheckCast(value); -#endif - return static_cast(value); - } - - static const int kLineOffsetNotFound; - - private: - Function(); - static void CheckCast(Value* obj); -}; -} // namespace v8 - -#endif // INCLUDE_V8_FUNCTION_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-initialization.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-initialization.h deleted file mode 100644 index 3b609292f62..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-initialization.h +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_INITIALIZATION_H_ -#define INCLUDE_V8_INITIALIZATION_H_ - -#include -#include - -#include "v8-internal.h" // NOLINT(build/include_directory) -#include "v8-isolate.h" // NOLINT(build/include_directory) -#include "v8-platform.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -// We reserve the V8_* prefix for macros defined in V8 public API and -// assume there are no name conflicts with the embedder's code. - -/** - * The v8 JavaScript engine. - */ -namespace v8 { - -class PageAllocator; -class Platform; -template -class PersistentValueMapBase; - -/** - * EntropySource is used as a callback function when v8 needs a source - * of entropy. - */ -using EntropySource = bool (*)(unsigned char* buffer, size_t length); - -/** - * ReturnAddressLocationResolver is used as a callback function when v8 is - * resolving the location of a return address on the stack. Profilers that - * change the return address on the stack can use this to resolve the stack - * location to wherever the profiler stashed the original return address. - * - * \param return_addr_location A location on stack where a machine - * return address resides. - * \returns Either return_addr_location, or else a pointer to the profiler's - * copy of the original return address. - * - * \note The resolver function must not cause garbage collection. - */ -using ReturnAddressLocationResolver = - uintptr_t (*)(uintptr_t return_addr_location); - -using DcheckErrorCallback = void (*)(const char* file, int line, - const char* message); - -/** - * Container class for static utility functions. - */ -class V8_EXPORT V8 { - public: - /** - * Hand startup data to V8, in case the embedder has chosen to build - * V8 with external startup data. - * - * Note: - * - By default the startup data is linked into the V8 library, in which - * case this function is not meaningful. - * - If this needs to be called, it needs to be called before V8 - * tries to make use of its built-ins. - * - To avoid unnecessary copies of data, V8 will point directly into the - * given data blob, so pretty please keep it around until V8 exit. - * - Compression of the startup blob might be useful, but needs to - * handled entirely on the embedders' side. - * - The call will abort if the data is invalid. - */ - static void SetSnapshotDataBlob(StartupData* startup_blob); - - /** Set the callback to invoke in case of Dcheck failures. */ - static void SetDcheckErrorHandler(DcheckErrorCallback that); - - /** - * Sets V8 flags from a string. - */ - static void SetFlagsFromString(const char* str); - static void SetFlagsFromString(const char* str, size_t length); - - /** - * Sets V8 flags from the command line. - */ - static void SetFlagsFromCommandLine(int* argc, char** argv, - bool remove_flags); - - /** Get the version string. */ - static const char* GetVersion(); - - /** - * Initializes V8. This function needs to be called before the first Isolate - * is created. It always returns true. - */ - V8_INLINE static bool Initialize() { - const int kBuildConfiguration = - (internal::PointerCompressionIsEnabled() ? kPointerCompression : 0) | - (internal::SmiValuesAre31Bits() ? k31BitSmis : 0) | - (internal::HeapSandboxIsEnabled() ? kHeapSandbox : 0) | - (internal::VirtualMemoryCageIsEnabled() ? kVirtualMemoryCage : 0); - return Initialize(kBuildConfiguration); - } - - /** - * Allows the host application to provide a callback which can be used - * as a source of entropy for random number generators. - */ - static void SetEntropySource(EntropySource source); - - /** - * Allows the host application to provide a callback that allows v8 to - * cooperate with a profiler that rewrites return addresses on stack. - */ - static void SetReturnAddressLocationResolver( - ReturnAddressLocationResolver return_address_resolver); - - /** - * Releases any resources used by v8 and stops any utility threads - * that may be running. Note that disposing v8 is permanent, it - * cannot be reinitialized. - * - * It should generally not be necessary to dispose v8 before exiting - * a process, this should happen automatically. It is only necessary - * to use if the process needs the resources taken up by v8. - */ - static bool Dispose(); - - /** - * Initialize the ICU library bundled with V8. The embedder should only - * invoke this method when using the bundled ICU. Returns true on success. - * - * If V8 was compiled with the ICU data in an external file, the location - * of the data file has to be provided. - */ - static bool InitializeICU(const char* icu_data_file = nullptr); - - /** - * Initialize the ICU library bundled with V8. The embedder should only - * invoke this method when using the bundled ICU. If V8 was compiled with - * the ICU data in an external file and when the default location of that - * file should be used, a path to the executable must be provided. - * Returns true on success. - * - * The default is a file called icudtl.dat side-by-side with the executable. - * - * Optionally, the location of the data file can be provided to override the - * default. - */ - static bool InitializeICUDefaultLocation(const char* exec_path, - const char* icu_data_file = nullptr); - - /** - * Initialize the external startup data. The embedder only needs to - * invoke this method when external startup data was enabled in a build. - * - * If V8 was compiled with the startup data in an external file, then - * V8 needs to be given those external files during startup. There are - * three ways to do this: - * - InitializeExternalStartupData(const char*) - * This will look in the given directory for the file "snapshot_blob.bin". - * - InitializeExternalStartupDataFromFile(const char*) - * As above, but will directly use the given file name. - * - Call SetSnapshotDataBlob. - * This will read the blobs from the given data structure and will - * not perform any file IO. - */ - static void InitializeExternalStartupData(const char* directory_path); - static void InitializeExternalStartupDataFromFile(const char* snapshot_blob); - - /** - * Sets the v8::Platform to use. This should be invoked before V8 is - * initialized. - */ - static void InitializePlatform(Platform* platform); - - /** - * Clears all references to the v8::Platform. This should be invoked after - * V8 was disposed. - */ - static void ShutdownPlatform(); - -#ifdef V8_VIRTUAL_MEMORY_CAGE - // - // Virtual Memory Cage related API. - // - // This API is not yet stable and subject to changes in the future. - // - - /** - * Initializes the virtual memory cage for V8. - * - * This must be invoked after the platform was initialized but before V8 is - * initialized. The virtual memory cage is torn down during platform shutdown. - * Returns true on success, false otherwise. - */ - static bool InitializeVirtualMemoryCage(); - - /** - * Provides access to the data page allocator for the virtual memory cage. - * - * This allocator allocates pages inside the data cage part of the virtual - * memory cage in which data buffers such as ArrayBuffer backing stores must - * be allocated. Objects in this region should generally consists purely of - * data and not contain any pointers. It should be assumed that an attacker - * can corrupt data inside the cage, and so in particular the contents of - * pages returned by this allocator, arbitrarily and concurrently. - * - * The virtual memory cage must have been initialized before. - */ - static PageAllocator* GetVirtualMemoryCageDataPageAllocator(); -#endif - - /** - * Activate trap-based bounds checking for WebAssembly. - * - * \param use_v8_signal_handler Whether V8 should install its own signal - * handler or rely on the embedder's. - */ - static bool EnableWebAssemblyTrapHandler(bool use_v8_signal_handler); - -#if defined(V8_OS_WIN) - /** - * On Win64, by default V8 does not emit unwinding data for jitted code, - * which means the OS cannot walk the stack frames and the system Structured - * Exception Handling (SEH) cannot unwind through V8-generated code: - * https://code.google.com/p/v8/issues/detail?id=3598. - * - * This function allows embedders to register a custom exception handler for - * exceptions in V8-generated code. - */ - static void SetUnhandledExceptionCallback( - UnhandledExceptionCallback unhandled_exception_callback); -#endif - - /** - * Get statistics about the shared memory usage. - */ - static void GetSharedMemoryStatistics(SharedMemoryStatistics* statistics); - - private: - V8(); - - enum BuildConfigurationFeatures { - kPointerCompression = 1 << 0, - k31BitSmis = 1 << 1, - kHeapSandbox = 1 << 2, - kVirtualMemoryCage = 1 << 3, - }; - - /** - * Checks that the embedder build configuration is compatible with - * the V8 binary and if so initializes V8. - */ - static bool Initialize(int build_config); - - friend class Context; - template - friend class PersistentValueMapBase; -}; - -} // namespace v8 - -#endif // INCLUDE_V8_INITIALIZATION_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-inspector-protocol.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-inspector-protocol.h deleted file mode 100644 index a5ffb7d6954..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-inspector-protocol.h +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2016 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef V8_V8_INSPECTOR_PROTOCOL_H_ -#define V8_V8_INSPECTOR_PROTOCOL_H_ - -#include "inspector/Debugger.h" // NOLINT(build/include_directory) -#include "inspector/Runtime.h" // NOLINT(build/include_directory) -#include "inspector/Schema.h" // NOLINT(build/include_directory) -#include "v8-inspector.h" // NOLINT(build/include_directory) - -#endif // V8_V8_INSPECTOR_PROTOCOL_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-inspector.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-inspector.h deleted file mode 100644 index 74592fdf573..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-inspector.h +++ /dev/null @@ -1,335 +0,0 @@ -// Copyright 2016 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef V8_V8_INSPECTOR_H_ -#define V8_V8_INSPECTOR_H_ - -#include - -#include -#include - -#include "v8-isolate.h" // NOLINT(build/include_directory) -#include "v8-local-handle.h" // NOLINT(build/include_directory) - -namespace v8 { -class Context; -class Name; -class Object; -class StackTrace; -class Value; -} // namespace v8 - -namespace v8_inspector { - -namespace protocol { -namespace Debugger { -namespace API { -class SearchMatch; -} -} -namespace Runtime { -namespace API { -class RemoteObject; -class StackTrace; -class StackTraceId; -} -} -namespace Schema { -namespace API { -class Domain; -} -} -} // namespace protocol - -class V8_EXPORT StringView { - public: - StringView() : m_is8Bit(true), m_length(0), m_characters8(nullptr) {} - - StringView(const uint8_t* characters, size_t length) - : m_is8Bit(true), m_length(length), m_characters8(characters) {} - - StringView(const uint16_t* characters, size_t length) - : m_is8Bit(false), m_length(length), m_characters16(characters) {} - - bool is8Bit() const { return m_is8Bit; } - size_t length() const { return m_length; } - - // TODO(dgozman): add DCHECK(m_is8Bit) to accessors once platform can be used - // here. - const uint8_t* characters8() const { return m_characters8; } - const uint16_t* characters16() const { return m_characters16; } - - private: - bool m_is8Bit; - size_t m_length; - union { - const uint8_t* m_characters8; - const uint16_t* m_characters16; - }; -}; - -class V8_EXPORT StringBuffer { - public: - virtual ~StringBuffer() = default; - virtual StringView string() const = 0; - // This method copies contents. - static std::unique_ptr create(StringView); -}; - -class V8_EXPORT V8ContextInfo { - public: - V8ContextInfo(v8::Local context, int contextGroupId, - StringView humanReadableName) - : context(context), - contextGroupId(contextGroupId), - humanReadableName(humanReadableName), - hasMemoryOnConsole(false) {} - - v8::Local context; - // Each v8::Context is a part of a group. The group id must be non-zero. - int contextGroupId; - StringView humanReadableName; - StringView origin; - StringView auxData; - bool hasMemoryOnConsole; - - static int executionContextId(v8::Local context); - - // Disallow copying and allocating this one. - enum NotNullTagEnum { NotNullLiteral }; - void* operator new(size_t) = delete; - void* operator new(size_t, NotNullTagEnum, void*) = delete; - void* operator new(size_t, void*) = delete; - V8ContextInfo(const V8ContextInfo&) = delete; - V8ContextInfo& operator=(const V8ContextInfo&) = delete; -}; - -class V8_EXPORT V8StackTrace { - public: - virtual StringView firstNonEmptySourceURL() const = 0; - virtual bool isEmpty() const = 0; - virtual StringView topSourceURL() const = 0; - virtual int topLineNumber() const = 0; - virtual int topColumnNumber() const = 0; - virtual int topScriptId() const = 0; - V8_DEPRECATE_SOON("Use V8::StackTrace::topScriptId() instead.") - int topScriptIdAsInteger() const { return topScriptId(); } - virtual StringView topFunctionName() const = 0; - - virtual ~V8StackTrace() = default; - virtual std::unique_ptr - buildInspectorObject() const = 0; - virtual std::unique_ptr - buildInspectorObject(int maxAsyncDepth) const = 0; - virtual std::unique_ptr toString() const = 0; - - // Safe to pass between threads, drops async chain. - virtual std::unique_ptr clone() = 0; -}; - -class V8_EXPORT V8InspectorSession { - public: - virtual ~V8InspectorSession() = default; - - // Cross-context inspectable values (DOM nodes in different worlds, etc.). - class V8_EXPORT Inspectable { - public: - virtual v8::Local get(v8::Local) = 0; - virtual ~Inspectable() = default; - }; - class V8_EXPORT CommandLineAPIScope { - public: - virtual ~CommandLineAPIScope() = default; - }; - virtual void addInspectedObject(std::unique_ptr) = 0; - - // Dispatching protocol messages. - static bool canDispatchMethod(StringView method); - virtual void dispatchProtocolMessage(StringView message) = 0; - virtual std::vector state() = 0; - virtual std::vector> - supportedDomains() = 0; - - virtual std::unique_ptr - initializeCommandLineAPIScope(int executionContextId) = 0; - - // Debugger actions. - virtual void schedulePauseOnNextStatement(StringView breakReason, - StringView breakDetails) = 0; - virtual void cancelPauseOnNextStatement() = 0; - virtual void breakProgram(StringView breakReason, - StringView breakDetails) = 0; - virtual void setSkipAllPauses(bool) = 0; - virtual void resume(bool setTerminateOnResume = false) = 0; - virtual void stepOver() = 0; - virtual std::vector> - searchInTextByLines(StringView text, StringView query, bool caseSensitive, - bool isRegex) = 0; - - // Remote objects. - virtual std::unique_ptr wrapObject( - v8::Local, v8::Local, StringView groupName, - bool generatePreview) = 0; - - virtual bool unwrapObject(std::unique_ptr* error, - StringView objectId, v8::Local*, - v8::Local*, - std::unique_ptr* objectGroup) = 0; - virtual void releaseObjectGroup(StringView) = 0; - virtual void triggerPreciseCoverageDeltaUpdate(StringView occasion) = 0; -}; - -class V8_EXPORT V8InspectorClient { - public: - virtual ~V8InspectorClient() = default; - - virtual void runMessageLoopOnPause(int contextGroupId) {} - virtual void quitMessageLoopOnPause() {} - virtual void runIfWaitingForDebugger(int contextGroupId) {} - - virtual void muteMetrics(int contextGroupId) {} - virtual void unmuteMetrics(int contextGroupId) {} - - virtual void beginUserGesture() {} - virtual void endUserGesture() {} - - virtual std::unique_ptr valueSubtype(v8::Local) { - return nullptr; - } - virtual std::unique_ptr descriptionForValueSubtype( - v8::Local, v8::Local) { - return nullptr; - } - virtual bool isInspectableHeapObject(v8::Local) { return true; } - - virtual v8::Local ensureDefaultContextInGroup( - int contextGroupId) { - return v8::Local(); - } - virtual void beginEnsureAllContextsInGroup(int contextGroupId) {} - virtual void endEnsureAllContextsInGroup(int contextGroupId) {} - - virtual void installAdditionalCommandLineAPI(v8::Local, - v8::Local) {} - virtual void consoleAPIMessage(int contextGroupId, - v8::Isolate::MessageErrorLevel level, - const StringView& message, - const StringView& url, unsigned lineNumber, - unsigned columnNumber, V8StackTrace*) {} - virtual v8::MaybeLocal memoryInfo(v8::Isolate*, - v8::Local) { - return v8::MaybeLocal(); - } - - virtual void consoleTime(const StringView& title) {} - virtual void consoleTimeEnd(const StringView& title) {} - virtual void consoleTimeStamp(const StringView& title) {} - virtual void consoleClear(int contextGroupId) {} - virtual double currentTimeMS() { return 0; } - typedef void (*TimerCallback)(void*); - virtual void startRepeatingTimer(double, TimerCallback, void* data) {} - virtual void cancelTimer(void* data) {} - - // TODO(dgozman): this was added to support service worker shadow page. We - // should not connect at all. - virtual bool canExecuteScripts(int contextGroupId) { return true; } - - virtual void maxAsyncCallStackDepthChanged(int depth) {} - - virtual std::unique_ptr resourceNameToUrl( - const StringView& resourceName) { - return nullptr; - } - - // The caller would defer to generating a random 64 bit integer if - // this method returns 0. - virtual int64_t generateUniqueId() { return 0; } -}; - -// These stack trace ids are intended to be passed between debuggers and be -// resolved later. This allows to track cross-debugger calls and step between -// them if a single client connects to multiple debuggers. -struct V8_EXPORT V8StackTraceId { - uintptr_t id; - std::pair debugger_id; - bool should_pause = false; - - V8StackTraceId(); - V8StackTraceId(const V8StackTraceId&) = default; - V8StackTraceId(uintptr_t id, const std::pair debugger_id); - V8StackTraceId(uintptr_t id, const std::pair debugger_id, - bool should_pause); - explicit V8StackTraceId(StringView); - V8StackTraceId& operator=(const V8StackTraceId&) = default; - V8StackTraceId& operator=(V8StackTraceId&&) noexcept = default; - ~V8StackTraceId() = default; - - bool IsInvalid() const; - std::unique_ptr ToString(); -}; - -class V8_EXPORT V8Inspector { - public: - static std::unique_ptr create(v8::Isolate*, V8InspectorClient*); - virtual ~V8Inspector() = default; - - // Contexts instrumentation. - virtual void contextCreated(const V8ContextInfo&) = 0; - virtual void contextDestroyed(v8::Local) = 0; - virtual void resetContextGroup(int contextGroupId) = 0; - virtual v8::MaybeLocal contextById(int contextId) = 0; - - // Various instrumentation. - virtual void idleStarted() = 0; - virtual void idleFinished() = 0; - - // Async stack traces instrumentation. - virtual void asyncTaskScheduled(StringView taskName, void* task, - bool recurring) = 0; - virtual void asyncTaskCanceled(void* task) = 0; - virtual void asyncTaskStarted(void* task) = 0; - virtual void asyncTaskFinished(void* task) = 0; - virtual void allAsyncTasksCanceled() = 0; - - virtual V8StackTraceId storeCurrentStackTrace(StringView description) = 0; - virtual void externalAsyncTaskStarted(const V8StackTraceId& parent) = 0; - virtual void externalAsyncTaskFinished(const V8StackTraceId& parent) = 0; - - // Exceptions instrumentation. - virtual unsigned exceptionThrown(v8::Local, StringView message, - v8::Local exception, - StringView detailedMessage, StringView url, - unsigned lineNumber, unsigned columnNumber, - std::unique_ptr, - int scriptId) = 0; - virtual void exceptionRevoked(v8::Local, unsigned exceptionId, - StringView message) = 0; - virtual bool associateExceptionData(v8::Local, - v8::Local exception, - v8::Local key, - v8::Local value) = 0; - - // Connection. - class V8_EXPORT Channel { - public: - virtual ~Channel() = default; - virtual void sendResponse(int callId, - std::unique_ptr message) = 0; - virtual void sendNotification(std::unique_ptr message) = 0; - virtual void flushProtocolNotifications() = 0; - }; - virtual std::unique_ptr connect(int contextGroupId, - Channel*, - StringView state) = 0; - - // API methods. - virtual std::unique_ptr createStackTrace( - v8::Local) = 0; - virtual std::unique_ptr captureStackTrace(bool fullStack) = 0; -}; - -} // namespace v8_inspector - -#endif // V8_V8_INSPECTOR_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-internal.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-internal.h deleted file mode 100644 index 6516a16219f..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-internal.h +++ /dev/null @@ -1,578 +0,0 @@ -// Copyright 2018 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_INTERNAL_H_ -#define INCLUDE_V8_INTERNAL_H_ - -#include -#include -#include -#include - -#include "v8-version.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Array; -class Context; -class Data; -class Isolate; -template -class Local; - -namespace internal { - -class Isolate; - -typedef uintptr_t Address; -static const Address kNullAddress = 0; - -/** - * Configuration of tagging scheme. - */ -const int kApiSystemPointerSize = sizeof(void*); -const int kApiDoubleSize = sizeof(double); -const int kApiInt32Size = sizeof(int32_t); -const int kApiInt64Size = sizeof(int64_t); -const int kApiSizetSize = sizeof(size_t); - -// Tag information for HeapObject. -const int kHeapObjectTag = 1; -const int kWeakHeapObjectTag = 3; -const int kHeapObjectTagSize = 2; -const intptr_t kHeapObjectTagMask = (1 << kHeapObjectTagSize) - 1; - -// Tag information for fowarding pointers stored in object headers. -// 0b00 at the lowest 2 bits in the header indicates that the map word is a -// forwarding pointer. -const int kForwardingTag = 0; -const int kForwardingTagSize = 2; -const intptr_t kForwardingTagMask = (1 << kForwardingTagSize) - 1; - -// Tag information for Smi. -const int kSmiTag = 0; -const int kSmiTagSize = 1; -const intptr_t kSmiTagMask = (1 << kSmiTagSize) - 1; - -template -struct SmiTagging; - -constexpr intptr_t kIntptrAllBitsSet = intptr_t{-1}; -constexpr uintptr_t kUintptrAllBitsSet = - static_cast(kIntptrAllBitsSet); - -// Smi constants for systems where tagged pointer is a 32-bit value. -template <> -struct SmiTagging<4> { - enum { kSmiShiftSize = 0, kSmiValueSize = 31 }; - - static constexpr intptr_t kSmiMinValue = - static_cast(kUintptrAllBitsSet << (kSmiValueSize - 1)); - static constexpr intptr_t kSmiMaxValue = -(kSmiMinValue + 1); - - V8_INLINE static int SmiToInt(const internal::Address value) { - int shift_bits = kSmiTagSize + kSmiShiftSize; - // Truncate and shift down (requires >> to be sign extending). - return static_cast(static_cast(value)) >> shift_bits; - } - V8_INLINE static constexpr bool IsValidSmi(intptr_t value) { - // Is value in range [kSmiMinValue, kSmiMaxValue]. - // Use unsigned operations in order to avoid undefined behaviour in case of - // signed integer overflow. - return (static_cast(value) - - static_cast(kSmiMinValue)) <= - (static_cast(kSmiMaxValue) - - static_cast(kSmiMinValue)); - } -}; - -// Smi constants for systems where tagged pointer is a 64-bit value. -template <> -struct SmiTagging<8> { - enum { kSmiShiftSize = 31, kSmiValueSize = 32 }; - - static constexpr intptr_t kSmiMinValue = - static_cast(kUintptrAllBitsSet << (kSmiValueSize - 1)); - static constexpr intptr_t kSmiMaxValue = -(kSmiMinValue + 1); - - V8_INLINE static int SmiToInt(const internal::Address value) { - int shift_bits = kSmiTagSize + kSmiShiftSize; - // Shift down and throw away top 32 bits. - return static_cast(static_cast(value) >> shift_bits); - } - V8_INLINE static constexpr bool IsValidSmi(intptr_t value) { - // To be representable as a long smi, the value must be a 32-bit integer. - return (value == static_cast(value)); - } -}; - -#ifdef V8_COMPRESS_POINTERS -static_assert( - kApiSystemPointerSize == kApiInt64Size, - "Pointer compression can be enabled only for 64-bit architectures"); -const int kApiTaggedSize = kApiInt32Size; -#else -const int kApiTaggedSize = kApiSystemPointerSize; -#endif - -constexpr bool PointerCompressionIsEnabled() { - return kApiTaggedSize != kApiSystemPointerSize; -} - -constexpr bool HeapSandboxIsEnabled() { -#ifdef V8_HEAP_SANDBOX - return true; -#else - return false; -#endif -} - -using ExternalPointer_t = Address; - -// If the heap sandbox is enabled, these tag values will be ORed with the -// external pointers in the external pointer table to prevent use of pointers of -// the wrong type. When a pointer is loaded, it is ANDed with the inverse of the -// expected type's tag. The tags are constructed in a way that guarantees that a -// failed type check will result in one or more of the top bits of the pointer -// to be set, rendering the pointer inacessible. This construction allows -// performing the type check and removing GC marking bits from the pointer at -// the same time. -enum ExternalPointerTag : uint64_t { - kExternalPointerNullTag = 0x0000000000000000, - kExternalStringResourceTag = 0x00ff000000000000, // 0b000000011111111 - kExternalStringResourceDataTag = 0x017f000000000000, // 0b000000101111111 - kForeignForeignAddressTag = 0x01bf000000000000, // 0b000000110111111 - kNativeContextMicrotaskQueueTag = 0x01df000000000000, // 0b000000111011111 - kEmbedderDataSlotPayloadTag = 0x01ef000000000000, // 0b000000111101111 - kCodeEntryPointTag = 0x01f7000000000000, // 0b000000111110111 -}; - -constexpr uint64_t kExternalPointerTagMask = 0xffff000000000000; - -#ifdef V8_31BIT_SMIS_ON_64BIT_ARCH -using PlatformSmiTagging = SmiTagging; -#else -using PlatformSmiTagging = SmiTagging; -#endif - -// TODO(ishell): Consinder adding kSmiShiftBits = kSmiShiftSize + kSmiTagSize -// since it's used much more often than the inividual constants. -const int kSmiShiftSize = PlatformSmiTagging::kSmiShiftSize; -const int kSmiValueSize = PlatformSmiTagging::kSmiValueSize; -const int kSmiMinValue = static_cast(PlatformSmiTagging::kSmiMinValue); -const int kSmiMaxValue = static_cast(PlatformSmiTagging::kSmiMaxValue); -constexpr bool SmiValuesAre31Bits() { return kSmiValueSize == 31; } -constexpr bool SmiValuesAre32Bits() { return kSmiValueSize == 32; } - -V8_INLINE static constexpr internal::Address IntToSmi(int value) { - return (static_cast
(value) << (kSmiTagSize + kSmiShiftSize)) | - kSmiTag; -} - -// Converts encoded external pointer to address. -V8_EXPORT Address DecodeExternalPointerImpl(const Isolate* isolate, - ExternalPointer_t pointer, - ExternalPointerTag tag); - -// {obj} must be the raw tagged pointer representation of a HeapObject -// that's guaranteed to never be in ReadOnlySpace. -V8_EXPORT internal::Isolate* IsolateFromNeverReadOnlySpaceObject(Address obj); - -// Returns if we need to throw when an error occurs. This infers the language -// mode based on the current context and the closure. This returns true if the -// language mode is strict. -V8_EXPORT bool ShouldThrowOnError(v8::internal::Isolate* isolate); - -V8_EXPORT bool CanHaveInternalField(int instance_type); - -/** - * This class exports constants and functionality from within v8 that - * is necessary to implement inline functions in the v8 api. Don't - * depend on functions and constants defined here. - */ -class Internals { -#ifdef V8_MAP_PACKING - V8_INLINE static constexpr internal::Address UnpackMapWord( - internal::Address mapword) { - // TODO(wenyuzhao): Clear header metadata. - return mapword ^ kMapWordXorMask; - } -#endif - - public: - // These values match non-compiler-dependent values defined within - // the implementation of v8. - static const int kHeapObjectMapOffset = 0; - static const int kMapInstanceTypeOffset = 1 * kApiTaggedSize + kApiInt32Size; - static const int kStringResourceOffset = - 1 * kApiTaggedSize + 2 * kApiInt32Size; - - static const int kOddballKindOffset = 4 * kApiTaggedSize + kApiDoubleSize; - static const int kJSObjectHeaderSize = 3 * kApiTaggedSize; - static const int kFixedArrayHeaderSize = 2 * kApiTaggedSize; - static const int kEmbedderDataArrayHeaderSize = 2 * kApiTaggedSize; - static const int kEmbedderDataSlotSize = kApiSystemPointerSize; -#ifdef V8_HEAP_SANDBOX - static const int kEmbedderDataSlotRawPayloadOffset = kApiTaggedSize; -#endif - static const int kNativeContextEmbedderDataOffset = 6 * kApiTaggedSize; - static const int kFullStringRepresentationMask = 0x0f; - static const int kStringEncodingMask = 0x8; - static const int kExternalTwoByteRepresentationTag = 0x02; - static const int kExternalOneByteRepresentationTag = 0x0a; - - static const uint32_t kNumIsolateDataSlots = 4; - - // IsolateData layout guarantees. - static const int kIsolateEmbedderDataOffset = 0; - static const int kIsolateFastCCallCallerFpOffset = - kNumIsolateDataSlots * kApiSystemPointerSize; - static const int kIsolateFastCCallCallerPcOffset = - kIsolateFastCCallCallerFpOffset + kApiSystemPointerSize; - static const int kIsolateFastApiCallTargetOffset = - kIsolateFastCCallCallerPcOffset + kApiSystemPointerSize; - static const int kIsolateCageBaseOffset = - kIsolateFastApiCallTargetOffset + kApiSystemPointerSize; - static const int kIsolateLongTaskStatsCounterOffset = - kIsolateCageBaseOffset + kApiSystemPointerSize; - static const int kIsolateStackGuardOffset = - kIsolateLongTaskStatsCounterOffset + kApiSizetSize; - static const int kIsolateRootsOffset = - kIsolateStackGuardOffset + 7 * kApiSystemPointerSize; - - static const int kExternalPointerTableBufferOffset = 0; - static const int kExternalPointerTableLengthOffset = - kExternalPointerTableBufferOffset + kApiSystemPointerSize; - static const int kExternalPointerTableCapacityOffset = - kExternalPointerTableLengthOffset + kApiInt32Size; - - static const int kUndefinedValueRootIndex = 4; - static const int kTheHoleValueRootIndex = 5; - static const int kNullValueRootIndex = 6; - static const int kTrueValueRootIndex = 7; - static const int kFalseValueRootIndex = 8; - static const int kEmptyStringRootIndex = 9; - - static const int kNodeClassIdOffset = 1 * kApiSystemPointerSize; - static const int kNodeFlagsOffset = 1 * kApiSystemPointerSize + 3; - static const int kNodeStateMask = 0x7; - static const int kNodeStateIsWeakValue = 2; - static const int kNodeStateIsPendingValue = 3; - - static const int kFirstNonstringType = 0x40; - static const int kOddballType = 0x43; - static const int kForeignType = 0x46; - static const int kJSSpecialApiObjectType = 0x410; - static const int kJSObjectType = 0x421; - static const int kFirstJSApiObjectType = 0x422; - static const int kLastJSApiObjectType = 0x80A; - - static const int kUndefinedOddballKind = 5; - static const int kNullOddballKind = 3; - - // Constants used by PropertyCallbackInfo to check if we should throw when an - // error occurs. - static const int kThrowOnError = 0; - static const int kDontThrow = 1; - static const int kInferShouldThrowMode = 2; - - // Soft limit for AdjustAmountofExternalAllocatedMemory. Trigger an - // incremental GC once the external memory reaches this limit. - static constexpr int kExternalAllocationSoftLimit = 64 * 1024 * 1024; - -#ifdef V8_MAP_PACKING - static const uintptr_t kMapWordMetadataMask = 0xffffULL << 48; - // The lowest two bits of mapwords are always `0b10` - static const uintptr_t kMapWordSignature = 0b10; - // XORing a (non-compressed) map with this mask ensures that the two - // low-order bits are 0b10. The 0 at the end makes this look like a Smi, - // although real Smis have all lower 32 bits unset. We only rely on these - // values passing as Smis in very few places. - static const int kMapWordXorMask = 0b11; -#endif - - V8_EXPORT static void CheckInitializedImpl(v8::Isolate* isolate); - V8_INLINE static void CheckInitialized(v8::Isolate* isolate) { -#ifdef V8_ENABLE_CHECKS - CheckInitializedImpl(isolate); -#endif - } - - V8_INLINE static bool HasHeapObjectTag(const internal::Address value) { - return (value & kHeapObjectTagMask) == static_cast
(kHeapObjectTag); - } - - V8_INLINE static int SmiValue(const internal::Address value) { - return PlatformSmiTagging::SmiToInt(value); - } - - V8_INLINE static constexpr internal::Address IntToSmi(int value) { - return internal::IntToSmi(value); - } - - V8_INLINE static constexpr bool IsValidSmi(intptr_t value) { - return PlatformSmiTagging::IsValidSmi(value); - } - - V8_INLINE static int GetInstanceType(const internal::Address obj) { - typedef internal::Address A; - A map = ReadTaggedPointerField(obj, kHeapObjectMapOffset); -#ifdef V8_MAP_PACKING - map = UnpackMapWord(map); -#endif - return ReadRawField(map, kMapInstanceTypeOffset); - } - - V8_INLINE static int GetOddballKind(const internal::Address obj) { - return SmiValue(ReadTaggedSignedField(obj, kOddballKindOffset)); - } - - V8_INLINE static bool IsExternalTwoByteString(int instance_type) { - int representation = (instance_type & kFullStringRepresentationMask); - return representation == kExternalTwoByteRepresentationTag; - } - - V8_INLINE static uint8_t GetNodeFlag(internal::Address* obj, int shift) { - uint8_t* addr = reinterpret_cast(obj) + kNodeFlagsOffset; - return *addr & static_cast(1U << shift); - } - - V8_INLINE static void UpdateNodeFlag(internal::Address* obj, bool value, - int shift) { - uint8_t* addr = reinterpret_cast(obj) + kNodeFlagsOffset; - uint8_t mask = static_cast(1U << shift); - *addr = static_cast((*addr & ~mask) | (value << shift)); - } - - V8_INLINE static uint8_t GetNodeState(internal::Address* obj) { - uint8_t* addr = reinterpret_cast(obj) + kNodeFlagsOffset; - return *addr & kNodeStateMask; - } - - V8_INLINE static void UpdateNodeState(internal::Address* obj, uint8_t value) { - uint8_t* addr = reinterpret_cast(obj) + kNodeFlagsOffset; - *addr = static_cast((*addr & ~kNodeStateMask) | value); - } - - V8_INLINE static void SetEmbedderData(v8::Isolate* isolate, uint32_t slot, - void* data) { - internal::Address addr = reinterpret_cast(isolate) + - kIsolateEmbedderDataOffset + - slot * kApiSystemPointerSize; - *reinterpret_cast(addr) = data; - } - - V8_INLINE static void* GetEmbedderData(const v8::Isolate* isolate, - uint32_t slot) { - internal::Address addr = reinterpret_cast(isolate) + - kIsolateEmbedderDataOffset + - slot * kApiSystemPointerSize; - return *reinterpret_cast(addr); - } - - V8_INLINE static void IncrementLongTasksStatsCounter(v8::Isolate* isolate) { - internal::Address addr = reinterpret_cast(isolate) + - kIsolateLongTaskStatsCounterOffset; - ++(*reinterpret_cast(addr)); - } - - V8_INLINE static internal::Address* GetRoot(v8::Isolate* isolate, int index) { - internal::Address addr = reinterpret_cast(isolate) + - kIsolateRootsOffset + - index * kApiSystemPointerSize; - return reinterpret_cast(addr); - } - - template - V8_INLINE static T ReadRawField(internal::Address heap_object_ptr, - int offset) { - internal::Address addr = heap_object_ptr + offset - kHeapObjectTag; -#ifdef V8_COMPRESS_POINTERS - if (sizeof(T) > kApiTaggedSize) { - // TODO(ishell, v8:8875): When pointer compression is enabled 8-byte size - // fields (external pointers, doubles and BigInt data) are only - // kTaggedSize aligned so we have to use unaligned pointer friendly way of - // accessing them in order to avoid undefined behavior in C++ code. - T r; - memcpy(&r, reinterpret_cast(addr), sizeof(T)); - return r; - } -#endif - return *reinterpret_cast(addr); - } - - V8_INLINE static internal::Address ReadTaggedPointerField( - internal::Address heap_object_ptr, int offset) { -#ifdef V8_COMPRESS_POINTERS - uint32_t value = ReadRawField(heap_object_ptr, offset); - internal::Address base = - GetPtrComprCageBaseFromOnHeapAddress(heap_object_ptr); - return base + static_cast(static_cast(value)); -#else - return ReadRawField(heap_object_ptr, offset); -#endif - } - - V8_INLINE static internal::Address ReadTaggedSignedField( - internal::Address heap_object_ptr, int offset) { -#ifdef V8_COMPRESS_POINTERS - uint32_t value = ReadRawField(heap_object_ptr, offset); - return static_cast(static_cast(value)); -#else - return ReadRawField(heap_object_ptr, offset); -#endif - } - - V8_INLINE static internal::Isolate* GetIsolateForHeapSandbox( - internal::Address obj) { -#ifdef V8_HEAP_SANDBOX - return internal::IsolateFromNeverReadOnlySpaceObject(obj); -#else - // Not used in non-sandbox mode. - return nullptr; -#endif - } - - V8_INLINE static Address DecodeExternalPointer( - const Isolate* isolate, ExternalPointer_t encoded_pointer, - ExternalPointerTag tag) { -#ifdef V8_HEAP_SANDBOX - return internal::DecodeExternalPointerImpl(isolate, encoded_pointer, tag); -#else - return encoded_pointer; -#endif - } - - V8_INLINE static internal::Address ReadExternalPointerField( - internal::Isolate* isolate, internal::Address heap_object_ptr, int offset, - ExternalPointerTag tag) { -#ifdef V8_HEAP_SANDBOX - internal::ExternalPointer_t encoded_value = - ReadRawField(heap_object_ptr, offset); - // We currently have to treat zero as nullptr in embedder slots. - return encoded_value ? DecodeExternalPointer(isolate, encoded_value, tag) - : 0; -#else - return ReadRawField
(heap_object_ptr, offset); -#endif - } - -#ifdef V8_COMPRESS_POINTERS - // See v8:7703 or src/ptr-compr.* for details about pointer compression. - static constexpr size_t kPtrComprCageReservationSize = size_t{1} << 32; - static constexpr size_t kPtrComprCageBaseAlignment = size_t{1} << 32; - - V8_INLINE static internal::Address GetPtrComprCageBaseFromOnHeapAddress( - internal::Address addr) { - return addr & -static_cast(kPtrComprCageBaseAlignment); - } - - V8_INLINE static internal::Address DecompressTaggedAnyField( - internal::Address heap_object_ptr, uint32_t value) { - internal::Address base = - GetPtrComprCageBaseFromOnHeapAddress(heap_object_ptr); - return base + static_cast(static_cast(value)); - } - -#endif // V8_COMPRESS_POINTERS -}; - -constexpr bool VirtualMemoryCageIsEnabled() { -#ifdef V8_VIRTUAL_MEMORY_CAGE - return true; -#else - return false; -#endif -} - -#ifdef V8_VIRTUAL_MEMORY_CAGE -// Size of the pointer compression cage located at the start of the virtual -// memory cage. -constexpr size_t kVirtualMemoryCagePointerCageSize = - Internals::kPtrComprCageReservationSize; - -// Size of the virtual memory cage, excluding the guard regions surrounding it. -constexpr size_t kVirtualMemoryCageSize = size_t{1} << 40; // 1 TB - -static_assert(kVirtualMemoryCageSize > kVirtualMemoryCagePointerCageSize, - "The virtual memory cage must be larger than the pointer " - "compression cage contained within it."); - -// Required alignment of the virtual memory cage. For simplicity, we require the -// size of the guard regions to be a multiple of this, so that this specifies -// the alignment of the cage including and excluding surrounding guard regions. -// The alignment requirement is due to the pointer compression cage being -// located at the start of the virtual memory cage. -constexpr size_t kVirtualMemoryCageAlignment = - Internals::kPtrComprCageBaseAlignment; - -// Size of the guard regions surrounding the virtual memory cage. This assumes a -// worst-case scenario of a 32-bit unsigned index being used to access an array -// of 64-bit values. -constexpr size_t kVirtualMemoryCageGuardRegionSize = size_t{32} << 30; // 32 GB - -static_assert((kVirtualMemoryCageGuardRegionSize % - kVirtualMemoryCageAlignment) == 0, - "The size of the virtual memory cage guard region must be a " - "multiple of its required alignment."); - -// Minimum possible size of the virtual memory cage, excluding the guard regions -// surrounding it. Used by unit tests. -constexpr size_t kVirtualMemoryCageMinimumSize = - 2 * kVirtualMemoryCagePointerCageSize; - -// For now, even if the virtual memory cage is enabled, we still allow backing -// stores to be allocated outside of it as fallback. This will simplify the -// initial rollout. However, if the heap sandbox is also enabled, we already use -// the "enforcing mode" of the virtual memory cage. This is useful for testing. -#ifdef V8_HEAP_SANDBOX -constexpr bool kAllowBackingStoresOutsideDataCage = false; -#else -constexpr bool kAllowBackingStoresOutsideDataCage = true; -#endif // V8_HEAP_SANDBOX - -#endif // V8_VIRTUAL_MEMORY_CAGE - -// Only perform cast check for types derived from v8::Data since -// other types do not implement the Cast method. -template -struct CastCheck { - template - static void Perform(T* data); -}; - -template <> -template -void CastCheck::Perform(T* data) { - T::Cast(data); -} - -template <> -template -void CastCheck::Perform(T* data) {} - -template -V8_INLINE void PerformCastCheck(T* data) { - CastCheck::value && - !std::is_same>::value>::Perform(data); -} - -// A base class for backing stores, which is needed due to vagaries of -// how static casts work with std::shared_ptr. -class BackingStoreBase {}; - -} // namespace internal - -V8_EXPORT bool CopyAndConvertArrayToCppBufferInt32(Local src, - int32_t* dst, - uint32_t max_length); - -V8_EXPORT bool CopyAndConvertArrayToCppBufferFloat64(Local src, - double* dst, - uint32_t max_length); - -} // namespace v8 - -#endif // INCLUDE_V8_INTERNAL_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-isolate.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-isolate.h deleted file mode 100644 index c018859c020..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-isolate.h +++ /dev/null @@ -1,1669 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_ISOLATE_H_ -#define INCLUDE_V8_ISOLATE_H_ - -#include -#include - -#include -#include -#include - -#include "cppgc/common.h" -#include "v8-array-buffer.h" // NOLINT(build/include_directory) -#include "v8-callbacks.h" // NOLINT(build/include_directory) -#include "v8-data.h" // NOLINT(build/include_directory) -#include "v8-debug.h" // NOLINT(build/include_directory) -#include "v8-embedder-heap.h" // NOLINT(build/include_directory) -#include "v8-function-callback.h" // NOLINT(build/include_directory) -#include "v8-internal.h" // NOLINT(build/include_directory) -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8-microtask.h" // NOLINT(build/include_directory) -#include "v8-persistent-handle.h" // NOLINT(build/include_directory) -#include "v8-primitive.h" // NOLINT(build/include_directory) -#include "v8-statistics.h" // NOLINT(build/include_directory) -#include "v8-unwinder.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class CppHeap; -class HeapProfiler; -class MicrotaskQueue; -class StartupData; -class ScriptOrModule; -class SharedArrayBuffer; - -namespace internal { -class MicrotaskQueue; -class ThreadLocalTop; -} // namespace internal - -namespace metrics { -class Recorder; -} // namespace metrics - -/** - * A set of constraints that specifies the limits of the runtime's memory use. - * You must set the heap size before initializing the VM - the size cannot be - * adjusted after the VM is initialized. - * - * If you are using threads then you should hold the V8::Locker lock while - * setting the stack limit and you must set a non-default stack limit separately - * for each thread. - * - * The arguments for set_max_semi_space_size, set_max_old_space_size, - * set_max_executable_size, set_code_range_size specify limits in MB. - * - * The argument for set_max_semi_space_size_in_kb is in KB. - */ -class V8_EXPORT ResourceConstraints { - public: - /** - * Configures the constraints with reasonable default values based on the - * provided heap size limit. The heap size includes both the young and - * the old generation. - * - * \param initial_heap_size_in_bytes The initial heap size or zero. - * By default V8 starts with a small heap and dynamically grows it to - * match the set of live objects. This may lead to ineffective - * garbage collections at startup if the live set is large. - * Setting the initial heap size avoids such garbage collections. - * Note that this does not affect young generation garbage collections. - * - * \param maximum_heap_size_in_bytes The hard limit for the heap size. - * When the heap size approaches this limit, V8 will perform series of - * garbage collections and invoke the NearHeapLimitCallback. If the garbage - * collections do not help and the callback does not increase the limit, - * then V8 will crash with V8::FatalProcessOutOfMemory. - */ - void ConfigureDefaultsFromHeapSize(size_t initial_heap_size_in_bytes, - size_t maximum_heap_size_in_bytes); - - /** - * Configures the constraints with reasonable default values based on the - * capabilities of the current device the VM is running on. - * - * \param physical_memory The total amount of physical memory on the current - * device, in bytes. - * \param virtual_memory_limit The amount of virtual memory on the current - * device, in bytes, or zero, if there is no limit. - */ - void ConfigureDefaults(uint64_t physical_memory, - uint64_t virtual_memory_limit); - - /** - * The address beyond which the VM's stack may not grow. - */ - uint32_t* stack_limit() const { return stack_limit_; } - void set_stack_limit(uint32_t* value) { stack_limit_ = value; } - - /** - * The amount of virtual memory reserved for generated code. This is relevant - * for 64-bit architectures that rely on code range for calls in code. - * - * When V8_COMPRESS_POINTERS_IN_SHARED_CAGE is defined, there is a shared - * process-wide code range that is lazily initialized. This value is used to - * configure that shared code range when the first Isolate is - * created. Subsequent Isolates ignore this value. - */ - size_t code_range_size_in_bytes() const { return code_range_size_; } - void set_code_range_size_in_bytes(size_t limit) { code_range_size_ = limit; } - - /** - * The maximum size of the old generation. - * When the old generation approaches this limit, V8 will perform series of - * garbage collections and invoke the NearHeapLimitCallback. - * If the garbage collections do not help and the callback does not - * increase the limit, then V8 will crash with V8::FatalProcessOutOfMemory. - */ - size_t max_old_generation_size_in_bytes() const { - return max_old_generation_size_; - } - void set_max_old_generation_size_in_bytes(size_t limit) { - max_old_generation_size_ = limit; - } - - /** - * The maximum size of the young generation, which consists of two semi-spaces - * and a large object space. This affects frequency of Scavenge garbage - * collections and should be typically much smaller that the old generation. - */ - size_t max_young_generation_size_in_bytes() const { - return max_young_generation_size_; - } - void set_max_young_generation_size_in_bytes(size_t limit) { - max_young_generation_size_ = limit; - } - - size_t initial_old_generation_size_in_bytes() const { - return initial_old_generation_size_; - } - void set_initial_old_generation_size_in_bytes(size_t initial_size) { - initial_old_generation_size_ = initial_size; - } - - size_t initial_young_generation_size_in_bytes() const { - return initial_young_generation_size_; - } - void set_initial_young_generation_size_in_bytes(size_t initial_size) { - initial_young_generation_size_ = initial_size; - } - - private: - static constexpr size_t kMB = 1048576u; - size_t code_range_size_ = 0; - size_t max_old_generation_size_ = 0; - size_t max_young_generation_size_ = 0; - size_t initial_old_generation_size_ = 0; - size_t initial_young_generation_size_ = 0; - uint32_t* stack_limit_ = nullptr; -}; - -/** - * Option flags passed to the SetRAILMode function. - * See documentation https://developers.google.com/web/tools/chrome-devtools/ - * profile/evaluate-performance/rail - */ -enum RAILMode : unsigned { - // Response performance mode: In this mode very low virtual machine latency - // is provided. V8 will try to avoid JavaScript execution interruptions. - // Throughput may be throttled. - PERFORMANCE_RESPONSE, - // Animation performance mode: In this mode low virtual machine latency is - // provided. V8 will try to avoid as many JavaScript execution interruptions - // as possible. Throughput may be throttled. This is the default mode. - PERFORMANCE_ANIMATION, - // Idle performance mode: The embedder is idle. V8 can complete deferred work - // in this mode. - PERFORMANCE_IDLE, - // Load performance mode: In this mode high throughput is provided. V8 may - // turn off latency optimizations. - PERFORMANCE_LOAD -}; - -/** - * Memory pressure level for the MemoryPressureNotification. - * kNone hints V8 that there is no memory pressure. - * kModerate hints V8 to speed up incremental garbage collection at the cost of - * of higher latency due to garbage collection pauses. - * kCritical hints V8 to free memory as soon as possible. Garbage collection - * pauses at this level will be large. - */ -enum class MemoryPressureLevel { kNone, kModerate, kCritical }; - -/** - * Isolate represents an isolated instance of the V8 engine. V8 isolates have - * completely separate states. Objects from one isolate must not be used in - * other isolates. The embedder can create multiple isolates and use them in - * parallel in multiple threads. An isolate can be entered by at most one - * thread at any given time. The Locker/Unlocker API must be used to - * synchronize. - */ -class V8_EXPORT Isolate { - public: - /** - * Initial configuration parameters for a new Isolate. - */ - struct V8_EXPORT CreateParams { - CreateParams(); - ~CreateParams(); - - /** - * Allows the host application to provide the address of a function that is - * notified each time code is added, moved or removed. - */ - JitCodeEventHandler code_event_handler = nullptr; - - /** - * ResourceConstraints to use for the new Isolate. - */ - ResourceConstraints constraints; - - /** - * Explicitly specify a startup snapshot blob. The embedder owns the blob. - */ - StartupData* snapshot_blob = nullptr; - - /** - * Enables the host application to provide a mechanism for recording - * statistics counters. - */ - CounterLookupCallback counter_lookup_callback = nullptr; - - /** - * Enables the host application to provide a mechanism for recording - * histograms. The CreateHistogram function returns a - * histogram which will later be passed to the AddHistogramSample - * function. - */ - CreateHistogramCallback create_histogram_callback = nullptr; - AddHistogramSampleCallback add_histogram_sample_callback = nullptr; - - /** - * The ArrayBuffer::Allocator to use for allocating and freeing the backing - * store of ArrayBuffers. - * - * If the shared_ptr version is used, the Isolate instance and every - * |BackingStore| allocated using this allocator hold a std::shared_ptr - * to the allocator, in order to facilitate lifetime - * management for the allocator instance. - */ - ArrayBuffer::Allocator* array_buffer_allocator = nullptr; - std::shared_ptr array_buffer_allocator_shared; - - /** - * Specifies an optional nullptr-terminated array of raw addresses in the - * embedder that V8 can match against during serialization and use for - * deserialization. This array and its content must stay valid for the - * entire lifetime of the isolate. - */ - const intptr_t* external_references = nullptr; - - /** - * Whether calling Atomics.wait (a function that may block) is allowed in - * this isolate. This can also be configured via SetAllowAtomicsWait. - */ - bool allow_atomics_wait = true; - - /** - * Termination is postponed when there is no active SafeForTerminationScope. - */ - bool only_terminate_in_safe_scope = false; - - /** - * The following parameters describe the offsets for addressing type info - * for wrapped API objects and are used by the fast C API - * (for details see v8-fast-api-calls.h). - */ - int embedder_wrapper_type_index = -1; - int embedder_wrapper_object_index = -1; - }; - - /** - * Stack-allocated class which sets the isolate for all operations - * executed within a local scope. - */ - class V8_EXPORT V8_NODISCARD Scope { - public: - explicit Scope(Isolate* isolate) : isolate_(isolate) { isolate->Enter(); } - - ~Scope() { isolate_->Exit(); } - - // Prevent copying of Scope objects. - Scope(const Scope&) = delete; - Scope& operator=(const Scope&) = delete; - - private: - Isolate* const isolate_; - }; - - /** - * Assert that no Javascript code is invoked. - */ - class V8_EXPORT V8_NODISCARD DisallowJavascriptExecutionScope { - public: - enum OnFailure { CRASH_ON_FAILURE, THROW_ON_FAILURE, DUMP_ON_FAILURE }; - - DisallowJavascriptExecutionScope(Isolate* isolate, OnFailure on_failure); - ~DisallowJavascriptExecutionScope(); - - // Prevent copying of Scope objects. - DisallowJavascriptExecutionScope(const DisallowJavascriptExecutionScope&) = - delete; - DisallowJavascriptExecutionScope& operator=( - const DisallowJavascriptExecutionScope&) = delete; - - private: - OnFailure on_failure_; - Isolate* isolate_; - - bool was_execution_allowed_assert_; - bool was_execution_allowed_throws_; - bool was_execution_allowed_dump_; - }; - - /** - * Introduce exception to DisallowJavascriptExecutionScope. - */ - class V8_EXPORT V8_NODISCARD AllowJavascriptExecutionScope { - public: - explicit AllowJavascriptExecutionScope(Isolate* isolate); - ~AllowJavascriptExecutionScope(); - - // Prevent copying of Scope objects. - AllowJavascriptExecutionScope(const AllowJavascriptExecutionScope&) = - delete; - AllowJavascriptExecutionScope& operator=( - const AllowJavascriptExecutionScope&) = delete; - - private: - Isolate* isolate_; - bool was_execution_allowed_assert_; - bool was_execution_allowed_throws_; - bool was_execution_allowed_dump_; - }; - - /** - * Do not run microtasks while this scope is active, even if microtasks are - * automatically executed otherwise. - */ - class V8_EXPORT V8_NODISCARD SuppressMicrotaskExecutionScope { - public: - explicit SuppressMicrotaskExecutionScope( - Isolate* isolate, MicrotaskQueue* microtask_queue = nullptr); - ~SuppressMicrotaskExecutionScope(); - - // Prevent copying of Scope objects. - SuppressMicrotaskExecutionScope(const SuppressMicrotaskExecutionScope&) = - delete; - SuppressMicrotaskExecutionScope& operator=( - const SuppressMicrotaskExecutionScope&) = delete; - - private: - internal::Isolate* const isolate_; - internal::MicrotaskQueue* const microtask_queue_; - internal::Address previous_stack_height_; - - friend class internal::ThreadLocalTop; - }; - - /** - * This scope allows terminations inside direct V8 API calls and forbid them - * inside any recursive API calls without explicit SafeForTerminationScope. - */ - class V8_EXPORT V8_NODISCARD SafeForTerminationScope { - public: - explicit SafeForTerminationScope(v8::Isolate* isolate); - ~SafeForTerminationScope(); - - // Prevent copying of Scope objects. - SafeForTerminationScope(const SafeForTerminationScope&) = delete; - SafeForTerminationScope& operator=(const SafeForTerminationScope&) = delete; - - private: - internal::Isolate* isolate_; - bool prev_value_; - }; - - /** - * Types of garbage collections that can be requested via - * RequestGarbageCollectionForTesting. - */ - enum GarbageCollectionType { - kFullGarbageCollection, - kMinorGarbageCollection - }; - - /** - * Features reported via the SetUseCounterCallback callback. Do not change - * assigned numbers of existing items; add new features to the end of this - * list. - */ - enum UseCounterFeature { - kUseAsm = 0, - kBreakIterator = 1, - kLegacyConst = 2, - kMarkDequeOverflow = 3, - kStoreBufferOverflow = 4, - kSlotsBufferOverflow = 5, - kObjectObserve = 6, - kForcedGC = 7, - kSloppyMode = 8, - kStrictMode = 9, - kStrongMode = 10, - kRegExpPrototypeStickyGetter = 11, - kRegExpPrototypeToString = 12, - kRegExpPrototypeUnicodeGetter = 13, - kIntlV8Parse = 14, - kIntlPattern = 15, - kIntlResolved = 16, - kPromiseChain = 17, - kPromiseAccept = 18, - kPromiseDefer = 19, - kHtmlCommentInExternalScript = 20, - kHtmlComment = 21, - kSloppyModeBlockScopedFunctionRedefinition = 22, - kForInInitializer = 23, - kArrayProtectorDirtied = 24, - kArraySpeciesModified = 25, - kArrayPrototypeConstructorModified = 26, - kArrayInstanceProtoModified = 27, - kArrayInstanceConstructorModified = 28, - kLegacyFunctionDeclaration = 29, - kRegExpPrototypeSourceGetter = 30, // Unused. - kRegExpPrototypeOldFlagGetter = 31, // Unused. - kDecimalWithLeadingZeroInStrictMode = 32, - kLegacyDateParser = 33, - kDefineGetterOrSetterWouldThrow = 34, - kFunctionConstructorReturnedUndefined = 35, - kAssigmentExpressionLHSIsCallInSloppy = 36, - kAssigmentExpressionLHSIsCallInStrict = 37, - kPromiseConstructorReturnedUndefined = 38, - kConstructorNonUndefinedPrimitiveReturn = 39, - kLabeledExpressionStatement = 40, - kLineOrParagraphSeparatorAsLineTerminator = 41, - kIndexAccessor = 42, - kErrorCaptureStackTrace = 43, - kErrorPrepareStackTrace = 44, - kErrorStackTraceLimit = 45, - kWebAssemblyInstantiation = 46, - kDeoptimizerDisableSpeculation = 47, - kArrayPrototypeSortJSArrayModifiedPrototype = 48, - kFunctionTokenOffsetTooLongForToString = 49, - kWasmSharedMemory = 50, - kWasmThreadOpcodes = 51, - kAtomicsNotify = 52, // Unused. - kAtomicsWake = 53, // Unused. - kCollator = 54, - kNumberFormat = 55, - kDateTimeFormat = 56, - kPluralRules = 57, - kRelativeTimeFormat = 58, - kLocale = 59, - kListFormat = 60, - kSegmenter = 61, - kStringLocaleCompare = 62, - kStringToLocaleUpperCase = 63, - kStringToLocaleLowerCase = 64, - kNumberToLocaleString = 65, - kDateToLocaleString = 66, - kDateToLocaleDateString = 67, - kDateToLocaleTimeString = 68, - kAttemptOverrideReadOnlyOnPrototypeSloppy = 69, - kAttemptOverrideReadOnlyOnPrototypeStrict = 70, - kOptimizedFunctionWithOneShotBytecode = 71, // Unused. - kRegExpMatchIsTrueishOnNonJSRegExp = 72, - kRegExpMatchIsFalseishOnJSRegExp = 73, - kDateGetTimezoneOffset = 74, // Unused. - kStringNormalize = 75, - kCallSiteAPIGetFunctionSloppyCall = 76, - kCallSiteAPIGetThisSloppyCall = 77, - kRegExpMatchAllWithNonGlobalRegExp = 78, - kRegExpExecCalledOnSlowRegExp = 79, - kRegExpReplaceCalledOnSlowRegExp = 80, - kDisplayNames = 81, - kSharedArrayBufferConstructed = 82, - kArrayPrototypeHasElements = 83, - kObjectPrototypeHasElements = 84, - kNumberFormatStyleUnit = 85, - kDateTimeFormatRange = 86, - kDateTimeFormatDateTimeStyle = 87, - kBreakIteratorTypeWord = 88, - kBreakIteratorTypeLine = 89, - kInvalidatedArrayBufferDetachingProtector = 90, - kInvalidatedArrayConstructorProtector = 91, - kInvalidatedArrayIteratorLookupChainProtector = 92, - kInvalidatedArraySpeciesLookupChainProtector = 93, - kInvalidatedIsConcatSpreadableLookupChainProtector = 94, - kInvalidatedMapIteratorLookupChainProtector = 95, - kInvalidatedNoElementsProtector = 96, - kInvalidatedPromiseHookProtector = 97, - kInvalidatedPromiseResolveLookupChainProtector = 98, - kInvalidatedPromiseSpeciesLookupChainProtector = 99, - kInvalidatedPromiseThenLookupChainProtector = 100, - kInvalidatedRegExpSpeciesLookupChainProtector = 101, - kInvalidatedSetIteratorLookupChainProtector = 102, - kInvalidatedStringIteratorLookupChainProtector = 103, - kInvalidatedStringLengthOverflowLookupChainProtector = 104, - kInvalidatedTypedArraySpeciesLookupChainProtector = 105, - kWasmSimdOpcodes = 106, - kVarRedeclaredCatchBinding = 107, - kWasmRefTypes = 108, - kWasmBulkMemory = 109, // Unused. - kWasmMultiValue = 110, - kWasmExceptionHandling = 111, - kInvalidatedMegaDOMProtector = 112, - - // If you add new values here, you'll also need to update Chromium's: - // web_feature.mojom, use_counter_callback.cc, and enums.xml. V8 changes to - // this list need to be landed first, then changes on the Chromium side. - kUseCounterFeatureCount // This enum value must be last. - }; - - enum MessageErrorLevel { - kMessageLog = (1 << 0), - kMessageDebug = (1 << 1), - kMessageInfo = (1 << 2), - kMessageError = (1 << 3), - kMessageWarning = (1 << 4), - kMessageAll = kMessageLog | kMessageDebug | kMessageInfo | kMessageError | - kMessageWarning, - }; - - using UseCounterCallback = void (*)(Isolate* isolate, - UseCounterFeature feature); - - /** - * Allocates a new isolate but does not initialize it. Does not change the - * currently entered isolate. - * - * Only Isolate::GetData() and Isolate::SetData(), which access the - * embedder-controlled parts of the isolate, are allowed to be called on the - * uninitialized isolate. To initialize the isolate, call - * Isolate::Initialize(). - * - * When an isolate is no longer used its resources should be freed - * by calling Dispose(). Using the delete operator is not allowed. - * - * V8::Initialize() must have run prior to this. - */ - static Isolate* Allocate(); - - /** - * Initialize an Isolate previously allocated by Isolate::Allocate(). - */ - static void Initialize(Isolate* isolate, const CreateParams& params); - - /** - * Creates a new isolate. Does not change the currently entered - * isolate. - * - * When an isolate is no longer used its resources should be freed - * by calling Dispose(). Using the delete operator is not allowed. - * - * V8::Initialize() must have run prior to this. - */ - static Isolate* New(const CreateParams& params); - - /** - * Returns the entered isolate for the current thread or NULL in - * case there is no current isolate. - * - * This method must not be invoked before V8::Initialize() was invoked. - */ - static Isolate* GetCurrent(); - - /** - * Returns the entered isolate for the current thread or NULL in - * case there is no current isolate. - * - * No checks are performed by this method. - */ - static Isolate* TryGetCurrent(); - - /** - * Clears the set of objects held strongly by the heap. This set of - * objects are originally built when a WeakRef is created or - * successfully dereferenced. - * - * This is invoked automatically after microtasks are run. See - * MicrotasksPolicy for when microtasks are run. - * - * This needs to be manually invoked only if the embedder is manually running - * microtasks via a custom MicrotaskQueue class's PerformCheckpoint. In that - * case, it is the embedder's responsibility to make this call at a time which - * does not interrupt synchronous ECMAScript code execution. - */ - void ClearKeptObjects(); - - /** - * Custom callback used by embedders to help V8 determine if it should abort - * when it throws and no internal handler is predicted to catch the - * exception. If --abort-on-uncaught-exception is used on the command line, - * then V8 will abort if either: - * - no custom callback is set. - * - the custom callback set returns true. - * Otherwise, the custom callback will not be called and V8 will not abort. - */ - using AbortOnUncaughtExceptionCallback = bool (*)(Isolate*); - void SetAbortOnUncaughtExceptionCallback( - AbortOnUncaughtExceptionCallback callback); - - /** - * This specifies the callback called by the upcoming dynamic - * import() language feature to load modules. - */ - V8_DEPRECATED( - "Use the version of SetHostImportModuleDynamicallyCallback that takes a " - "HostImportModuleDynamicallyWithImportAssertionsCallback instead") - void SetHostImportModuleDynamicallyCallback( - HostImportModuleDynamicallyCallback callback); - - /** - * This specifies the callback called by the upcoming dynamic - * import() language feature to load modules. - */ - void SetHostImportModuleDynamicallyCallback( - HostImportModuleDynamicallyWithImportAssertionsCallback callback); - - /** - * This specifies the callback called by the upcoming import.meta - * language feature to retrieve host-defined meta data for a module. - */ - void SetHostInitializeImportMetaObjectCallback( - HostInitializeImportMetaObjectCallback callback); - - /** - * This specifies the callback called when the stack property of Error - * is accessed. - */ - void SetPrepareStackTraceCallback(PrepareStackTraceCallback callback); - - /** - * Optional notification that the system is running low on memory. - * V8 uses these notifications to guide heuristics. - * It is allowed to call this function from another thread while - * the isolate is executing long running JavaScript code. - */ - void MemoryPressureNotification(MemoryPressureLevel level); - - /** - * Drop non-essential caches. Should only be called from testing code. - * The method can potentially block for a long time and does not necessarily - * trigger GC. - */ - void ClearCachesForTesting(); - - /** - * Methods below this point require holding a lock (using Locker) in - * a multi-threaded environment. - */ - - /** - * Sets this isolate as the entered one for the current thread. - * Saves the previously entered one (if any), so that it can be - * restored when exiting. Re-entering an isolate is allowed. - */ - void Enter(); - - /** - * Exits this isolate by restoring the previously entered one in the - * current thread. The isolate may still stay the same, if it was - * entered more than once. - * - * Requires: this == Isolate::GetCurrent(). - */ - void Exit(); - - /** - * Disposes the isolate. The isolate must not be entered by any - * thread to be disposable. - */ - void Dispose(); - - /** - * Dumps activated low-level V8 internal stats. This can be used instead - * of performing a full isolate disposal. - */ - void DumpAndResetStats(); - - /** - * Discards all V8 thread-specific data for the Isolate. Should be used - * if a thread is terminating and it has used an Isolate that will outlive - * the thread -- all thread-specific data for an Isolate is discarded when - * an Isolate is disposed so this call is pointless if an Isolate is about - * to be Disposed. - */ - void DiscardThreadSpecificMetadata(); - - /** - * Associate embedder-specific data with the isolate. |slot| has to be - * between 0 and GetNumberOfDataSlots() - 1. - */ - V8_INLINE void SetData(uint32_t slot, void* data); - - /** - * Retrieve embedder-specific data from the isolate. - * Returns NULL if SetData has never been called for the given |slot|. - */ - V8_INLINE void* GetData(uint32_t slot); - - /** - * Returns the maximum number of available embedder data slots. Valid slots - * are in the range of 0 - GetNumberOfDataSlots() - 1. - */ - V8_INLINE static uint32_t GetNumberOfDataSlots(); - - /** - * Return data that was previously attached to the isolate snapshot via - * SnapshotCreator, and removes the reference to it. - * Repeated call with the same index returns an empty MaybeLocal. - */ - template - V8_INLINE MaybeLocal GetDataFromSnapshotOnce(size_t index); - - /** - * Get statistics about the heap memory usage. - */ - void GetHeapStatistics(HeapStatistics* heap_statistics); - - /** - * Returns the number of spaces in the heap. - */ - size_t NumberOfHeapSpaces(); - - /** - * Get the memory usage of a space in the heap. - * - * \param space_statistics The HeapSpaceStatistics object to fill in - * statistics. - * \param index The index of the space to get statistics from, which ranges - * from 0 to NumberOfHeapSpaces() - 1. - * \returns true on success. - */ - bool GetHeapSpaceStatistics(HeapSpaceStatistics* space_statistics, - size_t index); - - /** - * Returns the number of types of objects tracked in the heap at GC. - */ - size_t NumberOfTrackedHeapObjectTypes(); - - /** - * Get statistics about objects in the heap. - * - * \param object_statistics The HeapObjectStatistics object to fill in - * statistics of objects of given type, which were live in the previous GC. - * \param type_index The index of the type of object to fill details about, - * which ranges from 0 to NumberOfTrackedHeapObjectTypes() - 1. - * \returns true on success. - */ - bool GetHeapObjectStatisticsAtLastGC(HeapObjectStatistics* object_statistics, - size_t type_index); - - /** - * Get statistics about code and its metadata in the heap. - * - * \param object_statistics The HeapCodeStatistics object to fill in - * statistics of code, bytecode and their metadata. - * \returns true on success. - */ - bool GetHeapCodeAndMetadataStatistics(HeapCodeStatistics* object_statistics); - - /** - * This API is experimental and may change significantly. - * - * Enqueues a memory measurement request and invokes the delegate with the - * results. - * - * \param delegate the delegate that defines which contexts to measure and - * reports the results. - * - * \param execution promptness executing the memory measurement. - * The kEager value is expected to be used only in tests. - */ - bool MeasureMemory( - std::unique_ptr delegate, - MeasureMemoryExecution execution = MeasureMemoryExecution::kDefault); - - /** - * Get a call stack sample from the isolate. - * \param state Execution state. - * \param frames Caller allocated buffer to store stack frames. - * \param frames_limit Maximum number of frames to capture. The buffer must - * be large enough to hold the number of frames. - * \param sample_info The sample info is filled up by the function - * provides number of actual captured stack frames and - * the current VM state. - * \note GetStackSample should only be called when the JS thread is paused or - * interrupted. Otherwise the behavior is undefined. - */ - void GetStackSample(const RegisterState& state, void** frames, - size_t frames_limit, SampleInfo* sample_info); - - /** - * Adjusts the amount of registered external memory. Used to give V8 an - * indication of the amount of externally allocated memory that is kept alive - * by JavaScript objects. V8 uses this to decide when to perform global - * garbage collections. Registering externally allocated memory will trigger - * global garbage collections more often than it would otherwise in an attempt - * to garbage collect the JavaScript objects that keep the externally - * allocated memory alive. - * - * \param change_in_bytes the change in externally allocated memory that is - * kept alive by JavaScript objects. - * \returns the adjusted value. - */ - int64_t AdjustAmountOfExternalAllocatedMemory(int64_t change_in_bytes); - - /** - * Returns the number of phantom handles without callbacks that were reset - * by the garbage collector since the last call to this function. - */ - size_t NumberOfPhantomHandleResetsSinceLastCall(); - - /** - * Returns heap profiler for this isolate. Will return NULL until the isolate - * is initialized. - */ - HeapProfiler* GetHeapProfiler(); - - /** - * Tells the VM whether the embedder is idle or not. - */ - void SetIdle(bool is_idle); - - /** Returns the ArrayBuffer::Allocator used in this isolate. */ - ArrayBuffer::Allocator* GetArrayBufferAllocator(); - - /** Returns true if this isolate has a current context. */ - bool InContext(); - - /** - * Returns the context of the currently running JavaScript, or the context - * on the top of the stack if no JavaScript is running. - */ - Local GetCurrentContext(); - - /** - * Returns either the last context entered through V8's C++ API, or the - * context of the currently running microtask while processing microtasks. - * If a context is entered while executing a microtask, that context is - * returned. - */ - Local GetEnteredOrMicrotaskContext(); - - /** - * Returns the Context that corresponds to the Incumbent realm in HTML spec. - * https://html.spec.whatwg.org/multipage/webappapis.html#incumbent - */ - Local GetIncumbentContext(); - - /** - * Schedules a v8::Exception::Error with the given message. - * See ThrowException for more details. Templatized to provide compile-time - * errors in case of too long strings (see v8::String::NewFromUtf8Literal). - */ - template - Local ThrowError(const char (&message)[N]) { - return ThrowError(String::NewFromUtf8Literal(this, message)); - } - Local ThrowError(Local message); - - /** - * Schedules an exception to be thrown when returning to JavaScript. When an - * exception has been scheduled it is illegal to invoke any JavaScript - * operation; the caller must return immediately and only after the exception - * has been handled does it become legal to invoke JavaScript operations. - */ - Local ThrowException(Local exception); - - using GCCallback = void (*)(Isolate* isolate, GCType type, - GCCallbackFlags flags); - using GCCallbackWithData = void (*)(Isolate* isolate, GCType type, - GCCallbackFlags flags, void* data); - - /** - * Enables the host application to receive a notification before a - * garbage collection. Allocations are allowed in the callback function, - * but the callback is not re-entrant: if the allocation inside it will - * trigger the garbage collection, the callback won't be called again. - * It is possible to specify the GCType filter for your callback. But it is - * not possible to register the same callback function two times with - * different GCType filters. - */ - void AddGCPrologueCallback(GCCallbackWithData callback, void* data = nullptr, - GCType gc_type_filter = kGCTypeAll); - void AddGCPrologueCallback(GCCallback callback, - GCType gc_type_filter = kGCTypeAll); - - /** - * This function removes callback which was installed by - * AddGCPrologueCallback function. - */ - void RemoveGCPrologueCallback(GCCallbackWithData, void* data = nullptr); - void RemoveGCPrologueCallback(GCCallback callback); - - /** - * Sets the embedder heap tracer for the isolate. - */ - void SetEmbedderHeapTracer(EmbedderHeapTracer* tracer); - - /* - * Gets the currently active heap tracer for the isolate. - */ - EmbedderHeapTracer* GetEmbedderHeapTracer(); - - /** - * Sets an embedder roots handle that V8 should consider when performing - * non-unified heap garbage collections. - * - * Using only EmbedderHeapTracer automatically sets up a default handler. - * The intended use case is for setting a custom handler after invoking - * `AttachCppHeap()`. - * - * V8 does not take ownership of the handler. - */ - void SetEmbedderRootsHandler(EmbedderRootsHandler* handler); - - /** - * Attaches a managed C++ heap as an extension to the JavaScript heap. The - * embedder maintains ownership of the CppHeap. At most one C++ heap can be - * attached to V8. - * - * This is an experimental feature and may still change significantly. - */ - void AttachCppHeap(CppHeap*); - - /** - * Detaches a managed C++ heap if one was attached using `AttachCppHeap()`. - * - * This is an experimental feature and may still change significantly. - */ - void DetachCppHeap(); - - /** - * This is an experimental feature and may still change significantly. - - * \returns the C++ heap managed by V8. Only available if such a heap has been - * attached using `AttachCppHeap()`. - */ - CppHeap* GetCppHeap() const; - - /** - * Use for |AtomicsWaitCallback| to indicate the type of event it receives. - */ - enum class AtomicsWaitEvent { - /** Indicates that this call is happening before waiting. */ - kStartWait, - /** `Atomics.wait()` finished because of an `Atomics.wake()` call. */ - kWokenUp, - /** `Atomics.wait()` finished because it timed out. */ - kTimedOut, - /** `Atomics.wait()` was interrupted through |TerminateExecution()|. */ - kTerminatedExecution, - /** `Atomics.wait()` was stopped through |AtomicsWaitWakeHandle|. */ - kAPIStopped, - /** `Atomics.wait()` did not wait, as the initial condition was not met. */ - kNotEqual - }; - - /** - * Passed to |AtomicsWaitCallback| as a means of stopping an ongoing - * `Atomics.wait` call. - */ - class V8_EXPORT AtomicsWaitWakeHandle { - public: - /** - * Stop this `Atomics.wait()` call and call the |AtomicsWaitCallback| - * with |kAPIStopped|. - * - * This function may be called from another thread. The caller has to ensure - * through proper synchronization that it is not called after - * the finishing |AtomicsWaitCallback|. - * - * Note that the ECMAScript specification does not plan for the possibility - * of wakeups that are neither coming from a timeout or an `Atomics.wake()` - * call, so this may invalidate assumptions made by existing code. - * The embedder may accordingly wish to schedule an exception in the - * finishing |AtomicsWaitCallback|. - */ - void Wake(); - }; - - /** - * Embedder callback for `Atomics.wait()` that can be added through - * |SetAtomicsWaitCallback|. - * - * This will be called just before starting to wait with the |event| value - * |kStartWait| and after finishing waiting with one of the other - * values of |AtomicsWaitEvent| inside of an `Atomics.wait()` call. - * - * |array_buffer| will refer to the underlying SharedArrayBuffer, - * |offset_in_bytes| to the location of the waited-on memory address inside - * the SharedArrayBuffer. - * - * |value| and |timeout_in_ms| will be the values passed to - * the `Atomics.wait()` call. If no timeout was used, |timeout_in_ms| - * will be `INFINITY`. - * - * In the |kStartWait| callback, |stop_handle| will be an object that - * is only valid until the corresponding finishing callback and that - * can be used to stop the wait process while it is happening. - * - * This callback may schedule exceptions, *unless* |event| is equal to - * |kTerminatedExecution|. - */ - using AtomicsWaitCallback = void (*)(AtomicsWaitEvent event, - Local array_buffer, - size_t offset_in_bytes, int64_t value, - double timeout_in_ms, - AtomicsWaitWakeHandle* stop_handle, - void* data); - - /** - * Set a new |AtomicsWaitCallback|. This overrides an earlier - * |AtomicsWaitCallback|, if there was any. If |callback| is nullptr, - * this unsets the callback. |data| will be passed to the callback - * as its last parameter. - */ - void SetAtomicsWaitCallback(AtomicsWaitCallback callback, void* data); - - /** - * Enables the host application to receive a notification after a - * garbage collection. Allocations are allowed in the callback function, - * but the callback is not re-entrant: if the allocation inside it will - * trigger the garbage collection, the callback won't be called again. - * It is possible to specify the GCType filter for your callback. But it is - * not possible to register the same callback function two times with - * different GCType filters. - */ - void AddGCEpilogueCallback(GCCallbackWithData callback, void* data = nullptr, - GCType gc_type_filter = kGCTypeAll); - void AddGCEpilogueCallback(GCCallback callback, - GCType gc_type_filter = kGCTypeAll); - - /** - * This function removes callback which was installed by - * AddGCEpilogueCallback function. - */ - void RemoveGCEpilogueCallback(GCCallbackWithData callback, - void* data = nullptr); - void RemoveGCEpilogueCallback(GCCallback callback); - - using GetExternallyAllocatedMemoryInBytesCallback = size_t (*)(); - - /** - * Set the callback that tells V8 how much memory is currently allocated - * externally of the V8 heap. Ideally this memory is somehow connected to V8 - * objects and may get freed-up when the corresponding V8 objects get - * collected by a V8 garbage collection. - */ - void SetGetExternallyAllocatedMemoryInBytesCallback( - GetExternallyAllocatedMemoryInBytesCallback callback); - - /** - * Forcefully terminate the current thread of JavaScript execution - * in the given isolate. - * - * This method can be used by any thread even if that thread has not - * acquired the V8 lock with a Locker object. - */ - void TerminateExecution(); - - /** - * Is V8 terminating JavaScript execution. - * - * Returns true if JavaScript execution is currently terminating - * because of a call to TerminateExecution. In that case there are - * still JavaScript frames on the stack and the termination - * exception is still active. - */ - bool IsExecutionTerminating(); - - /** - * Resume execution capability in the given isolate, whose execution - * was previously forcefully terminated using TerminateExecution(). - * - * When execution is forcefully terminated using TerminateExecution(), - * the isolate can not resume execution until all JavaScript frames - * have propagated the uncatchable exception which is generated. This - * method allows the program embedding the engine to handle the - * termination event and resume execution capability, even if - * JavaScript frames remain on the stack. - * - * This method can be used by any thread even if that thread has not - * acquired the V8 lock with a Locker object. - */ - void CancelTerminateExecution(); - - /** - * Request V8 to interrupt long running JavaScript code and invoke - * the given |callback| passing the given |data| to it. After |callback| - * returns control will be returned to the JavaScript code. - * There may be a number of interrupt requests in flight. - * Can be called from another thread without acquiring a |Locker|. - * Registered |callback| must not reenter interrupted Isolate. - */ - void RequestInterrupt(InterruptCallback callback, void* data); - - /** - * Returns true if there is ongoing background work within V8 that will - * eventually post a foreground task, like asynchronous WebAssembly - * compilation. - */ - bool HasPendingBackgroundTasks(); - - /** - * Request garbage collection in this Isolate. It is only valid to call this - * function if --expose_gc was specified. - * - * This should only be used for testing purposes and not to enforce a garbage - * collection schedule. It has strong negative impact on the garbage - * collection performance. Use IdleNotificationDeadline() or - * LowMemoryNotification() instead to influence the garbage collection - * schedule. - */ - void RequestGarbageCollectionForTesting(GarbageCollectionType type); - - /** - * Set the callback to invoke for logging event. - */ - void SetEventLogger(LogEventCallback that); - - /** - * Adds a callback to notify the host application right before a script - * is about to run. If a script re-enters the runtime during executing, the - * BeforeCallEnteredCallback is invoked for each re-entrance. - * Executing scripts inside the callback will re-trigger the callback. - */ - void AddBeforeCallEnteredCallback(BeforeCallEnteredCallback callback); - - /** - * Removes callback that was installed by AddBeforeCallEnteredCallback. - */ - void RemoveBeforeCallEnteredCallback(BeforeCallEnteredCallback callback); - - /** - * Adds a callback to notify the host application when a script finished - * running. If a script re-enters the runtime during executing, the - * CallCompletedCallback is only invoked when the outer-most script - * execution ends. Executing scripts inside the callback do not trigger - * further callbacks. - */ - void AddCallCompletedCallback(CallCompletedCallback callback); - - /** - * Removes callback that was installed by AddCallCompletedCallback. - */ - void RemoveCallCompletedCallback(CallCompletedCallback callback); - - /** - * Set the PromiseHook callback for various promise lifecycle - * events. - */ - void SetPromiseHook(PromiseHook hook); - - /** - * Set callback to notify about promise reject with no handler, or - * revocation of such a previous notification once the handler is added. - */ - void SetPromiseRejectCallback(PromiseRejectCallback callback); - - /** - * Runs the default MicrotaskQueue until it gets empty and perform other - * microtask checkpoint steps, such as calling ClearKeptObjects. Asserts that - * the MicrotasksPolicy is not kScoped. Any exceptions thrown by microtask - * callbacks are swallowed. - */ - void PerformMicrotaskCheckpoint(); - - /** - * Enqueues the callback to the default MicrotaskQueue - */ - void EnqueueMicrotask(Local microtask); - - /** - * Enqueues the callback to the default MicrotaskQueue - */ - void EnqueueMicrotask(MicrotaskCallback callback, void* data = nullptr); - - /** - * Controls how Microtasks are invoked. See MicrotasksPolicy for details. - */ - void SetMicrotasksPolicy(MicrotasksPolicy policy); - - /** - * Returns the policy controlling how Microtasks are invoked. - */ - MicrotasksPolicy GetMicrotasksPolicy() const; - - /** - * Adds a callback to notify the host application after - * microtasks were run on the default MicrotaskQueue. The callback is - * triggered by explicit RunMicrotasks call or automatic microtasks execution - * (see SetMicrotaskPolicy). - * - * Callback will trigger even if microtasks were attempted to run, - * but the microtasks queue was empty and no single microtask was actually - * executed. - * - * Executing scripts inside the callback will not re-trigger microtasks and - * the callback. - */ - void AddMicrotasksCompletedCallback( - MicrotasksCompletedCallbackWithData callback, void* data = nullptr); - - /** - * Removes callback that was installed by AddMicrotasksCompletedCallback. - */ - void RemoveMicrotasksCompletedCallback( - MicrotasksCompletedCallbackWithData callback, void* data = nullptr); - - /** - * Sets a callback for counting the number of times a feature of V8 is used. - */ - void SetUseCounterCallback(UseCounterCallback callback); - - /** - * Enables the host application to provide a mechanism for recording - * statistics counters. - */ - void SetCounterFunction(CounterLookupCallback); - - /** - * Enables the host application to provide a mechanism for recording - * histograms. The CreateHistogram function returns a - * histogram which will later be passed to the AddHistogramSample - * function. - */ - void SetCreateHistogramFunction(CreateHistogramCallback); - void SetAddHistogramSampleFunction(AddHistogramSampleCallback); - - /** - * Enables the host application to provide a mechanism for recording - * event based metrics. In order to use this interface - * include/v8-metrics.h - * needs to be included and the recorder needs to be derived from the - * Recorder base class defined there. - * This method can only be called once per isolate and must happen during - * isolate initialization before background threads are spawned. - */ - void SetMetricsRecorder( - const std::shared_ptr& metrics_recorder); - - /** - * Enables the host application to provide a mechanism for recording a - * predefined set of data as crash keys to be used in postmortem debugging in - * case of a crash. - */ - void SetAddCrashKeyCallback(AddCrashKeyCallback); - - /** - * Optional notification that the embedder is idle. - * V8 uses the notification to perform garbage collection. - * This call can be used repeatedly if the embedder remains idle. - * Returns true if the embedder should stop calling IdleNotificationDeadline - * until real work has been done. This indicates that V8 has done - * as much cleanup as it will be able to do. - * - * The deadline_in_seconds argument specifies the deadline V8 has to finish - * garbage collection work. deadline_in_seconds is compared with - * MonotonicallyIncreasingTime() and should be based on the same timebase as - * that function. There is no guarantee that the actual work will be done - * within the time limit. - */ - bool IdleNotificationDeadline(double deadline_in_seconds); - - /** - * Optional notification that the system is running low on memory. - * V8 uses these notifications to attempt to free memory. - */ - void LowMemoryNotification(); - - /** - * Optional notification that a context has been disposed. V8 uses these - * notifications to guide the GC heuristic and cancel FinalizationRegistry - * cleanup tasks. Returns the number of context disposals - including this one - * - since the last time V8 had a chance to clean up. - * - * The optional parameter |dependant_context| specifies whether the disposed - * context was depending on state from other contexts or not. - */ - int ContextDisposedNotification(bool dependant_context = true); - - /** - * Optional notification that the isolate switched to the foreground. - * V8 uses these notifications to guide heuristics. - */ - void IsolateInForegroundNotification(); - - /** - * Optional notification that the isolate switched to the background. - * V8 uses these notifications to guide heuristics. - */ - void IsolateInBackgroundNotification(); - - /** - * Optional notification which will enable the memory savings mode. - * V8 uses this notification to guide heuristics which may result in a - * smaller memory footprint at the cost of reduced runtime performance. - */ - void EnableMemorySavingsMode(); - - /** - * Optional notification which will disable the memory savings mode. - */ - void DisableMemorySavingsMode(); - - /** - * Optional notification to tell V8 the current performance requirements - * of the embedder based on RAIL. - * V8 uses these notifications to guide heuristics. - * This is an unfinished experimental feature. Semantics and implementation - * may change frequently. - */ - void SetRAILMode(RAILMode rail_mode); - - /** - * Update load start time of the RAIL mode - */ - void UpdateLoadStartTime(); - - /** - * Optional notification to tell V8 the current isolate is used for debugging - * and requires higher heap limit. - */ - void IncreaseHeapLimitForDebugging(); - - /** - * Restores the original heap limit after IncreaseHeapLimitForDebugging(). - */ - void RestoreOriginalHeapLimit(); - - /** - * Returns true if the heap limit was increased for debugging and the - * original heap limit was not restored yet. - */ - bool IsHeapLimitIncreasedForDebugging(); - - /** - * Allows the host application to provide the address of a function that is - * notified each time code is added, moved or removed. - * - * \param options options for the JIT code event handler. - * \param event_handler the JIT code event handler, which will be invoked - * each time code is added, moved or removed. - * \note \p event_handler won't get notified of existent code. - * \note since code removal notifications are not currently issued, the - * \p event_handler may get notifications of code that overlaps earlier - * code notifications. This happens when code areas are reused, and the - * earlier overlapping code areas should therefore be discarded. - * \note the events passed to \p event_handler and the strings they point to - * are not guaranteed to live past each call. The \p event_handler must - * copy strings and other parameters it needs to keep around. - * \note the set of events declared in JitCodeEvent::EventType is expected to - * grow over time, and the JitCodeEvent structure is expected to accrue - * new members. The \p event_handler function must ignore event codes - * it does not recognize to maintain future compatibility. - * \note Use Isolate::CreateParams to get events for code executed during - * Isolate setup. - */ - void SetJitCodeEventHandler(JitCodeEventOptions options, - JitCodeEventHandler event_handler); - - /** - * Modifies the stack limit for this Isolate. - * - * \param stack_limit An address beyond which the Vm's stack may not grow. - * - * \note If you are using threads then you should hold the V8::Locker lock - * while setting the stack limit and you must set a non-default stack - * limit separately for each thread. - */ - void SetStackLimit(uintptr_t stack_limit); - - /** - * Returns a memory range that can potentially contain jitted code. Code for - * V8's 'builtins' will not be in this range if embedded builtins is enabled. - * - * On Win64, embedders are advised to install function table callbacks for - * these ranges, as default SEH won't be able to unwind through jitted code. - * The first page of the code range is reserved for the embedder and is - * committed, writable, and executable, to be used to store unwind data, as - * documented in - * https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64. - * - * Might be empty on other platforms. - * - * https://code.google.com/p/v8/issues/detail?id=3598 - */ - void GetCodeRange(void** start, size_t* length_in_bytes); - - /** - * As GetCodeRange, but for embedded builtins (these live in a distinct - * memory region from other V8 Code objects). - */ - void GetEmbeddedCodeRange(const void** start, size_t* length_in_bytes); - - /** - * Returns the JSEntryStubs necessary for use with the Unwinder API. - */ - JSEntryStubs GetJSEntryStubs(); - - static constexpr size_t kMinCodePagesBufferSize = 32; - - /** - * Copies the code heap pages currently in use by V8 into |code_pages_out|. - * |code_pages_out| must have at least kMinCodePagesBufferSize capacity and - * must be empty. - * - * Signal-safe, does not allocate, does not access the V8 heap. - * No code on the stack can rely on pages that might be missing. - * - * Returns the number of pages available to be copied, which might be greater - * than |capacity|. In this case, only |capacity| pages will be copied into - * |code_pages_out|. The caller should provide a bigger buffer on the next - * call in order to get all available code pages, but this is not required. - */ - size_t CopyCodePages(size_t capacity, MemoryRange* code_pages_out); - - /** Set the callback to invoke in case of fatal errors. */ - void SetFatalErrorHandler(FatalErrorCallback that); - - /** Set the callback to invoke in case of OOM errors. */ - void SetOOMErrorHandler(OOMErrorCallback that); - - /** - * Add a callback to invoke in case the heap size is close to the heap limit. - * If multiple callbacks are added, only the most recently added callback is - * invoked. - */ - void AddNearHeapLimitCallback(NearHeapLimitCallback callback, void* data); - - /** - * Remove the given callback and restore the heap limit to the - * given limit. If the given limit is zero, then it is ignored. - * If the current heap size is greater than the given limit, - * then the heap limit is restored to the minimal limit that - * is possible for the current heap size. - */ - void RemoveNearHeapLimitCallback(NearHeapLimitCallback callback, - size_t heap_limit); - - /** - * If the heap limit was changed by the NearHeapLimitCallback, then the - * initial heap limit will be restored once the heap size falls below the - * given threshold percentage of the initial heap limit. - * The threshold percentage is a number in (0.0, 1.0) range. - */ - void AutomaticallyRestoreInitialHeapLimit(double threshold_percent = 0.5); - - /** - * Set the callback to invoke to check if code generation from - * strings should be allowed. - */ - void SetModifyCodeGenerationFromStringsCallback( - ModifyCodeGenerationFromStringsCallback2 callback); - - /** - * Set the callback to invoke to check if wasm code generation should - * be allowed. - */ - void SetAllowWasmCodeGenerationCallback( - AllowWasmCodeGenerationCallback callback); - - /** - * Embedder over{ride|load} injection points for wasm APIs. The expectation - * is that the embedder sets them at most once. - */ - void SetWasmModuleCallback(ExtensionCallback callback); - void SetWasmInstanceCallback(ExtensionCallback callback); - - void SetWasmStreamingCallback(WasmStreamingCallback callback); - - void SetWasmLoadSourceMapCallback(WasmLoadSourceMapCallback callback); - - void SetWasmSimdEnabledCallback(WasmSimdEnabledCallback callback); - - void SetWasmExceptionsEnabledCallback(WasmExceptionsEnabledCallback callback); - - void SetSharedArrayBufferConstructorEnabledCallback( - SharedArrayBufferConstructorEnabledCallback callback); - - /** - * This function can be called by the embedder to signal V8 that the dynamic - * enabling of features has finished. V8 can now set up dynamically added - * features. - */ - void InstallConditionalFeatures(Local context); - - /** - * Check if V8 is dead and therefore unusable. This is the case after - * fatal errors such as out-of-memory situations. - */ - bool IsDead(); - - /** - * Adds a message listener (errors only). - * - * The same message listener can be added more than once and in that - * case it will be called more than once for each message. - * - * If data is specified, it will be passed to the callback when it is called. - * Otherwise, the exception object will be passed to the callback instead. - */ - bool AddMessageListener(MessageCallback that, - Local data = Local()); - - /** - * Adds a message listener. - * - * The same message listener can be added more than once and in that - * case it will be called more than once for each message. - * - * If data is specified, it will be passed to the callback when it is called. - * Otherwise, the exception object will be passed to the callback instead. - * - * A listener can listen for particular error levels by providing a mask. - */ - bool AddMessageListenerWithErrorLevel(MessageCallback that, - int message_levels, - Local data = Local()); - - /** - * Remove all message listeners from the specified callback function. - */ - void RemoveMessageListeners(MessageCallback that); - - /** Callback function for reporting failed access checks.*/ - void SetFailedAccessCheckCallbackFunction(FailedAccessCheckCallback); - - /** - * Tells V8 to capture current stack trace when uncaught exception occurs - * and report it to the message listeners. The option is off by default. - */ - void SetCaptureStackTraceForUncaughtExceptions( - bool capture, int frame_limit = 10, - StackTrace::StackTraceOptions options = StackTrace::kOverview); - - /** - * Iterates through all external resources referenced from current isolate - * heap. GC is not invoked prior to iterating, therefore there is no - * guarantee that visited objects are still alive. - */ - void VisitExternalResources(ExternalResourceVisitor* visitor); - - /** - * Iterates through all the persistent handles in the current isolate's heap - * that have class_ids. - */ - void VisitHandlesWithClassIds(PersistentHandleVisitor* visitor); - - /** - * Iterates through all the persistent handles in the current isolate's heap - * that have class_ids and are weak to be marked as inactive if there is no - * pending activity for the handle. - */ - void VisitWeakHandles(PersistentHandleVisitor* visitor); - - /** - * Check if this isolate is in use. - * True if at least one thread Enter'ed this isolate. - */ - bool IsInUse(); - - /** - * Set whether calling Atomics.wait (a function that may block) is allowed in - * this isolate. This can also be configured via - * CreateParams::allow_atomics_wait. - */ - void SetAllowAtomicsWait(bool allow); - - /** - * Time zone redetection indicator for - * DateTimeConfigurationChangeNotification. - * - * kSkip indicates V8 that the notification should not trigger redetecting - * host time zone. kRedetect indicates V8 that host time zone should be - * redetected, and used to set the default time zone. - * - * The host time zone detection may require file system access or similar - * operations unlikely to be available inside a sandbox. If v8 is run inside a - * sandbox, the host time zone has to be detected outside the sandbox before - * calling DateTimeConfigurationChangeNotification function. - */ - enum class TimeZoneDetection { kSkip, kRedetect }; - - /** - * Notification that the embedder has changed the time zone, daylight savings - * time or other date / time configuration parameters. V8 keeps a cache of - * various values used for date / time computation. This notification will - * reset those cached values for the current context so that date / time - * configuration changes would be reflected. - * - * This API should not be called more than needed as it will negatively impact - * the performance of date operations. - */ - void DateTimeConfigurationChangeNotification( - TimeZoneDetection time_zone_detection = TimeZoneDetection::kSkip); - - /** - * Notification that the embedder has changed the locale. V8 keeps a cache of - * various values used for locale computation. This notification will reset - * those cached values for the current context so that locale configuration - * changes would be reflected. - * - * This API should not be called more than needed as it will negatively impact - * the performance of locale operations. - */ - void LocaleConfigurationChangeNotification(); - - Isolate() = delete; - ~Isolate() = delete; - Isolate(const Isolate&) = delete; - Isolate& operator=(const Isolate&) = delete; - // Deleting operator new and delete here is allowed as ctor and dtor is also - // deleted. - void* operator new(size_t size) = delete; - void* operator new[](size_t size) = delete; - void operator delete(void*, size_t) = delete; - void operator delete[](void*, size_t) = delete; - - private: - template - friend class PersistentValueMapBase; - - internal::Address* GetDataFromSnapshotOnce(size_t index); - void ReportExternalAllocationLimitReached(); -}; - -void Isolate::SetData(uint32_t slot, void* data) { - using I = internal::Internals; - I::SetEmbedderData(this, slot, data); -} - -void* Isolate::GetData(uint32_t slot) { - using I = internal::Internals; - return I::GetEmbedderData(this, slot); -} - -uint32_t Isolate::GetNumberOfDataSlots() { - using I = internal::Internals; - return I::kNumIsolateDataSlots; -} - -template -MaybeLocal Isolate::GetDataFromSnapshotOnce(size_t index) { - T* data = reinterpret_cast(GetDataFromSnapshotOnce(index)); - if (data) internal::PerformCastCheck(data); - return Local(data); -} - -} // namespace v8 - -#endif // INCLUDE_V8_ISOLATE_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-json.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-json.h deleted file mode 100644 index 23d918fc973..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-json.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_JSON_H_ -#define INCLUDE_V8_JSON_H_ - -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Context; -class Value; -class String; - -/** - * A JSON Parser and Stringifier. - */ -class V8_EXPORT JSON { - public: - /** - * Tries to parse the string |json_string| and returns it as value if - * successful. - * - * \param the context in which to parse and create the value. - * \param json_string The string to parse. - * \return The corresponding value if successfully parsed. - */ - static V8_WARN_UNUSED_RESULT MaybeLocal Parse( - Local context, Local json_string); - - /** - * Tries to stringify the JSON-serializable object |json_object| and returns - * it as string if successful. - * - * \param json_object The JSON-serializable object to stringify. - * \return The corresponding string if successfully stringified. - */ - static V8_WARN_UNUSED_RESULT MaybeLocal Stringify( - Local context, Local json_object, - Local gap = Local()); -}; - -} // namespace v8 - -#endif // INCLUDE_V8_JSON_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-local-handle.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-local-handle.h deleted file mode 100644 index 66a8e93af60..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-local-handle.h +++ /dev/null @@ -1,459 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_LOCAL_HANDLE_H_ -#define INCLUDE_V8_LOCAL_HANDLE_H_ - -#include - -#include - -#include "v8-internal.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Boolean; -template -class BasicTracedReference; -class Context; -class EscapableHandleScope; -template -class Eternal; -template -class FunctionCallbackInfo; -class Isolate; -template -class MaybeLocal; -template -class NonCopyablePersistentTraits; -class Object; -template > -class Persistent; -template -class PersistentBase; -template -class PersistentValueMapBase; -template -class PersistentValueVector; -class Primitive; -class Private; -template -class PropertyCallbackInfo; -template -class ReturnValue; -class String; -template -class Traced; -template -class TracedGlobal; -template -class TracedReference; -class TracedReferenceBase; -class Utils; - -namespace internal { -template -class CustomArguments; -} // namespace internal - -namespace api_internal { -// Called when ToLocalChecked is called on an empty Local. -V8_EXPORT void ToLocalEmpty(); -} // namespace api_internal - -/** - * A stack-allocated class that governs a number of local handles. - * After a handle scope has been created, all local handles will be - * allocated within that handle scope until either the handle scope is - * deleted or another handle scope is created. If there is already a - * handle scope and a new one is created, all allocations will take - * place in the new handle scope until it is deleted. After that, - * new handles will again be allocated in the original handle scope. - * - * After the handle scope of a local handle has been deleted the - * garbage collector will no longer track the object stored in the - * handle and may deallocate it. The behavior of accessing a handle - * for which the handle scope has been deleted is undefined. - */ -class V8_EXPORT V8_NODISCARD HandleScope { - public: - explicit HandleScope(Isolate* isolate); - - ~HandleScope(); - - /** - * Counts the number of allocated handles. - */ - static int NumberOfHandles(Isolate* isolate); - - V8_INLINE Isolate* GetIsolate() const { - return reinterpret_cast(isolate_); - } - - HandleScope(const HandleScope&) = delete; - void operator=(const HandleScope&) = delete; - - protected: - V8_INLINE HandleScope() = default; - - void Initialize(Isolate* isolate); - - static internal::Address* CreateHandle(internal::Isolate* isolate, - internal::Address value); - - private: - // Declaring operator new and delete as deleted is not spec compliant. - // Therefore declare them private instead to disable dynamic alloc - void* operator new(size_t size); - void* operator new[](size_t size); - void operator delete(void*, size_t); - void operator delete[](void*, size_t); - - internal::Isolate* isolate_; - internal::Address* prev_next_; - internal::Address* prev_limit_; - - // Local::New uses CreateHandle with an Isolate* parameter. - template - friend class Local; - - // Object::GetInternalField and Context::GetEmbedderData use CreateHandle with - // a HeapObject in their shortcuts. - friend class Object; - friend class Context; -}; - -/** - * An object reference managed by the v8 garbage collector. - * - * All objects returned from v8 have to be tracked by the garbage collector so - * that it knows that the objects are still alive. Also, because the garbage - * collector may move objects, it is unsafe to point directly to an object. - * Instead, all objects are stored in handles which are known by the garbage - * collector and updated whenever an object moves. Handles should always be - * passed by value (except in cases like out-parameters) and they should never - * be allocated on the heap. - * - * There are two types of handles: local and persistent handles. - * - * Local handles are light-weight and transient and typically used in local - * operations. They are managed by HandleScopes. That means that a HandleScope - * must exist on the stack when they are created and that they are only valid - * inside of the HandleScope active during their creation. For passing a local - * handle to an outer HandleScope, an EscapableHandleScope and its Escape() - * method must be used. - * - * Persistent handles can be used when storing objects across several - * independent operations and have to be explicitly deallocated when they're no - * longer used. - * - * It is safe to extract the object stored in the handle by dereferencing the - * handle (for instance, to extract the Object* from a Local); the value - * will still be governed by a handle behind the scenes and the same rules apply - * to these values as to their handles. - */ -template -class Local { - public: - V8_INLINE Local() : val_(nullptr) {} - template - V8_INLINE Local(Local that) : val_(reinterpret_cast(*that)) { - /** - * This check fails when trying to convert between incompatible - * handles. For example, converting from a Local to a - * Local. - */ - static_assert(std::is_base_of::value, "type check"); - } - - /** - * Returns true if the handle is empty. - */ - V8_INLINE bool IsEmpty() const { return val_ == nullptr; } - - /** - * Sets the handle to be empty. IsEmpty() will then return true. - */ - V8_INLINE void Clear() { val_ = nullptr; } - - V8_INLINE T* operator->() const { return val_; } - - V8_INLINE T* operator*() const { return val_; } - - /** - * Checks whether two handles are the same. - * Returns true if both are empty, or if the objects to which they refer - * are identical. - * - * If both handles refer to JS objects, this is the same as strict equality. - * For primitives, such as numbers or strings, a `false` return value does not - * indicate that the values aren't equal in the JavaScript sense. - * Use `Value::StrictEquals()` to check primitives for equality. - */ - template - V8_INLINE bool operator==(const Local& that) const { - internal::Address* a = reinterpret_cast(this->val_); - internal::Address* b = reinterpret_cast(that.val_); - if (a == nullptr) return b == nullptr; - if (b == nullptr) return false; - return *a == *b; - } - - template - V8_INLINE bool operator==(const PersistentBase& that) const { - internal::Address* a = reinterpret_cast(this->val_); - internal::Address* b = reinterpret_cast(that.val_); - if (a == nullptr) return b == nullptr; - if (b == nullptr) return false; - return *a == *b; - } - - /** - * Checks whether two handles are different. - * Returns true if only one of the handles is empty, or if - * the objects to which they refer are different. - * - * If both handles refer to JS objects, this is the same as strict - * non-equality. For primitives, such as numbers or strings, a `true` return - * value does not indicate that the values aren't equal in the JavaScript - * sense. Use `Value::StrictEquals()` to check primitives for equality. - */ - template - V8_INLINE bool operator!=(const Local& that) const { - return !operator==(that); - } - - template - V8_INLINE bool operator!=(const Persistent& that) const { - return !operator==(that); - } - - /** - * Cast a handle to a subclass, e.g. Local to Local. - * This is only valid if the handle actually refers to a value of the - * target type. - */ - template - V8_INLINE static Local Cast(Local that) { -#ifdef V8_ENABLE_CHECKS - // If we're going to perform the type check then we have to check - // that the handle isn't empty before doing the checked cast. - if (that.IsEmpty()) return Local(); -#endif - return Local(T::Cast(*that)); - } - - /** - * Calling this is equivalent to Local::Cast(). - * In particular, this is only valid if the handle actually refers to a value - * of the target type. - */ - template - V8_INLINE Local As() const { - return Local::Cast(*this); - } - - /** - * Create a local handle for the content of another handle. - * The referee is kept alive by the local handle even when - * the original handle is destroyed/disposed. - */ - V8_INLINE static Local New(Isolate* isolate, Local that) { - return New(isolate, that.val_); - } - - V8_INLINE static Local New(Isolate* isolate, - const PersistentBase& that) { - return New(isolate, that.val_); - } - - V8_INLINE static Local New(Isolate* isolate, - const BasicTracedReference& that) { - return New(isolate, *that); - } - - private: - friend class TracedReferenceBase; - friend class Utils; - template - friend class Eternal; - template - friend class PersistentBase; - template - friend class Persistent; - template - friend class Local; - template - friend class MaybeLocal; - template - friend class FunctionCallbackInfo; - template - friend class PropertyCallbackInfo; - friend class String; - friend class Object; - friend class Context; - friend class Isolate; - friend class Private; - template - friend class internal::CustomArguments; - friend Local Undefined(Isolate* isolate); - friend Local Null(Isolate* isolate); - friend Local True(Isolate* isolate); - friend Local False(Isolate* isolate); - friend class HandleScope; - friend class EscapableHandleScope; - template - friend class PersistentValueMapBase; - template - friend class PersistentValueVector; - template - friend class ReturnValue; - template - friend class Traced; - template - friend class TracedGlobal; - template - friend class BasicTracedReference; - template - friend class TracedReference; - - explicit V8_INLINE Local(T* that) : val_(that) {} - V8_INLINE static Local New(Isolate* isolate, T* that) { - if (that == nullptr) return Local(); - T* that_ptr = that; - internal::Address* p = reinterpret_cast(that_ptr); - return Local(reinterpret_cast(HandleScope::CreateHandle( - reinterpret_cast(isolate), *p))); - } - T* val_; -}; - -#if !defined(V8_IMMINENT_DEPRECATION_WARNINGS) -// Handle is an alias for Local for historical reasons. -template -using Handle = Local; -#endif - -/** - * A MaybeLocal<> is a wrapper around Local<> that enforces a check whether - * the Local<> is empty before it can be used. - * - * If an API method returns a MaybeLocal<>, the API method can potentially fail - * either because an exception is thrown, or because an exception is pending, - * e.g. because a previous API call threw an exception that hasn't been caught - * yet, or because a TerminateExecution exception was thrown. In that case, an - * empty MaybeLocal is returned. - */ -template -class MaybeLocal { - public: - V8_INLINE MaybeLocal() : val_(nullptr) {} - template - V8_INLINE MaybeLocal(Local that) : val_(reinterpret_cast(*that)) { - static_assert(std::is_base_of::value, "type check"); - } - - V8_INLINE bool IsEmpty() const { return val_ == nullptr; } - - /** - * Converts this MaybeLocal<> to a Local<>. If this MaybeLocal<> is empty, - * |false| is returned and |out| is left untouched. - */ - template - V8_WARN_UNUSED_RESULT V8_INLINE bool ToLocal(Local* out) const { - out->val_ = IsEmpty() ? nullptr : this->val_; - return !IsEmpty(); - } - - /** - * Converts this MaybeLocal<> to a Local<>. If this MaybeLocal<> is empty, - * V8 will crash the process. - */ - V8_INLINE Local ToLocalChecked() { - if (V8_UNLIKELY(val_ == nullptr)) api_internal::ToLocalEmpty(); - return Local(val_); - } - - /** - * Converts this MaybeLocal<> to a Local<>, using a default value if this - * MaybeLocal<> is empty. - */ - template - V8_INLINE Local FromMaybe(Local default_value) const { - return IsEmpty() ? default_value : Local(val_); - } - - private: - T* val_; -}; - -/** - * A HandleScope which first allocates a handle in the current scope - * which will be later filled with the escape value. - */ -class V8_EXPORT V8_NODISCARD EscapableHandleScope : public HandleScope { - public: - explicit EscapableHandleScope(Isolate* isolate); - V8_INLINE ~EscapableHandleScope() = default; - - /** - * Pushes the value into the previous scope and returns a handle to it. - * Cannot be called twice. - */ - template - V8_INLINE Local Escape(Local value) { - internal::Address* slot = - Escape(reinterpret_cast(*value)); - return Local(reinterpret_cast(slot)); - } - - template - V8_INLINE MaybeLocal EscapeMaybe(MaybeLocal value) { - return Escape(value.FromMaybe(Local())); - } - - EscapableHandleScope(const EscapableHandleScope&) = delete; - void operator=(const EscapableHandleScope&) = delete; - - private: - // Declaring operator new and delete as deleted is not spec compliant. - // Therefore declare them private instead to disable dynamic alloc - void* operator new(size_t size); - void* operator new[](size_t size); - void operator delete(void*, size_t); - void operator delete[](void*, size_t); - - internal::Address* Escape(internal::Address* escape_value); - internal::Address* escape_slot_; -}; - -/** - * A SealHandleScope acts like a handle scope in which no handle allocations - * are allowed. It can be useful for debugging handle leaks. - * Handles can be allocated within inner normal HandleScopes. - */ -class V8_EXPORT V8_NODISCARD SealHandleScope { - public: - explicit SealHandleScope(Isolate* isolate); - ~SealHandleScope(); - - SealHandleScope(const SealHandleScope&) = delete; - void operator=(const SealHandleScope&) = delete; - - private: - // Declaring operator new and delete as deleted is not spec compliant. - // Therefore declare them private instead to disable dynamic alloc - void* operator new(size_t size); - void* operator new[](size_t size); - void operator delete(void*, size_t); - void operator delete[](void*, size_t); - - internal::Isolate* const isolate_; - internal::Address* prev_limit_; - int prev_sealed_level_; -}; - -} // namespace v8 - -#endif // INCLUDE_V8_LOCAL_HANDLE_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-locker.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-locker.h deleted file mode 100644 index b90fc5ed917..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-locker.h +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_LOCKER_H_ -#define INCLUDE_V8_LOCKER_H_ - -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -namespace internal { -class Isolate; -} // namespace internal - -class Isolate; - -/** - * Multiple threads in V8 are allowed, but only one thread at a time is allowed - * to use any given V8 isolate, see the comments in the Isolate class. The - * definition of 'using a V8 isolate' includes accessing handles or holding onto - * object pointers obtained from V8 handles while in the particular V8 isolate. - * It is up to the user of V8 to ensure, perhaps with locking, that this - * constraint is not violated. In addition to any other synchronization - * mechanism that may be used, the v8::Locker and v8::Unlocker classes must be - * used to signal thread switches to V8. - * - * v8::Locker is a scoped lock object. While it's active, i.e. between its - * construction and destruction, the current thread is allowed to use the locked - * isolate. V8 guarantees that an isolate can be locked by at most one thread at - * any time. In other words, the scope of a v8::Locker is a critical section. - * - * Sample usage: - * \code - * ... - * { - * v8::Locker locker(isolate); - * v8::Isolate::Scope isolate_scope(isolate); - * ... - * // Code using V8 and isolate goes here. - * ... - * } // Destructor called here - * \endcode - * - * If you wish to stop using V8 in a thread A you can do this either by - * destroying the v8::Locker object as above or by constructing a v8::Unlocker - * object: - * - * \code - * { - * isolate->Exit(); - * v8::Unlocker unlocker(isolate); - * ... - * // Code not using V8 goes here while V8 can run in another thread. - * ... - * } // Destructor called here. - * isolate->Enter(); - * \endcode - * - * The Unlocker object is intended for use in a long-running callback from V8, - * where you want to release the V8 lock for other threads to use. - * - * The v8::Locker is a recursive lock, i.e. you can lock more than once in a - * given thread. This can be useful if you have code that can be called either - * from code that holds the lock or from code that does not. The Unlocker is - * not recursive so you can not have several Unlockers on the stack at once, and - * you can not use an Unlocker in a thread that is not inside a Locker's scope. - * - * An unlocker will unlock several lockers if it has to and reinstate the - * correct depth of locking on its destruction, e.g.: - * - * \code - * // V8 not locked. - * { - * v8::Locker locker(isolate); - * Isolate::Scope isolate_scope(isolate); - * // V8 locked. - * { - * v8::Locker another_locker(isolate); - * // V8 still locked (2 levels). - * { - * isolate->Exit(); - * v8::Unlocker unlocker(isolate); - * // V8 not locked. - * } - * isolate->Enter(); - * // V8 locked again (2 levels). - * } - * // V8 still locked (1 level). - * } - * // V8 Now no longer locked. - * \endcode - */ -class V8_EXPORT Unlocker { - public: - /** - * Initialize Unlocker for a given Isolate. - */ - V8_INLINE explicit Unlocker(Isolate* isolate) { Initialize(isolate); } - - ~Unlocker(); - - private: - void Initialize(Isolate* isolate); - - internal::Isolate* isolate_; -}; - -class V8_EXPORT Locker { - public: - /** - * Initialize Locker for a given Isolate. - */ - V8_INLINE explicit Locker(Isolate* isolate) { Initialize(isolate); } - - ~Locker(); - - /** - * Returns whether or not the locker for a given isolate, is locked by the - * current thread. - */ - static bool IsLocked(Isolate* isolate); - - /** - * Returns whether v8::Locker is being used by this V8 instance. - */ - static bool IsActive(); - - // Disallow copying and assigning. - Locker(const Locker&) = delete; - void operator=(const Locker&) = delete; - - private: - void Initialize(Isolate* isolate); - - bool has_lock_; - bool top_level_; - internal::Isolate* isolate_; -}; - -} // namespace v8 - -#endif // INCLUDE_V8_LOCKER_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-maybe.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-maybe.h deleted file mode 100644 index 0532a510059..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-maybe.h +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_MAYBE_H_ -#define INCLUDE_V8_MAYBE_H_ - -#include "v8-internal.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -namespace api_internal { -// Called when ToChecked is called on an empty Maybe. -V8_EXPORT void FromJustIsNothing(); -} // namespace api_internal - -/** - * A simple Maybe type, representing an object which may or may not have a - * value, see https://hackage.haskell.org/package/base/docs/Data-Maybe.html. - * - * If an API method returns a Maybe<>, the API method can potentially fail - * either because an exception is thrown, or because an exception is pending, - * e.g. because a previous API call threw an exception that hasn't been caught - * yet, or because a TerminateExecution exception was thrown. In that case, a - * "Nothing" value is returned. - */ -template -class Maybe { - public: - V8_INLINE bool IsNothing() const { return !has_value_; } - V8_INLINE bool IsJust() const { return has_value_; } - - /** - * An alias for |FromJust|. Will crash if the Maybe<> is nothing. - */ - V8_INLINE T ToChecked() const { return FromJust(); } - - /** - * Short-hand for ToChecked(), which doesn't return a value. To be used, where - * the actual value of the Maybe is not needed like Object::Set. - */ - V8_INLINE void Check() const { - if (V8_UNLIKELY(!IsJust())) api_internal::FromJustIsNothing(); - } - - /** - * Converts this Maybe<> to a value of type T. If this Maybe<> is - * nothing (empty), |false| is returned and |out| is left untouched. - */ - V8_WARN_UNUSED_RESULT V8_INLINE bool To(T* out) const { - if (V8_LIKELY(IsJust())) *out = value_; - return IsJust(); - } - - /** - * Converts this Maybe<> to a value of type T. If this Maybe<> is - * nothing (empty), V8 will crash the process. - */ - V8_INLINE T FromJust() const { - if (V8_UNLIKELY(!IsJust())) api_internal::FromJustIsNothing(); - return value_; - } - - /** - * Converts this Maybe<> to a value of type T, using a default value if this - * Maybe<> is nothing (empty). - */ - V8_INLINE T FromMaybe(const T& default_value) const { - return has_value_ ? value_ : default_value; - } - - V8_INLINE bool operator==(const Maybe& other) const { - return (IsJust() == other.IsJust()) && - (!IsJust() || FromJust() == other.FromJust()); - } - - V8_INLINE bool operator!=(const Maybe& other) const { - return !operator==(other); - } - - private: - Maybe() : has_value_(false) {} - explicit Maybe(const T& t) : has_value_(true), value_(t) {} - - bool has_value_; - T value_; - - template - friend Maybe Nothing(); - template - friend Maybe Just(const U& u); -}; - -template -inline Maybe Nothing() { - return Maybe(); -} - -template -inline Maybe Just(const T& t) { - return Maybe(t); -} - -// A template specialization of Maybe for the case of T = void. -template <> -class Maybe { - public: - V8_INLINE bool IsNothing() const { return !is_valid_; } - V8_INLINE bool IsJust() const { return is_valid_; } - - V8_INLINE bool operator==(const Maybe& other) const { - return IsJust() == other.IsJust(); - } - - V8_INLINE bool operator!=(const Maybe& other) const { - return !operator==(other); - } - - private: - struct JustTag {}; - - Maybe() : is_valid_(false) {} - explicit Maybe(JustTag) : is_valid_(true) {} - - bool is_valid_; - - template - friend Maybe Nothing(); - friend Maybe JustVoid(); -}; - -inline Maybe JustVoid() { return Maybe(Maybe::JustTag()); } - -} // namespace v8 - -#endif // INCLUDE_V8_MAYBE_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-memory-span.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-memory-span.h deleted file mode 100644 index b26af4f705b..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-memory-span.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_MEMORY_SPAN_H_ -#define INCLUDE_V8_MEMORY_SPAN_H_ - -#include - -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -/** - * Points to an unowned continous buffer holding a known number of elements. - * - * This is similar to std::span (under consideration for C++20), but does not - * require advanced C++ support. In the (far) future, this may be replaced with - * or aliased to std::span. - * - * To facilitate future migration, this class exposes a subset of the interface - * implemented by std::span. - */ -template -class V8_EXPORT MemorySpan { - public: - /** The default constructor creates an empty span. */ - constexpr MemorySpan() = default; - - constexpr MemorySpan(T* data, size_t size) : data_(data), size_(size) {} - - /** Returns a pointer to the beginning of the buffer. */ - constexpr T* data() const { return data_; } - /** Returns the number of elements that the buffer holds. */ - constexpr size_t size() const { return size_; } - - private: - T* data_ = nullptr; - size_t size_ = 0; -}; - -} // namespace v8 -#endif // INCLUDE_V8_MEMORY_SPAN_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-message.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-message.h deleted file mode 100644 index 195ca79bd91..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-message.h +++ /dev/null @@ -1,234 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_MESSAGE_H_ -#define INCLUDE_V8_MESSAGE_H_ - -#include - -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8-maybe.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Integer; -class PrimitiveArray; -class StackTrace; -class String; -class Value; - -/** - * The optional attributes of ScriptOrigin. - */ -class ScriptOriginOptions { - public: - V8_INLINE ScriptOriginOptions(bool is_shared_cross_origin = false, - bool is_opaque = false, bool is_wasm = false, - bool is_module = false) - : flags_((is_shared_cross_origin ? kIsSharedCrossOrigin : 0) | - (is_wasm ? kIsWasm : 0) | (is_opaque ? kIsOpaque : 0) | - (is_module ? kIsModule : 0)) {} - V8_INLINE ScriptOriginOptions(int flags) - : flags_(flags & - (kIsSharedCrossOrigin | kIsOpaque | kIsWasm | kIsModule)) {} - - bool IsSharedCrossOrigin() const { - return (flags_ & kIsSharedCrossOrigin) != 0; - } - bool IsOpaque() const { return (flags_ & kIsOpaque) != 0; } - bool IsWasm() const { return (flags_ & kIsWasm) != 0; } - bool IsModule() const { return (flags_ & kIsModule) != 0; } - - int Flags() const { return flags_; } - - private: - enum { - kIsSharedCrossOrigin = 1, - kIsOpaque = 1 << 1, - kIsWasm = 1 << 2, - kIsModule = 1 << 3 - }; - const int flags_; -}; - -/** - * The origin, within a file, of a script. - */ -class V8_EXPORT ScriptOrigin { - public: - V8_DEPRECATE_SOON("Use constructor with primitive C++ types") - ScriptOrigin( - Local resource_name, Local resource_line_offset, - Local resource_column_offset, - Local resource_is_shared_cross_origin = Local(), - Local script_id = Local(), - Local source_map_url = Local(), - Local resource_is_opaque = Local(), - Local is_wasm = Local(), - Local is_module = Local(), - Local host_defined_options = Local()); - V8_DEPRECATE_SOON("Use constructor that takes an isolate") - explicit ScriptOrigin( - Local resource_name, int resource_line_offset = 0, - int resource_column_offset = 0, - bool resource_is_shared_cross_origin = false, int script_id = -1, - Local source_map_url = Local(), - bool resource_is_opaque = false, bool is_wasm = false, - bool is_module = false, - Local host_defined_options = Local()); - V8_INLINE ScriptOrigin( - Isolate* isolate, Local resource_name, - int resource_line_offset = 0, int resource_column_offset = 0, - bool resource_is_shared_cross_origin = false, int script_id = -1, - Local source_map_url = Local(), - bool resource_is_opaque = false, bool is_wasm = false, - bool is_module = false, - Local host_defined_options = Local()) - : isolate_(isolate), - resource_name_(resource_name), - resource_line_offset_(resource_line_offset), - resource_column_offset_(resource_column_offset), - options_(resource_is_shared_cross_origin, resource_is_opaque, is_wasm, - is_module), - script_id_(script_id), - source_map_url_(source_map_url), - host_defined_options_(host_defined_options) {} - - V8_INLINE Local ResourceName() const; - V8_DEPRECATE_SOON("Use getter with primitive C++ types.") - V8_INLINE Local ResourceLineOffset() const; - V8_DEPRECATE_SOON("Use getter with primitive C++ types.") - V8_INLINE Local ResourceColumnOffset() const; - V8_DEPRECATE_SOON("Use getter with primitive C++ types.") - V8_INLINE Local ScriptID() const; - V8_INLINE int LineOffset() const; - V8_INLINE int ColumnOffset() const; - V8_INLINE int ScriptId() const; - V8_INLINE Local SourceMapUrl() const; - V8_INLINE Local HostDefinedOptions() const; - V8_INLINE ScriptOriginOptions Options() const { return options_; } - - private: - Isolate* isolate_; - Local resource_name_; - int resource_line_offset_; - int resource_column_offset_; - ScriptOriginOptions options_; - int script_id_; - Local source_map_url_; - Local host_defined_options_; -}; - -/** - * An error message. - */ -class V8_EXPORT Message { - public: - Local Get() const; - - /** - * Return the isolate to which the Message belongs. - */ - Isolate* GetIsolate() const; - - V8_WARN_UNUSED_RESULT MaybeLocal GetSource( - Local context) const; - V8_WARN_UNUSED_RESULT MaybeLocal GetSourceLine( - Local context) const; - - /** - * Returns the origin for the script from where the function causing the - * error originates. - */ - ScriptOrigin GetScriptOrigin() const; - - /** - * Returns the resource name for the script from where the function causing - * the error originates. - */ - Local GetScriptResourceName() const; - - /** - * Exception stack trace. By default stack traces are not captured for - * uncaught exceptions. SetCaptureStackTraceForUncaughtExceptions allows - * to change this option. - */ - Local GetStackTrace() const; - - /** - * Returns the number, 1-based, of the line where the error occurred. - */ - V8_WARN_UNUSED_RESULT Maybe GetLineNumber(Local context) const; - - /** - * Returns the index within the script of the first character where - * the error occurred. - */ - int GetStartPosition() const; - - /** - * Returns the index within the script of the last character where - * the error occurred. - */ - int GetEndPosition() const; - - /** - * Returns the Wasm function index where the error occurred. Returns -1 if - * message is not from a Wasm script. - */ - int GetWasmFunctionIndex() const; - - /** - * Returns the error level of the message. - */ - int ErrorLevel() const; - - /** - * Returns the index within the line of the first character where - * the error occurred. - */ - int GetStartColumn() const; - V8_WARN_UNUSED_RESULT Maybe GetStartColumn(Local context) const; - - /** - * Returns the index within the line of the last character where - * the error occurred. - */ - int GetEndColumn() const; - V8_WARN_UNUSED_RESULT Maybe GetEndColumn(Local context) const; - - /** - * Passes on the value set by the embedder when it fed the script from which - * this Message was generated to V8. - */ - bool IsSharedCrossOrigin() const; - bool IsOpaque() const; - - // TODO(1245381): Print to a string instead of on a FILE. - static void PrintCurrentStackTrace(Isolate* isolate, FILE* out); - - static const int kNoLineNumberInfo = 0; - static const int kNoColumnInfo = 0; - static const int kNoScriptIdInfo = 0; - static const int kNoWasmFunctionIndexInfo = -1; -}; - -Local ScriptOrigin::ResourceName() const { return resource_name_; } - -Local ScriptOrigin::HostDefinedOptions() const { - return host_defined_options_; -} - -int ScriptOrigin::LineOffset() const { return resource_line_offset_; } - -int ScriptOrigin::ColumnOffset() const { return resource_column_offset_; } - -int ScriptOrigin::ScriptId() const { return script_id_; } - -Local ScriptOrigin::SourceMapUrl() const { return source_map_url_; } - -} // namespace v8 - -#endif // INCLUDE_V8_MESSAGE_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-metrics.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-metrics.h deleted file mode 100644 index 29e54401067..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-metrics.h +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright 2020 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef V8_METRICS_H_ -#define V8_METRICS_H_ - -#include -#include - -#include - -#include "v8-internal.h" // NOLINT(build/include_directory) -#include "v8-local-handle.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Context; -class Isolate; - -namespace metrics { - -struct GarbageCollectionPhases { - int64_t compact_wall_clock_duration_in_us = -1; - int64_t mark_wall_clock_duration_in_us = -1; - int64_t sweep_wall_clock_duration_in_us = -1; - int64_t weak_wall_clock_duration_in_us = -1; -}; - -struct GarbageCollectionSizes { - int64_t bytes_before = -1; - int64_t bytes_after = -1; - int64_t bytes_freed = -1; -}; - -struct GarbageCollectionFullCycle { - GarbageCollectionPhases total; - GarbageCollectionPhases total_cpp; - GarbageCollectionPhases main_thread; - GarbageCollectionPhases main_thread_cpp; - GarbageCollectionPhases main_thread_atomic; - GarbageCollectionPhases main_thread_atomic_cpp; - GarbageCollectionPhases main_thread_incremental; - GarbageCollectionPhases main_thread_incremental_cpp; - GarbageCollectionSizes objects; - GarbageCollectionSizes objects_cpp; - GarbageCollectionSizes memory; - GarbageCollectionSizes memory_cpp; - double collection_rate_in_percent; - double collection_rate_cpp_in_percent; - double efficiency_in_bytes_per_us; - double efficiency_cpp_in_bytes_per_us; - double main_thread_efficiency_in_bytes_per_us; - double main_thread_efficiency_cpp_in_bytes_per_us; -}; - -struct GarbageCollectionFullMainThreadIncrementalMark { - int64_t wall_clock_duration_in_us = -1; - int64_t cpp_wall_clock_duration_in_us = -1; -}; - -struct GarbageCollectionFullMainThreadBatchedIncrementalMark { - std::vector events; -}; - -struct GarbageCollectionFullMainThreadIncrementalSweep { - int64_t wall_clock_duration_in_us = -1; - int64_t cpp_wall_clock_duration_in_us = -1; -}; - -struct GarbageCollectionFullMainThreadBatchedIncrementalSweep { - std::vector events; -}; - -struct GarbageCollectionYoungCycle { - int64_t total_wall_clock_duration_in_us = -1; - int64_t main_thread_wall_clock_duration_in_us = -1; - double collection_rate_in_percent; - double efficiency_in_bytes_per_us; - double main_thread_efficiency_in_bytes_per_us; -}; - -struct WasmModuleDecoded { - bool async = false; - bool streamed = false; - bool success = false; - size_t module_size_in_bytes = 0; - size_t function_count = 0; - int64_t wall_clock_duration_in_us = -1; - int64_t cpu_duration_in_us = -1; -}; - -struct WasmModuleCompiled { - bool async = false; - bool streamed = false; - bool cached = false; - bool deserialized = false; - bool lazy = false; - bool success = false; - size_t code_size_in_bytes = 0; - size_t liftoff_bailout_count = 0; - int64_t wall_clock_duration_in_us = -1; - int64_t cpu_duration_in_us = -1; -}; - -struct WasmModuleInstantiated { - bool async = false; - bool success = false; - size_t imported_function_count = 0; - int64_t wall_clock_duration_in_us = -1; -}; - -struct WasmModuleTieredUp { - bool lazy = false; - size_t code_size_in_bytes = 0; - int64_t wall_clock_duration_in_us = -1; - int64_t cpu_duration_in_us = -1; -}; - -struct WasmModulesPerIsolate { - size_t count = 0; -}; - -#define V8_MAIN_THREAD_METRICS_EVENTS(V) \ - V(GarbageCollectionFullCycle) \ - V(GarbageCollectionFullMainThreadIncrementalMark) \ - V(GarbageCollectionFullMainThreadBatchedIncrementalMark) \ - V(GarbageCollectionFullMainThreadIncrementalSweep) \ - V(GarbageCollectionFullMainThreadBatchedIncrementalSweep) \ - V(GarbageCollectionYoungCycle) \ - V(WasmModuleDecoded) \ - V(WasmModuleCompiled) \ - V(WasmModuleInstantiated) \ - V(WasmModuleTieredUp) - -#define V8_THREAD_SAFE_METRICS_EVENTS(V) V(WasmModulesPerIsolate) - -/** - * This class serves as a base class for recording event-based metrics in V8. - * There a two kinds of metrics, those which are expected to be thread-safe and - * whose implementation is required to fulfill this requirement and those whose - * implementation does not have that requirement and only needs to be - * executable on the main thread. If such an event is triggered from a - * background thread, it will be delayed and executed by the foreground task - * runner. - * - * The thread-safe events are listed in the V8_THREAD_SAFE_METRICS_EVENTS - * macro above while the main thread event are listed in - * V8_MAIN_THREAD_METRICS_EVENTS above. For the former, a virtual method - * AddMainThreadEvent(const E& event, v8::Context::Token token) will be - * generated and for the latter AddThreadSafeEvent(const E& event). - * - * Thread-safe events are not allowed to access the context and therefore do - * not carry a context ID with them. These IDs can be generated using - * Recorder::GetContextId() and the ID will be valid throughout the lifetime - * of the isolate. It is not guaranteed that the ID will still resolve to - * a valid context using Recorder::GetContext() at the time the metric is - * recorded. In this case, an empty handle will be returned. - * - * The embedder is expected to call v8::Isolate::SetMetricsRecorder() - * providing its implementation and have the virtual methods overwritten - * for the events it cares about. - */ -class V8_EXPORT Recorder { - public: - // A unique identifier for a context in this Isolate. - // It is guaranteed to not be reused throughout the lifetime of the Isolate. - class ContextId { - public: - ContextId() : id_(kEmptyId) {} - - bool IsEmpty() const { return id_ == kEmptyId; } - static const ContextId Empty() { return ContextId{kEmptyId}; } - - bool operator==(const ContextId& other) const { return id_ == other.id_; } - bool operator!=(const ContextId& other) const { return id_ != other.id_; } - - private: - friend class ::v8::Context; - friend class ::v8::internal::Isolate; - - explicit ContextId(uintptr_t id) : id_(id) {} - - static constexpr uintptr_t kEmptyId = 0; - uintptr_t id_; - }; - - virtual ~Recorder() = default; - -#define ADD_MAIN_THREAD_EVENT(E) \ - virtual void AddMainThreadEvent(const E& event, ContextId context_id) {} - V8_MAIN_THREAD_METRICS_EVENTS(ADD_MAIN_THREAD_EVENT) -#undef ADD_MAIN_THREAD_EVENT - -#define ADD_THREAD_SAFE_EVENT(E) \ - virtual void AddThreadSafeEvent(const E& event) {} - V8_THREAD_SAFE_METRICS_EVENTS(ADD_THREAD_SAFE_EVENT) -#undef ADD_THREAD_SAFE_EVENT - - virtual void NotifyIsolateDisposal() {} - - // Return the context with the given id or an empty handle if the context - // was already garbage collected. - static MaybeLocal GetContext(Isolate* isolate, ContextId id); - // Return the unique id corresponding to the given context. - static ContextId GetContextId(Local context); -}; - -/** - * Experimental API intended for the LongTasks UKM (crbug.com/1173527). - * The Reset() method should be called at the start of a potential - * long task. The Get() method returns durations of V8 work that - * happened during the task. - * - * This API is experimental and may be removed/changed in the future. - */ -struct V8_EXPORT LongTaskStats { - /** - * Resets durations of V8 work for the new task. - */ - V8_INLINE static void Reset(Isolate* isolate) { - v8::internal::Internals::IncrementLongTasksStatsCounter(isolate); - } - - /** - * Returns durations of V8 work that happened since the last Reset(). - */ - static LongTaskStats Get(Isolate* isolate); - - int64_t gc_full_atomic_wall_clock_duration_us = 0; - int64_t gc_full_incremental_wall_clock_duration_us = 0; - int64_t gc_young_wall_clock_duration_us = 0; -}; - -} // namespace metrics -} // namespace v8 - -#endif // V8_METRICS_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-microtask-queue.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-microtask-queue.h deleted file mode 100644 index af9caa54a8f..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-microtask-queue.h +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_MICROTASKS_QUEUE_H_ -#define INCLUDE_V8_MICROTASKS_QUEUE_H_ - -#include - -#include - -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8-microtask.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Function; - -namespace internal { -class Isolate; -class MicrotaskQueue; -} // namespace internal - -/** - * Represents the microtask queue, where microtasks are stored and processed. - * https://html.spec.whatwg.org/multipage/webappapis.html#microtask-queue - * https://html.spec.whatwg.org/multipage/webappapis.html#enqueuejob(queuename,-job,-arguments) - * https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint - * - * A MicrotaskQueue instance may be associated to multiple Contexts by passing - * it to Context::New(), and they can be detached by Context::DetachGlobal(). - * The embedder must keep the MicrotaskQueue instance alive until all associated - * Contexts are gone or detached. - * - * Use the same instance of MicrotaskQueue for all Contexts that may access each - * other synchronously. E.g. for Web embedding, use the same instance for all - * origins that share the same URL scheme and eTLD+1. - */ -class V8_EXPORT MicrotaskQueue { - public: - /** - * Creates an empty MicrotaskQueue instance. - */ - static std::unique_ptr New( - Isolate* isolate, MicrotasksPolicy policy = MicrotasksPolicy::kAuto); - - virtual ~MicrotaskQueue() = default; - - /** - * Enqueues the callback to the queue. - */ - virtual void EnqueueMicrotask(Isolate* isolate, - Local microtask) = 0; - - /** - * Enqueues the callback to the queue. - */ - virtual void EnqueueMicrotask(v8::Isolate* isolate, - MicrotaskCallback callback, - void* data = nullptr) = 0; - - /** - * Adds a callback to notify the embedder after microtasks were run. The - * callback is triggered by explicit RunMicrotasks call or automatic - * microtasks execution (see Isolate::SetMicrotasksPolicy). - * - * Callback will trigger even if microtasks were attempted to run, - * but the microtasks queue was empty and no single microtask was actually - * executed. - * - * Executing scripts inside the callback will not re-trigger microtasks and - * the callback. - */ - virtual void AddMicrotasksCompletedCallback( - MicrotasksCompletedCallbackWithData callback, void* data = nullptr) = 0; - - /** - * Removes callback that was installed by AddMicrotasksCompletedCallback. - */ - virtual void RemoveMicrotasksCompletedCallback( - MicrotasksCompletedCallbackWithData callback, void* data = nullptr) = 0; - - /** - * Runs microtasks if no microtask is running on this MicrotaskQueue instance. - */ - virtual void PerformCheckpoint(Isolate* isolate) = 0; - - /** - * Returns true if a microtask is running on this MicrotaskQueue instance. - */ - virtual bool IsRunningMicrotasks() const = 0; - - /** - * Returns the current depth of nested MicrotasksScope that has - * kRunMicrotasks. - */ - virtual int GetMicrotasksScopeDepth() const = 0; - - MicrotaskQueue(const MicrotaskQueue&) = delete; - MicrotaskQueue& operator=(const MicrotaskQueue&) = delete; - - private: - friend class internal::MicrotaskQueue; - MicrotaskQueue() = default; -}; - -/** - * This scope is used to control microtasks when MicrotasksPolicy::kScoped - * is used on Isolate. In this mode every non-primitive call to V8 should be - * done inside some MicrotasksScope. - * Microtasks are executed when topmost MicrotasksScope marked as kRunMicrotasks - * exits. - * kDoNotRunMicrotasks should be used to annotate calls not intended to trigger - * microtasks. - */ -class V8_EXPORT V8_NODISCARD MicrotasksScope { - public: - enum Type { kRunMicrotasks, kDoNotRunMicrotasks }; - - MicrotasksScope(Isolate* isolate, Type type); - MicrotasksScope(Isolate* isolate, MicrotaskQueue* microtask_queue, Type type); - ~MicrotasksScope(); - - /** - * Runs microtasks if no kRunMicrotasks scope is currently active. - */ - static void PerformCheckpoint(Isolate* isolate); - - /** - * Returns current depth of nested kRunMicrotasks scopes. - */ - static int GetCurrentDepth(Isolate* isolate); - - /** - * Returns true while microtasks are being executed. - */ - static bool IsRunningMicrotasks(Isolate* isolate); - - // Prevent copying. - MicrotasksScope(const MicrotasksScope&) = delete; - MicrotasksScope& operator=(const MicrotasksScope&) = delete; - - private: - internal::Isolate* const isolate_; - internal::MicrotaskQueue* const microtask_queue_; - bool run_; -}; - -} // namespace v8 - -#endif // INCLUDE_V8_MICROTASKS_QUEUE_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-microtask.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-microtask.h deleted file mode 100644 index c159203608d..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-microtask.h +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_MICROTASK_H_ -#define INCLUDE_V8_MICROTASK_H_ - -namespace v8 { - -class Isolate; - -// --- Microtasks Callbacks --- -using MicrotasksCompletedCallbackWithData = void (*)(Isolate*, void*); -using MicrotaskCallback = void (*)(void* data); - -/** - * Policy for running microtasks: - * - explicit: microtasks are invoked with the - * Isolate::PerformMicrotaskCheckpoint() method; - * - scoped: microtasks invocation is controlled by MicrotasksScope objects; - * - auto: microtasks are invoked when the script call depth decrements - * to zero. - */ -enum class MicrotasksPolicy { kExplicit, kScoped, kAuto }; - -} // namespace v8 - -#endif // INCLUDE_V8_MICROTASK_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-object.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-object.h deleted file mode 100644 index 114e452a380..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-object.h +++ /dev/null @@ -1,770 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_OBJECT_H_ -#define INCLUDE_V8_OBJECT_H_ - -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8-maybe.h" // NOLINT(build/include_directory) -#include "v8-persistent-handle.h" // NOLINT(build/include_directory) -#include "v8-primitive.h" // NOLINT(build/include_directory) -#include "v8-traced-handle.h" // NOLINT(build/include_directory) -#include "v8-value.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Array; -class Function; -class FunctionTemplate; -template -class PropertyCallbackInfo; - -/** - * A private symbol - * - * This is an experimental feature. Use at your own risk. - */ -class V8_EXPORT Private : public Data { - public: - /** - * Returns the print name string of the private symbol, or undefined if none. - */ - Local Name() const; - - /** - * Create a private symbol. If name is not empty, it will be the description. - */ - static Local New(Isolate* isolate, - Local name = Local()); - - /** - * Retrieve a global private symbol. If a symbol with this name has not - * been retrieved in the same isolate before, it is created. - * Note that private symbols created this way are never collected, so - * they should only be used for statically fixed properties. - * Also, there is only one global name space for the names used as keys. - * To minimize the potential for clashes, use qualified names as keys, - * e.g., "Class#property". - */ - static Local ForApi(Isolate* isolate, Local name); - - V8_INLINE static Private* Cast(Data* data); - - private: - Private(); - - static void CheckCast(Data* that); -}; - -/** - * An instance of a Property Descriptor, see Ecma-262 6.2.4. - * - * Properties in a descriptor are present or absent. If you do not set - * `enumerable`, `configurable`, and `writable`, they are absent. If `value`, - * `get`, or `set` are absent, but you must specify them in the constructor, use - * empty handles. - * - * Accessors `get` and `set` must be callable or undefined if they are present. - * - * \note Only query properties if they are present, i.e., call `x()` only if - * `has_x()` returns true. - * - * \code - * // var desc = {writable: false} - * v8::PropertyDescriptor d(Local()), false); - * d.value(); // error, value not set - * if (d.has_writable()) { - * d.writable(); // false - * } - * - * // var desc = {value: undefined} - * v8::PropertyDescriptor d(v8::Undefined(isolate)); - * - * // var desc = {get: undefined} - * v8::PropertyDescriptor d(v8::Undefined(isolate), Local())); - * \endcode - */ -class V8_EXPORT PropertyDescriptor { - public: - // GenericDescriptor - PropertyDescriptor(); - - // DataDescriptor - explicit PropertyDescriptor(Local value); - - // DataDescriptor with writable property - PropertyDescriptor(Local value, bool writable); - - // AccessorDescriptor - PropertyDescriptor(Local get, Local set); - - ~PropertyDescriptor(); - - Local value() const; - bool has_value() const; - - Local get() const; - bool has_get() const; - Local set() const; - bool has_set() const; - - void set_enumerable(bool enumerable); - bool enumerable() const; - bool has_enumerable() const; - - void set_configurable(bool configurable); - bool configurable() const; - bool has_configurable() const; - - bool writable() const; - bool has_writable() const; - - struct PrivateData; - PrivateData* get_private() const { return private_; } - - PropertyDescriptor(const PropertyDescriptor&) = delete; - void operator=(const PropertyDescriptor&) = delete; - - private: - PrivateData* private_; -}; - -/** - * PropertyAttribute. - */ -enum PropertyAttribute { - /** None. **/ - None = 0, - /** ReadOnly, i.e., not writable. **/ - ReadOnly = 1 << 0, - /** DontEnum, i.e., not enumerable. **/ - DontEnum = 1 << 1, - /** DontDelete, i.e., not configurable. **/ - DontDelete = 1 << 2 -}; - -/** - * Accessor[Getter|Setter] are used as callback functions when - * setting|getting a particular property. See Object and ObjectTemplate's - * method SetAccessor. - */ -using AccessorGetterCallback = - void (*)(Local property, const PropertyCallbackInfo& info); -using AccessorNameGetterCallback = - void (*)(Local property, const PropertyCallbackInfo& info); - -using AccessorSetterCallback = void (*)(Local property, - Local value, - const PropertyCallbackInfo& info); -using AccessorNameSetterCallback = - void (*)(Local property, Local value, - const PropertyCallbackInfo& info); - -/** - * Access control specifications. - * - * Some accessors should be accessible across contexts. These - * accessors have an explicit access control parameter which specifies - * the kind of cross-context access that should be allowed. - * - * TODO(dcarney): Remove PROHIBITS_OVERWRITING as it is now unused. - */ -enum AccessControl { - DEFAULT = 0, - ALL_CAN_READ = 1, - ALL_CAN_WRITE = 1 << 1, - PROHIBITS_OVERWRITING = 1 << 2 -}; - -/** - * Property filter bits. They can be or'ed to build a composite filter. - */ -enum PropertyFilter { - ALL_PROPERTIES = 0, - ONLY_WRITABLE = 1, - ONLY_ENUMERABLE = 2, - ONLY_CONFIGURABLE = 4, - SKIP_STRINGS = 8, - SKIP_SYMBOLS = 16 -}; - -/** - * Options for marking whether callbacks may trigger JS-observable side effects. - * Side-effect-free callbacks are allowlisted during debug evaluation with - * throwOnSideEffect. It applies when calling a Function, FunctionTemplate, - * or an Accessor callback. For Interceptors, please see - * PropertyHandlerFlags's kHasNoSideEffect. - * Callbacks that only cause side effects to the receiver are allowlisted if - * invoked on receiver objects that are created within the same debug-evaluate - * call, as these objects are temporary and the side effect does not escape. - */ -enum class SideEffectType { - kHasSideEffect, - kHasNoSideEffect, - kHasSideEffectToReceiver -}; - -/** - * Keys/Properties filter enums: - * - * KeyCollectionMode limits the range of collected properties. kOwnOnly limits - * the collected properties to the given Object only. kIncludesPrototypes will - * include all keys of the objects's prototype chain as well. - */ -enum class KeyCollectionMode { kOwnOnly, kIncludePrototypes }; - -/** - * kIncludesIndices allows for integer indices to be collected, while - * kSkipIndices will exclude integer indices from being collected. - */ -enum class IndexFilter { kIncludeIndices, kSkipIndices }; - -/** - * kConvertToString will convert integer indices to strings. - * kKeepNumbers will return numbers for integer indices. - */ -enum class KeyConversionMode { kConvertToString, kKeepNumbers, kNoNumbers }; - -/** - * Integrity level for objects. - */ -enum class IntegrityLevel { kFrozen, kSealed }; - -/** - * A JavaScript object (ECMA-262, 4.3.3) - */ -class V8_EXPORT Object : public Value { - public: - /** - * Set only return Just(true) or Empty(), so if it should never fail, use - * result.Check(). - */ - V8_WARN_UNUSED_RESULT Maybe Set(Local context, - Local key, Local value); - - V8_WARN_UNUSED_RESULT Maybe Set(Local context, uint32_t index, - Local value); - - // Implements CreateDataProperty (ECMA-262, 7.3.4). - // - // Defines a configurable, writable, enumerable property with the given value - // on the object unless the property already exists and is not configurable - // or the object is not extensible. - // - // Returns true on success. - V8_WARN_UNUSED_RESULT Maybe CreateDataProperty(Local context, - Local key, - Local value); - V8_WARN_UNUSED_RESULT Maybe CreateDataProperty(Local context, - uint32_t index, - Local value); - - // Implements DefineOwnProperty. - // - // In general, CreateDataProperty will be faster, however, does not allow - // for specifying attributes. - // - // Returns true on success. - V8_WARN_UNUSED_RESULT Maybe DefineOwnProperty( - Local context, Local key, Local value, - PropertyAttribute attributes = None); - - // Implements Object.DefineProperty(O, P, Attributes), see Ecma-262 19.1.2.4. - // - // The defineProperty function is used to add an own property or - // update the attributes of an existing own property of an object. - // - // Both data and accessor descriptors can be used. - // - // In general, CreateDataProperty is faster, however, does not allow - // for specifying attributes or an accessor descriptor. - // - // The PropertyDescriptor can change when redefining a property. - // - // Returns true on success. - V8_WARN_UNUSED_RESULT Maybe DefineProperty( - Local context, Local key, PropertyDescriptor& descriptor); - - V8_WARN_UNUSED_RESULT MaybeLocal Get(Local context, - Local key); - - V8_WARN_UNUSED_RESULT MaybeLocal Get(Local context, - uint32_t index); - - /** - * Gets the property attributes of a property which can be None or - * any combination of ReadOnly, DontEnum and DontDelete. Returns - * None when the property doesn't exist. - */ - V8_WARN_UNUSED_RESULT Maybe GetPropertyAttributes( - Local context, Local key); - - /** - * Returns Object.getOwnPropertyDescriptor as per ES2016 section 19.1.2.6. - */ - V8_WARN_UNUSED_RESULT MaybeLocal GetOwnPropertyDescriptor( - Local context, Local key); - - /** - * Object::Has() calls the abstract operation HasProperty(O, P) described - * in ECMA-262, 7.3.10. Has() returns - * true, if the object has the property, either own or on the prototype chain. - * Interceptors, i.e., PropertyQueryCallbacks, are called if present. - * - * Has() has the same side effects as JavaScript's `variable in object`. - * For example, calling Has() on a revoked proxy will throw an exception. - * - * \note Has() converts the key to a name, which possibly calls back into - * JavaScript. - * - * See also v8::Object::HasOwnProperty() and - * v8::Object::HasRealNamedProperty(). - */ - V8_WARN_UNUSED_RESULT Maybe Has(Local context, - Local key); - - V8_WARN_UNUSED_RESULT Maybe Delete(Local context, - Local key); - - V8_WARN_UNUSED_RESULT Maybe Has(Local context, uint32_t index); - - V8_WARN_UNUSED_RESULT Maybe Delete(Local context, - uint32_t index); - - /** - * Note: SideEffectType affects the getter only, not the setter. - */ - V8_WARN_UNUSED_RESULT Maybe SetAccessor( - Local context, Local name, - AccessorNameGetterCallback getter, - AccessorNameSetterCallback setter = nullptr, - MaybeLocal data = MaybeLocal(), - AccessControl settings = DEFAULT, PropertyAttribute attribute = None, - SideEffectType getter_side_effect_type = SideEffectType::kHasSideEffect, - SideEffectType setter_side_effect_type = SideEffectType::kHasSideEffect); - - void SetAccessorProperty(Local name, Local getter, - Local setter = Local(), - PropertyAttribute attribute = None, - AccessControl settings = DEFAULT); - - /** - * Sets a native data property like Template::SetNativeDataProperty, but - * this method sets on this object directly. - */ - V8_WARN_UNUSED_RESULT Maybe SetNativeDataProperty( - Local context, Local name, - AccessorNameGetterCallback getter, - AccessorNameSetterCallback setter = nullptr, - Local data = Local(), PropertyAttribute attributes = None, - SideEffectType getter_side_effect_type = SideEffectType::kHasSideEffect, - SideEffectType setter_side_effect_type = SideEffectType::kHasSideEffect); - - /** - * Attempts to create a property with the given name which behaves like a data - * property, except that the provided getter is invoked (and provided with the - * data value) to supply its value the first time it is read. After the - * property is accessed once, it is replaced with an ordinary data property. - * - * Analogous to Template::SetLazyDataProperty. - */ - V8_WARN_UNUSED_RESULT Maybe SetLazyDataProperty( - Local context, Local name, - AccessorNameGetterCallback getter, Local data = Local(), - PropertyAttribute attributes = None, - SideEffectType getter_side_effect_type = SideEffectType::kHasSideEffect, - SideEffectType setter_side_effect_type = SideEffectType::kHasSideEffect); - - /** - * Functionality for private properties. - * This is an experimental feature, use at your own risk. - * Note: Private properties are not inherited. Do not rely on this, since it - * may change. - */ - Maybe HasPrivate(Local context, Local key); - Maybe SetPrivate(Local context, Local key, - Local value); - Maybe DeletePrivate(Local context, Local key); - MaybeLocal GetPrivate(Local context, Local key); - - /** - * Returns an array containing the names of the enumerable properties - * of this object, including properties from prototype objects. The - * array returned by this method contains the same values as would - * be enumerated by a for-in statement over this object. - */ - V8_WARN_UNUSED_RESULT MaybeLocal GetPropertyNames( - Local context); - V8_WARN_UNUSED_RESULT MaybeLocal GetPropertyNames( - Local context, KeyCollectionMode mode, - PropertyFilter property_filter, IndexFilter index_filter, - KeyConversionMode key_conversion = KeyConversionMode::kKeepNumbers); - - /** - * This function has the same functionality as GetPropertyNames but - * the returned array doesn't contain the names of properties from - * prototype objects. - */ - V8_WARN_UNUSED_RESULT MaybeLocal GetOwnPropertyNames( - Local context); - - /** - * Returns an array containing the names of the filtered properties - * of this object, including properties from prototype objects. The - * array returned by this method contains the same values as would - * be enumerated by a for-in statement over this object. - */ - V8_WARN_UNUSED_RESULT MaybeLocal GetOwnPropertyNames( - Local context, PropertyFilter filter, - KeyConversionMode key_conversion = KeyConversionMode::kKeepNumbers); - - /** - * Get the prototype object. This does not skip objects marked to - * be skipped by __proto__ and it does not consult the security - * handler. - */ - Local GetPrototype(); - - /** - * Set the prototype object. This does not skip objects marked to - * be skipped by __proto__ and it does not consult the security - * handler. - */ - V8_WARN_UNUSED_RESULT Maybe SetPrototype(Local context, - Local prototype); - - /** - * Finds an instance of the given function template in the prototype - * chain. - */ - Local FindInstanceInPrototypeChain(Local tmpl); - - /** - * Call builtin Object.prototype.toString on this object. - * This is different from Value::ToString() that may call - * user-defined toString function. This one does not. - */ - V8_WARN_UNUSED_RESULT MaybeLocal ObjectProtoToString( - Local context); - - /** - * Returns the name of the function invoked as a constructor for this object. - */ - Local GetConstructorName(); - - /** - * Sets the integrity level of the object. - */ - Maybe SetIntegrityLevel(Local context, IntegrityLevel level); - - /** Gets the number of internal fields for this Object. */ - int InternalFieldCount() const; - - /** Same as above, but works for PersistentBase. */ - V8_INLINE static int InternalFieldCount( - const PersistentBase& object) { - return object.val_->InternalFieldCount(); - } - - /** Same as above, but works for BasicTracedReference. */ - V8_INLINE static int InternalFieldCount( - const BasicTracedReference& object) { - return object->InternalFieldCount(); - } - - /** Gets the value from an internal field. */ - V8_INLINE Local GetInternalField(int index); - - /** Sets the value in an internal field. */ - void SetInternalField(int index, Local value); - - /** - * Gets a 2-byte-aligned native pointer from an internal field. This field - * must have been set by SetAlignedPointerInInternalField, everything else - * leads to undefined behavior. - */ - V8_INLINE void* GetAlignedPointerFromInternalField(int index); - - /** Same as above, but works for PersistentBase. */ - V8_INLINE static void* GetAlignedPointerFromInternalField( - const PersistentBase& object, int index) { - return object.val_->GetAlignedPointerFromInternalField(index); - } - - /** Same as above, but works for TracedGlobal. */ - V8_INLINE static void* GetAlignedPointerFromInternalField( - const BasicTracedReference& object, int index) { - return object->GetAlignedPointerFromInternalField(index); - } - - /** - * Sets a 2-byte-aligned native pointer in an internal field. To retrieve such - * a field, GetAlignedPointerFromInternalField must be used, everything else - * leads to undefined behavior. - */ - void SetAlignedPointerInInternalField(int index, void* value); - void SetAlignedPointerInInternalFields(int argc, int indices[], - void* values[]); - - /** - * HasOwnProperty() is like JavaScript's Object.prototype.hasOwnProperty(). - * - * See also v8::Object::Has() and v8::Object::HasRealNamedProperty(). - */ - V8_WARN_UNUSED_RESULT Maybe HasOwnProperty(Local context, - Local key); - V8_WARN_UNUSED_RESULT Maybe HasOwnProperty(Local context, - uint32_t index); - /** - * Use HasRealNamedProperty() if you want to check if an object has an own - * property without causing side effects, i.e., without calling interceptors. - * - * This function is similar to v8::Object::HasOwnProperty(), but it does not - * call interceptors. - * - * \note Consider using non-masking interceptors, i.e., the interceptors are - * not called if the receiver has the real named property. See - * `v8::PropertyHandlerFlags::kNonMasking`. - * - * See also v8::Object::Has(). - */ - V8_WARN_UNUSED_RESULT Maybe HasRealNamedProperty(Local context, - Local key); - V8_WARN_UNUSED_RESULT Maybe HasRealIndexedProperty( - Local context, uint32_t index); - V8_WARN_UNUSED_RESULT Maybe HasRealNamedCallbackProperty( - Local context, Local key); - - /** - * If result.IsEmpty() no real property was located in the prototype chain. - * This means interceptors in the prototype chain are not called. - */ - V8_WARN_UNUSED_RESULT MaybeLocal GetRealNamedPropertyInPrototypeChain( - Local context, Local key); - - /** - * Gets the property attributes of a real property in the prototype chain, - * which can be None or any combination of ReadOnly, DontEnum and DontDelete. - * Interceptors in the prototype chain are not called. - */ - V8_WARN_UNUSED_RESULT Maybe - GetRealNamedPropertyAttributesInPrototypeChain(Local context, - Local key); - - /** - * If result.IsEmpty() no real property was located on the object or - * in the prototype chain. - * This means interceptors in the prototype chain are not called. - */ - V8_WARN_UNUSED_RESULT MaybeLocal GetRealNamedProperty( - Local context, Local key); - - /** - * Gets the property attributes of a real property which can be - * None or any combination of ReadOnly, DontEnum and DontDelete. - * Interceptors in the prototype chain are not called. - */ - V8_WARN_UNUSED_RESULT Maybe GetRealNamedPropertyAttributes( - Local context, Local key); - - /** Tests for a named lookup interceptor.*/ - bool HasNamedLookupInterceptor() const; - - /** Tests for an index lookup interceptor.*/ - bool HasIndexedLookupInterceptor() const; - - /** - * Returns the identity hash for this object. The current implementation - * uses a hidden property on the object to store the identity hash. - * - * The return value will never be 0. Also, it is not guaranteed to be - * unique. - */ - int GetIdentityHash(); - - /** - * Clone this object with a fast but shallow copy. Values will point - * to the same values as the original object. - */ - // TODO(dcarney): take an isolate and optionally bail out? - Local Clone(); - - /** - * Returns the context in which the object was created. - */ - V8_DEPRECATE_SOON("Use MaybeLocal GetCreationContext()") - Local CreationContext(); - MaybeLocal GetCreationContext(); - - /** Same as above, but works for Persistents */ - V8_DEPRECATE_SOON( - "Use MaybeLocal GetCreationContext(const " - "PersistentBase& object)") - static Local CreationContext(const PersistentBase& object); - V8_INLINE static MaybeLocal GetCreationContext( - const PersistentBase& object) { - return object.val_->GetCreationContext(); - } - - /** - * Checks whether a callback is set by the - * ObjectTemplate::SetCallAsFunctionHandler method. - * When an Object is callable this method returns true. - */ - bool IsCallable() const; - - /** - * True if this object is a constructor. - */ - bool IsConstructor() const; - - /** - * True if this object can carry information relevant to the embedder in its - * embedder fields, false otherwise. This is generally true for objects - * constructed through function templates but also holds for other types where - * V8 automatically adds internal fields at compile time, such as e.g. - * v8::ArrayBuffer. - */ - bool IsApiWrapper() const; - - /** - * True if this object was created from an object template which was marked - * as undetectable. See v8::ObjectTemplate::MarkAsUndetectable for more - * information. - */ - bool IsUndetectable() const; - - /** - * Call an Object as a function if a callback is set by the - * ObjectTemplate::SetCallAsFunctionHandler method. - */ - V8_WARN_UNUSED_RESULT MaybeLocal CallAsFunction(Local context, - Local recv, - int argc, - Local argv[]); - - /** - * Call an Object as a constructor if a callback is set by the - * ObjectTemplate::SetCallAsFunctionHandler method. - * Note: This method behaves like the Function::NewInstance method. - */ - V8_WARN_UNUSED_RESULT MaybeLocal CallAsConstructor( - Local context, int argc, Local argv[]); - - /** - * Return the isolate to which the Object belongs to. - */ - Isolate* GetIsolate(); - - /** - * If this object is a Set, Map, WeakSet or WeakMap, this returns a - * representation of the elements of this object as an array. - * If this object is a SetIterator or MapIterator, this returns all - * elements of the underlying collection, starting at the iterator's current - * position. - * For other types, this will return an empty MaybeLocal (without - * scheduling an exception). - */ - MaybeLocal PreviewEntries(bool* is_key_value); - - static Local New(Isolate* isolate); - - /** - * Creates a JavaScript object with the given properties, and - * a the given prototype_or_null (which can be any JavaScript - * value, and if it's null, the newly created object won't have - * a prototype at all). This is similar to Object.create(). - * All properties will be created as enumerable, configurable - * and writable properties. - */ - static Local New(Isolate* isolate, Local prototype_or_null, - Local* names, Local* values, - size_t length); - - V8_INLINE static Object* Cast(Value* obj); - - /** - * Support for TC39 "dynamic code brand checks" proposal. - * - * This API allows to query whether an object was constructed from a - * "code like" ObjectTemplate. - * - * See also: v8::ObjectTemplate::SetCodeLike - */ - bool IsCodeLike(Isolate* isolate) const; - - private: - Object(); - static void CheckCast(Value* obj); - Local SlowGetInternalField(int index); - void* SlowGetAlignedPointerFromInternalField(int index); -}; - -// --- Implementation --- - -Local Object::GetInternalField(int index) { -#ifndef V8_ENABLE_CHECKS - using A = internal::Address; - using I = internal::Internals; - A obj = *reinterpret_cast(this); - // Fast path: If the object is a plain JSObject, which is the common case, we - // know where to find the internal fields and can return the value directly. - int instance_type = I::GetInstanceType(obj); - if (v8::internal::CanHaveInternalField(instance_type)) { - int offset = I::kJSObjectHeaderSize + (I::kEmbedderDataSlotSize * index); - A value = I::ReadRawField(obj, offset); -#ifdef V8_COMPRESS_POINTERS - // We read the full pointer value and then decompress it in order to avoid - // dealing with potential endiannes issues. - value = I::DecompressTaggedAnyField(obj, static_cast(value)); -#endif - internal::Isolate* isolate = - internal::IsolateFromNeverReadOnlySpaceObject(obj); - A* result = HandleScope::CreateHandle(isolate, value); - return Local(reinterpret_cast(result)); - } -#endif - return SlowGetInternalField(index); -} - -void* Object::GetAlignedPointerFromInternalField(int index) { -#ifndef V8_ENABLE_CHECKS - using A = internal::Address; - using I = internal::Internals; - A obj = *reinterpret_cast(this); - // Fast path: If the object is a plain JSObject, which is the common case, we - // know where to find the internal fields and can return the value directly. - auto instance_type = I::GetInstanceType(obj); - if (v8::internal::CanHaveInternalField(instance_type)) { - int offset = I::kJSObjectHeaderSize + (I::kEmbedderDataSlotSize * index); -#ifdef V8_HEAP_SANDBOX - offset += I::kEmbedderDataSlotRawPayloadOffset; -#endif - internal::Isolate* isolate = I::GetIsolateForHeapSandbox(obj); - A value = I::ReadExternalPointerField( - isolate, obj, offset, internal::kEmbedderDataSlotPayloadTag); - return reinterpret_cast(value); - } -#endif - return SlowGetAlignedPointerFromInternalField(index); -} - -Private* Private::Cast(Data* data) { -#ifdef V8_ENABLE_CHECKS - CheckCast(data); -#endif - return reinterpret_cast(data); -} - -Object* Object::Cast(v8::Value* value) { -#ifdef V8_ENABLE_CHECKS - CheckCast(value); -#endif - return static_cast(value); -} - -} // namespace v8 - -#endif // INCLUDE_V8_OBJECT_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-persistent-handle.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-persistent-handle.h deleted file mode 100644 index a6c21268d6a..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-persistent-handle.h +++ /dev/null @@ -1,590 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_PERSISTENT_HANDLE_H_ -#define INCLUDE_V8_PERSISTENT_HANDLE_H_ - -#include "v8-internal.h" // NOLINT(build/include_directory) -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8-weak-callback-info.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Isolate; -template -class PersistentValueMapBase; -template -class PersistentValueVector; -template -class Global; -template -class PersistentBase; -template -class PersistentValueMap; -class Value; - -namespace api_internal { -V8_EXPORT Value* Eternalize(v8::Isolate* isolate, Value* handle); -V8_EXPORT internal::Address* CopyGlobalReference(internal::Address* from); -V8_EXPORT void DisposeGlobal(internal::Address* global_handle); -V8_EXPORT void MakeWeak(internal::Address** location_addr); -V8_EXPORT void* ClearWeak(internal::Address* location); -V8_EXPORT void AnnotateStrongRetainer(internal::Address* location, - const char* label); -V8_EXPORT internal::Address* GlobalizeReference(internal::Isolate* isolate, - internal::Address* handle); -V8_EXPORT void MoveGlobalReference(internal::Address** from, - internal::Address** to); -} // namespace api_internal - -/** - * Eternal handles are set-once handles that live for the lifetime of the - * isolate. - */ -template -class Eternal { - public: - V8_INLINE Eternal() : val_(nullptr) {} - template - V8_INLINE Eternal(Isolate* isolate, Local handle) : val_(nullptr) { - Set(isolate, handle); - } - // Can only be safely called if already set. - V8_INLINE Local Get(Isolate* isolate) const { - // The eternal handle will never go away, so as with the roots, we don't - // even need to open a handle. - return Local(val_); - } - - V8_INLINE bool IsEmpty() const { return val_ == nullptr; } - - template - void Set(Isolate* isolate, Local handle) { - static_assert(std::is_base_of::value, "type check"); - val_ = reinterpret_cast( - api_internal::Eternalize(isolate, reinterpret_cast(*handle))); - } - - private: - T* val_; -}; - -namespace api_internal { -V8_EXPORT void MakeWeak(internal::Address* location, void* data, - WeakCallbackInfo::Callback weak_callback, - WeakCallbackType type); -} // namespace api_internal - -/** - * An object reference that is independent of any handle scope. Where - * a Local handle only lives as long as the HandleScope in which it was - * allocated, a PersistentBase handle remains valid until it is explicitly - * disposed using Reset(). - * - * A persistent handle contains a reference to a storage cell within - * the V8 engine which holds an object value and which is updated by - * the garbage collector whenever the object is moved. A new storage - * cell can be created using the constructor or PersistentBase::Reset and - * existing handles can be disposed using PersistentBase::Reset. - * - */ -template -class PersistentBase { - public: - /** - * If non-empty, destroy the underlying storage cell - * IsEmpty() will return true after this call. - */ - V8_INLINE void Reset(); - - /** - * If non-empty, destroy the underlying storage cell - * and create a new one with the contents of other if other is non empty - */ - template - V8_INLINE void Reset(Isolate* isolate, const Local& other); - - /** - * If non-empty, destroy the underlying storage cell - * and create a new one with the contents of other if other is non empty - */ - template - V8_INLINE void Reset(Isolate* isolate, const PersistentBase& other); - - V8_INLINE bool IsEmpty() const { return val_ == nullptr; } - V8_INLINE void Empty() { val_ = 0; } - - V8_INLINE Local Get(Isolate* isolate) const { - return Local::New(isolate, *this); - } - - template - V8_INLINE bool operator==(const PersistentBase& that) const { - internal::Address* a = reinterpret_cast(this->val_); - internal::Address* b = reinterpret_cast(that.val_); - if (a == nullptr) return b == nullptr; - if (b == nullptr) return false; - return *a == *b; - } - - template - V8_INLINE bool operator==(const Local& that) const { - internal::Address* a = reinterpret_cast(this->val_); - internal::Address* b = reinterpret_cast(that.val_); - if (a == nullptr) return b == nullptr; - if (b == nullptr) return false; - return *a == *b; - } - - template - V8_INLINE bool operator!=(const PersistentBase& that) const { - return !operator==(that); - } - - template - V8_INLINE bool operator!=(const Local& that) const { - return !operator==(that); - } - - /** - * Install a finalization callback on this object. - * NOTE: There is no guarantee as to *when* or even *if* the callback is - * invoked. The invocation is performed solely on a best effort basis. - * As always, GC-based finalization should *not* be relied upon for any - * critical form of resource management! - * - * The callback is supposed to reset the handle. No further V8 API may be - * called in this callback. In case additional work involving V8 needs to be - * done, a second callback can be scheduled using - * WeakCallbackInfo::SetSecondPassCallback. - */ - template - V8_INLINE void SetWeak(P* parameter, - typename WeakCallbackInfo

::Callback callback, - WeakCallbackType type); - - /** - * Turns this handle into a weak phantom handle without finalization callback. - * The handle will be reset automatically when the garbage collector detects - * that the object is no longer reachable. - * A related function Isolate::NumberOfPhantomHandleResetsSinceLastCall - * returns how many phantom handles were reset by the garbage collector. - */ - V8_INLINE void SetWeak(); - - template - V8_INLINE P* ClearWeak(); - - // TODO(dcarney): remove this. - V8_INLINE void ClearWeak() { ClearWeak(); } - - /** - * Annotates the strong handle with the given label, which is then used by the - * heap snapshot generator as a name of the edge from the root to the handle. - * The function does not take ownership of the label and assumes that the - * label is valid as long as the handle is valid. - */ - V8_INLINE void AnnotateStrongRetainer(const char* label); - - /** Returns true if the handle's reference is weak. */ - V8_INLINE bool IsWeak() const; - - /** - * Assigns a wrapper class ID to the handle. - */ - V8_INLINE void SetWrapperClassId(uint16_t class_id); - - /** - * Returns the class ID previously assigned to this handle or 0 if no class ID - * was previously assigned. - */ - V8_INLINE uint16_t WrapperClassId() const; - - PersistentBase(const PersistentBase& other) = delete; - void operator=(const PersistentBase&) = delete; - - private: - friend class Isolate; - friend class Utils; - template - friend class Local; - template - friend class Persistent; - template - friend class Global; - template - friend class PersistentBase; - template - friend class ReturnValue; - template - friend class PersistentValueMapBase; - template - friend class PersistentValueVector; - friend class Object; - - explicit V8_INLINE PersistentBase(T* val) : val_(val) {} - V8_INLINE static T* New(Isolate* isolate, T* that); - - T* val_; -}; - -/** - * Default traits for Persistent. This class does not allow - * use of the copy constructor or assignment operator. - * At present kResetInDestructor is not set, but that will change in a future - * version. - */ -template -class NonCopyablePersistentTraits { - public: - using NonCopyablePersistent = Persistent>; - static const bool kResetInDestructor = false; - template - V8_INLINE static void Copy(const Persistent& source, - NonCopyablePersistent* dest) { - static_assert(sizeof(S) < 0, - "NonCopyablePersistentTraits::Copy is not instantiable"); - } -}; - -/** - * Helper class traits to allow copying and assignment of Persistent. - * This will clone the contents of storage cell, but not any of the flags, etc. - */ -template -struct CopyablePersistentTraits { - using CopyablePersistent = Persistent>; - static const bool kResetInDestructor = true; - template - static V8_INLINE void Copy(const Persistent& source, - CopyablePersistent* dest) { - // do nothing, just allow copy - } -}; - -/** - * A PersistentBase which allows copy and assignment. - * - * Copy, assignment and destructor behavior is controlled by the traits - * class M. - * - * Note: Persistent class hierarchy is subject to future changes. - */ -template -class Persistent : public PersistentBase { - public: - /** - * A Persistent with no storage cell. - */ - V8_INLINE Persistent() : PersistentBase(nullptr) {} - /** - * Construct a Persistent from a Local. - * When the Local is non-empty, a new storage cell is created - * pointing to the same object, and no flags are set. - */ - template - V8_INLINE Persistent(Isolate* isolate, Local that) - : PersistentBase(PersistentBase::New(isolate, *that)) { - static_assert(std::is_base_of::value, "type check"); - } - /** - * Construct a Persistent from a Persistent. - * When the Persistent is non-empty, a new storage cell is created - * pointing to the same object, and no flags are set. - */ - template - V8_INLINE Persistent(Isolate* isolate, const Persistent& that) - : PersistentBase(PersistentBase::New(isolate, *that)) { - static_assert(std::is_base_of::value, "type check"); - } - /** - * The copy constructors and assignment operator create a Persistent - * exactly as the Persistent constructor, but the Copy function from the - * traits class is called, allowing the setting of flags based on the - * copied Persistent. - */ - V8_INLINE Persistent(const Persistent& that) : PersistentBase(nullptr) { - Copy(that); - } - template - V8_INLINE Persistent(const Persistent& that) : PersistentBase(0) { - Copy(that); - } - V8_INLINE Persistent& operator=(const Persistent& that) { - Copy(that); - return *this; - } - template - V8_INLINE Persistent& operator=(const Persistent& that) { - Copy(that); - return *this; - } - /** - * The destructor will dispose the Persistent based on the - * kResetInDestructor flags in the traits class. Since not calling dispose - * can result in a memory leak, it is recommended to always set this flag. - */ - V8_INLINE ~Persistent() { - if (M::kResetInDestructor) this->Reset(); - } - - // TODO(dcarney): this is pretty useless, fix or remove - template - V8_INLINE static Persistent& Cast(const Persistent& that) { -#ifdef V8_ENABLE_CHECKS - // If we're going to perform the type check then we have to check - // that the handle isn't empty before doing the checked cast. - if (!that.IsEmpty()) T::Cast(*that); -#endif - return reinterpret_cast&>(const_cast&>(that)); - } - - // TODO(dcarney): this is pretty useless, fix or remove - template - V8_INLINE Persistent& As() const { - return Persistent::Cast(*this); - } - - private: - friend class Isolate; - friend class Utils; - template - friend class Local; - template - friend class Persistent; - template - friend class ReturnValue; - - explicit V8_INLINE Persistent(T* that) : PersistentBase(that) {} - V8_INLINE T* operator*() const { return this->val_; } - template - V8_INLINE void Copy(const Persistent& that); -}; - -/** - * A PersistentBase which has move semantics. - * - * Note: Persistent class hierarchy is subject to future changes. - */ -template -class Global : public PersistentBase { - public: - /** - * A Global with no storage cell. - */ - V8_INLINE Global() : PersistentBase(nullptr) {} - - /** - * Construct a Global from a Local. - * When the Local is non-empty, a new storage cell is created - * pointing to the same object, and no flags are set. - */ - template - V8_INLINE Global(Isolate* isolate, Local that) - : PersistentBase(PersistentBase::New(isolate, *that)) { - static_assert(std::is_base_of::value, "type check"); - } - - /** - * Construct a Global from a PersistentBase. - * When the Persistent is non-empty, a new storage cell is created - * pointing to the same object, and no flags are set. - */ - template - V8_INLINE Global(Isolate* isolate, const PersistentBase& that) - : PersistentBase(PersistentBase::New(isolate, that.val_)) { - static_assert(std::is_base_of::value, "type check"); - } - - /** - * Move constructor. - */ - V8_INLINE Global(Global&& other); - - V8_INLINE ~Global() { this->Reset(); } - - /** - * Move via assignment. - */ - template - V8_INLINE Global& operator=(Global&& rhs); - - /** - * Pass allows returning uniques from functions, etc. - */ - Global Pass() { return static_cast(*this); } - - /* - * For compatibility with Chromium's base::Bind (base::Passed). - */ - using MoveOnlyTypeForCPP03 = void; - - Global(const Global&) = delete; - void operator=(const Global&) = delete; - - private: - template - friend class ReturnValue; - V8_INLINE T* operator*() const { return this->val_; } -}; - -// UniquePersistent is an alias for Global for historical reason. -template -using UniquePersistent = Global; - -/** - * Interface for iterating through all the persistent handles in the heap. - */ -class V8_EXPORT PersistentHandleVisitor { - public: - virtual ~PersistentHandleVisitor() = default; - virtual void VisitPersistentHandle(Persistent* value, - uint16_t class_id) {} -}; - -template -T* PersistentBase::New(Isolate* isolate, T* that) { - if (that == nullptr) return nullptr; - internal::Address* p = reinterpret_cast(that); - return reinterpret_cast(api_internal::GlobalizeReference( - reinterpret_cast(isolate), p)); -} - -template -template -void Persistent::Copy(const Persistent& that) { - static_assert(std::is_base_of::value, "type check"); - this->Reset(); - if (that.IsEmpty()) return; - internal::Address* p = reinterpret_cast(that.val_); - this->val_ = reinterpret_cast(api_internal::CopyGlobalReference(p)); - M::Copy(that, this); -} - -template -bool PersistentBase::IsWeak() const { - using I = internal::Internals; - if (this->IsEmpty()) return false; - return I::GetNodeState(reinterpret_cast(this->val_)) == - I::kNodeStateIsWeakValue; -} - -template -void PersistentBase::Reset() { - if (this->IsEmpty()) return; - api_internal::DisposeGlobal(reinterpret_cast(this->val_)); - val_ = nullptr; -} - -/** - * If non-empty, destroy the underlying storage cell - * and create a new one with the contents of other if other is non empty - */ -template -template -void PersistentBase::Reset(Isolate* isolate, const Local& other) { - static_assert(std::is_base_of::value, "type check"); - Reset(); - if (other.IsEmpty()) return; - this->val_ = New(isolate, other.val_); -} - -/** - * If non-empty, destroy the underlying storage cell - * and create a new one with the contents of other if other is non empty - */ -template -template -void PersistentBase::Reset(Isolate* isolate, - const PersistentBase& other) { - static_assert(std::is_base_of::value, "type check"); - Reset(); - if (other.IsEmpty()) return; - this->val_ = New(isolate, other.val_); -} - -template -template -V8_INLINE void PersistentBase::SetWeak( - P* parameter, typename WeakCallbackInfo

::Callback callback, - WeakCallbackType type) { - using Callback = WeakCallbackInfo::Callback; -#if (__GNUC__ >= 8) && !defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-function-type" -#endif - api_internal::MakeWeak(reinterpret_cast(this->val_), - parameter, reinterpret_cast(callback), type); -#if (__GNUC__ >= 8) && !defined(__clang__) -#pragma GCC diagnostic pop -#endif -} - -template -void PersistentBase::SetWeak() { - api_internal::MakeWeak(reinterpret_cast(&this->val_)); -} - -template -template -P* PersistentBase::ClearWeak() { - return reinterpret_cast(api_internal::ClearWeak( - reinterpret_cast(this->val_))); -} - -template -void PersistentBase::AnnotateStrongRetainer(const char* label) { - api_internal::AnnotateStrongRetainer( - reinterpret_cast(this->val_), label); -} - -template -void PersistentBase::SetWrapperClassId(uint16_t class_id) { - using I = internal::Internals; - if (this->IsEmpty()) return; - internal::Address* obj = reinterpret_cast(this->val_); - uint8_t* addr = reinterpret_cast(obj) + I::kNodeClassIdOffset; - *reinterpret_cast(addr) = class_id; -} - -template -uint16_t PersistentBase::WrapperClassId() const { - using I = internal::Internals; - if (this->IsEmpty()) return 0; - internal::Address* obj = reinterpret_cast(this->val_); - uint8_t* addr = reinterpret_cast(obj) + I::kNodeClassIdOffset; - return *reinterpret_cast(addr); -} - -template -Global::Global(Global&& other) : PersistentBase(other.val_) { - if (other.val_ != nullptr) { - api_internal::MoveGlobalReference( - reinterpret_cast(&other.val_), - reinterpret_cast(&this->val_)); - other.val_ = nullptr; - } -} - -template -template -Global& Global::operator=(Global&& rhs) { - static_assert(std::is_base_of::value, "type check"); - if (this != &rhs) { - this->Reset(); - if (rhs.val_ != nullptr) { - this->val_ = rhs.val_; - api_internal::MoveGlobalReference( - reinterpret_cast(&rhs.val_), - reinterpret_cast(&this->val_)); - rhs.val_ = nullptr; - } - } - return *this; -} - -} // namespace v8 - -#endif // INCLUDE_V8_PERSISTENT_HANDLE_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-platform.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-platform.h deleted file mode 100644 index dee399fa771..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-platform.h +++ /dev/null @@ -1,713 +0,0 @@ -// Copyright 2013 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef V8_V8_PLATFORM_H_ -#define V8_V8_PLATFORM_H_ - -#include -#include -#include // For abort. -#include -#include - -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Isolate; - -// Valid priorities supported by the task scheduling infrastructure. -enum class TaskPriority : uint8_t { - /** - * Best effort tasks are not critical for performance of the application. The - * platform implementation should preempt such tasks if higher priority tasks - * arrive. - */ - kBestEffort, - /** - * User visible tasks are long running background tasks that will - * improve performance and memory usage of the application upon completion. - * Example: background compilation and garbage collection. - */ - kUserVisible, - /** - * User blocking tasks are highest priority tasks that block the execution - * thread (e.g. major garbage collection). They must be finished as soon as - * possible. - */ - kUserBlocking, -}; - -/** - * A Task represents a unit of work. - */ -class Task { - public: - virtual ~Task() = default; - - virtual void Run() = 0; -}; - -/** - * An IdleTask represents a unit of work to be performed in idle time. - * The Run method is invoked with an argument that specifies the deadline in - * seconds returned by MonotonicallyIncreasingTime(). - * The idle task is expected to complete by this deadline. - */ -class IdleTask { - public: - virtual ~IdleTask() = default; - virtual void Run(double deadline_in_seconds) = 0; -}; - -/** - * A TaskRunner allows scheduling of tasks. The TaskRunner may still be used to - * post tasks after the isolate gets destructed, but these tasks may not get - * executed anymore. All tasks posted to a given TaskRunner will be invoked in - * sequence. Tasks can be posted from any thread. - */ -class TaskRunner { - public: - /** - * Schedules a task to be invoked by this TaskRunner. The TaskRunner - * implementation takes ownership of |task|. - */ - virtual void PostTask(std::unique_ptr task) = 0; - - /** - * Schedules a task to be invoked by this TaskRunner. The TaskRunner - * implementation takes ownership of |task|. The |task| cannot be nested - * within other task executions. - * - * Tasks which shouldn't be interleaved with JS execution must be posted with - * |PostNonNestableTask| or |PostNonNestableDelayedTask|. This is because the - * embedder may process tasks in a callback which is called during JS - * execution. - * - * In particular, tasks which execute JS must be non-nestable, since JS - * execution is not allowed to nest. - * - * Requires that |TaskRunner::NonNestableTasksEnabled()| is true. - */ - virtual void PostNonNestableTask(std::unique_ptr task) {} - - /** - * Schedules a task to be invoked by this TaskRunner. The task is scheduled - * after the given number of seconds |delay_in_seconds|. The TaskRunner - * implementation takes ownership of |task|. - */ - virtual void PostDelayedTask(std::unique_ptr task, - double delay_in_seconds) = 0; - - /** - * Schedules a task to be invoked by this TaskRunner. The task is scheduled - * after the given number of seconds |delay_in_seconds|. The TaskRunner - * implementation takes ownership of |task|. The |task| cannot be nested - * within other task executions. - * - * Tasks which shouldn't be interleaved with JS execution must be posted with - * |PostNonNestableTask| or |PostNonNestableDelayedTask|. This is because the - * embedder may process tasks in a callback which is called during JS - * execution. - * - * In particular, tasks which execute JS must be non-nestable, since JS - * execution is not allowed to nest. - * - * Requires that |TaskRunner::NonNestableDelayedTasksEnabled()| is true. - */ - virtual void PostNonNestableDelayedTask(std::unique_ptr task, - double delay_in_seconds) {} - - /** - * Schedules an idle task to be invoked by this TaskRunner. The task is - * scheduled when the embedder is idle. Requires that - * |TaskRunner::IdleTasksEnabled()| is true. Idle tasks may be reordered - * relative to other task types and may be starved for an arbitrarily long - * time if no idle time is available. The TaskRunner implementation takes - * ownership of |task|. - */ - virtual void PostIdleTask(std::unique_ptr task) = 0; - - /** - * Returns true if idle tasks are enabled for this TaskRunner. - */ - virtual bool IdleTasksEnabled() = 0; - - /** - * Returns true if non-nestable tasks are enabled for this TaskRunner. - */ - virtual bool NonNestableTasksEnabled() const { return false; } - - /** - * Returns true if non-nestable delayed tasks are enabled for this TaskRunner. - */ - virtual bool NonNestableDelayedTasksEnabled() const { return false; } - - TaskRunner() = default; - virtual ~TaskRunner() = default; - - TaskRunner(const TaskRunner&) = delete; - TaskRunner& operator=(const TaskRunner&) = delete; -}; - -/** - * Delegate that's passed to Job's worker task, providing an entry point to - * communicate with the scheduler. - */ -class JobDelegate { - public: - /** - * Returns true if this thread should return from the worker task on the - * current thread ASAP. Workers should periodically invoke ShouldYield (or - * YieldIfNeeded()) as often as is reasonable. - */ - virtual bool ShouldYield() = 0; - - /** - * Notifies the scheduler that max concurrency was increased, and the number - * of worker should be adjusted accordingly. See Platform::PostJob() for more - * details. - */ - virtual void NotifyConcurrencyIncrease() = 0; - - /** - * Returns a task_id unique among threads currently running this job, such - * that GetTaskId() < worker count. To achieve this, the same task_id may be - * reused by a different thread after a worker_task returns. - */ - virtual uint8_t GetTaskId() = 0; - - /** - * Returns true if the current task is called from the thread currently - * running JobHandle::Join(). - */ - virtual bool IsJoiningThread() const = 0; -}; - -/** - * Handle returned when posting a Job. Provides methods to control execution of - * the posted Job. - */ -class JobHandle { - public: - virtual ~JobHandle() = default; - - /** - * Notifies the scheduler that max concurrency was increased, and the number - * of worker should be adjusted accordingly. See Platform::PostJob() for more - * details. - */ - virtual void NotifyConcurrencyIncrease() = 0; - - /** - * Contributes to the job on this thread. Doesn't return until all tasks have - * completed and max concurrency becomes 0. When Join() is called and max - * concurrency reaches 0, it should not increase again. This also promotes - * this Job's priority to be at least as high as the calling thread's - * priority. - */ - virtual void Join() = 0; - - /** - * Forces all existing workers to yield ASAP. Waits until they have all - * returned from the Job's callback before returning. - */ - virtual void Cancel() = 0; - - /* - * Forces all existing workers to yield ASAP but doesn’t wait for them. - * Warning, this is dangerous if the Job's callback is bound to or has access - * to state which may be deleted after this call. - */ - virtual void CancelAndDetach() = 0; - - /** - * Returns true if there's any work pending or any worker running. - */ - virtual bool IsActive() = 0; - - /** - * Returns true if associated with a Job and other methods may be called. - * Returns false after Join() or Cancel() was called. This may return true - * even if no workers are running and IsCompleted() returns true - */ - virtual bool IsValid() = 0; - - /** - * Returns true if job priority can be changed. - */ - virtual bool UpdatePriorityEnabled() const { return false; } - - /** - * Update this Job's priority. - */ - virtual void UpdatePriority(TaskPriority new_priority) {} -}; - -/** - * A JobTask represents work to run in parallel from Platform::PostJob(). - */ -class JobTask { - public: - virtual ~JobTask() = default; - - virtual void Run(JobDelegate* delegate) = 0; - - /** - * Controls the maximum number of threads calling Run() concurrently, given - * the number of threads currently assigned to this job and executing Run(). - * Run() is only invoked if the number of threads previously running Run() was - * less than the value returned. Since GetMaxConcurrency() is a leaf function, - * it must not call back any JobHandle methods. - */ - virtual size_t GetMaxConcurrency(size_t worker_count) const = 0; -}; - -/** - * The interface represents complex arguments to trace events. - */ -class ConvertableToTraceFormat { - public: - virtual ~ConvertableToTraceFormat() = default; - - /** - * Append the class info to the provided |out| string. The appended - * data must be a valid JSON object. Strings must be properly quoted, and - * escaped. There is no processing applied to the content after it is - * appended. - */ - virtual void AppendAsTraceFormat(std::string* out) const = 0; -}; - -/** - * V8 Tracing controller. - * - * Can be implemented by an embedder to record trace events from V8. - */ -class TracingController { - public: - virtual ~TracingController() = default; - - // In Perfetto mode, trace events are written using Perfetto's Track Event - // API directly without going through the embedder. However, it is still - // possible to observe tracing being enabled and disabled. -#if !defined(V8_USE_PERFETTO) - /** - * Called by TRACE_EVENT* macros, don't call this directly. - * The name parameter is a category group for example: - * TRACE_EVENT0("v8,parse", "V8.Parse") - * The pointer returned points to a value with zero or more of the bits - * defined in CategoryGroupEnabledFlags. - **/ - virtual const uint8_t* GetCategoryGroupEnabled(const char* name) { - static uint8_t no = 0; - return &no; - } - - /** - * Adds a trace event to the platform tracing system. These function calls are - * usually the result of a TRACE_* macro from trace_event_common.h when - * tracing and the category of the particular trace are enabled. It is not - * advisable to call these functions on their own; they are really only meant - * to be used by the trace macros. The returned handle can be used by - * UpdateTraceEventDuration to update the duration of COMPLETE events. - */ - virtual uint64_t AddTraceEvent( - char phase, const uint8_t* category_enabled_flag, const char* name, - const char* scope, uint64_t id, uint64_t bind_id, int32_t num_args, - const char** arg_names, const uint8_t* arg_types, - const uint64_t* arg_values, - std::unique_ptr* arg_convertables, - unsigned int flags) { - return 0; - } - virtual uint64_t AddTraceEventWithTimestamp( - char phase, const uint8_t* category_enabled_flag, const char* name, - const char* scope, uint64_t id, uint64_t bind_id, int32_t num_args, - const char** arg_names, const uint8_t* arg_types, - const uint64_t* arg_values, - std::unique_ptr* arg_convertables, - unsigned int flags, int64_t timestamp) { - return 0; - } - - /** - * Sets the duration field of a COMPLETE trace event. It must be called with - * the handle returned from AddTraceEvent(). - **/ - virtual void UpdateTraceEventDuration(const uint8_t* category_enabled_flag, - const char* name, uint64_t handle) {} -#endif // !defined(V8_USE_PERFETTO) - - class TraceStateObserver { - public: - virtual ~TraceStateObserver() = default; - virtual void OnTraceEnabled() = 0; - virtual void OnTraceDisabled() = 0; - }; - - /** Adds tracing state change observer. */ - virtual void AddTraceStateObserver(TraceStateObserver*) {} - - /** Removes tracing state change observer. */ - virtual void RemoveTraceStateObserver(TraceStateObserver*) {} -}; - -/** - * A V8 memory page allocator. - * - * Can be implemented by an embedder to manage large host OS allocations. - */ -class PageAllocator { - public: - virtual ~PageAllocator() = default; - - /** - * Gets the page granularity for AllocatePages and FreePages. Addresses and - * lengths for those calls should be multiples of AllocatePageSize(). - */ - virtual size_t AllocatePageSize() = 0; - - /** - * Gets the page granularity for SetPermissions and ReleasePages. Addresses - * and lengths for those calls should be multiples of CommitPageSize(). - */ - virtual size_t CommitPageSize() = 0; - - /** - * Sets the random seed so that GetRandomMmapAddr() will generate repeatable - * sequences of random mmap addresses. - */ - virtual void SetRandomMmapSeed(int64_t seed) = 0; - - /** - * Returns a randomized address, suitable for memory allocation under ASLR. - * The address will be aligned to AllocatePageSize. - */ - virtual void* GetRandomMmapAddr() = 0; - - /** - * Memory permissions. - */ - enum Permission { - kNoAccess, - kRead, - kReadWrite, - kReadWriteExecute, - kReadExecute, - // Set this when reserving memory that will later require kReadWriteExecute - // permissions. The resulting behavior is platform-specific, currently - // this is used to set the MAP_JIT flag on Apple Silicon. - // TODO(jkummerow): Remove this when Wasm has a platform-independent - // w^x implementation. - kNoAccessWillJitLater - }; - - /** - * Allocates memory in range with the given alignment and permission. - */ - virtual void* AllocatePages(void* address, size_t length, size_t alignment, - Permission permissions) = 0; - - /** - * Frees memory in a range that was allocated by a call to AllocatePages. - */ - virtual bool FreePages(void* address, size_t length) = 0; - - /** - * Releases memory in a range that was allocated by a call to AllocatePages. - */ - virtual bool ReleasePages(void* address, size_t length, - size_t new_length) = 0; - - /** - * Sets permissions on pages in an allocated range. - */ - virtual bool SetPermissions(void* address, size_t length, - Permission permissions) = 0; - - /** - * Frees memory in the given [address, address + size) range. address and size - * should be operating system page-aligned. The next write to this - * memory area brings the memory transparently back. This should be treated as - * a hint to the OS that the pages are no longer needed. It does not guarantee - * that the pages will be discarded immediately or at all. - */ - virtual bool DiscardSystemPages(void* address, size_t size) { return true; } - - /** - * Decommits any wired memory pages in the given range, allowing the OS to - * reclaim them, and marks the region as inacessible (kNoAccess). The address - * range stays reserved and can be accessed again later by changing its - * permissions. However, in that case the memory content is guaranteed to be - * zero-initialized again. The memory must have been previously allocated by a - * call to AllocatePages. Returns true on success, false otherwise. - */ -#ifdef V8_VIRTUAL_MEMORY_CAGE - // Implementing this API is required when the virtual memory cage is enabled. - virtual bool DecommitPages(void* address, size_t size) = 0; -#else - // Otherwise, it is optional for now. - virtual bool DecommitPages(void* address, size_t size) { return false; } -#endif - - /** - * INTERNAL ONLY: This interface has not been stabilised and may change - * without notice from one release to another without being deprecated first. - */ - class SharedMemoryMapping { - public: - // Implementations are expected to free the shared memory mapping in the - // destructor. - virtual ~SharedMemoryMapping() = default; - virtual void* GetMemory() const = 0; - }; - - /** - * INTERNAL ONLY: This interface has not been stabilised and may change - * without notice from one release to another without being deprecated first. - */ - class SharedMemory { - public: - // Implementations are expected to free the shared memory in the destructor. - virtual ~SharedMemory() = default; - virtual std::unique_ptr RemapTo( - void* new_address) const = 0; - virtual void* GetMemory() const = 0; - virtual size_t GetSize() const = 0; - }; - - /** - * INTERNAL ONLY: This interface has not been stabilised and may change - * without notice from one release to another without being deprecated first. - * - * Reserve pages at a fixed address returning whether the reservation is - * possible. The reserved memory is detached from the PageAllocator and so - * should not be freed by it. It's intended for use with - * SharedMemory::RemapTo, where ~SharedMemoryMapping would free the memory. - */ - virtual bool ReserveForSharedMemoryMapping(void* address, size_t size) { - return false; - } - - /** - * INTERNAL ONLY: This interface has not been stabilised and may change - * without notice from one release to another without being deprecated first. - * - * Allocates shared memory pages. Not all PageAllocators need support this and - * so this method need not be overridden. - * Allocates a new read-only shared memory region of size |length| and copies - * the memory at |original_address| into it. - */ - virtual std::unique_ptr AllocateSharedPages( - size_t length, const void* original_address) { - return {}; - } - - /** - * INTERNAL ONLY: This interface has not been stabilised and may change - * without notice from one release to another without being deprecated first. - * - * If not overridden and changed to return true, V8 will not attempt to call - * AllocateSharedPages or RemapSharedPages. If overridden, AllocateSharedPages - * and RemapSharedPages must also be overridden. - */ - virtual bool CanAllocateSharedPages() { return false; } -}; - -/** - * V8 Platform abstraction layer. - * - * The embedder has to provide an implementation of this interface before - * initializing the rest of V8. - */ -class Platform { - public: - virtual ~Platform() = default; - - /** - * Allows the embedder to manage memory page allocations. - */ - virtual PageAllocator* GetPageAllocator() { - // TODO(bbudge) Make this abstract after all embedders implement this. - return nullptr; - } - - /** - * Enables the embedder to respond in cases where V8 can't allocate large - * blocks of memory. V8 retries the failed allocation once after calling this - * method. On success, execution continues; otherwise V8 exits with a fatal - * error. - * Embedder overrides of this function must NOT call back into V8. - */ - virtual void OnCriticalMemoryPressure() { - // TODO(bbudge) Remove this when embedders override the following method. - // See crbug.com/634547. - } - - /** - * Enables the embedder to respond in cases where V8 can't allocate large - * memory regions. The |length| parameter is the amount of memory needed. - * Returns true if memory is now available. Returns false if no memory could - * be made available. V8 will retry allocations until this method returns - * false. - * - * Embedder overrides of this function must NOT call back into V8. - */ - virtual bool OnCriticalMemoryPressure(size_t length) { return false; } - - /** - * Gets the number of worker threads used by - * Call(BlockingTask)OnWorkerThread(). This can be used to estimate the number - * of tasks a work package should be split into. A return value of 0 means - * that there are no worker threads available. Note that a value of 0 won't - * prohibit V8 from posting tasks using |CallOnWorkerThread|. - */ - virtual int NumberOfWorkerThreads() = 0; - - /** - * Returns a TaskRunner which can be used to post a task on the foreground. - * The TaskRunner's NonNestableTasksEnabled() must be true. This function - * should only be called from a foreground thread. - */ - virtual std::shared_ptr GetForegroundTaskRunner( - Isolate* isolate) = 0; - - /** - * Schedules a task to be invoked on a worker thread. - */ - virtual void CallOnWorkerThread(std::unique_ptr task) = 0; - - /** - * Schedules a task that blocks the main thread to be invoked with - * high-priority on a worker thread. - */ - virtual void CallBlockingTaskOnWorkerThread(std::unique_ptr task) { - // Embedders may optionally override this to process these tasks in a high - // priority pool. - CallOnWorkerThread(std::move(task)); - } - - /** - * Schedules a task to be invoked with low-priority on a worker thread. - */ - virtual void CallLowPriorityTaskOnWorkerThread(std::unique_ptr task) { - // Embedders may optionally override this to process these tasks in a low - // priority pool. - CallOnWorkerThread(std::move(task)); - } - - /** - * Schedules a task to be invoked on a worker thread after |delay_in_seconds| - * expires. - */ - virtual void CallDelayedOnWorkerThread(std::unique_ptr task, - double delay_in_seconds) = 0; - - /** - * Returns true if idle tasks are enabled for the given |isolate|. - */ - virtual bool IdleTasksEnabled(Isolate* isolate) { return false; } - - /** - * Posts |job_task| to run in parallel. Returns a JobHandle associated with - * the Job, which can be joined or canceled. - * This avoids degenerate cases: - * - Calling CallOnWorkerThread() for each work item, causing significant - * overhead. - * - Fixed number of CallOnWorkerThread() calls that split the work and might - * run for a long time. This is problematic when many components post - * "num cores" tasks and all expect to use all the cores. In these cases, - * the scheduler lacks context to be fair to multiple same-priority requests - * and/or ability to request lower priority work to yield when high priority - * work comes in. - * A canonical implementation of |job_task| looks like: - * class MyJobTask : public JobTask { - * public: - * MyJobTask(...) : worker_queue_(...) {} - * // JobTask: - * void Run(JobDelegate* delegate) override { - * while (!delegate->ShouldYield()) { - * // Smallest unit of work. - * auto work_item = worker_queue_.TakeWorkItem(); // Thread safe. - * if (!work_item) return; - * ProcessWork(work_item); - * } - * } - * - * size_t GetMaxConcurrency() const override { - * return worker_queue_.GetSize(); // Thread safe. - * } - * }; - * auto handle = PostJob(TaskPriority::kUserVisible, - * std::make_unique(...)); - * handle->Join(); - * - * PostJob() and methods of the returned JobHandle/JobDelegate, must never be - * called while holding a lock that could be acquired by JobTask::Run or - * JobTask::GetMaxConcurrency -- that could result in a deadlock. This is - * because [1] JobTask::GetMaxConcurrency may be invoked while holding - * internal lock (A), hence JobTask::GetMaxConcurrency can only use a lock (B) - * if that lock is *never* held while calling back into JobHandle from any - * thread (A=>B/B=>A deadlock) and [2] JobTask::Run or - * JobTask::GetMaxConcurrency may be invoked synchronously from JobHandle - * (B=>JobHandle::foo=>B deadlock). - * - * A sufficient PostJob() implementation that uses the default Job provided in - * libplatform looks like: - * std::unique_ptr PostJob( - * TaskPriority priority, std::unique_ptr job_task) override { - * return v8::platform::NewDefaultJobHandle( - * this, priority, std::move(job_task), NumberOfWorkerThreads()); - * } - */ - virtual std::unique_ptr PostJob( - TaskPriority priority, std::unique_ptr job_task) = 0; - - /** - * Monotonically increasing time in seconds from an arbitrary fixed point in - * the past. This function is expected to return at least - * millisecond-precision values. For this reason, - * it is recommended that the fixed point be no further in the past than - * the epoch. - **/ - virtual double MonotonicallyIncreasingTime() = 0; - - /** - * Current wall-clock time in milliseconds since epoch. - * This function is expected to return at least millisecond-precision values. - */ - virtual double CurrentClockTimeMillis() = 0; - - typedef void (*StackTracePrinter)(); - - /** - * Returns a function pointer that print a stack trace of the current stack - * on invocation. Disables printing of the stack trace if nullptr. - */ - virtual StackTracePrinter GetStackTracePrinter() { return nullptr; } - - /** - * Returns an instance of a v8::TracingController. This must be non-nullptr. - */ - virtual TracingController* GetTracingController() = 0; - - /** - * Tells the embedder to generate and upload a crashdump during an unexpected - * but non-critical scenario. - */ - virtual void DumpWithoutCrashing() {} - - protected: - /** - * Default implementation of current wall-clock time in milliseconds - * since epoch. Useful for implementing |CurrentClockTimeMillis| if - * nothing special needed. - */ - V8_EXPORT static double SystemClockTimeMillis(); -}; - -} // namespace v8 - -#endif // V8_V8_PLATFORM_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-primitive-object.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-primitive-object.h deleted file mode 100644 index 573932d0789..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-primitive-object.h +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_PRIMITIVE_OBJECT_H_ -#define INCLUDE_V8_PRIMITIVE_OBJECT_H_ - -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8-object.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Isolate; - -/** - * A Number object (ECMA-262, 4.3.21). - */ -class V8_EXPORT NumberObject : public Object { - public: - static Local New(Isolate* isolate, double value); - - double ValueOf() const; - - V8_INLINE static NumberObject* Cast(Value* value) { -#ifdef V8_ENABLE_CHECKS - CheckCast(value); -#endif - return static_cast(value); - } - - private: - static void CheckCast(Value* obj); -}; - -/** - * A BigInt object (https://tc39.github.io/proposal-bigint) - */ -class V8_EXPORT BigIntObject : public Object { - public: - static Local New(Isolate* isolate, int64_t value); - - Local ValueOf() const; - - V8_INLINE static BigIntObject* Cast(Value* value) { -#ifdef V8_ENABLE_CHECKS - CheckCast(value); -#endif - return static_cast(value); - } - - private: - static void CheckCast(Value* obj); -}; - -/** - * A Boolean object (ECMA-262, 4.3.15). - */ -class V8_EXPORT BooleanObject : public Object { - public: - static Local New(Isolate* isolate, bool value); - - bool ValueOf() const; - - V8_INLINE static BooleanObject* Cast(Value* value) { -#ifdef V8_ENABLE_CHECKS - CheckCast(value); -#endif - return static_cast(value); - } - - private: - static void CheckCast(Value* obj); -}; - -/** - * A String object (ECMA-262, 4.3.18). - */ -class V8_EXPORT StringObject : public Object { - public: - static Local New(Isolate* isolate, Local value); - - Local ValueOf() const; - - V8_INLINE static StringObject* Cast(Value* value) { -#ifdef V8_ENABLE_CHECKS - CheckCast(value); -#endif - return static_cast(value); - } - - private: - static void CheckCast(Value* obj); -}; - -/** - * A Symbol object (ECMA-262 edition 6). - */ -class V8_EXPORT SymbolObject : public Object { - public: - static Local New(Isolate* isolate, Local value); - - Local ValueOf() const; - - V8_INLINE static SymbolObject* Cast(Value* value) { -#ifdef V8_ENABLE_CHECKS - CheckCast(value); -#endif - return static_cast(value); - } - - private: - static void CheckCast(Value* obj); -}; - -} // namespace v8 - -#endif // INCLUDE_V8_PRIMITIVE_OBJECT_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-primitive.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-primitive.h deleted file mode 100644 index 59d959da057..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-primitive.h +++ /dev/null @@ -1,858 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_PRIMITIVE_H_ -#define INCLUDE_V8_PRIMITIVE_H_ - -#include "v8-data.h" // NOLINT(build/include_directory) -#include "v8-internal.h" // NOLINT(build/include_directory) -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8-value.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Context; -class Isolate; -class String; - -namespace internal { -class ExternalString; -class ScopedExternalStringLock; -} // namespace internal - -/** - * The superclass of primitive values. See ECMA-262 4.3.2. - */ -class V8_EXPORT Primitive : public Value {}; - -/** - * A primitive boolean value (ECMA-262, 4.3.14). Either the true - * or false value. - */ -class V8_EXPORT Boolean : public Primitive { - public: - bool Value() const; - V8_INLINE static Boolean* Cast(v8::Data* data) { -#ifdef V8_ENABLE_CHECKS - CheckCast(data); -#endif - return static_cast(data); - } - - V8_INLINE static Local New(Isolate* isolate, bool value); - - private: - static void CheckCast(v8::Data* that); -}; - -/** - * An array to hold Primitive values. This is used by the embedder to - * pass host defined options to the ScriptOptions during compilation. - * - * This is passed back to the embedder as part of - * HostImportModuleDynamicallyCallback for module loading. - */ -class V8_EXPORT PrimitiveArray { - public: - static Local New(Isolate* isolate, int length); - int Length() const; - void Set(Isolate* isolate, int index, Local item); - Local Get(Isolate* isolate, int index); -}; - -/** - * A superclass for symbols and strings. - */ -class V8_EXPORT Name : public Primitive { - public: - /** - * Returns the identity hash for this object. The current implementation - * uses an inline property on the object to store the identity hash. - * - * The return value will never be 0. Also, it is not guaranteed to be - * unique. - */ - int GetIdentityHash(); - - V8_INLINE static Name* Cast(Data* data) { -#ifdef V8_ENABLE_CHECKS - CheckCast(data); -#endif - return static_cast(data); - } - - private: - static void CheckCast(Data* that); -}; - -/** - * A flag describing different modes of string creation. - * - * Aside from performance implications there are no differences between the two - * creation modes. - */ -enum class NewStringType { - /** - * Create a new string, always allocating new storage memory. - */ - kNormal, - - /** - * Acts as a hint that the string should be created in the - * old generation heap space and be deduplicated if an identical string - * already exists. - */ - kInternalized -}; - -/** - * A JavaScript string value (ECMA-262, 4.3.17). - */ -class V8_EXPORT String : public Name { - public: - static constexpr int kMaxLength = - internal::kApiSystemPointerSize == 4 ? (1 << 28) - 16 : (1 << 29) - 24; - - enum Encoding { - UNKNOWN_ENCODING = 0x1, - TWO_BYTE_ENCODING = 0x0, - ONE_BYTE_ENCODING = 0x8 - }; - /** - * Returns the number of characters (UTF-16 code units) in this string. - */ - int Length() const; - - /** - * Returns the number of bytes in the UTF-8 encoded - * representation of this string. - */ - int Utf8Length(Isolate* isolate) const; - - /** - * Returns whether this string is known to contain only one byte data, - * i.e. ISO-8859-1 code points. - * Does not read the string. - * False negatives are possible. - */ - bool IsOneByte() const; - - /** - * Returns whether this string contain only one byte data, - * i.e. ISO-8859-1 code points. - * Will read the entire string in some cases. - */ - bool ContainsOnlyOneByte() const; - - /** - * Write the contents of the string to an external buffer. - * If no arguments are given, expects the buffer to be large - * enough to hold the entire string and NULL terminator. Copies - * the contents of the string and the NULL terminator into the - * buffer. - * - * WriteUtf8 will not write partial UTF-8 sequences, preferring to stop - * before the end of the buffer. - * - * Copies up to length characters into the output buffer. - * Only null-terminates if there is enough space in the buffer. - * - * \param buffer The buffer into which the string will be copied. - * \param start The starting position within the string at which - * copying begins. - * \param length The number of characters to copy from the string. For - * WriteUtf8 the number of bytes in the buffer. - * \param nchars_ref The number of characters written, can be NULL. - * \param options Various options that might affect performance of this or - * subsequent operations. - * \return The number of characters copied to the buffer excluding the null - * terminator. For WriteUtf8: The number of bytes copied to the buffer - * including the null terminator (if written). - */ - enum WriteOptions { - NO_OPTIONS = 0, - HINT_MANY_WRITES_EXPECTED = 1, - NO_NULL_TERMINATION = 2, - PRESERVE_ONE_BYTE_NULL = 4, - // Used by WriteUtf8 to replace orphan surrogate code units with the - // unicode replacement character. Needs to be set to guarantee valid UTF-8 - // output. - REPLACE_INVALID_UTF8 = 8 - }; - - // 16-bit character codes. - int Write(Isolate* isolate, uint16_t* buffer, int start = 0, int length = -1, - int options = NO_OPTIONS) const; - // One byte characters. - int WriteOneByte(Isolate* isolate, uint8_t* buffer, int start = 0, - int length = -1, int options = NO_OPTIONS) const; - // UTF-8 encoded characters. - int WriteUtf8(Isolate* isolate, char* buffer, int length = -1, - int* nchars_ref = nullptr, int options = NO_OPTIONS) const; - - /** - * A zero length string. - */ - V8_INLINE static Local Empty(Isolate* isolate); - - /** - * Returns true if the string is external. - */ - bool IsExternal() const; - - /** - * Returns true if the string is both external and two-byte. - */ - bool IsExternalTwoByte() const; - - /** - * Returns true if the string is both external and one-byte. - */ - bool IsExternalOneByte() const; - - class V8_EXPORT ExternalStringResourceBase { - public: - virtual ~ExternalStringResourceBase() = default; - - /** - * If a string is cacheable, the value returned by - * ExternalStringResource::data() may be cached, otherwise it is not - * expected to be stable beyond the current top-level task. - */ - virtual bool IsCacheable() const { return true; } - - // Disallow copying and assigning. - ExternalStringResourceBase(const ExternalStringResourceBase&) = delete; - void operator=(const ExternalStringResourceBase&) = delete; - - protected: - ExternalStringResourceBase() = default; - - /** - * Internally V8 will call this Dispose method when the external string - * resource is no longer needed. The default implementation will use the - * delete operator. This method can be overridden in subclasses to - * control how allocated external string resources are disposed. - */ - virtual void Dispose() { delete this; } - - /** - * For a non-cacheable string, the value returned by - * |ExternalStringResource::data()| has to be stable between |Lock()| and - * |Unlock()|, that is the string must behave as is |IsCacheable()| returned - * true. - * - * These two functions must be thread-safe, and can be called from anywhere. - * They also must handle lock depth, in the sense that each can be called - * several times, from different threads, and unlocking should only happen - * when the balance of Lock() and Unlock() calls is 0. - */ - virtual void Lock() const {} - - /** - * Unlocks the string. - */ - virtual void Unlock() const {} - - private: - friend class internal::ExternalString; - friend class v8::String; - friend class internal::ScopedExternalStringLock; - }; - - /** - * An ExternalStringResource is a wrapper around a two-byte string - * buffer that resides outside V8's heap. Implement an - * ExternalStringResource to manage the life cycle of the underlying - * buffer. Note that the string data must be immutable. - */ - class V8_EXPORT ExternalStringResource : public ExternalStringResourceBase { - public: - /** - * Override the destructor to manage the life cycle of the underlying - * buffer. - */ - ~ExternalStringResource() override = default; - - /** - * The string data from the underlying buffer. If the resource is cacheable - * then data() must return the same value for all invocations. - */ - virtual const uint16_t* data() const = 0; - - /** - * The length of the string. That is, the number of two-byte characters. - */ - virtual size_t length() const = 0; - - /** - * Returns the cached data from the underlying buffer. This method can be - * called only for cacheable resources (i.e. IsCacheable() == true) and only - * after UpdateDataCache() was called. - */ - const uint16_t* cached_data() const { - CheckCachedDataInvariants(); - return cached_data_; - } - - /** - * Update {cached_data_} with the data from the underlying buffer. This can - * be called only for cacheable resources. - */ - void UpdateDataCache(); - - protected: - ExternalStringResource() = default; - - private: - void CheckCachedDataInvariants() const; - - const uint16_t* cached_data_ = nullptr; - }; - - /** - * An ExternalOneByteStringResource is a wrapper around an one-byte - * string buffer that resides outside V8's heap. Implement an - * ExternalOneByteStringResource to manage the life cycle of the - * underlying buffer. Note that the string data must be immutable - * and that the data must be Latin-1 and not UTF-8, which would require - * special treatment internally in the engine and do not allow efficient - * indexing. Use String::New or convert to 16 bit data for non-Latin1. - */ - - class V8_EXPORT ExternalOneByteStringResource - : public ExternalStringResourceBase { - public: - /** - * Override the destructor to manage the life cycle of the underlying - * buffer. - */ - ~ExternalOneByteStringResource() override = default; - - /** - * The string data from the underlying buffer. If the resource is cacheable - * then data() must return the same value for all invocations. - */ - virtual const char* data() const = 0; - - /** The number of Latin-1 characters in the string.*/ - virtual size_t length() const = 0; - - /** - * Returns the cached data from the underlying buffer. If the resource is - * uncacheable or if UpdateDataCache() was not called before, it has - * undefined behaviour. - */ - const char* cached_data() const { - CheckCachedDataInvariants(); - return cached_data_; - } - - /** - * Update {cached_data_} with the data from the underlying buffer. This can - * be called only for cacheable resources. - */ - void UpdateDataCache(); - - protected: - ExternalOneByteStringResource() = default; - - private: - void CheckCachedDataInvariants() const; - - const char* cached_data_ = nullptr; - }; - - /** - * If the string is an external string, return the ExternalStringResourceBase - * regardless of the encoding, otherwise return NULL. The encoding of the - * string is returned in encoding_out. - */ - V8_INLINE ExternalStringResourceBase* GetExternalStringResourceBase( - Encoding* encoding_out) const; - - /** - * Get the ExternalStringResource for an external string. Returns - * NULL if IsExternal() doesn't return true. - */ - V8_INLINE ExternalStringResource* GetExternalStringResource() const; - - /** - * Get the ExternalOneByteStringResource for an external one-byte string. - * Returns NULL if IsExternalOneByte() doesn't return true. - */ - const ExternalOneByteStringResource* GetExternalOneByteStringResource() const; - - V8_INLINE static String* Cast(v8::Data* data) { -#ifdef V8_ENABLE_CHECKS - CheckCast(data); -#endif - return static_cast(data); - } - - /** - * Allocates a new string from a UTF-8 literal. This is equivalent to calling - * String::NewFromUtf(isolate, "...").ToLocalChecked(), but without the check - * overhead. - * - * When called on a string literal containing '\0', the inferred length is the - * length of the input array minus 1 (for the final '\0') and not the value - * returned by strlen. - **/ - template - static V8_WARN_UNUSED_RESULT Local NewFromUtf8Literal( - Isolate* isolate, const char (&literal)[N], - NewStringType type = NewStringType::kNormal) { - static_assert(N <= kMaxLength, "String is too long"); - return NewFromUtf8Literal(isolate, literal, type, N - 1); - } - - /** Allocates a new string from UTF-8 data. Only returns an empty value when - * length > kMaxLength. **/ - static V8_WARN_UNUSED_RESULT MaybeLocal NewFromUtf8( - Isolate* isolate, const char* data, - NewStringType type = NewStringType::kNormal, int length = -1); - - /** Allocates a new string from Latin-1 data. Only returns an empty value - * when length > kMaxLength. **/ - static V8_WARN_UNUSED_RESULT MaybeLocal NewFromOneByte( - Isolate* isolate, const uint8_t* data, - NewStringType type = NewStringType::kNormal, int length = -1); - - /** Allocates a new string from UTF-16 data. Only returns an empty value when - * length > kMaxLength. **/ - static V8_WARN_UNUSED_RESULT MaybeLocal NewFromTwoByte( - Isolate* isolate, const uint16_t* data, - NewStringType type = NewStringType::kNormal, int length = -1); - - /** - * Creates a new string by concatenating the left and the right strings - * passed in as parameters. - */ - static Local Concat(Isolate* isolate, Local left, - Local right); - - /** - * Creates a new external string using the data defined in the given - * resource. When the external string is no longer live on V8's heap the - * resource will be disposed by calling its Dispose method. The caller of - * this function should not otherwise delete or modify the resource. Neither - * should the underlying buffer be deallocated or modified except through the - * destructor of the external string resource. - */ - static V8_WARN_UNUSED_RESULT MaybeLocal NewExternalTwoByte( - Isolate* isolate, ExternalStringResource* resource); - - /** - * Associate an external string resource with this string by transforming it - * in place so that existing references to this string in the JavaScript heap - * will use the external string resource. The external string resource's - * character contents need to be equivalent to this string. - * Returns true if the string has been changed to be an external string. - * The string is not modified if the operation fails. See NewExternal for - * information on the lifetime of the resource. - */ - bool MakeExternal(ExternalStringResource* resource); - - /** - * Creates a new external string using the one-byte data defined in the given - * resource. When the external string is no longer live on V8's heap the - * resource will be disposed by calling its Dispose method. The caller of - * this function should not otherwise delete or modify the resource. Neither - * should the underlying buffer be deallocated or modified except through the - * destructor of the external string resource. - */ - static V8_WARN_UNUSED_RESULT MaybeLocal NewExternalOneByte( - Isolate* isolate, ExternalOneByteStringResource* resource); - - /** - * Associate an external string resource with this string by transforming it - * in place so that existing references to this string in the JavaScript heap - * will use the external string resource. The external string resource's - * character contents need to be equivalent to this string. - * Returns true if the string has been changed to be an external string. - * The string is not modified if the operation fails. See NewExternal for - * information on the lifetime of the resource. - */ - bool MakeExternal(ExternalOneByteStringResource* resource); - - /** - * Returns true if this string can be made external. - */ - bool CanMakeExternal() const; - - /** - * Returns true if the strings values are equal. Same as JS ==/===. - */ - bool StringEquals(Local str) const; - - /** - * Converts an object to a UTF-8-encoded character array. Useful if - * you want to print the object. If conversion to a string fails - * (e.g. due to an exception in the toString() method of the object) - * then the length() method returns 0 and the * operator returns - * NULL. - */ - class V8_EXPORT Utf8Value { - public: - Utf8Value(Isolate* isolate, Local obj); - ~Utf8Value(); - char* operator*() { return str_; } - const char* operator*() const { return str_; } - int length() const { return length_; } - - // Disallow copying and assigning. - Utf8Value(const Utf8Value&) = delete; - void operator=(const Utf8Value&) = delete; - - private: - char* str_; - int length_; - }; - - /** - * Converts an object to a two-byte (UTF-16-encoded) string. - * If conversion to a string fails (eg. due to an exception in the toString() - * method of the object) then the length() method returns 0 and the * operator - * returns NULL. - */ - class V8_EXPORT Value { - public: - Value(Isolate* isolate, Local obj); - ~Value(); - uint16_t* operator*() { return str_; } - const uint16_t* operator*() const { return str_; } - int length() const { return length_; } - - // Disallow copying and assigning. - Value(const Value&) = delete; - void operator=(const Value&) = delete; - - private: - uint16_t* str_; - int length_; - }; - - private: - void VerifyExternalStringResourceBase(ExternalStringResourceBase* v, - Encoding encoding) const; - void VerifyExternalStringResource(ExternalStringResource* val) const; - ExternalStringResource* GetExternalStringResourceSlow() const; - ExternalStringResourceBase* GetExternalStringResourceBaseSlow( - String::Encoding* encoding_out) const; - - static Local NewFromUtf8Literal(Isolate* isolate, - const char* literal, - NewStringType type, int length); - - static void CheckCast(v8::Data* that); -}; - -// Zero-length string specialization (templated string size includes -// terminator). -template <> -inline V8_WARN_UNUSED_RESULT Local String::NewFromUtf8Literal( - Isolate* isolate, const char (&literal)[1], NewStringType type) { - return String::Empty(isolate); -} - -/** - * Interface for iterating through all external resources in the heap. - */ -class V8_EXPORT ExternalResourceVisitor { - public: - virtual ~ExternalResourceVisitor() = default; - virtual void VisitExternalString(Local string) {} -}; - -/** - * A JavaScript symbol (ECMA-262 edition 6) - */ -class V8_EXPORT Symbol : public Name { - public: - /** - * Returns the description string of the symbol, or undefined if none. - */ - V8_DEPRECATE_SOON("Use Symbol::Description(isolate)") - Local Description() const; - Local Description(Isolate* isolate) const; - - /** - * Create a symbol. If description is not empty, it will be used as the - * description. - */ - static Local New(Isolate* isolate, - Local description = Local()); - - /** - * Access global symbol registry. - * Note that symbols created this way are never collected, so - * they should only be used for statically fixed properties. - * Also, there is only one global name space for the descriptions used as - * keys. - * To minimize the potential for clashes, use qualified names as keys. - */ - static Local For(Isolate* isolate, Local description); - - /** - * Retrieve a global symbol. Similar to |For|, but using a separate - * registry that is not accessible by (and cannot clash with) JavaScript code. - */ - static Local ForApi(Isolate* isolate, Local description); - - // Well-known symbols - static Local GetAsyncIterator(Isolate* isolate); - static Local GetHasInstance(Isolate* isolate); - static Local GetIsConcatSpreadable(Isolate* isolate); - static Local GetIterator(Isolate* isolate); - static Local GetMatch(Isolate* isolate); - static Local GetReplace(Isolate* isolate); - static Local GetSearch(Isolate* isolate); - static Local GetSplit(Isolate* isolate); - static Local GetToPrimitive(Isolate* isolate); - static Local GetToStringTag(Isolate* isolate); - static Local GetUnscopables(Isolate* isolate); - - V8_INLINE static Symbol* Cast(Data* data) { -#ifdef V8_ENABLE_CHECKS - CheckCast(data); -#endif - return static_cast(data); - } - - private: - Symbol(); - static void CheckCast(Data* that); -}; - -/** - * A JavaScript number value (ECMA-262, 4.3.20) - */ -class V8_EXPORT Number : public Primitive { - public: - double Value() const; - static Local New(Isolate* isolate, double value); - V8_INLINE static Number* Cast(v8::Data* data) { -#ifdef V8_ENABLE_CHECKS - CheckCast(data); -#endif - return static_cast(data); - } - - private: - Number(); - static void CheckCast(v8::Data* that); -}; - -/** - * A JavaScript value representing a signed integer. - */ -class V8_EXPORT Integer : public Number { - public: - static Local New(Isolate* isolate, int32_t value); - static Local NewFromUnsigned(Isolate* isolate, uint32_t value); - int64_t Value() const; - V8_INLINE static Integer* Cast(v8::Data* data) { -#ifdef V8_ENABLE_CHECKS - CheckCast(data); -#endif - return static_cast(data); - } - - private: - Integer(); - static void CheckCast(v8::Data* that); -}; - -/** - * A JavaScript value representing a 32-bit signed integer. - */ -class V8_EXPORT Int32 : public Integer { - public: - int32_t Value() const; - V8_INLINE static Int32* Cast(v8::Data* data) { -#ifdef V8_ENABLE_CHECKS - CheckCast(data); -#endif - return static_cast(data); - } - - private: - Int32(); - static void CheckCast(v8::Data* that); -}; - -/** - * A JavaScript value representing a 32-bit unsigned integer. - */ -class V8_EXPORT Uint32 : public Integer { - public: - uint32_t Value() const; - V8_INLINE static Uint32* Cast(v8::Data* data) { -#ifdef V8_ENABLE_CHECKS - CheckCast(data); -#endif - return static_cast(data); - } - - private: - Uint32(); - static void CheckCast(v8::Data* that); -}; - -/** - * A JavaScript BigInt value (https://tc39.github.io/proposal-bigint) - */ -class V8_EXPORT BigInt : public Primitive { - public: - static Local New(Isolate* isolate, int64_t value); - static Local NewFromUnsigned(Isolate* isolate, uint64_t value); - /** - * Creates a new BigInt object using a specified sign bit and a - * specified list of digits/words. - * The resulting number is calculated as: - * - * (-1)^sign_bit * (words[0] * (2^64)^0 + words[1] * (2^64)^1 + ...) - */ - static MaybeLocal NewFromWords(Local context, int sign_bit, - int word_count, const uint64_t* words); - - /** - * Returns the value of this BigInt as an unsigned 64-bit integer. - * If `lossless` is provided, it will reflect whether the return value was - * truncated or wrapped around. In particular, it is set to `false` if this - * BigInt is negative. - */ - uint64_t Uint64Value(bool* lossless = nullptr) const; - - /** - * Returns the value of this BigInt as a signed 64-bit integer. - * If `lossless` is provided, it will reflect whether this BigInt was - * truncated or not. - */ - int64_t Int64Value(bool* lossless = nullptr) const; - - /** - * Returns the number of 64-bit words needed to store the result of - * ToWordsArray(). - */ - int WordCount() const; - - /** - * Writes the contents of this BigInt to a specified memory location. - * `sign_bit` must be provided and will be set to 1 if this BigInt is - * negative. - * `*word_count` has to be initialized to the length of the `words` array. - * Upon return, it will be set to the actual number of words that would - * be needed to store this BigInt (i.e. the return value of `WordCount()`). - */ - void ToWordsArray(int* sign_bit, int* word_count, uint64_t* words) const; - - V8_INLINE static BigInt* Cast(v8::Data* data) { -#ifdef V8_ENABLE_CHECKS - CheckCast(data); -#endif - return static_cast(data); - } - - private: - BigInt(); - static void CheckCast(v8::Data* that); -}; - -Local String::Empty(Isolate* isolate) { - using S = internal::Address; - using I = internal::Internals; - I::CheckInitialized(isolate); - S* slot = I::GetRoot(isolate, I::kEmptyStringRootIndex); - return Local(reinterpret_cast(slot)); -} - -String::ExternalStringResource* String::GetExternalStringResource() const { - using A = internal::Address; - using I = internal::Internals; - A obj = *reinterpret_cast(this); - - ExternalStringResource* result; - if (I::IsExternalTwoByteString(I::GetInstanceType(obj))) { - internal::Isolate* isolate = I::GetIsolateForHeapSandbox(obj); - A value = - I::ReadExternalPointerField(isolate, obj, I::kStringResourceOffset, - internal::kExternalStringResourceTag); - result = reinterpret_cast(value); - } else { - result = GetExternalStringResourceSlow(); - } -#ifdef V8_ENABLE_CHECKS - VerifyExternalStringResource(result); -#endif - return result; -} - -String::ExternalStringResourceBase* String::GetExternalStringResourceBase( - String::Encoding* encoding_out) const { - using A = internal::Address; - using I = internal::Internals; - A obj = *reinterpret_cast(this); - int type = I::GetInstanceType(obj) & I::kFullStringRepresentationMask; - *encoding_out = static_cast(type & I::kStringEncodingMask); - ExternalStringResourceBase* resource; - if (type == I::kExternalOneByteRepresentationTag || - type == I::kExternalTwoByteRepresentationTag) { - internal::Isolate* isolate = I::GetIsolateForHeapSandbox(obj); - A value = - I::ReadExternalPointerField(isolate, obj, I::kStringResourceOffset, - internal::kExternalStringResourceTag); - resource = reinterpret_cast(value); - } else { - resource = GetExternalStringResourceBaseSlow(encoding_out); - } -#ifdef V8_ENABLE_CHECKS - VerifyExternalStringResourceBase(resource, *encoding_out); -#endif - return resource; -} - -// --- Statics --- - -V8_INLINE Local Undefined(Isolate* isolate) { - using S = internal::Address; - using I = internal::Internals; - I::CheckInitialized(isolate); - S* slot = I::GetRoot(isolate, I::kUndefinedValueRootIndex); - return Local(reinterpret_cast(slot)); -} - -V8_INLINE Local Null(Isolate* isolate) { - using S = internal::Address; - using I = internal::Internals; - I::CheckInitialized(isolate); - S* slot = I::GetRoot(isolate, I::kNullValueRootIndex); - return Local(reinterpret_cast(slot)); -} - -V8_INLINE Local True(Isolate* isolate) { - using S = internal::Address; - using I = internal::Internals; - I::CheckInitialized(isolate); - S* slot = I::GetRoot(isolate, I::kTrueValueRootIndex); - return Local(reinterpret_cast(slot)); -} - -V8_INLINE Local False(Isolate* isolate) { - using S = internal::Address; - using I = internal::Internals; - I::CheckInitialized(isolate); - S* slot = I::GetRoot(isolate, I::kFalseValueRootIndex); - return Local(reinterpret_cast(slot)); -} - -Local Boolean::New(Isolate* isolate, bool value) { - return value ? True(isolate) : False(isolate); -} - -} // namespace v8 - -#endif // INCLUDE_V8_PRIMITIVE_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-profiler.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-profiler.h deleted file mode 100644 index f2354cac38e..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-profiler.h +++ /dev/null @@ -1,1122 +0,0 @@ -// Copyright 2010 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef V8_V8_PROFILER_H_ -#define V8_V8_PROFILER_H_ - -#include - -#include -#include -#include - -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8-message.h" // NOLINT(build/include_directory) -#include "v8-persistent-handle.h" // NOLINT(build/include_directory) - -/** - * Profiler support for the V8 JavaScript engine. - */ -namespace v8 { - -class HeapGraphNode; -struct HeapStatsUpdate; -class Object; - -using NativeObject = void*; -using SnapshotObjectId = uint32_t; - -struct CpuProfileDeoptFrame { - int script_id; - size_t position; -}; - -namespace internal { -class CpuProfile; -} // namespace internal - -} // namespace v8 - -#ifdef V8_OS_WIN -template class V8_EXPORT std::vector; -#endif - -namespace v8 { - -struct V8_EXPORT CpuProfileDeoptInfo { - /** A pointer to a static string owned by v8. */ - const char* deopt_reason; - std::vector stack; -}; - -} // namespace v8 - -#ifdef V8_OS_WIN -template class V8_EXPORT std::vector; -#endif - -namespace v8 { - -/** - * CpuProfileNode represents a node in a call graph. - */ -class V8_EXPORT CpuProfileNode { - public: - struct LineTick { - /** The 1-based number of the source line where the function originates. */ - int line; - - /** The count of samples associated with the source line. */ - unsigned int hit_count; - }; - - // An annotation hinting at the source of a CpuProfileNode. - enum SourceType { - // User-supplied script with associated resource information. - kScript = 0, - // Native scripts and provided builtins. - kBuiltin = 1, - // Callbacks into native code. - kCallback = 2, - // VM-internal functions or state. - kInternal = 3, - // A node that failed to symbolize. - kUnresolved = 4, - }; - - /** Returns function name (empty string for anonymous functions.) */ - Local GetFunctionName() const; - - /** - * Returns function name (empty string for anonymous functions.) - * The string ownership is *not* passed to the caller. It stays valid until - * profile is deleted. The function is thread safe. - */ - const char* GetFunctionNameStr() const; - - /** Returns id of the script where function is located. */ - int GetScriptId() const; - - /** Returns resource name for script from where the function originates. */ - Local GetScriptResourceName() const; - - /** - * Returns resource name for script from where the function originates. - * The string ownership is *not* passed to the caller. It stays valid until - * profile is deleted. The function is thread safe. - */ - const char* GetScriptResourceNameStr() const; - - /** - * Return true if the script from where the function originates is flagged as - * being shared cross-origin. - */ - bool IsScriptSharedCrossOrigin() const; - - /** - * Returns the number, 1-based, of the line where the function originates. - * kNoLineNumberInfo if no line number information is available. - */ - int GetLineNumber() const; - - /** - * Returns 1-based number of the column where the function originates. - * kNoColumnNumberInfo if no column number information is available. - */ - int GetColumnNumber() const; - - /** - * Returns the number of the function's source lines that collect the samples. - */ - unsigned int GetHitLineCount() const; - - /** Returns the set of source lines that collect the samples. - * The caller allocates buffer and responsible for releasing it. - * True if all available entries are copied, otherwise false. - * The function copies nothing if buffer is not large enough. - */ - bool GetLineTicks(LineTick* entries, unsigned int length) const; - - /** Returns bailout reason for the function - * if the optimization was disabled for it. - */ - const char* GetBailoutReason() const; - - /** - * Returns the count of samples where the function was currently executing. - */ - unsigned GetHitCount() const; - - /** Returns id of the node. The id is unique within the tree */ - unsigned GetNodeId() const; - - /** - * Gets the type of the source which the node was captured from. - */ - SourceType GetSourceType() const; - - /** Returns child nodes count of the node. */ - int GetChildrenCount() const; - - /** Retrieves a child node by index. */ - const CpuProfileNode* GetChild(int index) const; - - /** Retrieves the ancestor node, or null if the root. */ - const CpuProfileNode* GetParent() const; - - /** Retrieves deopt infos for the node. */ - const std::vector& GetDeoptInfos() const; - - static const int kNoLineNumberInfo = Message::kNoLineNumberInfo; - static const int kNoColumnNumberInfo = Message::kNoColumnInfo; -}; - - -/** - * CpuProfile contains a CPU profile in a form of top-down call tree - * (from main() down to functions that do all the work). - */ -class V8_EXPORT CpuProfile { - public: - /** Returns CPU profile title. */ - Local GetTitle() const; - - /** Returns the root node of the top down call tree. */ - const CpuProfileNode* GetTopDownRoot() const; - - /** - * Returns number of samples recorded. The samples are not recorded unless - * |record_samples| parameter of CpuProfiler::StartCpuProfiling is true. - */ - int GetSamplesCount() const; - - /** - * Returns profile node corresponding to the top frame the sample at - * the given index. - */ - const CpuProfileNode* GetSample(int index) const; - - /** - * Returns the timestamp of the sample. The timestamp is the number of - * microseconds since some unspecified starting point. - * The point is equal to the starting point used by GetStartTime. - */ - int64_t GetSampleTimestamp(int index) const; - - /** - * Returns time when the profile recording was started (in microseconds) - * since some unspecified starting point. - */ - int64_t GetStartTime() const; - - /** - * Returns time when the profile recording was stopped (in microseconds) - * since some unspecified starting point. - * The point is equal to the starting point used by GetStartTime. - */ - int64_t GetEndTime() const; - - /** - * Deletes the profile and removes it from CpuProfiler's list. - * All pointers to nodes previously returned become invalid. - */ - void Delete(); -}; - -enum CpuProfilingMode { - // In the resulting CpuProfile tree, intermediate nodes in a stack trace - // (from the root to a leaf) will have line numbers that point to the start - // line of the function, rather than the line of the callsite of the child. - kLeafNodeLineNumbers, - // In the resulting CpuProfile tree, nodes are separated based on the line - // number of their callsite in their parent. - kCallerLineNumbers, -}; - -// Determines how names are derived for functions sampled. -enum CpuProfilingNamingMode { - // Use the immediate name of functions at compilation time. - kStandardNaming, - // Use more verbose naming for functions without names, inferred from scope - // where possible. - kDebugNaming, -}; - -enum CpuProfilingLoggingMode { - // Enables logging when a profile is active, and disables logging when all - // profiles are detached. - kLazyLogging, - // Enables logging for the lifetime of the CpuProfiler. Calls to - // StartRecording are faster, at the expense of runtime overhead. - kEagerLogging, -}; - -// Enum for returning profiling status. Once StartProfiling is called, -// we want to return to clients whether the profiling was able to start -// correctly, or return a descriptive error. -enum class CpuProfilingStatus { - kStarted, - kAlreadyStarted, - kErrorTooManyProfilers -}; - -/** - * Delegate for when max samples reached and samples are discarded. - */ -class V8_EXPORT DiscardedSamplesDelegate { - public: - DiscardedSamplesDelegate() {} - - virtual ~DiscardedSamplesDelegate() = default; - virtual void Notify() = 0; -}; - -/** - * Optional profiling attributes. - */ -class V8_EXPORT CpuProfilingOptions { - public: - // Indicates that the sample buffer size should not be explicitly limited. - static const unsigned kNoSampleLimit = UINT_MAX; - - /** - * \param mode Type of computation of stack frame line numbers. - * \param max_samples The maximum number of samples that should be recorded by - * the profiler. Samples obtained after this limit will be - * discarded. - * \param sampling_interval_us controls the profile-specific target - * sampling interval. The provided sampling - * interval will be snapped to the next lowest - * non-zero multiple of the profiler's sampling - * interval, set via SetSamplingInterval(). If - * zero, the sampling interval will be equal to - * the profiler's sampling interval. - * \param filter_context If specified, profiles will only contain frames - * using this context. Other frames will be elided. - */ - CpuProfilingOptions( - CpuProfilingMode mode = kLeafNodeLineNumbers, - unsigned max_samples = kNoSampleLimit, int sampling_interval_us = 0, - MaybeLocal filter_context = MaybeLocal()); - - CpuProfilingMode mode() const { return mode_; } - unsigned max_samples() const { return max_samples_; } - int sampling_interval_us() const { return sampling_interval_us_; } - - private: - friend class internal::CpuProfile; - - bool has_filter_context() const { return !filter_context_.IsEmpty(); } - void* raw_filter_context() const; - - CpuProfilingMode mode_; - unsigned max_samples_; - int sampling_interval_us_; - CopyablePersistentTraits::CopyablePersistent filter_context_; -}; - -/** - * Interface for controlling CPU profiling. Instance of the - * profiler can be created using v8::CpuProfiler::New method. - */ -class V8_EXPORT CpuProfiler { - public: - /** - * Creates a new CPU profiler for the |isolate|. The isolate must be - * initialized. The profiler object must be disposed after use by calling - * |Dispose| method. - */ - static CpuProfiler* New(Isolate* isolate, - CpuProfilingNamingMode = kDebugNaming, - CpuProfilingLoggingMode = kLazyLogging); - - /** - * Synchronously collect current stack sample in all profilers attached to - * the |isolate|. The call does not affect number of ticks recorded for - * the current top node. - */ - static void CollectSample(Isolate* isolate); - - /** - * Disposes the CPU profiler object. - */ - void Dispose(); - - /** - * Changes default CPU profiler sampling interval to the specified number - * of microseconds. Default interval is 1000us. This method must be called - * when there are no profiles being recorded. - */ - void SetSamplingInterval(int us); - - /** - * Sets whether or not the profiler should prioritize consistency of sample - * periodicity on Windows. Disabling this can greatly reduce CPU usage, but - * may result in greater variance in sample timings from the platform's - * scheduler. Defaults to enabled. This method must be called when there are - * no profiles being recorded. - */ - void SetUsePreciseSampling(bool); - - /** - * Starts collecting a CPU profile. Title may be an empty string. Several - * profiles may be collected at once. Attempts to start collecting several - * profiles with the same title are silently ignored. - */ - CpuProfilingStatus StartProfiling( - Local title, CpuProfilingOptions options, - std::unique_ptr delegate = nullptr); - - /** - * Starts profiling with the same semantics as above, except with expanded - * parameters. - * - * |record_samples| parameter controls whether individual samples should - * be recorded in addition to the aggregated tree. - * - * |max_samples| controls the maximum number of samples that should be - * recorded by the profiler. Samples obtained after this limit will be - * discarded. - */ - CpuProfilingStatus StartProfiling( - Local title, CpuProfilingMode mode, bool record_samples = false, - unsigned max_samples = CpuProfilingOptions::kNoSampleLimit); - /** - * The same as StartProfiling above, but the CpuProfilingMode defaults to - * kLeafNodeLineNumbers mode, which was the previous default behavior of the - * profiler. - */ - CpuProfilingStatus StartProfiling(Local title, - bool record_samples = false); - - /** - * Stops collecting CPU profile with a given title and returns it. - * If the title given is empty, finishes the last profile started. - */ - CpuProfile* StopProfiling(Local title); - - /** - * Generate more detailed source positions to code objects. This results in - * better results when mapping profiling samples to script source. - */ - static void UseDetailedSourcePositionsForProfiling(Isolate* isolate); - - private: - CpuProfiler(); - ~CpuProfiler(); - CpuProfiler(const CpuProfiler&); - CpuProfiler& operator=(const CpuProfiler&); -}; - -/** - * HeapSnapshotEdge represents a directed connection between heap - * graph nodes: from retainers to retained nodes. - */ -class V8_EXPORT HeapGraphEdge { - public: - enum Type { - kContextVariable = 0, // A variable from a function context. - kElement = 1, // An element of an array. - kProperty = 2, // A named object property. - kInternal = 3, // A link that can't be accessed from JS, - // thus, its name isn't a real property name - // (e.g. parts of a ConsString). - kHidden = 4, // A link that is needed for proper sizes - // calculation, but may be hidden from user. - kShortcut = 5, // A link that must not be followed during - // sizes calculation. - kWeak = 6 // A weak reference (ignored by the GC). - }; - - /** Returns edge type (see HeapGraphEdge::Type). */ - Type GetType() const; - - /** - * Returns edge name. This can be a variable name, an element index, or - * a property name. - */ - Local GetName() const; - - /** Returns origin node. */ - const HeapGraphNode* GetFromNode() const; - - /** Returns destination node. */ - const HeapGraphNode* GetToNode() const; -}; - - -/** - * HeapGraphNode represents a node in a heap graph. - */ -class V8_EXPORT HeapGraphNode { - public: - enum Type { - kHidden = 0, // Hidden node, may be filtered when shown to user. - kArray = 1, // An array of elements. - kString = 2, // A string. - kObject = 3, // A JS object (except for arrays and strings). - kCode = 4, // Compiled code. - kClosure = 5, // Function closure. - kRegExp = 6, // RegExp. - kHeapNumber = 7, // Number stored in the heap. - kNative = 8, // Native object (not from V8 heap). - kSynthetic = 9, // Synthetic object, usually used for grouping - // snapshot items together. - kConsString = 10, // Concatenated string. A pair of pointers to strings. - kSlicedString = 11, // Sliced string. A fragment of another string. - kSymbol = 12, // A Symbol (ES6). - kBigInt = 13 // BigInt. - }; - - /** Returns node type (see HeapGraphNode::Type). */ - Type GetType() const; - - /** - * Returns node name. Depending on node's type this can be the name - * of the constructor (for objects), the name of the function (for - * closures), string value, or an empty string (for compiled code). - */ - Local GetName() const; - - /** - * Returns node id. For the same heap object, the id remains the same - * across all snapshots. - */ - SnapshotObjectId GetId() const; - - /** Returns node's own size, in bytes. */ - size_t GetShallowSize() const; - - /** Returns child nodes count of the node. */ - int GetChildrenCount() const; - - /** Retrieves a child by index. */ - const HeapGraphEdge* GetChild(int index) const; -}; - - -/** - * An interface for exporting data from V8, using "push" model. - */ -class V8_EXPORT OutputStream { - public: - enum WriteResult { - kContinue = 0, - kAbort = 1 - }; - virtual ~OutputStream() = default; - /** Notify about the end of stream. */ - virtual void EndOfStream() = 0; - /** Get preferred output chunk size. Called only once. */ - virtual int GetChunkSize() { return 1024; } - /** - * Writes the next chunk of snapshot data into the stream. Writing - * can be stopped by returning kAbort as function result. EndOfStream - * will not be called in case writing was aborted. - */ - virtual WriteResult WriteAsciiChunk(char* data, int size) = 0; - /** - * Writes the next chunk of heap stats data into the stream. Writing - * can be stopped by returning kAbort as function result. EndOfStream - * will not be called in case writing was aborted. - */ - virtual WriteResult WriteHeapStatsChunk(HeapStatsUpdate* data, int count) { - return kAbort; - } -}; - -/** - * HeapSnapshots record the state of the JS heap at some moment. - */ -class V8_EXPORT HeapSnapshot { - public: - enum SerializationFormat { - kJSON = 0 // See format description near 'Serialize' method. - }; - - /** Returns the root node of the heap graph. */ - const HeapGraphNode* GetRoot() const; - - /** Returns a node by its id. */ - const HeapGraphNode* GetNodeById(SnapshotObjectId id) const; - - /** Returns total nodes count in the snapshot. */ - int GetNodesCount() const; - - /** Returns a node by index. */ - const HeapGraphNode* GetNode(int index) const; - - /** Returns a max seen JS object Id. */ - SnapshotObjectId GetMaxSnapshotJSObjectId() const; - - /** - * Deletes the snapshot and removes it from HeapProfiler's list. - * All pointers to nodes, edges and paths previously returned become - * invalid. - */ - void Delete(); - - /** - * Prepare a serialized representation of the snapshot. The result - * is written into the stream provided in chunks of specified size. - * The total length of the serialized snapshot is unknown in - * advance, it can be roughly equal to JS heap size (that means, - * it can be really big - tens of megabytes). - * - * For the JSON format, heap contents are represented as an object - * with the following structure: - * - * { - * snapshot: { - * title: "...", - * uid: nnn, - * meta: { meta-info }, - * node_count: nnn, - * edge_count: nnn - * }, - * nodes: [nodes array], - * edges: [edges array], - * strings: [strings array] - * } - * - * Nodes reference strings, other nodes, and edges by their indexes - * in corresponding arrays. - */ - void Serialize(OutputStream* stream, - SerializationFormat format = kJSON) const; -}; - - -/** - * An interface for reporting progress and controlling long-running - * activities. - */ -class V8_EXPORT ActivityControl { - public: - enum ControlOption { - kContinue = 0, - kAbort = 1 - }; - virtual ~ActivityControl() = default; - /** - * Notify about current progress. The activity can be stopped by - * returning kAbort as the callback result. - */ - virtual ControlOption ReportProgressValue(int done, int total) = 0; -}; - -/** - * AllocationProfile is a sampled profile of allocations done by the program. - * This is structured as a call-graph. - */ -class V8_EXPORT AllocationProfile { - public: - struct Allocation { - /** - * Size of the sampled allocation object. - */ - size_t size; - - /** - * The number of objects of such size that were sampled. - */ - unsigned int count; - }; - - /** - * Represents a node in the call-graph. - */ - struct Node { - /** - * Name of the function. May be empty for anonymous functions or if the - * script corresponding to this function has been unloaded. - */ - Local name; - - /** - * Name of the script containing the function. May be empty if the script - * name is not available, or if the script has been unloaded. - */ - Local script_name; - - /** - * id of the script where the function is located. May be equal to - * v8::UnboundScript::kNoScriptId in cases where the script doesn't exist. - */ - int script_id; - - /** - * Start position of the function in the script. - */ - int start_position; - - /** - * 1-indexed line number where the function starts. May be - * kNoLineNumberInfo if no line number information is available. - */ - int line_number; - - /** - * 1-indexed column number where the function starts. May be - * kNoColumnNumberInfo if no line number information is available. - */ - int column_number; - - /** - * Unique id of the node. - */ - uint32_t node_id; - - /** - * List of callees called from this node for which we have sampled - * allocations. The lifetime of the children is scoped to the containing - * AllocationProfile. - */ - std::vector children; - - /** - * List of self allocations done by this node in the call-graph. - */ - std::vector allocations; - }; - - /** - * Represent a single sample recorded for an allocation. - */ - struct Sample { - /** - * id of the node in the profile tree. - */ - uint32_t node_id; - - /** - * Size of the sampled allocation object. - */ - size_t size; - - /** - * The number of objects of such size that were sampled. - */ - unsigned int count; - - /** - * Unique time-ordered id of the allocation sample. Can be used to track - * what samples were added or removed between two snapshots. - */ - uint64_t sample_id; - }; - - /** - * Returns the root node of the call-graph. The root node corresponds to an - * empty JS call-stack. The lifetime of the returned Node* is scoped to the - * containing AllocationProfile. - */ - virtual Node* GetRootNode() = 0; - virtual const std::vector& GetSamples() = 0; - - virtual ~AllocationProfile() = default; - - static const int kNoLineNumberInfo = Message::kNoLineNumberInfo; - static const int kNoColumnNumberInfo = Message::kNoColumnInfo; -}; - -/** - * An object graph consisting of embedder objects and V8 objects. - * Edges of the graph are strong references between the objects. - * The embedder can build this graph during heap snapshot generation - * to include the embedder objects in the heap snapshot. - * Usage: - * 1) Define derived class of EmbedderGraph::Node for embedder objects. - * 2) Set the build embedder graph callback on the heap profiler using - * HeapProfiler::AddBuildEmbedderGraphCallback. - * 3) In the callback use graph->AddEdge(node1, node2) to add an edge from - * node1 to node2. - * 4) To represent references from/to V8 object, construct V8 nodes using - * graph->V8Node(value). - */ -class V8_EXPORT EmbedderGraph { - public: - class Node { - public: - /** - * Detachedness specifies whether an object is attached or detached from the - * main application state. While unkown in general, there may be objects - * that specifically know their state. V8 passes this information along in - * the snapshot. Users of the snapshot may use it to annotate the object - * graph. - */ - enum class Detachedness : uint8_t { - kUnknown = 0, - kAttached = 1, - kDetached = 2, - }; - - Node() = default; - virtual ~Node() = default; - virtual const char* Name() = 0; - virtual size_t SizeInBytes() = 0; - /** - * The corresponding V8 wrapper node if not null. - * During heap snapshot generation the embedder node and the V8 wrapper - * node will be merged into one node to simplify retaining paths. - */ - virtual Node* WrapperNode() { return nullptr; } - virtual bool IsRootNode() { return false; } - /** Must return true for non-V8 nodes. */ - virtual bool IsEmbedderNode() { return true; } - /** - * Optional name prefix. It is used in Chrome for tagging detached nodes. - */ - virtual const char* NamePrefix() { return nullptr; } - - /** - * Returns the NativeObject that can be used for querying the - * |HeapSnapshot|. - */ - virtual NativeObject GetNativeObject() { return nullptr; } - - /** - * Detachedness state of a given object. While unkown in general, there may - * be objects that specifically know their state. V8 passes this information - * along in the snapshot. Users of the snapshot may use it to annotate the - * object graph. - */ - virtual Detachedness GetDetachedness() { return Detachedness::kUnknown; } - - Node(const Node&) = delete; - Node& operator=(const Node&) = delete; - }; - - /** - * Returns a node corresponding to the given V8 value. Ownership is not - * transferred. The result pointer is valid while the graph is alive. - */ - virtual Node* V8Node(const v8::Local& value) = 0; - - /** - * Adds the given node to the graph and takes ownership of the node. - * Returns a raw pointer to the node that is valid while the graph is alive. - */ - virtual Node* AddNode(std::unique_ptr node) = 0; - - /** - * Adds an edge that represents a strong reference from the given - * node |from| to the given node |to|. The nodes must be added to the graph - * before calling this function. - * - * If name is nullptr, the edge will have auto-increment indexes, otherwise - * it will be named accordingly. - */ - virtual void AddEdge(Node* from, Node* to, const char* name = nullptr) = 0; - - virtual ~EmbedderGraph() = default; -}; - -/** - * Interface for controlling heap profiling. Instance of the - * profiler can be retrieved using v8::Isolate::GetHeapProfiler. - */ -class V8_EXPORT HeapProfiler { - public: - enum SamplingFlags { - kSamplingNoFlags = 0, - kSamplingForceGC = 1 << 0, - }; - - /** - * Callback function invoked during heap snapshot generation to retrieve - * the embedder object graph. The callback should use graph->AddEdge(..) to - * add references between the objects. - * The callback must not trigger garbage collection in V8. - */ - typedef void (*BuildEmbedderGraphCallback)(v8::Isolate* isolate, - v8::EmbedderGraph* graph, - void* data); - - /** - * Callback function invoked during heap snapshot generation to retrieve - * the detachedness state of an object referenced by a TracedReference. - * - * The callback takes Local as parameter to allow the embedder to - * unpack the TracedReference into a Local and reuse that Local for different - * purposes. - */ - using GetDetachednessCallback = EmbedderGraph::Node::Detachedness (*)( - v8::Isolate* isolate, const v8::Local& v8_value, - uint16_t class_id, void* data); - - /** Returns the number of snapshots taken. */ - int GetSnapshotCount(); - - /** Returns a snapshot by index. */ - const HeapSnapshot* GetHeapSnapshot(int index); - - /** - * Returns SnapshotObjectId for a heap object referenced by |value| if - * it has been seen by the heap profiler, kUnknownObjectId otherwise. - */ - SnapshotObjectId GetObjectId(Local value); - - /** - * Returns SnapshotObjectId for a native object referenced by |value| if it - * has been seen by the heap profiler, kUnknownObjectId otherwise. - */ - SnapshotObjectId GetObjectId(NativeObject value); - - /** - * Returns heap object with given SnapshotObjectId if the object is alive, - * otherwise empty handle is returned. - */ - Local FindObjectById(SnapshotObjectId id); - - /** - * Clears internal map from SnapshotObjectId to heap object. The new objects - * will not be added into it unless a heap snapshot is taken or heap object - * tracking is kicked off. - */ - void ClearObjectIds(); - - /** - * A constant for invalid SnapshotObjectId. GetSnapshotObjectId will return - * it in case heap profiler cannot find id for the object passed as - * parameter. HeapSnapshot::GetNodeById will always return NULL for such id. - */ - static const SnapshotObjectId kUnknownObjectId = 0; - - /** - * Callback interface for retrieving user friendly names of global objects. - */ - class ObjectNameResolver { - public: - /** - * Returns name to be used in the heap snapshot for given node. Returned - * string must stay alive until snapshot collection is completed. - */ - virtual const char* GetName(Local object) = 0; - - protected: - virtual ~ObjectNameResolver() = default; - }; - - /** - * Takes a heap snapshot and returns it. - */ - const HeapSnapshot* TakeHeapSnapshot( - ActivityControl* control = nullptr, - ObjectNameResolver* global_object_name_resolver = nullptr, - bool treat_global_objects_as_roots = true, - bool capture_numeric_value = false); - - /** - * Starts tracking of heap objects population statistics. After calling - * this method, all heap objects relocations done by the garbage collector - * are being registered. - * - * |track_allocations| parameter controls whether stack trace of each - * allocation in the heap will be recorded and reported as part of - * HeapSnapshot. - */ - void StartTrackingHeapObjects(bool track_allocations = false); - - /** - * Adds a new time interval entry to the aggregated statistics array. The - * time interval entry contains information on the current heap objects - * population size. The method also updates aggregated statistics and - * reports updates for all previous time intervals via the OutputStream - * object. Updates on each time interval are provided as a stream of the - * HeapStatsUpdate structure instances. - * If |timestamp_us| is supplied, timestamp of the new entry will be written - * into it. The return value of the function is the last seen heap object Id. - * - * StartTrackingHeapObjects must be called before the first call to this - * method. - */ - SnapshotObjectId GetHeapStats(OutputStream* stream, - int64_t* timestamp_us = nullptr); - - /** - * Stops tracking of heap objects population statistics, cleans up all - * collected data. StartHeapObjectsTracking must be called again prior to - * calling GetHeapStats next time. - */ - void StopTrackingHeapObjects(); - - /** - * Starts gathering a sampling heap profile. A sampling heap profile is - * similar to tcmalloc's heap profiler and Go's mprof. It samples object - * allocations and builds an online 'sampling' heap profile. At any point in - * time, this profile is expected to be a representative sample of objects - * currently live in the system. Each sampled allocation includes the stack - * trace at the time of allocation, which makes this really useful for memory - * leak detection. - * - * This mechanism is intended to be cheap enough that it can be used in - * production with minimal performance overhead. - * - * Allocations are sampled using a randomized Poisson process. On average, one - * allocation will be sampled every |sample_interval| bytes allocated. The - * |stack_depth| parameter controls the maximum number of stack frames to be - * captured on each allocation. - * - * NOTE: This is a proof-of-concept at this point. Right now we only sample - * newspace allocations. Support for paged space allocation (e.g. pre-tenured - * objects, large objects, code objects, etc.) and native allocations - * doesn't exist yet, but is anticipated in the future. - * - * Objects allocated before the sampling is started will not be included in - * the profile. - * - * Returns false if a sampling heap profiler is already running. - */ - bool StartSamplingHeapProfiler(uint64_t sample_interval = 512 * 1024, - int stack_depth = 16, - SamplingFlags flags = kSamplingNoFlags); - - /** - * Stops the sampling heap profile and discards the current profile. - */ - void StopSamplingHeapProfiler(); - - /** - * Returns the sampled profile of allocations allocated (and still live) since - * StartSamplingHeapProfiler was called. The ownership of the pointer is - * transferred to the caller. Returns nullptr if sampling heap profiler is not - * active. - */ - AllocationProfile* GetAllocationProfile(); - - /** - * Deletes all snapshots taken. All previously returned pointers to - * snapshots and their contents become invalid after this call. - */ - void DeleteAllHeapSnapshots(); - - void AddBuildEmbedderGraphCallback(BuildEmbedderGraphCallback callback, - void* data); - void RemoveBuildEmbedderGraphCallback(BuildEmbedderGraphCallback callback, - void* data); - - void SetGetDetachednessCallback(GetDetachednessCallback callback, void* data); - - /** - * Default value of persistent handle class ID. Must not be used to - * define a class. Can be used to reset a class of a persistent - * handle. - */ - static const uint16_t kPersistentHandleNoClassId = 0; - - private: - HeapProfiler(); - ~HeapProfiler(); - HeapProfiler(const HeapProfiler&); - HeapProfiler& operator=(const HeapProfiler&); -}; - -/** - * A struct for exporting HeapStats data from V8, using "push" model. - * See HeapProfiler::GetHeapStats. - */ -struct HeapStatsUpdate { - HeapStatsUpdate(uint32_t index, uint32_t count, uint32_t size) - : index(index), count(count), size(size) { } - uint32_t index; // Index of the time interval that was changed. - uint32_t count; // New value of count field for the interval with this index. - uint32_t size; // New value of size field for the interval with this index. -}; - -#define CODE_EVENTS_LIST(V) \ - V(Builtin) \ - V(Callback) \ - V(Eval) \ - V(Function) \ - V(InterpretedFunction) \ - V(Handler) \ - V(BytecodeHandler) \ - V(LazyCompile) \ - V(RegExp) \ - V(Script) \ - V(Stub) \ - V(Relocation) - -/** - * Note that this enum may be extended in the future. Please include a default - * case if this enum is used in a switch statement. - */ -enum CodeEventType { - kUnknownType = 0 -#define V(Name) , k##Name##Type - CODE_EVENTS_LIST(V) -#undef V -}; - -/** - * Representation of a code creation event - */ -class V8_EXPORT CodeEvent { - public: - uintptr_t GetCodeStartAddress(); - size_t GetCodeSize(); - Local GetFunctionName(); - Local GetScriptName(); - int GetScriptLine(); - int GetScriptColumn(); - /** - * NOTE (mmarchini): We can't allocate objects in the heap when we collect - * existing code, and both the code type and the comment are not stored in the - * heap, so we return those as const char*. - */ - CodeEventType GetCodeType(); - const char* GetComment(); - - static const char* GetCodeEventTypeName(CodeEventType code_event_type); - - uintptr_t GetPreviousCodeStartAddress(); -}; - -/** - * Interface to listen to code creation and code relocation events. - */ -class V8_EXPORT CodeEventHandler { - public: - /** - * Creates a new listener for the |isolate|. The isolate must be initialized. - * The listener object must be disposed after use by calling |Dispose| method. - * Multiple listeners can be created for the same isolate. - */ - explicit CodeEventHandler(Isolate* isolate); - virtual ~CodeEventHandler(); - - /** - * Handle is called every time a code object is created or moved. Information - * about each code event will be available through the `code_event` - * parameter. - * - * When the CodeEventType is kRelocationType, the code for this CodeEvent has - * moved from `GetPreviousCodeStartAddress()` to `GetCodeStartAddress()`. - */ - virtual void Handle(CodeEvent* code_event) = 0; - - /** - * Call `Enable()` to starts listening to code creation and code relocation - * events. These events will be handled by `Handle()`. - */ - void Enable(); - - /** - * Call `Disable()` to stop listening to code creation and code relocation - * events. - */ - void Disable(); - - private: - CodeEventHandler(); - CodeEventHandler(const CodeEventHandler&); - CodeEventHandler& operator=(const CodeEventHandler&); - void* internal_listener_; -}; - -} // namespace v8 - - -#endif // V8_V8_PROFILER_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-promise.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-promise.h deleted file mode 100644 index 9da8e4b4e86..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-promise.h +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_PROMISE_H_ -#define INCLUDE_V8_PROMISE_H_ - -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8-object.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Context; - -#ifndef V8_PROMISE_INTERNAL_FIELD_COUNT -// The number of required internal fields can be defined by embedder. -#define V8_PROMISE_INTERNAL_FIELD_COUNT 0 -#endif - -/** - * An instance of the built-in Promise constructor (ES6 draft). - */ -class V8_EXPORT Promise : public Object { - public: - /** - * State of the promise. Each value corresponds to one of the possible values - * of the [[PromiseState]] field. - */ - enum PromiseState { kPending, kFulfilled, kRejected }; - - class V8_EXPORT Resolver : public Object { - public: - /** - * Create a new resolver, along with an associated promise in pending state. - */ - static V8_WARN_UNUSED_RESULT MaybeLocal New( - Local context); - - /** - * Extract the associated promise. - */ - Local GetPromise(); - - /** - * Resolve/reject the associated promise with a given value. - * Ignored if the promise is no longer pending. - */ - V8_WARN_UNUSED_RESULT Maybe Resolve(Local context, - Local value); - - V8_WARN_UNUSED_RESULT Maybe Reject(Local context, - Local value); - - V8_INLINE static Resolver* Cast(Value* value) { -#ifdef V8_ENABLE_CHECKS - CheckCast(value); -#endif - return static_cast(value); - } - - private: - Resolver(); - static void CheckCast(Value* obj); - }; - - /** - * Register a resolution/rejection handler with a promise. - * The handler is given the respective resolution/rejection value as - * an argument. If the promise is already resolved/rejected, the handler is - * invoked at the end of turn. - */ - V8_WARN_UNUSED_RESULT MaybeLocal Catch(Local context, - Local handler); - - V8_WARN_UNUSED_RESULT MaybeLocal Then(Local context, - Local handler); - - V8_WARN_UNUSED_RESULT MaybeLocal Then(Local context, - Local on_fulfilled, - Local on_rejected); - - /** - * Returns true if the promise has at least one derived promise, and - * therefore resolve/reject handlers (including default handler). - */ - bool HasHandler() const; - - /** - * Returns the content of the [[PromiseResult]] field. The Promise must not - * be pending. - */ - Local Result(); - - /** - * Returns the value of the [[PromiseState]] field. - */ - PromiseState State(); - - /** - * Marks this promise as handled to avoid reporting unhandled rejections. - */ - void MarkAsHandled(); - - /** - * Marks this promise as silent to prevent pausing the debugger when the - * promise is rejected. - */ - void MarkAsSilent(); - - V8_INLINE static Promise* Cast(Value* value) { -#ifdef V8_ENABLE_CHECKS - CheckCast(value); -#endif - return static_cast(value); - } - - static const int kEmbedderFieldCount = V8_PROMISE_INTERNAL_FIELD_COUNT; - - private: - Promise(); - static void CheckCast(Value* obj); -}; - -/** - * PromiseHook with type kInit is called when a new promise is - * created. When a new promise is created as part of the chain in the - * case of Promise.then or in the intermediate promises created by - * Promise.{race, all}/AsyncFunctionAwait, we pass the parent promise - * otherwise we pass undefined. - * - * PromiseHook with type kResolve is called at the beginning of - * resolve or reject function defined by CreateResolvingFunctions. - * - * PromiseHook with type kBefore is called at the beginning of the - * PromiseReactionJob. - * - * PromiseHook with type kAfter is called right at the end of the - * PromiseReactionJob. - */ -enum class PromiseHookType { kInit, kResolve, kBefore, kAfter }; - -using PromiseHook = void (*)(PromiseHookType type, Local promise, - Local parent); - -// --- Promise Reject Callback --- -enum PromiseRejectEvent { - kPromiseRejectWithNoHandler = 0, - kPromiseHandlerAddedAfterReject = 1, - kPromiseRejectAfterResolved = 2, - kPromiseResolveAfterResolved = 3, -}; - -class PromiseRejectMessage { - public: - PromiseRejectMessage(Local promise, PromiseRejectEvent event, - Local value) - : promise_(promise), event_(event), value_(value) {} - - V8_INLINE Local GetPromise() const { return promise_; } - V8_INLINE PromiseRejectEvent GetEvent() const { return event_; } - V8_INLINE Local GetValue() const { return value_; } - - private: - Local promise_; - PromiseRejectEvent event_; - Local value_; -}; - -using PromiseRejectCallback = void (*)(PromiseRejectMessage message); - -} // namespace v8 - -#endif // INCLUDE_V8_PROMISE_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-proxy.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-proxy.h deleted file mode 100644 index a08db8805c6..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-proxy.h +++ /dev/null @@ -1,50 +0,0 @@ - -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_PROXY_H_ -#define INCLUDE_V8_PROXY_H_ - -#include "v8-context.h" // NOLINT(build/include_directory) -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8-object.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Context; - -/** - * An instance of the built-in Proxy constructor (ECMA-262, 6th Edition, - * 26.2.1). - */ -class V8_EXPORT Proxy : public Object { - public: - Local GetTarget(); - Local GetHandler(); - bool IsRevoked() const; - void Revoke(); - - /** - * Creates a new Proxy for the target object. - */ - static MaybeLocal New(Local context, - Local local_target, - Local local_handler); - - V8_INLINE static Proxy* Cast(Value* value) { -#ifdef V8_ENABLE_CHECKS - CheckCast(value); -#endif - return static_cast(value); - } - - private: - Proxy(); - static void CheckCast(Value* obj); -}; - -} // namespace v8 - -#endif // INCLUDE_V8_PROXY_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-regexp.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-regexp.h deleted file mode 100644 index 3791bc03687..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-regexp.h +++ /dev/null @@ -1,105 +0,0 @@ - -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_REGEXP_H_ -#define INCLUDE_V8_REGEXP_H_ - -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8-object.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Context; - -/** - * An instance of the built-in RegExp constructor (ECMA-262, 15.10). - */ -class V8_EXPORT RegExp : public Object { - public: - /** - * Regular expression flag bits. They can be or'ed to enable a set - * of flags. - * The kLinear value ('l') is experimental and can only be used with - * --enable-experimental-regexp-engine. RegExps with kLinear flag are - * guaranteed to be executed in asymptotic linear time wrt. the length of - * the subject string. - */ - enum Flags { - kNone = 0, - kGlobal = 1 << 0, - kIgnoreCase = 1 << 1, - kMultiline = 1 << 2, - kSticky = 1 << 3, - kUnicode = 1 << 4, - kDotAll = 1 << 5, - kLinear = 1 << 6, - kHasIndices = 1 << 7, - }; - - static constexpr int kFlagCount = 8; - - /** - * Creates a regular expression from the given pattern string and - * the flags bit field. May throw a JavaScript exception as - * described in ECMA-262, 15.10.4.1. - * - * For example, - * RegExp::New(v8::String::New("foo"), - * static_cast(kGlobal | kMultiline)) - * is equivalent to evaluating "/foo/gm". - */ - static V8_WARN_UNUSED_RESULT MaybeLocal New(Local context, - Local pattern, - Flags flags); - - /** - * Like New, but additionally specifies a backtrack limit. If the number of - * backtracks done in one Exec call hits the limit, a match failure is - * immediately returned. - */ - static V8_WARN_UNUSED_RESULT MaybeLocal NewWithBacktrackLimit( - Local context, Local pattern, Flags flags, - uint32_t backtrack_limit); - - /** - * Executes the current RegExp instance on the given subject string. - * Equivalent to RegExp.prototype.exec as described in - * - * https://tc39.es/ecma262/#sec-regexp.prototype.exec - * - * On success, an Array containing the matched strings is returned. On - * failure, returns Null. - * - * Note: modifies global context state, accessible e.g. through RegExp.input. - */ - V8_WARN_UNUSED_RESULT MaybeLocal Exec(Local context, - Local subject); - - /** - * Returns the value of the source property: a string representing - * the regular expression. - */ - Local GetSource() const; - - /** - * Returns the flags bit field. - */ - Flags GetFlags() const; - - V8_INLINE static RegExp* Cast(Value* value) { -#ifdef V8_ENABLE_CHECKS - CheckCast(value); -#endif - return static_cast(value); - } - - private: - static void CheckCast(Value* obj); -}; - -} // namespace v8 - -#endif // INCLUDE_V8_REGEXP_H_ diff --git a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-script.h b/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-script.h deleted file mode 100644 index d17089932cc..00000000000 --- a/android/sdk/src/main/jni/third_party/v8/latest/official-release/include/v8/v8-script.h +++ /dev/null @@ -1,771 +0,0 @@ -// Copyright 2021 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef INCLUDE_V8_SCRIPT_H_ -#define INCLUDE_V8_SCRIPT_H_ - -#include -#include - -#include -#include - -#include "v8-data.h" // NOLINT(build/include_directory) -#include "v8-local-handle.h" // NOLINT(build/include_directory) -#include "v8-maybe.h" // NOLINT(build/include_directory) -#include "v8-message.h" // NOLINT(build/include_directory) -#include "v8config.h" // NOLINT(build/include_directory) - -namespace v8 { - -class Function; -class Object; -class PrimitiveArray; -class Script; - -namespace internal { -class BackgroundDeserializeTask; -struct ScriptStreamingData; -} // namespace internal - -/** - * A container type that holds relevant metadata for module loading. - * - * This is passed back to the embedder as part of - * HostImportModuleDynamicallyCallback for module loading. - */ -class V8_EXPORT ScriptOrModule { - public: - /** - * The name that was passed by the embedder as ResourceName to the - * ScriptOrigin. This can be either a v8::String or v8::Undefined. - */ - Local GetResourceName(); - - /** - * The options that were passed by the embedder as HostDefinedOptions to - * the ScriptOrigin. - */ - Local GetHostDefinedOptions(); -}; - -/** - * A compiled JavaScript script, not yet tied to a Context. - */ -class V8_EXPORT UnboundScript { - public: - /** - * Binds the script to the currently entered context. - */ - Local +``` + + > For special instructions, a new animation will be automatically created after the actions are replaced, you need to manually start the new animation. There are two methods: + > + >* Replace the actions => After a certain delay (e.g. SetTimeout) or in `actionsDidUpdate` (supported after 2.14.0), call `this.[animation ref].start()` (recommended). + > * Set `playing = false`=> replace actions => delay after a certain time (such as setTimeout) or in `actionsDidUpdate` (supported after 2.14.0), set `playing = true` + + > Version `2.6.0` and the above support `backgroundColor` background color gradient animation, reference [gradient animation DEMO](https://github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/native-demos/animations/color-change.vue) + > + > * set `actions` to decorate `backgroundColor` + > * set `valueType` to `color` + > * set `startValue` and `toValue` to [color value](style/color.md) + + > Version `2.12.2` and the above support parameters `repeatCount: 'loop'`, use `repeatCount: -1` for lower version. + +## Events + +| Props | Description | Type | Supported Platforms | +| ------------- | ------------------------------------------------------------ | ----------------------------------------- | -------- | +| start | Called when animation starts | `Function` | `Android、iOS、Web-Renderer` | +| end | Called when animation ends | `Function`| `Android、iOS、Web-Renderer` | +| repeat | Called each time the loop is played | `Function` | `Android` | + +## Methods + +> minimum supported version 2.5.2 + +### start + +`() => void` Manually call the animation start (`playing` attribute set to `true` will automatically call the `start` function) + +### pause + +`() => void` Manually call the animation to pause (`playing` attribute set to `false` will automatically call the `pause` function) + +### resume + +`() => void` Manually call the animation to continue (`playing` property set to `false` and then set to `true` will automatically call the `resume` function) + +### create + +`() => void` Manually call the animation to create + +### reset + +`() => void` Reset the start marker + +### destroy + +`() => void` Destroy the animation + +--- + +# dialog + +[[Example: demo-dialog.vue]](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/native-demos/demo-dialog.vue) + +Used for modal pop-up window, the default background is transparent, needs to add a `
` with background color to fill. + +## Attributes + +| Props | Description | Type | Supported Platforms | +| ------------- | ------------------------------------------------------------ | ----------------------------------------- | -------- | +| animationType | Animation effects | `enum(none, slide, fade, slide_fade)` | `Android、iOS、Web-Renderer` | +| supportedOrientations | Supports screen orientation reversal | `enum(portrait, portrait-upside-down, landscape, landscape-left, landscape-right)[]` | `iOS` | +| immersionStatusBar | Whether it is an immersive status bar. `default: true` | `boolean` | `Android` | +| darkStatusBarText | Whether main body text is bright color, the default font color is black. The Modal background will be dark after changing it to true, the font color will be changed to white. | `boolean` | `Android、iOS` | +| transparent | Whether the background is transparent. `default: true` | `boolean` | `Android、iOS、Web-Renderer` | + +## Events + +| Event name | Description | Type | Supported Platforms | +| ------------- | ------------------------------------------------------------ | ----------------------------------------- | -------- | +| show | This callback function is called when `Modal` is displayed. | `Function` | `Android、iOS、Web-Renderer` | +| orientationChange | Screen rotation direction changes | `Function` | `Android、iOS` | +| requestClose | Called when the `Modal` requires to close, generally called in the Android system hardware when the return button is triggered, generally to close the popup window inside processing. | `Function` | `Android` | + +--- + +# swiper + +[[Example: demo-swiper.vue]](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/native-demos/demo-swiper.vue) + +A container that supports paging, its each child container component will be regarded as a separate page, corresponding the native `ViewPager` component, it can only contain `` components. + +## Attributes + +| Props | Description | Type | Supported Platforms | +| ------------------------ | ------------------------------------------------------------ | -------------------------------------------- | -------- | +| bounces | Whether to open the springback effect, the default is `true` | `boolean` | `iOS` | +| current | Change the current page number in real time | `number` | `Android、iOS、Web-Renderer` | +| initialPage | Specify a number, it will be used to determine the default display page index after initialization, the default is 0 when it is not specified | `number` | `Android、iOS、Web-Renderer` | +| needAnimation | Whether animation is required when switching pages. | `boolean` | `Android、iOS` | +| scrollEnabled | Specify whether ViewPager can slide, the default is true | `boolean` | `Android、iOS、Web-Renderer` | +| direction | Set viewPager scroll direction, the default is horizontal scroll, use `vertical` for vertical scroll | `string` | `Android` | + +## Events + +| Event name | Description | Type | Supported Platforms | +| ------------- | ------------------------------------------------------------ | ----------------------------------------- | -------- | +| dragging | Called when dragged. | `Function` | `Android、iOS、Web-Renderer` | +| dropped | Called when the drag is done. Called when a scrolling page action is detected. | `Function` | `Android、iOS、Web-Renderer` | +| stateChanged* | Called when finger behavior changes, including idle, dragging and settling states, returned through state parameter | `Function` | `Android、iOS、Web-Renderer` | + +* stateChanged The meaning of the three values: + * idle idle state + * dragging Dragging + * settling Called after releasing the hand, and then return to idle immediately + +--- + +# swiper-slide + +[[Example: demo-swiper.vue]](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/native-demos/demo-swiper.vue) + +Subcontainer of the flipping component container. + +--- + +# pull-header + +[[Example: demo-pull-header.vue]](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/native-demos/demo-pull-header-footer.vue) + +Dropdown refresh component, nested in `ul` as first child element + +## Events + +| Event name | Description | Type | Supported Platforms | +| ------------- | ------------------------------------------------------------ | ----------------------------------------- | -------- | +| idle | Called once when the sliding distance is in the pull-header area. The parameter is contentOffset | `Function` | `Android、iOS` | +| pulling | Called once after the sliding distance exceeds pull-header area. The parameter is contentOffset | `Function` | `Android、iOS` | +| released | Called once after the sliding beyond the distance. | `Function` | `Android、iOS` | + +## Methods + +### collapsePullHeader + +`(otions: { time: number }) => void` collapse the top refresh bar ``. When using the `pull-header`, you need to call the method to take back the pull-header after each pull-down refresh ends. + +> options parameter,`minimum supported version 2.14.0` +> +>* time: number: specify how much delay collapsing the pull-header, the unit is ms. + +### expandPullHeader + +`() => void` Expand the refresh bar ``. When the refresh ends, you need to actively call `collapsePullHeader` to put away the pull-header. + +--- + +# pull-footer + +[[Example: demo-pull-footer.vue]](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/native-demos/demo-pull-header-footer.vue) + +Pull-up refresh component, nested in `ul` as last child element + +## Events + +| Event name | Description | Type | Supported Platforms | +| ------------- | ------------------------------------------------------------ | ----------------------------------------- | -------- | +| idle | Called once when the sliding distance is in the pull-header area. The parameter is contentOffset | `Function` | `Android、iOS` | +| pulling | Called once after the sliding distance exceeds pull-header area. The parameter is contentOffset | `Function` | `Android、iOS` | +| refresh | Called once after the sliding beyond the distance. | `Function` | `Android、iOS` | + + +## Methods + +### collapsePullFooter + +`() => void` Collapse the bottom refresh bar ``. + +--- + +# waterfall + +> minimum supported version 2.9.0 + +[[Example: demo-waterfall]](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/native-demos/demo-waterfall.vue) + +Waterfall flow component, the child element must be `waterfall-item`, waterfall flow component drop-down needs to use `ul-refresh-wrapper` in the outermost layer when refresh, you can use `pull-footer` in the `waterfall` to show the pull-up loading text. + +## Attributes + +| Props | Description | Type | Supported Platforms | +| ----------------- | ----------------------------------------------------- | ---------- | -------- | +| columnSpacing | Horizontal spacing before each column of waterfall | `number` | `Android、iOS` | +| interItemSpacing | Vertical space between items | `number` | `Android、iOS` | +| contentInset | Content indentation, default `{ top:0, left:0, bottom:0, right:0 }` | `Object` | `Android、iOS` | +| containBannerView | Whether to include `bannerView`, there can be only one bannerView, `Android` support after version `2.15.0` | `boolean` | `Android、iOS` | +| containPullHeader | Whether to include `pull-header`; `Android` is not supported, can use `ul-refresh` component instead | `boolean` | `iOS` | +| containPullFooter | Whether to include `pull-footer` | `boolean` | `Android、iOS` | +| numberOfColumns | Number of waterfall columns, Default: 2 | `number` | `Android、iOS` | +| preloadItemNumber | Number of items preloaded in advance before sliding to the bottom of waterfall | `number` | `Android、iOS` | + +## Events + +| Event name | Description | `type` | Supported Platforms | +| --------------------- | -------------- | ---------- | -------- | +| endReached | When all the data has been rendered, and the list is scrolled to the last one, `onEndReached` will be called. | `Function` | `Android、iOS` | +| scroll | Called when the sliding event of `WaterFall` is called. `startEdgePos` is that scroll offset from the top edge of the List; `endEdgePos` is the scroll offset from the bottom edge of the List; `firstVisibleRowIndex` is the index of the first element in the currently visible area; `lastVisibleRowIndex` is the index of the last element in the currently visible area; `visibleRowFrames` is the information (x, y, width, height) of all items in the currently visible area | `{ nativeEvent: { startEdgePos: number, endEdgePos: number, firstVisibleRowIndex: number, lastVisibleRowIndex: number, visibleRowFrames: Object[] } }` | `Android、iOS` | + +## Methods + +### scrollToIndex + +`(obj: { index: number, animated: boolean }) => void` Notifies which item the Waterfall will slide to. + +> * `index`: number - the index of the item slides to +> * `animated`: boolean - Whether the sliding process uses animation, default is `true` + +### scrollToContentOffset + +`(obj: { xOffset: number, yOffset: number, animated: boolean }) => void` Tells the Waterfall to slide to a specific coordinate offset. + +> * `xOffset`: number - offset of the slide in X direction +> * `yOffset`: number - offset of the slide in Y direction +> * `animated`: boolean - Whether the sliding process uses animation, default is `true` + +--- + +# waterfall-item + +> minimum supported version 2.9.0 + +Cell container of the waterfall component, waterfall child element + +| Props | Description | Type | Supported Platforms | +| --------------------- | ------------------------------------------------------------ | ----------------------------------------------------------- | -------- | +| type | Specify a function, in which the type of the corresponding entry is returned (returns the natural number of the Number type, the default is 0), the List will reuse the same type of entry, so the reasonable type split can improve the performance of the List. | `number` | `Android、iOS` | +| key | Specifies a function that returns the Key value of the corresponding entry, as described in [Vue official documentation](//cn.vuejs.org/v2/guide/list.html) | `string` | `Android、iOS` | diff --git a/docs/en-us/hippy-vue/gesture.md b/docs/en-us/hippy-vue/gesture.md new file mode 100644 index 00000000000..7a798220268 --- /dev/null +++ b/docs/en-us/hippy-vue/gesture.md @@ -0,0 +1,62 @@ +# Gesture System + +Gesture system of hippy is relatively more convenient to use. The main difference is that it does not need to rely on other event components. All components including div, p, label, img or various custom controls can listen to click events and touch events. + +--- + +# Click Events + +Click events include four types: long press, click, press and finger lift, which are notified by the following four interfaces: + +1. click: this function is called when the control is clicked. +2longClick: this function is called when the control is long pressed. + +## Example + +The click state effect can be achieved by using onPressIn and onPressOut together. For example, the following example code realizes the function of changing the background color when clicked: + +# Touch Events + +Touch events are handled similarly to click events and can be used on any Vue component. Touch events are mainly composed of the following callback functions: + +1. touchstart(event): When the user starts to press the finger on the control, this function is called back and the touch screen point information is passed in as a parameter. +2. touchmove(event): When the user moves the finger in the control, this function is called continuously and the touch point information of the control is informed through the event parameter. +3. touchend(event): When the touch screen operation ends, this function is called back when the user lifts his finger on the control, and the event parameter will also notify the current touch screen point information. +4. touchcancel(event): This function will be called back when a system event interrupts the touch screen during the user's touch screen process, such as incoming phone calls, component changes (such as setting hidden), sliding gestures of other components, and will inform the front-end touch screen point information through event parameter. `Note: If touchcancel is called, touchend will not be called.` + +The above callback functions all take a parameter `Event` instance, which is similar to Web, including attributes `type`, `target`, `currentTarget`, `event params` and so on: + +> After version 2.16.0, `nativeParmas` attribute is added on `Event`, which contains all parameters sending from native. + +# Event Bubble + +[[Event bubble example]](//github.com/Tencent/Hippy/tree/master/examples/hippy-vue-demo/src/components/native-demos/demo-dialog.vue) + +Both click events and touch events can be defined in the callback function whether the event needs to be bubbled to the upper component. When a click or touch event occurs, the native will look for the lowest-level control declared under the touch screen point to handle the event. + +HippyVue enables `bubbling` by default, you can use `stopPropagation` function or `stop` modifier to stop `bubbling`. + +# Event Capture + +Vue is not supported + +# Event Interception + +In some scenarios, the parent control needs to intercept the gesture events of the child control first, so Hippy also provides a gesture event interception mechanism, which is controlled by two properties of the parent control: `onInterceptTouchEvent` and `onInterceptPullUpEvent`. These two properties are only valid for components that can contain child controls. Controls such as `` do not support these two properties: + +- onInterceptTouchEvent: This property determines whether the parent control intercepts gesture events of all child controls, true is intercepted, false is not intercepted (default is false). When the parent control sets this property to true, all its child controls will not receive any touch event and click event callbacks, regardless of whether event handlers are set. When pressing, moving, raising a finger, and clicking and long-pressing occur in the parent control area, the native sends events to the parent control for processing by default. If the child control is already processing touch events before the parent control sets onInterceptTouchEvent to true, then the child control will receive an onTouchCancel callback (if the child control has registered this function). +- onInterceptPullUpEvent: The function of this property is similar to onInterceptTouchEvent, but the conditions for determining whether the parent control intercepts the event are slightly different.When the value is true, when the user slides the finger up in the current parent control area, all subsequent touch events will be intercepted and processed by the parent control. All child controls will not receive any touch event callbacks, regardless of whether the event handler is set; if the child controls are already processing touch events before the interception takes effect, the child controls will receive an onTouchCancel callback. When false, the parent control will not intercept events, the default is false. + +Note that due to the different interception conditions of these two properties, after the onInterceptTouchEvent flag is set to true, all touch events of the child control will be invalid, while onInterceptPullUpEvent will not affect the click event of the child control. + +Let's take code as an example: + +```vue + +``` diff --git a/docs/en-us/hippy-vue/introduction.md b/docs/en-us/hippy-vue/introduction.md new file mode 100644 index 00000000000..666f553cac1 --- /dev/null +++ b/docs/en-us/hippy-vue/introduction.md @@ -0,0 +1,38 @@ +# hippy-vue introduction + +hippy-vue is based on the official vue 2.x source code, and implements a custom rendering layer by rewriting the [node-ops](//github.com/Tencent/Hippy/blob/master/packages/hippy-vue/src/runtime/node-ops.js) plug-in, but it is not only a rendering layer to the terminal, but also realizes the mapping of front-end components to the terminal and CSS syntax parsing. Unlike other cross-end frameworks, it strives to bring the Web-side development experience to the terminal while maintaining compatibility with the Web ecosystem. + +--- + +# Architecture Diagram + +hippy-vue architecture diagram +
+
+ +# Initialization + +```javascript +import Vue from '@hippy/vue'; +import App from './app.vue'; +const app = new Vue({ + // App name specified by terminal + appName: 'Demo', + rootView: '#root', + // Render entrance + render: h => h(App), +}); + +/** + * $start is a callback triggered after Hippy is started + * @param {Function} callback - callback function after successful engine loading + * @param {Object} instance - instance object of vue + * @param {Object} initialProps - initial parameters given from terminal to front-end, some customer attributes needed to startup can be put in the entry file. + */ +app.$start((instance, initialProps) => { + console.log('instance', instance, 'initialProps', initialProps); +}); + +//If you need to get the initialProps on the first View rendering, you can read app.$options.$superProps directly + +``` diff --git a/docs/en-us/hippy-vue/native-event.md b/docs/en-us/hippy-vue/native-event.md new file mode 100644 index 00000000000..2f458344fc3 --- /dev/null +++ b/docs/en-us/hippy-vue/native-event.md @@ -0,0 +1,101 @@ +# Events + +Some events are not sent to a single UI, but to the whole business, such as the flip of the screen, the change of the network, etc., we call it `native events`. + +Hippy provides two methods to manage global events: + ++ `Hippy.on`, `Hippy.off`, `Hippy.emit` is framework-less EventBus, mainly to listen to some special C++ events such as `dealloc`, `destroyInstance`. It can be also used to customize JS global events. + ++ `app.$on`, `app.$off`, `app.$emit` is Vue EventBus, which not only being used to customize JS global events, but also to handle all `NativeEvent` dispatching, such as `rotate` event. + +--- + +# Event Listener + +Listen for the rotate event here, which has the callback parameter result. + +```js +// Take out the Vue instance saved in the entry file setApp(). +const app = getApp(); + +export default { + method: { + listener(rsp) { + console.log(rsp.result); + } + }, + mounted() { + // Listen to the rotate event through the app, and call a callback through this.listener when the event occurs. + app.$on('rotate', this.listener); + } +} + +``` + +# Event emit + +If you need to send events manually, you can call through `app.$`. + +```js +const app = getApp(); +app.$emit('rotate', { width: 100, height: 100 }); +``` + +# Event remove + +If you don't need to use, please remember to call the listening remove method. It is typically executed during the component's unload life cycle. + +```js +const app = getApp(); +app.$off('rotate', this.listener); +``` + +# JS Engine Destroy Event + +`Minimum supported version 2.3.4` + +This event will be triggered before the hippy js engine is destroyed to ensure that the last js code in the callback function is executed. The hippy business can do some operations when leaving by monitoring the `dealloc` event, but the callback function cannot use `async` + +```jsx +Hippy.on('dealloc', () => { + // do something +}); +``` + +# RootView Destroy Event + +`Minimum supported version 2.3.4` + +This event is triggered when RootView is unloaded. unlike `dealloc`, `destroyInstance` is triggered earlier than `dealloc`, but does not block the JS thread. + +```jsx +Hippy.on('destroyInstance', () => { + // do something +}); +``` + +# Container Size Change Event + +`Android all versions supported, iOS minimum supported version 2.16.0` + +When the container size changes, such as screen rotation, folding screen switch, etc., this event will be called. + +```jsx +app.$on('onSizeChanged', ({ oldWidth, oldHeight, width, height }) => { + // oldWidth: old width;oldHeight: old height;width: new width; height: new height + console.log('size', oldWidth, oldHeight, width, height); +}); +``` + +# System night mode change event + +`Only supported by iOS, the minimum supported version is 2.16.6, (Note: The page will be recreated when Android modifies the night mode)` + +This event is triggered when the system night mode changes + +```jsx +app.$on('onNightModeChanged', ({ NightMode, RootViewTag }) => { + // NightMode: whether the current night mode, the value is 0 or 1; RootViewTag: the Tag of the HippyRootView that sends the event + console.log(`onDarkModeChanged: ${NightMode}, rootViewTag: ${RootViewTag}`); +}); +``` diff --git a/docs/en-us/hippy-vue/router.md b/docs/en-us/hippy-vue/router.md new file mode 100644 index 00000000000..68fd94cac28 --- /dev/null +++ b/docs/en-us/hippy-vue/router.md @@ -0,0 +1,7 @@ +# Router + +Vue-router implements [@hippy/vue-router](//www.npmjs.com/package/@hippy/vue-router) by slightly modifying the official router and provides the same interface. + +The documentation can be directly referenced: [vue-router official website](//router.vuejs.org/). + +Animations for page switching are not supported because the `` component is not yet implemented. diff --git a/docs/en-us/hippy-vue/style.md b/docs/en-us/hippy-vue/style.md new file mode 100644 index 00000000000..18ef5dd006b --- /dev/null +++ b/docs/en-us/hippy-vue/style.md @@ -0,0 +1,41 @@ +# Styles + +Standard Hippy does not allow units of length - but for browser compatibility, hippy-vue uses the 1px = 1pt conversion scheme - which removes the px from the CSS unit and turns it into a number without units in Hippy. + +However, there are still some problems. If relative units such as rem and vh are written into Hippy business, it may be more important to find and avoid more significant risks in time. Therefore, only px units are converted now, and other units are allowed to be reported errors at the terminal level. + +HippyVue provides `beforeLoadStyle` Vue options hook function, for developers to do custom modify CSS styles, such as + +```js + new Vue({ + // ... + beforeLoadStyle(decl) { + let { type, property, value } = decl; + console.log('property|value', property, value); // => height, 1rem + // For example, process the rem units + if(typeof value === 'string' && /rem$/.test(value)) { + // ...value = xxx + } + return { ...decl, value} + } + }); +``` + +# CSS Selector + +At present, the basic `Universal`, `Type`, `ID`, `Class`, `Grouping` selectors have been implemented, and the basic combinators except for sibling have been supported. + + +## Scoped & Attribute + +> `2.15.0` support Vue `scoped` style, `2.15.2` support to merge styles on root element of child component. + +> `2.15.1` support `Attribute` selector and Vue `deep` selector. + +How to use? + +1. Update `@hippy/vue` and `@hippy/vue-css-loader` to `2.15.1+` version. +2. Vue2.0 set `Vue.config.scoped = true;`(Vue2.0 `scoped` and `Attribute` disabled by default,Vue3.0 enabled by default) + + + diff --git a/docs/en-us/hippy-vue/vue-native.md b/docs/en-us/hippy-vue/vue-native.md new file mode 100644 index 00000000000..ea998e55d7a --- /dev/null +++ b/docs/en-us/hippy-vue/vue-native.md @@ -0,0 +1,380 @@ + + +# Modules + +hippy-vue binds a `Native` prop on Vue to obtain native device information and call native module. It can also be used to monitor if it is running in a Hippy environment. + +> Corresponding Demo: [demo-vue-native.vue](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/native-demos/demo-vue-native.vue) + +--- + +# Get native information + +It doesn't need any method, directly get values. + +## version + +Get the version of hippy-vue + +* Example + +```javascript +console.log(Vue.Native.version); // => 2.0.0 +``` + +## Device + +Get the device name. For iphone, it can get specific iPhone version. For Android, it can only get the text of `Android device` currently. + +## OSVersion + +iOS version + +## APILevel + +Android operating system version. + +## SDKVersion + +Hippy native SDK version. + +## Platform + +Gets the operating system type. + +* Example + +```javascript +console.log(Vue.Native.Platform); // => android +``` + +## Dimensions + +Gets the screen resolution. + +* Example + +```javascript +const { window, screen } = Vue.Native.Dimensions; +console.log(`Screen Size:${screen.height}x${screen.width}`); // => 640x480 +console.log(`Window size with status bar: ${window.height}x${window.width}`); // => 640x460 +``` + +## PixelRatio + +Gets the device pixel scale. + +* Example + +```javascript +console.log(Vue.Native.PixelRatio); // => 3 +``` + +## isIPhoneX + +Gets whether it is the heteromorphic screenthe iPhoneX. + +## screenIsVertical + +Whether the screen is switched to landscape mode. + +## OnePixel + +The dp/pt value of one pixel. + +## Localization + +>* Minimum Supported Version 2.8.0 + +output internationalization-related information `object: { country: string , language: string, direction: number }`, where `direction` is 0 for LTR direction and 1 for RTL direction + +--- + +# AsyncStorage + +>* Minimum Supported Version 2.7.0 +>* the capability of AsyncStorage are consistent with that of the localStorage module, which is mounted unde a global variable, and localStorage is available in all versions + +[[AsyncStorage Example(the same as Hippy-React AsyncStorage)]](//github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo/src/modules/AsyncStorage/index.jsx) + +AsyncStorage is a simple, asynchronous, persistent Key-Value storage system. + +* Example + +``` js +Vue.Native.AsyncStorage.setItem('itemKey', 'itemValue'); +Vue.Native.AsyncStorage.getItem('itemKey'); +``` + +## Methods + +### AsyncStorage.getAllKeys + +`() => Promise` get all the keys of AsyncStorage + +### AsyncStorage.getItem + +`(key: string) => Promise` get data according to the key + +> * key: string - Target key to obtain value + +### AsyncStorage.multiGet + +`(key: string[]) => Promise<[key: string, value: value][]>` Batch requests for cached data with multiple key arrays at once, the return value will be returned in the form of a two-dimensional array of key-value pairs in the callback function. + +> * key: string[] - The target key array for which the value needs to be obtained + +### AsyncStorage.multiRemove + +`(key: string[]) => void` Call this function to batch delete the key values preserved in the passed-in keys array in AsyncStorage. + +> * key: string[] - Target key array to be deleted + +### AsyncStorage.multiSet + +`(keyValuePairs: [key: string, value: value][]) => void` Call this function to bulk store key-value pair objects. + +> * keyValuePairs: [key: string, value: value][] - The two-dimensional array of storage key value that needs to be set. + +### AsyncStorage.removeItem + +`(key: string) => void` delete the data according to the key value. + +> * key: string - Target key to be deleted + +### AsyncStorage.setItem + +`(key: string, value: string) => void` save key-value pairs according to the key and value. + +> * key: string - The target key for which the value needs to be obtained +> * value: string - The target value for which the value needs to be obtained + +--- + +# BackAndroid + +[[BackAndroid Example]](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/main-native.js) + +You can monitor the rollback of Android entity keys, do actions before exiting or intercepting the rollback of entity keys. + +>* Minimum Supported Version 2.7.0 +>* Note: This method requires the native to intercept the event of the entity return button. Please refer to [onBackPressed method of android-demo](//github.com/Tencent/Hippy/blob/master/examples/android-demo/example/src/main/java/com/tencent/mtt/hippy/example/MyActivity.java) + +## Methods + +### BackAndroid.addListener + +`(handler: () => boolean) => { remove: Function }` Monitor the Android entity rollback function, call the function handler when the rollback function is called. When the callback function returns true, intercepting the rollback operation of the native; When the callback function returns false, the rollback is not intercepted. This method will return the object that contains `remove()` method. The monitor can be removed by calling the `remove ()` method, the same as `BackAndroid.RemoveListener`. + +> * handler: Function - Callback function called when the entity key is rolled back + +### BackAndroid.exitApp + +`() => void` Directly execute the exit App logic of the native. + +### BackAndroid.removeListener + +`(handler: () => boolean) => void` Removes the BackAndroid listener for Android entity callback events. + +* handler: Function - It is recommended to use the object returned by `addListener` that contains the `remove()` method, or it could be the previous BackAndroid callback function. + +--- + +# callNative/callNativeWithPromise + +Method to call the native module, `callNative` is commonly used in module method calls with no return values, `callNativeWithPromise` is commonly used in module method calls with return values, it will return a Promise with the results. + +# callUIFunction + +Invoke a native method defined by a component + +`callUIFunction(instance: ref, method: string, options: Array)` + +> * instance: reference Ref of the component +> * method:Method name, e.g. `scrollToIndex` for ListView +> * options: Data to be passed, such as `[xIndex, yIndex, animated]` of ListView + +--- + +# ConsoleModule + +> Minimum Supported Version 2.10.0 + +Provides the ability to output front-end logs to iOS native logs and [Android logcat](//developer.android.com/studio/command-line/logcat) + +## Methods + +### ConsoleModule.log + +`(...value: string) => void` + +### ConsoleModule.info + +`(...value: string) => void` + +### ConsoleModule.warn + +`(...value: string) => void` + +### ConsoleModule.error + +`(...value: string) => void` + +> * Both `log` and `info` are output as native INFO level log by default +> * Hippy version 2.10.0 and after will separate the original js `console` method and `ConsoleModule` method, `console` will no longer output log to the native + +--- + +# Cookie + +The `set-cookie` Header returned by the fetch service in Hippy will automatically save the Cookie, which will be brought with the request next time. Then the native provides this interface so that the service can obtain or modify the saved Cookie. + +## Methods + +### getAll(url) + +| Props | Type | Require | Description | +| -------- | -------- | -------- | -------- | +| url | string | yes | Gets the cookie set under the specified URL. After version `2.14.0`, expired Cookies would not be returned. | + +Return Value: + +* `Prmoise` like `name=hippy;network=mobile` string. + +### set(url, keyValue, expireDate) + +Attributes: + +| Props | Type | Require | Description | +| -------- | -------- | -------- | -------- | +| url | string | yes | Gets the cookie set under the specified URL | +| keyValue | string | yes | The full string that needs to be set to the Cookie, for example`name=hippy;network=mobile`. After version `2.14.0`, `empty string` would clear all Cookies under the specific URL. | +| expireDate | Date | no | Date type of expiration time, it will not expired if not fill in. | + +--- + +# getElemCss + +Gets the CSS style for a concrete node. + +> Minimum Supported Version `2.10.1` + +`(ref: ElementNode) => {}` + +* Example: + +```js +this.demon1Point = this.$refs['demo-1-point']; +console.log(Vue.Native.getElemCss(this.demon1Point)) // => { height: 80, left: 0, position: "absolute" } +``` + +--- + +# ImageLoaderModule + +Can do the corresponding operations to the remote image through this module. + +> Minimum Supported Version `2.7.0` + +## Methods + +### ImageLoaderModule.getSize + +`(url: string) => Promise<{width, height}>` Gets the size of the picture (the picture is preloaded at the same time). + +> * url - picture address + +### ImageLoaderModule.prefetch + +`(url: string) => void` Used to preload pictures. + +> * url - picture address + +--- + +# measureInAppWindow + +> Minimum Supported Version `2.11.0` + +Measure the size and position of a component within the scope of the App window. Note that this method can be called only after the node instance is actually displayed (after the layout event). + +`(ref) => Promise<{top: number, left: number, right: number, bottom: number, width: number, height: number}>` + +> * Promise resolve parameters can get the coordinates and width and height of the reference component within the scope of the App window, if an error occurs or [node is optimized (only in Android)](hippy-vue/components?id=special prop within that style) return {top: -1, left: -1, right: -1, bottom: -1, width: -1, height: -1} + +--- + +# getBoundingClientRect + +> Minimum supported version `2.15.3`, `measureInWindow` and `measureInAppWindow` will be deprecated soon. + +Measure the size and position of a component within the scope of the App Container(RootView) or App Window(Screen). + +`(instance: ref, options: { relToContainer: boolean }) => Promise` + +> * instance: reference of the element of component. +> * options: optional,`relToContainer` indicates whether to be measured relative to the App Container(RootView). Default is `false`, meaning relative to App Window(Screen). When measured relative to the App Container(RootView), status bar is included in `iOS`, but `Android` not. +> * DOMRect: same with [MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getBoundingClientRect) introduction, which can get the size and position of a component. If something goes wrong or [the node is optimized (Android only)](style/layout?id=collapsable), `Promise.reject` error will be thrown. + +--- + +# NetInfo + +Through the interface can obtain the current equipment network status, also can register a listener. When the system network switches, you will get a notice. + +> Minimum Supported Version 2.7.0 + +For Android developers, before requesting network status, you need to add the following configurations to the app's `AndroidManifest.xml`: + +```xml + +``` + +## Network Status + +Determine whether the device is connected to the Internet and used a mobile data network in an asynchronous manner. + +* `NONE` - The device is offline. +* `WIFI` - Device connected to the Internet via wifi +* `CELL` - Device connected to the Internet via mobile network +* `UNKNOWN` - Abnormal or unknown networking status + +## Methods + +### NetInfo.addEventListener + +`(eventName: string, handler: Function) => NetInfoRevoker` Add a network change listener. + +> * eventName: 'change' - event name +> * handler: ({ network_info:string }) => any - Callback function called when the network changes + +### NetInfo.fetch + +`() => Promise` Used to get the current network status. + +### NetInfo.removeEventListener + +`(eventName: string, handler: NetInfoRevoker | Function) => void` remove event listener + +> * eventName: 'change' - event name +> * handler: Function - the corresponding event monitoring needs to be deleted + +--- + +# parseColor + +Color value type conversion, obtain the `int32` type color value that can be identified by the native through the API. Can be used to pass parameters through the interface when directly comminicate with the native (such as `callNative`). + +| Props | Type | Require | Description | +| -------- | -------- | -------- | -------- | +| color | `string` `number` | yes | Converted color values, supported types: `rgb`,`rgba`, `hex` | + +return value: + +* `number`: The return value is `int32Color` that can be recognized by the native + +* Example: + +``` js +const int32Color = Vue.Native.parseColor('#40b883') // int32Color: 4282431619 +``` diff --git a/docs/en-us/hippy-vue/vue3.md b/docs/en-us/hippy-vue/vue3.md new file mode 100644 index 00000000000..4653c8ec382 --- /dev/null +++ b/docs/en-us/hippy-vue/vue3.md @@ -0,0 +1,650 @@ +# Hippy-Vue-Next + +
+ +# Introduction + +@hippy/vue-next is based on the existing logic of @hippy/vue, through the [createRenderer()](//github.com/vuejs/vue-next/blob/v3.0.0-alpha.0/packages/runtime-core/src/renderer.ts#L154) API provided by Vue3.x, there is no need to invade Vue code, and Vue can be directly referenced through external libraries, which can follow the Vue ecosystem in time, the implementation principle is basically the same as @hippy/vue. + +@hippy/vue-next all code is written in typescript, which can have better program robustness and type hints. And the overall architecture of @hippy/vue-next has also been optimized. + + +# Architecture + +hippy-vue-next structure +
+
+ +# How to use + +The capabilities supported by @hippy/vue-next are basically the same as @hippy/vue. Therefore, there is no additional introduction about Hippy-Vue components, modules, styles, etc., you can refer to the relevant content in [Hippy-Vue](hippy-vue/introduction), this document only explains the differences. + +## Initialization + +```javascript +// app.ts +import { defineComponent, ref } from 'vue'; +import { type HippyApp, createApp } from '@hippy/vue-next'; + +// To create a Hippy App instance, it should be noted that Vue3.x uses Typescript, and you need to use defineComponent to wrap the component object +const app: HippyApp = createApp(defineComponent({ + setup() { + const counter = ref(0); + return { + counter, + } + } +}), { + // Hippy App Name must be set, the sample project can use Demo + appName: 'Demo', +}); + +// start Hippy App +app.$start().then(({ superProps, rootViewId }) => { + // superProps is the initialization parameter passed in by Native. If you need to do routing preprocessing and other operations, you can let Native pass in the corresponding parameters + // rootViewId is the id of the native root node mounted by the current Hippy instance of Native + // mount app, render to screen + app.mount('#mount'); +}) +``` + +If you want to use Vue-Router, you need to use additional initialization logic + +```javascript + +// Vue + Vue Router + +// app.vue + + + +// index.vue + + + +// app.ts +import { defineComponent, ref } from 'vue'; +import { type HippyApp, createApp } from '@hippy/vue-next'; +import { createHippyRouter } from '@hippy/vue-router-next-history'; +import { type Router } from 'vue-router'; +import App from 'app.vue'; + +// To create a Hippy App instance, it should be noted that Vue3.x uses Typescript, and you need to use defineComponent to wrap the component object +const app: HippyApp = createApp(App, { + // Hippy App Name must be set, the sample project can use Demo + appName: 'Demo', +}); + +// routes list +const routes = [ + { + path: '/', + component: Index, + }, +]; + +// create router +const router: Router = createHippyRouter({ + routes, +}); + +// use router +app.use(router); + +// start Hippy App +app.$start().then(({ superProps, rootViewId }) => { + // superProps is the initialization parameter passed in by Native. If you need to do routing preprocessing and other operations, you can let Native pass in the corresponding parameters + // rootViewId is the id of the native root node mounted by the current Hippy instance of Native + + // Because the memory history of vue-router is now used, the initial position needs to be pushed manually, otherwise the router will not be ready + // In the browser, vue-router matches according to location.href, and pushes the root path '/' by default. + // If you want to jump to the specified page by default like in the browser, you can let the native pass the initialized path from superProps, + // and then perform operations such as router.push({ path: 'other path' }) through the value of path + router.push('/'); + + // mount app, render to screen + app.mount('#mount'); +}) +``` + +> @hippy/vue-router-next-history modify vue-router's history mode. Added the logic of returning the history record first when the hardware back key is triggered for Android. + +If you don't need this, you can use original vue-router to implement routing: + +```javascript +import { createRouter, createMemoryHistory, type Router } from 'vue-router'; + +// routes list +const routes = [ + { + path: '/', + component: Index, + }, +]; + +const router = createRouter({ + history: createMemoryHistory(), + routes, +}); +``` + +# Custom Components & Modules + +In @hippy/vue-next, the `registerElement` method is also available for registering custom components and mapping tags in the template to native components. +It is worth noting that, similar to Native, in @hippy/vue, the `registerElement` method is attached to the global Vue object. +Similarly, in @hippy/vue-next, the `registerElement` method is also exported separately. + +```javascript +import { registerElement } from '@hippy/vue-next'; +``` + +## Register Custom Component + +```javascript +// custom-tag.ts +import { registerElement } from '@hippy/vue-next' + +/** + * register custom tag + */ +export function registerCustomTag(): void { + // native component name + const nativeComponentName = 'CustomTagView' + // custom tag name + const htmlTagName = 'h-custom-tag' + // register native custom component named "CustomTagView", native component name must same with native real name. + // this method establish mapping between our "h-custom-tag" to native "CustomTagView" + registerElement(htmlTagName, { + component: { + name: nativeComponentName + } + }) +} + +// app.ts +import { defineComponent, ref } from 'vue'; +import { type HippyApp, createApp } from '@hippy/vue-next'; +import { registerCustomTag } from './custom-tag' + +// register +registerCustomTag() + +// create hippy app instance +const app: HippyApp = createApp(defineComponent({ + setup() { + const counter = ref(0); + return { + counter, + } + } +}), { + // Hippy App Name, required, use demo for test + appName: 'Demo', +}); + +// ...other code + +``` + +## Binding Native Event Return Values + +Because @hippy/vue-next adopts a consistent event model with the browser and aims to unify events on both ends (sometimes the return values of events may differ), +a solution was implemented to manually modify the event return values. This requires explicitly declaring the return values for each event. +This step is handled during the registration of custom components using the `processEventData` method, which takes two parameters. + +- evtData Include event instance `handler` and event name `__evt` +- nativeEventParams native event real return values + +Eg: @hippy/vue-next's [swiper](https://github.com/Tencent/Hippy/blob/master/packages/hippy-vue-next/src/native-component/swiper.ts) native component, +it was the real rendered node by `swiper` that handle the event return values + +```javascript + // register swiper tag + registerElement('hi-swiper', { + component: { + name: 'ViewPager', // native component name + processEventData( + evtData: EventsUnionType, + nativeEventParams: { [key: string]: NeedToTyped }, + ) { + // handler: event instance,__evt: native event name + const { handler: event, __evt: nativeEventName } = evtData; + switch (nativeEventName) { + case 'onPageSelected': + // Explicitly assigning the value of nativeEventParams from the native event to the event bound to the event in @hippy/vue-next + // This way, the event parameters received in the pageSelected event of the swiper component will include currentSlide. + event.currentSlide = nativeEventParams.position; + break; + case 'onPageScroll': + event.nextSlide = nativeEventParams.position; + event.offset = nativeEventParams.offset; + break; + case 'onPageScrollStateChanged': + event.state = nativeEventParams.pageScrollState; + break; + default: + } + return event; + }, + }, + }); +``` + +## Use `Vue` Component Implement Custom Component + +When your custom component involves more complex interactions, events, and lifecycle methods, simply using `registerElement` may not be sufficient. +It can only achieve basic mapping of element names to components and basic parameter mapping. In such cases, you can use Vue to register separate +components to implement this complex custom component. For information on registering components in Vue, you can refer to the [Component Registration](https://cn.vuejs.org/guide/components/registration.html) guide. +Please note that there are some differences in component registration between Vue 3 and Vue 2. +You can also refer to the implementation of [swiper](https://github.com/Tencent/Hippy/blob/master/packages/hippy-vue-next/src/native-component/swiper.ts) components in the @hippy/vue library + +### Event Handle + +When using components registered with Vue, if you want to pass terminal events to the outer component, you need to handle it differently. +There are two ways to achieve this. + +- Use `render` Function(Recommend) + +```javascript +import { createApp } from 'vue' + +const vueApp = createApp({}) + +// notice Vue3 register component isn't global now +vueApp.component('Swiper', { + // ... other code + render() { + /* + * Use "render" function + * "pageScroll" is the event name passed to native(automaticlly transform to "onPageScroll") + * "dragging" is the event name user used + */ + const on = getEventRedirects.call(this, [ + ['dropped', 'pageSelected'], + ['dragging', 'pageScroll'], + ['stateChanged', 'pageScrollStateChanged'], + ]); + + return h( + 'hi-swiper', + { + ...on, + ref: 'swiper', + initialPage: this.$initialSlide, + }, + this.$slots.default ? this.$slots.default() : null, + ); + }, +}); + +// register native custom component "ViewPager" +registerElement('hi-swiper', { + component: { + name: 'ViewPager', + }, +}); +``` + + +- Use Vue `SFC` + +```javascript +// swiper.vue + + + +// app.ts +import { registerElement } from '@hippy/vue-next' +import { createApp } from 'vue' +import Swiper from './swiper.vue' + +// register custom native component +registerElement('hi-swiper', { + component: { + name: 'ViewPager', + }, +}); + +// create vue instance +const vueApp = createApp({}) +// register vue component +vueApp.component('Swiper', Swiper) +``` + +> When registering a custom tag using the Single File Component (SFC) approach, Vue treats it as a component. However, if the component is not explicitly registered, +> it will result in an error. Therefore, we need to use isCustomElement to inform Vue that this is our [custom component](https://cn.vuejs.org/api/application.html#app-config-compileroptions-iscustomelement), +> just render directly. +> Attention, hippy-webpack.dev.js, hippy-webpack.android.js, hippy-webpack.ios.js both need to be handled, first by development builds and other for production builds. + +```javascript +// src/scripts/hippy-webpack.dev.js & src/scripts/hippy-webpack.android.js & src/scripts/hippy-webpack.ios.js both need to be handled + +/** + * determine tag is custom tag or not, should handle by your project + */ +function isCustomTag(tag) { + return tag === 'hi-swiper' +} + +// vue loader part +{ + test: /\.vue$/, + use: [ + { + loader: 'vue-loader', + options: { + compilerOptions: { + // disable vue3 dom patch flag,because hippy do not support innerHTML + hoistStatic: false, + // whitespace handler, default is 'condense', it can be set 'preserve' + whitespace: 'condense', + // register custom element that won't transform as Vue component + isCustomElement: tag => isCustomTag(tag) + }, + }, + }, + ], +}, +``` + +# Server Side Render + +@hippy/vue-next is now supported SSR, the specific code can be viewed in [Demo](https://github.com/Tencent/Hippy/tree/master/examples/hippy-vue-next-ssr-demo)'s SSR Part +, For the implementation and principle of Vue SSR, you can refer to the [official document](https://cn.vuejs.org/guide/scaling-up/ssr.html)。 + +## How To Use SSR + +Read `How To Use SSR` in [Demo](https://github.com/Tencent/Hippy/tree/master/examples/hippy-vue-next-ssr-demo) + +## Principle + +### SSR Architecture + +hippy-vue-next SSR Architecture + +### Description + +The implementation of @hippy/vue-next SSR involves three operating environments: compile time, client runtime, and server runtime. On the basis of vue-next ssr, we developed @hippy/vue-next-server-renderer +Used for server-side runtime node rendering, developed @hippy/vue-next-compiler-ssr for compiling vue template files at compile time. And @hippy/vue-next-style-parser for server-side rendering +Style insertion for Native Node List. Let's illustrate what @hippy/vue-next SSR does through the compilation and runtime process of a template + +We have a template like `
` + +- Compiler + + Through @hippy/vue-next-compiler-ssr, our template transform to render funtions like + + ```javascript + _push(`{"id":${ssrGetUniqueId()},"index":0,"name":"View","tagName":"div","props":{"class":"test-class","id": "test",},"children":[]},`) + ``` + +- Server Side Runtime + + Through @hippy/vue-next-server-renderer, render function obtained during compilation is executed to obtain the json object of the corresponding node. + Note that the ssrGetUniqueId method in the render function is provided in @hippy/vue-next-server-renderer, where the server-renderer will also process + the attribute values of the nodes, and finally get the json object of the Native Node + + ```javascript + { "id":1,"index":0,"name":"View","tagName":"div","props":{"class":"test-class","id": "test",},"children":[] } + ``` + + > For the handwritten non-sfc template rendering function, it cannot be processed in the compiler, and it is also executed in the server-renderer + +- Client Side Runtime + + Through @hippy/vue-next-style-parser, nodes returned by server are insert styles, and insert node props by @hippy/vue-next. Then insert native nodes to + native to complete rendering node on screen. + After the node is inserted to the screen, the asynchronous jsBundle on the client side is loaded asynchronously through the global.dynamicLoad provided + by the system to complete the Hydrate on the client side and execute the follow-up process. + +## Different + +There are some differences between the Demo initialization of the SSR version and the initialization of the asynchronous version. Here is a detailed description of the differences + +- src/main-native.ts Change + +1. Use createSSRApp to replace the previous createApp, createApp only supports CSR rendering, while createSSRApp supports both CSR and SSR +2. The ssrNodeList parameter is added during initialization as the Hydrate initialization node list. Here the initialized node list returned by our server is stored in global.hippySSRNodes, and pass it as a parameter to createSSRApp when calling it. +3. Call app.mount after router.isReady is completed, because if you don’t wait for the routing to complete, it will be different from the node rendered by the server, causing Hydrate to report an error + +```javascript +- import { createApp } from '@hippy/vue-next'; ++ import { createSSRApp } from '@hippy/vue-next'; +- const app: HippyApp = createApp(App, { ++ const app: HippyApp = createSSRApp(App, { + // ssr rendered node list, use for hydration ++ ssrNodeList: global.hippySSRNodes, +}); ++ router.isReady().then(() => { ++ // mount app ++ app.mount('#root'); ++ }); +``` + +- src/main-server.ts Add + +main-server.ts is the business jsBundle running on the server side, so no code splitting is required. The whole can be built as a bundle. Its core function is to complete the first-screen rendering logic on the server side, process the obtained first-screen Hippy node, insert node attributes and store (if it exists), and return. +And return the maximum uniqueId of the currently generated node for subsequent use by the client. + +>Note that the server-side code is executed synchronously. If a data request is made asynchronously, the request may have been returned before the data is obtained. For this problem, Vue SSR provides a dedicated API to handle this problem: +>[onServerPrefetch](https://cn.vuejs.org/api/composition-api-lifecycle.html#onserverprefetch). +>There is also an example of using onServerPrefetch in app.vue of [Demo](https://github.com/Tencent/Hippy/blob/master/examples/hippy-vue-next-ssr-demo/src/app.vue) + +- server.ts Add + +server.ts is the entry file executed by the server. Its role is to provide a Web Server, receive the SSR CGI request from the client, and return the result to the client as response data, including the rendering node list, store, and global style list. + +- src/main-client.ts Add + +main-client.ts is the entry file executed by the client. Unlike the previous pure client rendering, the client entry file of SSR only includes the request to obtain the first screen node, insert the first screen node style, and insert the node into the terminal to complete the rendering. related logic. + +- src/ssr-node-ops.ts Add + +ssr-node-ops.ts encapsulates the operation logic of inserting, updating, and deleting SSR nodes that do not depend on @hippy/vue-next runtime. + +- src/webpack-plugin.ts Add + +webpack-plugin.ts encapsulates the initialization logic of Hippy App required for SSR rendering. + + +# Additional Differences + +@hippy/vue-next is basically functionally aligned with @hippy/vue now, but the APIs are slightly different from @hippy/vue, and there are still some problems that have not been solved, here is the description: + +- Vue.Native + + In @hippy/vue, the capabilities provided by Native are provided by the Native attribute mounted on the global Vue. In Vue3.x, this implementation is no longer feasible. You can access Native as follows: + + ```javascript + import { Native } from '@hippy/vue-next'; + + console.log('do somethig', Native.xxx) + ``` + +- Global Event + + In @hippy/vue,global event used by `Vue.$on` or `Vue.$off`,now in @hippy/vue-next,we provide `EventBus` to do that. + + ```javascript + import { EventBus } from '@hippy/vue-next'; + + // Listen container size change event(Only Android) + EventBus.$on('onSizeChanged', ({ oldWidth, oldHeight, width, height }) => { + // oldWidth: old widht;oldHeight: old height;width: new width; height: new height + console.log('size', oldWidth, oldHeight, width, height); + }); + // trigger global event + EventBus.$emit('eventName', { + ...args, // event params + }); + +- v-model directive + + Because the built-in instructions in Vue3.x are implemented by inserting code at compiling time, the v-model instruction has not yet been found a good way to deal with it. A temporary solution can be used to implement the corresponding function: + + ```javascript + // For the specific usage, please refer to the example in demo-input.vue in demo + + + ``` + +- HMR for Keep-Alive + + In the sample code, our routing component is wrapped in the Keep-Alive component, but currently the routing component wrapped with Keep-Alive cannot achieve hot update during development, and the entire instance needs to be refreshed to complete the refresh. + There is no such problem if it is not wrapped in Keep-Alive. At present, the [official problem](https://github.com/vuejs/core/pull/5165) has not been resolved. The problem can be solved by upgrading Vue after waiting for the official solution. + + !> vue@3.2.45+ has fixed this [problem](https://github.com/vuejs/core/pull/7049). When developing with version 3.2.45 and above, the components in keep-alive can also be hot updated + +- Vue3.x Proxy Value + + Because the reactivity of Vue3.x is implemented by "Proxy", so the object we get is actually an instance of Proxy instead of the original object, so we need to pay attention when calling the native interface, the native does not Know the Proxy object, + You need to use the [`toRaw`](https://vuejs.org/api/reactivity-advanced.html#toraw) method provided by Vue3.x to get the original object and pass it to the native API. + +- Type hints for native APIs and customized components + + @hippy/vue-next provides type hints for native APIs. + If there is a customized native api, it can also be extended in a similar way + + ```javascript + declare module '@hippy/vue-next' { + export interface NativeInterfaceMap { + // then you can have type hints in Native.callNative, Native.callNativeWithPromise + } + } + ``` + + @hippy/vue-next also provides event types with reference to the event declaration of `lib.dom.d.ts`. For details, please refer to hippy-event.ts. If you need to extend the built-in events, you can use a similar way + + ```javascript + declare module '@hippy/vue-next' { + export interface HippyEvent { + testProp: number; + } + } + ``` + + When using `registerElement` to register components, `type narrowing` is used to provide accurate type hints in switch cases. If you also need similar type hints when registering customized components, you can use the following methods: + + ```javascript + export interface HippyGlobalEventHandlersEventMap { + // extend new event name and related event interface + onTest: CustomEvent; + // extend existing event interface + onAnotherTest: HippyEvent; + } + ``` + + For more information, please refer to [extend.ts](https://github.com/Tencent/Hippy/blob/master/examples/hippy-vue-next-demo/src/extend.ts). + +- whitespace handler + + Vue2.x Vue-Loader `compilerOptions.whitespace` default is `preserve`, Vue3.x default is `condense`(refer to [Vue3 whitespace introduction](https://vuejs.org/api/application.html#app-config-compileroptions-whitespace)). + + Disable `trim` is different from Vue2.x, which will be set in `createApp` options. + + ```javascript + // entry file + const app: HippyApp = createApp(App, { + // hippy native module name + appName: 'Demo', + // trimWhitespace default is true + trimWhitespace: false, + }); + + // webpack script + rules: [ + { + test: /\.vue$/, + use: [ + { + loader: vueLoader, + options: { + compilerOptions: { + // whitespace handler, default is 'condense' + whitespace: 'condense', + }, + }, + }], + }, + ] + ``` + +- dialog + + The first child element of `` cannot be set style `{ position: absolute }`. If you want to cover full screen by the content of ``, you can set `{ flex: 1 }` or explicit with/height value for the first child element, which is consistent with Hippy3.0. + +# Examples + +For more details, please check [example project](https://github.com/Tencent/Hippy/tree/master/examples/hippy-vue-next-demo) directly. diff --git a/docs/en-us/hippy-vue/web.md b/docs/en-us/hippy-vue/web.md new file mode 100644 index 00000000000..30141cf239d --- /dev/null +++ b/docs/en-us/hippy-vue/web.md @@ -0,0 +1,5 @@ +# Web Isomorphic + +## WebRenderer Scheme + +Hippy use [`WebRenderer`](web/integration.md) scheme to add a conversion layer based on common communication protocols. Business developers can use the same set of Hippy syntax to map business code to components and modules implemented in JavaScript. Whether the upper layer uses React, Vue or other third-party frameworks, it can be compatible. diff --git a/docs/en-us/index.html b/docs/en-us/index.html new file mode 100644 index 00000000000..e80bf32966b --- /dev/null +++ b/docs/en-us/index.html @@ -0,0 +1,174 @@ + + + + + Hippy + + + + + + + + + + + + + + + + + + + +
Loading ...
+ + + + + + + + + + + + + + diff --git a/docs/en-us/index.md b/docs/en-us/index.md new file mode 100644 index 00000000000..28849440451 --- /dev/null +++ b/docs/en-us/index.md @@ -0,0 +1,295 @@ +
+
logo
+
+

+ + Hippy + +

+
Cross-Platform Framework for Developers
+ GitHub Repo stars + + GitHub release (latest SemVer) + +

+ GitHub + Get Started +

+
+
+

⚡ High Performance

+

Reusable ListView with ultimate smoothness experience, efficient data communication via binding mode

+

📱 Cross Platform

+

Different platforms maintain the same interface, support smooth migration to Web

+

📚 Easy to Learn

+

React / Vue driven framework and full Flex Layout supported.

+
+
+
+ +# Hippy overview + +Hippy is like a simplified browser, which has done a lot of work from the bottom layer, smoothed out the differences between iOS and Android, and provided a development experience close to the Web. At present, the upper layer supports two sets of interface frameworks, React and Vue, through which front-end developers can convert front-end codes into native instructions to develop native apps. + +At the same time, Hippy has made a lot of optimization from the bottom layer, providing top performance in startup speed, reusable list components, rendering efficiency, animation speed, network communication, etc. + +## Feature Comparison + +Hippy implemented a lot of interfaces according to browser, convenient for developers to use, here are a few Hippy unique features. + +| Classifications| Properties | Description | Support| +| ---- | ------------------------ | ------------------------ | -------- | +| Interface| fetch | Http/Https protocol request | ✅ | +| | WebSocket | Instant Messaging Based on Http | ✅ | +| Events| onClick | Click Event | ✅ | +| | onTouchStart/onTouchDown | Triggered when start to touch screen | ✅ | +| | onTouchMove | Triggered when move on screen | ✅ | +| | onTouchEnd | Triggered when end to touch screen | ✅ | +| | onTouchCancel | Triggered when touch screen canceled | ✅ | +| Style| zIndex | Layer level | ✅ | +| | backgroundImage | Background image | ✅ | + +## Package Volume + +Hippy's package volume is also very competitive in the industry. + +![Pack Volume 1](assets/img/baodaxiao.png) + +The above figure is an empty APK, showing the comparison of package size among different native SDKs. + +![Pack Volume2](assets/img/jsbao.png) + +The above figure shows the comparison of package size of JS bundle with the simplest ListView. + +## Rendering Performance + +Comparison of ListView performance when sliding, Hippy can always maintain a very smooth state. + +Rendering Performance + +## Memory Consumption + +In terms of memory consumption, Hippy has a slight advantage when initializing the List, and the difference in memory consumption is getting bigger and bigger after sliding a few screens. + +![Memory footprint](assets/img/listmeicun.png) + +## Web-like development experience + +Hippy has also made a lot of optimizations in the development experience, including but not limited to onClick, onTouch series touch screen events like browsers, simpler animation schemes, hippy-vue provides full compatibility with Vue, etc. + +## Who Using it + +
+ + QQ Brower +

QQ Browser

+
+ + Mobile QQ +

Mobile QQ

+
+ + Tencent News +

Tencent News

+
+ + WeSing +

+ WeSing +

+
+ + QQ Music +

+ QQ Music +

+
+ + Tencent Map +

Tencent Map

+
+ + Camps of Kings +

+ Camps of Kings +

+
+ + shanxian +

ShanXian

+
+ + Tencent TV +

+ Tencent TV +

+
+ + Tencent Start(TV) +

+ Tencent Start(TV) +

+
+ + Weishi +

+ Weishi +

+
+ + Tencent App Market +

+ Tencent App Market +

+
+ + Tencent Wealth Management +

+ Tencent WM +

+
+ + Tencent ZXG App +

+ Tencent ZXG App +

+
+ + NOW Live +

+ NOW Live +

+
+ + >Tencent Joy Club +

+ Tencent Joy Club +

+
+ + Wi-Fi Butler +

+ Wi-Fi Butler +

+
+ + DaFeng App +

+ DaFeng App +

+
+ + Tencent OM +

+ Tencent OM +

+
+ + VOOV Live +

+ VOOV Live +

+
+
+ +## Team Contribution + +
+ + TME WeSing Team +

TME WeSing Team

+
+ + TME QQ Music Team +

TME QQ Music Team

+
+ + CDG Finance Team +

CDG Finance Team

+
+ + QGraphics Team +

QGraphics Team

+
+ + IVWEB Team +

IVWEB Team

+
+ + Tour Yunnan WII team +

Tour Yunnan WII team

+
+
+ +## Contributors + +
+ + Super Zheng +

Super Zheng

+
+ + xuqingkuang +

+ XQ Kuang

+
+ + siguangli2018 +

siguangli2018

+
+ + luomy +

luomy

+
+ + churchill-zhang +

churchill-zhang

+
+ + old kidd +

old kidd

+
+ + Zoom Chan +

Zoom Chan

+
+ ilikethese +

ilikethese

+
+ Box Tsang +

Box Tsang

+
+ jerome han +

jerome han

+
+ tsangint

tsangint

+
+ RonkTsang

RonkTsang

+
+ ElfSundae

Elf Sundae

+
+ zousandian

Three O 'clock.

+
+ dequanzhu

dequanzhu

+
+ kassadin

kassadin

+
+ Arylo Yeung

Arylo Yeung

+
+
+ +## Communication + +* [Article Column](https://cloud.tencent.com/developer/column/84006) +* WeCom Group, use WeChat or WeCom scan to join. + + ![WeCom Group QR code](assets/img/wechat-group.jpeg) + +## Summary + +If you're ready, [Getting start to Hippy](guide/integration.md). diff --git a/docs/en-us/ios/_sidebar.md b/docs/en-us/ios/_sidebar.md new file mode 100644 index 00000000000..0c8a7c21751 --- /dev/null +++ b/docs/en-us/ios/_sidebar.md @@ -0,0 +1,7 @@ + + +* [iOS Integration](ios/integration.md) +* [Native Events](ios/event.md) +* [Custom Adapters](ios/custom-adapter.md) +* [Custom UI Components](ios/custom-component.md) +* [Custom Modules](ios/custom-module.md) diff --git a/docs/en-us/ios/custom-adapter.md b/docs/en-us/ios/custom-adapter.md new file mode 100644 index 00000000000..918d0445e2b --- /dev/null +++ b/docs/en-us/ios/custom-adapter.md @@ -0,0 +1,50 @@ +# Custom Adapters + +--- + +# HippyImageViewCustomLoader + +In the Hippy SDK, the default `HippyImageView` will be used to download and display picture data according to source property. However, in some cases, the business side wants to use custom image loading logic (such as caching, or intercepting data for a specific URL). Thus, the SDK provides a protocol for this called `HippyImageViewCustomLoader`. + +Users should implement this protocol and return data according to the URL of the picture by themselves. `HippyImageView` will display the picture according to the returned data consequently. + +```objectivec +@protocol HippyImageViewCustomLoader +@required +/** +* imageView: +*/ +- (void)imageView:(HippyImageView *)imageView + loadAtUrl:(NSURL *)url + placeholderImage:(UIImage *)placeholderImage + context:(void *)context + progress:(void (^)(long long, long long))progressBlock + completed:(void (^)(NSData *, NSURL *, NSError *))completedBlock; + +- (void)cancelImageDownload:(HippyImageView *)imageView withUrl:(NSURL *)url; +@end +``` + +# Protocol implementation + +```objectivec +@interface CustomImageLoader : NSObject + +@end + +@implementation CustomImageLoader +HIPPY_EXPORT_MODULE() +- (void)imageView:(HippyImageView *)imageView loadAtUrl:(NSURL *)url placeholderImage:(UIImage *)placeholderImage context:(void *)context progress:(void (^)(long long, long long))progressBlock completed:(void (^)(NSData *, NSURL *, NSError *))completedBlock { + + NSError *error = NULL; + // get image data and return data or error + NSData *imageData = getImageData(url, &error); + // pass result through block + completedBlock(imageData, url, error); +} +@end +``` + +The business side must add `HIPPY_EXPORT_MODULE()` code to register this ImageLoader module in the Hippy framework, and the system will automatically find the module that implements the`HippyImageViewCustomLoader` protocol and use it as the ImageLoader. + +P.S. if multiple modules implement the `HippyImageViewCustomLoader` protocol, only one of them will be used as the default ImageLoader. diff --git a/docs/en-us/ios/custom-component.md b/docs/en-us/ios/custom-component.md new file mode 100644 index 00000000000..3771b8430bb --- /dev/null +++ b/docs/en-us/ios/custom-component.md @@ -0,0 +1,105 @@ +# Custom UI Components + +A large number of UI components are used in App development, and the Hippy SDK already contains some basic UI, such as View, Text, Image, etc. +Also, it's easy for users to customize their own components. + +--- + +# Component Extension + +Let's take the creation of MyView as an example to show you how to extend a component from scratch. + +>This article only introduces iOS work. Please check the corresponding documents for web Frontend. + +Extending a UI component involves the following: + +1. Create the corresponding ViewManager +2. Register classes and bind Frontend components +3. Bind `View` properties and methods +4. Create corresponding `shadowView` and `View` + +## Create corresponding ViewManager + +>ViewManager is the corresponding view Management component, responsible for directly calling properties and methods from front-end view and native view. +>The most basic view Manager in the SDK is the `HippyView` manager, which encapsulates the basic method and is responsible for managing the `HippyView`. +>User-defined ViewManager must inherit from `HippyViewManager`. + +HippyMyViewManager.h + +```objectivec +@interface HippyMyViewManager:HippyViewManager +@end +``` + +HippyMyViewManager.m + +```objectivec +@implementation HippyMyViewManager +HIPPY_EXPORT_MODULE(MyView) +HIPPY_EXPORT_VIEW_PROPERTY(backgroundColor, UIColor) +HIPPY_REMAP_VIEW_PROPERTY(opacity, alpha, CGFloat) +HIPPY_CUSTOM_VIEW_PROPERTY(overflow, CSSOverflow, HippyView) +{ + if (json) { + view.clipsToBounds = [HippyConvert CSSOverflow:json] != CSSOverflowVisible; + } else { + view.clipsToBounds = defaultView.clipsToBounds; + } +} +- (HippyView *)view { + return [HippyMyView new]; +} +- (HippyShadowView *)shadowView { + return [HippyShadowView new]; +} +HIPPY_EXPORT_METHOD(focus:(nonnull NSNumber *)reactTag) { + // do sth +} +HIPPY_EXPORT_METHOD(focus:(nonnull NSNumber *)reactTag callback:(HippyResponseSenderBlock)callback) { + // do sth + NSArray *result = xxx; + callback(result); +} +``` + +## Type export + +`HIPPY_EXPORT_MODULE()` registers the `HippyMyViewManager` class, and the front-end will assign instance objects through `HippyMyViewManager` when operating on `MyView`. + +Parameters of `HIPPY_EXPORT_MODULE()` are optional, representing the corresponding View name. +If the user does not fill in parameters, default class name will be used. + +Note: there is a special processing logic in the SDK. If the string in the parameter ends with `Manager`, the SDK will delete the tailing `Manager` and use as the View name. + +## Parameter export + +`HIPPY_EXPORT_VIEW_PROPERTY` binds the parameters between native view and the front-end. When the parameter value is set at front-end, the `setter` method will be automatically invoked to set the parameter to the native. +`HIPPY_REMAP_VIEW_PROPERTY()` corresponds the parameter name between front-end and the native. Take the above code as an example, the `opacity` parameter of the front-end corresponds to the `alpha` parameter of the native. This macro contains three parameters. The first is the front-end parameter name, the second is the corresponding native parameter name, and the third is the parameter type. In addition, this macro uses `keyPath` method when setting native parameters, that is, the native can use the `keyPath` parameter. +`HIPPY_CUSTOM_VIEW_PROPERTY()` allows native to parse the front-end parameters by itself. The SDK transfers the original JSON type data from the front-end to the function body (the user can use the method in the `HippyConvert` class to parse the corresponding data), and the user can parse the data after obtaining the data. + +>This method has two hidden parameters, `view` and `defaultView`. `View` is the view that the current front-end renders. Default is a temporary view created when the front-end rendering parameter is nil, assigned with its default parameter. + +## Method export + +`HIPPY_EXPORT_METHOD` enables the front-end to call native methods. There are three calling modes, `callNative`, `callNativeWithCallbackId`, `callNativeWithPromise`. You can refer to the snippet above. + +* callNative: this method does not require the native to return any values. + +* callNativeWithCallbackId: This method requires the native to return data in a single block. The block type is `HippyResponseSenderBlock` and the parameter is an `NSArray` variable. + +* callNativeWithPromise: Corresponding to Promise in front-end, and the service corresponding to the native needs to return a resolve block or a reject block according to its own situation. The datatype of resolve block is`HippyPromiseResolveBlock`, and the parameter is an object that can be Jsonified. If the argument is nil, the JS side converts it to undefined. The datatype of reject block data is `HippyPromiseRejectBlock`, which includes an error code, error information, and error instance object (NSError). + +A `ViewManager` can manage multiple instances of one type. In order to distinguish which View is currently operated in the `ViewManager`, the first parameter corresponding to each export method is the tag value corresponding to the View. The user can find the view corresponding to the operation according to the tag. + +>As the export method will not be called in the main thread, if the user needs to perform UI action, it must be assigned to the main thread. We recommend using the [self.bridge, uiManager addUIBlock:] method in the export method, where the block type should be`HippyViewManagerUIBlock`. + +> `typedef void (^HippyViewManagerUIBlock)(HippyUIManager *uiManager, NSDictionary *viewRegistry)`。The second parameter is a dictionary, in which the key is the corresponding view tag value, and the value is the corresponding view. + +## Create shadowView and View + +In the OC layer, the `HippyUIManager` is responsible for mapping the parsed results of the JS layer to the view level of the OC layer. The `HippyShadowView` is not the real view, but merely a mapping result. Each `HippyShadowView` corresponds to a real view, but it has completed the basic layout. +>`HippyView` builds a true view based on the mapping results of `HippyShadowView`. Therefore, for a custom view manager in most cases, just create a `HippyShadowView`. + +The `HippyUIManager` will call the [HippyMyViewManager view] method to create a real view, and users need to implement this method and return the `HippyMyView` for their own need. + +At this point, a simple `HippyMyViewManager` and `HippyMyView` has been created created. diff --git a/docs/en-us/ios/custom-module.md b/docs/en-us/ios/custom-module.md new file mode 100644 index 00000000000..f19bda76a4a --- /dev/null +++ b/docs/en-us/ios/custom-module.md @@ -0,0 +1,102 @@ +# Custom Modules + +In addition to the UI, there will also be scenarios in APP development to call device modules, such as obtaining the current network status, initiating HTTP network requests, etc. The SDK already encapsulates some common modules, but it's also convenient to customize functional modules. + +> **note: Don't include `Hippy` (case-insensitive) in the name of a custom module and its methods, or you may encounter problems of "not finding the module or method" on iOS.** + +>The export of classes and methods in module extensions is very similar to the export of classes and methods in UI components. It is recommended to read UI component extensions first and then read the module extensions article. + +After using `HIPPY_EXPORT_MODULE()` to extended custom module, a module instance will be created every time the APP is started, and this instance will be used by the front-end to call component modules all the time. You can understand it as a single instance. +>modules don't have the concept of attributes, so don't try to bind attributes to modules. + +Each module has a similar method export scheme to that of a UI component. There are three call mode as well: `callhandler`, `callNativeWithCallbackId`, and `callnativewithhandler`. + +We divide the SDK modules into two types: + +* Non-Event: When the business needs some information or needs the native to execute some instruction, just need to call the native code directly through the interface. +* Event: When the business needs the native to monitor certain event. The native notifies the front-end when an event is triggered. + +--- + +# Non-Event Module Extension + +Extending a Non-Event module component involves the following: + +* Establishing a corresponding module class and binding a front-end component, + >Non-event module only needs to inherit from `NSObject` type。Use `HIPPY_EXPORT_MODULE()` macro to export in implementation files. +* Binding front-end method + >Similar to extending UI components, `HIPPY_EXPORT_METHOD()` macro to bind front-end method. Note that method names need to maintain consistency + +TestModule.h + +```objectivec +@interface TestModule : NSObject +@end +``` + +TestModule.m + +``` objectivec +@implementation TestModule +HIPPY_EXPORT_MODULE() +HIPPY_EXPORT_METHOD(click) { + // implement font-end click +} +@end +``` + +# Event Type Module Extension + +In addition to all the characteristics of Non-Event modules, Event modules also have the ability to monitor and feedback events. The front-end may have a`MyModule.addListener(string eventname)` method call to drive the native to listen for an event, as well as a mechanism to receive native event callbacks. The native encapsulates these mechanisms as a base class`HippyEventObserverModule`. All Event modules must inherit from this base class and implement the necessary methods. +Extending an Event module involves the following: + +* To create the corresponding event-type module class, you must inherit from`HippyEventObserverModule` and bind the front-end component. + >Export `HIPPY_EXPORT_MODULE()` macro in implementation files +* Binding front-end method + + >Similar to extending UI components, `HIPPY_EXPORT_METHOD()` macro is used to bind front-end methods. Note that method names need to maintain consistency. + +* implement `[MyModule addEventObserverForName:]` and`[MyModule removeEventObserverForName:]` methods to turn on/off monitoring an event + + >The two methods has been implemented as empty in `HippyEventObserverModule`. MyModule can implement this if needed. Meanwhile, whether this method needs to be implemented depends on whether the front-end has a corresponding `MyModule.addListener()` operation, i.e., the native is expected to listen for an event. If not, the native is not required to implement it. + +* Notify front-end after event triggered + + >The native uses `[MyModule sendEvent:params:]` method to notify the front-end。This method is already implemented in the base class. The users need to fill in parameters and call the method. + >The first parameter is event-name, required to be the same between front-end and the native. + >The second parameter is event-info, with the datatype of `NSDictionary` + +TestModule.h + +``` objectivec +// inherit from HippyEventObserverModule +@interface TestModule : HippyEventObserverModule +@end +``` + +TestModule.m + +``` objectivec +@implementation TestModule +HIPPY_EXPORT_MODULE() +HIPPY_EXPORT_METHOD(click) { + // implement "click" +} +- (void) addEventObserverForName:(NSString *)eventName { + // listen to customevent + if ([eventName isEqualToString:@"customevent"]) { + addLisener(eventName); + } +} +- (void) removeEventObserverForName:(NSString *)eventName { + // remove customevent + if ([eventName isEqualToString:@"customevent"]) { + removeLisener(eventName); + } +} +- (void) eventOccur { + // notify front-end + [self sendEvent:@"customevent" params:@{@"key": @"value"}]; +} +@end +``` diff --git a/docs/en-us/ios/event.md b/docs/en-us/ios/event.md new file mode 100644 index 00000000000..112254124d5 --- /dev/null +++ b/docs/en-us/ios/event.md @@ -0,0 +1,37 @@ +# Native Events + +When the native network switches or the horizontal and vertical screens change, the native needs to send some global broadcast events to the front-end, so that the front-end can control the business state according to different states. + +--- + +# Native Transmission + +The native should call the following code where the event is sent: + +```objectivec +// you can also refer to HippyEventObserverModule.m +[self sendEvent: @"rotate" params: @{@"foo":@"bar"}]; +- (void)sendEvent:(NSString *)eventName params:(NSDictionary *)params +{ + HippyAssertParam(eventName); + // "EventDispatcher" & "receiveNativeEvent" are constants, cannot be changed + [self.bridge.eventDispatcher dispatchEvent:@"EventDispatcher" methodName:@"receiveNativeEvent" args:@{@"eventName": eventName, @"extra": params ? : @{}}]; +} +``` + +# Front-end Reception + +Here is a code snippet to send an event called `rotate` to the front-end. There is a parameter in the event called `result`. Reception processing is then performed at the front-end. + +PS: the listener addition method of the latest version of Hippy has been changed from `addEventListener` to `addListener` + +```jsx +import { HippyEventEmitter } from '@hippy/react'; + +let hippyEventEmitter = new HippyEventEmitter(); +this.call = hippyEventEmitter.addListener("rotate", (e) => { + // log result: { foo: 'bar' } + console.log(e) ; +}); +``` + diff --git a/docs/en-us/ios/integration.md b/docs/en-us/ios/integration.md new file mode 100644 index 00000000000..37e19cdbc48 --- /dev/null +++ b/docs/en-us/ios/integration.md @@ -0,0 +1,141 @@ +# iOS integration + +> Note: The following documents assume that you already have some experience in iOS development. + +This tutorial describes how to integrate Hippy into an existing iOS project. + +--- + +## 1. Use Cocoapods to integrate iOS SDK + +[CocoaPods](https://cocoapods.org/) is a popular package management tool for iOS and macOS development. We'll use this to add Hippy's iOS Framework to an existing iOS project. + +It is recommended to use Homebrew to install CocoaPods. The installation command is as follows: + +```shell +brew install cocoapods +``` + +The specific steps are as follows: + +1. First, determine the Hippy iOS SDK version to be integrated, such as 2.17.0, and record it, which will be used in the Podfile. + + > You can go to "[version query address](https://github.com/Tencent/Hippy/releases)" to check the latest version information + +2. Secondly, prepare the Podfile file of the existing iOS project + + The Podfile file is the configuration file of the CocoaPods package management tool. If the current project does not have this file, the simplest way to create it is to use the CocoaPods init command and execute the following command in the iOS project file directory: + + ```shell + pod init + ``` + + The generated Podfile will contain some demo settings that you can adjust according to the purpose of the integration. + + In order to integrate Hippy SDK into the project, we need to modify the Podfile file, add hippy to it, and specify the integrated version. The modified Podfile should look like this: + + ```text + platform:ios, '11.0' + + # TargetName is most likely your project name + targetTargetName do + + # Specify here the hippy version number recorded in step 1 + pod 'hippy', '2.17.0' + + end + ``` + + > IMPORTANT NOTE: + > + > When integrating some historical versions from `2.13.0` to `2.16.x`, if hippy is accessed in the form of a static link library, the `force_load` compilation parameter needs to be set to load all symbols of hippy, otherwise the Hippy application will not be able to run. The versions that need to be set are as follows: + > + > `2.13.0` to `2.13.13` + > + > `2.14.0` to `2.14.10` + > + > `2.15.0` to `2.15.7` + > + > `2.16.0` to `2.16.5` + > + > `force_load` can be set in a variety of ways. You can choose any of the following configurations and adjust according to the actual situation: + > + > * Add `*-force_load "${PODS_CONFIGURATION_BUILD_DIR}/hippy/libhippy.a"*` directly to the Build Settings - `Other Linker Flags` configuration of the corresponding target of the main project. + > + > * Add `post_install hook` to the Podfile configuration file of the App project, and add `force_load` to xcconfig by yourself. + > + +3. Finally, execute in the command line + + ```shell + pod install + ``` + + After the command is successfully executed, use the project file with the `.xcworkspace` suffix generated by CocoaPods to open the project. + +## 2. Write SDK access code and load local or remote Hippy resource package + +The initialization of Hippy SDK only requires two steps: + +1. Initialize a HippyBridge instance. HippyBridge is the most important concept of Hippy. It is a bridge for communication between the Native side and the JS side. It also carries the main context information of the Hippy application. + +2. Initialize a HippyRootView instance through the HippyBridge instance. HippyRootView is another important concept of Hippy applications. Hippy applications will be displayed by it. Therefore, it can be said that creating a business means creating a `HippyRootView`. + +Currently, Hippy provides subcontracted loading interfaces and non-subcontracted loading interfaces. The usage methods are as follows: + +### Method 1. Use subcontracting loading interface + +```objective +/** This method is suitable for the following scenarios: + * Prepare the JS environment before the business starts, and load package 1. When the business starts, load package 2 to reduce package loading time. + * We recommend package 1 as a basic package, which has nothing to do with the business. It only contains some common basic components and is common to all businesses. + * Package 2 is loaded as business code +*/ + +// First load package 1 and create a HippyBridge instance +// Assume commonBundlePath is the path of package 1 +// Tips: For detailed parameter description, please refer to the header file: HippyBridge.h +NSURL *commonBundlePath = getCommonBundlePath(); +HippyBridge *bridge = [[HippyBridge alloc] initWithDelegate:self + bundleURL:commonBundlePath + moduleProvider:nil + launchOptions:your_launchOptions + executorKey:nil]; + +// Then create a HippyRootView instance through the above bridge and package 2 address +// Assume businessBundlePath is the path of package 2 +// Tips: For detailed parameter description, please refer to the header file: HippyRootView.h +HippyRootView *rootView = [[HippyRootView alloc] initWithBridge:bridge + businessURL:businessBundlePath + moduleName:@"Your_Hippy_App_Name" + initialProperties:@{} + shareOptions:nil + delegate:nil]; + +// Finally, set the frame for the generated rootView and mount it on the specified VC. +rootView.frame = self.view.bounds; +rootView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; +[self.view addSubview:rootView]; + +// At this point, you have completed the initialization of a Hippy application. The SDK will automatically load resources and start running the Hippy application. +``` + +### Method 2. Use non-packaging loading interface + +```objective +//Similar to the above-mentioned use of the subcontracted loading interface, you first need to create a HippyBridge instance. +// The difference is that when creating a HippyRootView instance, there is no need to pass in the business package, that is, businessBundlePath. You can create it directly using the following interface. +// Tips: For detailed parameter description, please refer to the header file: HippyRootView.h +- (instancetype)initWithBridge:(HippyBridge *)bridge + moduleName:(NSString *)moduleName + initialProperties:(nullable NSDictionary *)initialProperties + shareOptions:(nullable NSDictionary *)shareOptions + delegate:(nullable id)delegate; +``` + +> A simple sample project is provided in the Hippy repository, including all the above access codes, as well as more notes. +> +> It is recommended to refer to this example to complete the integration of the SDK into existing projects: [iOS Demo](https://github.com/Tencent/Hippy/tree/master/examples/ios-demo) +> + +!> Using subpackage loading, you can combine a series of strategies, such as preloading bridge in advance, global single bridge, etc. to optimize page opening speed. diff --git a/docs/en-us/structure/_sidebar.md b/docs/en-us/structure/_sidebar.md new file mode 100644 index 00000000000..96e3dbb2f71 --- /dev/null +++ b/docs/en-us/structure/_sidebar.md @@ -0,0 +1,4 @@ + + +* [Hippy Architecture](structure/introduction.md) +* [HippyCore Architecture](structure/core.md) diff --git a/docs/en-us/structure/core.md b/docs/en-us/structure/core.md new file mode 100644 index 00000000000..c39b7cb93f7 --- /dev/null +++ b/docs/en-us/structure/core.md @@ -0,0 +1,99 @@ +# HippyCore Architecture + +During developed Hippy, JS often needs to access some dual-native (Android and iOS) general capabilities. Hippy recommends using `internalBinding` to realize the basic capability extension (we call this capability [Core architecture](//github.com/Tencent/Hippy/tree/master/core)). Like the internalBinding of Node.js, it uses C++ for development, directly shares JS and C++ running environment and data, and provides very high communication performance between JS and native. + +Its principle is directly insert functions, classes, etc in JS context. With these functions or methods, JS can directly access the C++ code. + +However, if platform dependency is involved, sub-platform bridging is still required. + +At present, the [Timer](../guide/timer.md) and [Log](../guide/console.md) module in Hippy are implemented by Hippy Core. + +![Core architecture comparison](../assets/img/hippy-core.png) + +# C++ Module Extension + +We'll use TestModule as an example, extending a Module that will show how JS can invoke the native capabilities and return the results to JS. + +## Inherit ModuleBase + +Create test-module.h under [core/modules/](//github.com/Tencent/Hippy/tree/master/core/modules) + +```cpp +#ifndef CORE_MODULES_TEST_MODULE_H_ +#define CORE_MODULES_TEST_MODULE_H_ + +#include "core/modules/module-base.h" +#include "core/napi/callback-info.h" + +class TestModule : public ModuleBase { + public: + explicit TestModule(hippy::napi::napi_context context){}; + void RetStr(const hippy::napi::CallbackInfo& info); + void Print(const hippy::napi::CallbackInfo& info); +}; + +#endif // CORE_MODULES_TEST_MODULE_H_ +``` + +Create test-module.cc under [core/modules/](//github.com/Tencent/Hippy/tree/master/core/modules) + +```cpp +#include "core/modules/module-register.h" +#include "core/modules/test-module.h" +#include "core/napi/js-native-api.h" +#include "core/base/logging.h" +REGISTER_MODULE(TestModule, RetStr) +REGISTER_MODULE(TestModule, Print) + +void TestModule::RetStr(const hippy::napi::CallbackInfo& info) { + std::shared_ptr env = info.GetEnv(); + hippy::napi::napi_context context = env->getContext(); + HIPPY_CHECK(context); + + info.GetReturnValue()->Set(hippy::napi::napi_create_string(context, "hello world")); +} + +void TestModule::Print(const hippy::napi::CallbackInfo& info) { + std::shared_ptr env = info.GetEnv(); + hippy::napi::napi_context context = env->getContext(); + HIPPY_CHECK(context); + HIPPY_LOG(hippy::Debug, "hello world"); + + info.GetReturnValue()->SetUndefined(); +} + +``` + +# JS Bridge + +Dual-platform general module is generally placed under [core/js/global](//github.com/Tencent/Hippy/tree/master/core/js/global), and we add TestModule.js under global + +```js +const TestModule = internalBinding('TestModule'); + +global.TestModule = TestModule; +``` + +[core/js/entry/](//github.com/Tencent/Hippy/tree/master/core/js/entry) + +```js +require('../../global/TestModule.js'); +``` + +Run `npm run buildcore` in the Hippy directory, which will generate the corresponding dual-platform C source code: + +* [native-source-code-android.cc](//github.com/Tencent/Hippy/blob/master/core/napi/v8/native-source-code-android.cc) +* [native-source-code-ios.cc](//github.com/Tencent/Hippy/blob/master/core/napi/jsc/native-source-code-ios.cc) + +# Recompile Core + +There is no need to make other changes, just recompile the native SDK. The native SDK can link to `core` corresponding C++ code in the core directory. It is necessary to pay attention to the compilation environment of cmake, ndk, etc. For Android. + +# Effects + +```js +global.TestModule.Print(); +global.TestModule.RetStr(); +2019-11-08 17:32:57.630 7004-7066/? D/HippyCore: hello world +``` + diff --git a/docs/en-us/structure/introduction.md b/docs/en-us/structure/introduction.md new file mode 100644 index 00000000000..be90fbb78c2 --- /dev/null +++ b/docs/en-us/structure/introduction.md @@ -0,0 +1,24 @@ +# Architecture + +--- + +# Hippy 2.x Architecture + +The Hippy 2.x architecture is mainly divide into three layers: UI(JS) layer `Hippy-React` and`Hippy-Vue` instruction generation responsible for driving UI; The middle layer [C++ HippyCore](structure/core.md) is responsible for smoothing platform differences and providing high-performance modules; Render Layers `Android` and `iOS` are responsible for providing the bottom-modules and components of the terminal and communicating with the layout engine. + +
+ +![2.0 architecture](../assets/img/2.0-structure.png) + +
+
+
+ +# Hippy 3.x Architecture + +Hippy is upgrading the 3.x architecture. In 3.x, the specific implementation in the business and rendering layers can be switched according to the actual user scenario: the driver layer is no longer limited to JS driver, and other languages (such as DSL/Dart/WASM, etc.) can also be selected for driving. DOM Manager sinks from Java/OC to C++, as an intermediate hub, in addition to receiving and processing the messages from the upper layer to create and maintain DOM Tree, it is also responsible for interfacing and communicating with different rendering engines, typesetting engines and debugging tools. In the rendering layer, the rendering engine can also select other rendering renderers, such as Flutter(Voltron) rendering, in addition to supporting the existing Native rendering. Hippy 3.x can make up for some shortcomings of current Hippy 2.x in performance, double-end consistency, and component support, so look forward to it! + +
+
+3.0 architecture +
diff --git a/docs/en-us/style/_sidebar.md b/docs/en-us/style/_sidebar.md new file mode 100644 index 00000000000..120a47b0a23 --- /dev/null +++ b/docs/en-us/style/_sidebar.md @@ -0,0 +1,7 @@ + + +* [Layout](style/layout.md) +* [Appearance](style/appearance.md) +* [Color](style/color.md) +* [Transform](style/transform.md) +* [SetNativeProps](style/setNativeProps.md) diff --git a/docs/en-us/style/appearance.md b/docs/en-us/style/appearance.md new file mode 100644 index 00000000000..b900205f18a --- /dev/null +++ b/docs/en-us/style/appearance.md @@ -0,0 +1,331 @@ +# Appearance + +Includes foreground, background, border, opacity, font and other appearance styles. + +--- + +# borderColor + +> Android default value is `transparent`,iOS default value is `black` + +| Type | Required| Supported Platforms +| ------------------ | -------- | --- | +| [color](style/color.md) |No| Android,iOS + +# borderTopColor + +> Android default value is `transparent`,iOS default value is `black` + +| Type | Required| Supported Platforms +| ------------------ | -------- | --- | +| [color](style/color.md) |No| Android,iOS + +# borderBottomColor + +> Android default value is `transparent`,iOS default value is `black` + +| Type | Required| Supported Platforms +| ------------------ | -------- | --- | +| [color](style/color.md) |No| Android,iOS + +# borderLeftColor + +> Android default value is `transparent`,iOS default value is `black` + +| Type | Required| Supported Platforms +| ------------------ | -------- | --- | +| [color](style/color.md) |No| Android,iOS + +# borderRightColor + +> Android default value is `transparent`,iOS default value is `black` + +| Type | Required| Supported Platforms +| ------------------ | -------- | --- | +| [color](style/color.md) |No| Android,iOS + +# borderRadius + +| Type | Required| Supported Platforms +| ------ | -------- | --- | +| number |No | Android,iOS + +# borderTopLeftRadius + +| Type | Required| Supported Platforms +| ------ | -------- | --- | +| number |No | Android,iOS + +# borderTopRightRadius + +| Type | Required| Supported Platforms +| ------ | -------- | --- | +| number |No | Android,iOS + +# borderBottomLeftRadius + +| Type | Required| Supported Platforms +| ------ | -------- | --- | +| number |No | Android,iOS + +# borderBottomRightRadius + +| Type | Required| Supported Platforms +| ------ | -------- | --- | +| number |No | Android,iOS + +# borderWidth + +| Type | Required| Supported Platforms +| ------ | -------- | --- | +| number |No | Android,iOS + +# borderTopWidth + +| Type | Required| Supported Platforms +| ------ | -------- | --- | +| number |No | iOS + +# borderBottomWidth + +| Type | Required| Supported Platforms +| ------ | -------- | --- | +| number |No | iOS + +# borderLeftWidth + +| Type | Required| Supported Platforms +| ------ | -------- | --- | +| number |No | iOS + +# borderRightWidth + +| Type | Required| Supported Platforms +| ------ | -------- | --- | +| number |No | iOS + +# backgroundColor + +| Type | Required| Supported Platforms +| ------------------ | -------- | --- | +| [color](style/color.md) |No | Android,iOS + +# borderStyle + +> Default value is `solid` + +| Type | Required| Supported Platforms +| --------------------------------- | -------- | --- | +| enum('solid', 'dotted', 'dashed') |No | Android,iOS.'dotted' and 'dashed' only support iOS for the time being + + +# boxShadow + +| Type | Required| Supported Platforms| +| ------ | -------- | --------| +| [Hippy-React reference example](https://github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/src/components/BoxShadow/index.jsx) |No| Android, iOS. Android has different implementation (see example for details) +| [Hippy-Vue reference example](https://github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/demos/demo-shadow.vue) |No| Android, iOS. Android has different implementation (see example for details) + +# color + +Font color + +| Type | Required| Supported Platforms +| ------ | -------- | --- | +| [color](style/color.md) | No | Android,iOS + +# fontFamily + +Font name, such as`PingFangSC-Regular` + +For custom fonts, refer to [custom font instructions](guide/custom-font) + +| Type | Required| Supported Platforms +| ------ | -------- | --- | +| string |No| Android,iOS + +# fontSize + +Font size + +> Default value is `14` + +| Type | Required| Supported Platforms +| ------ | -------- | --- | +| number |No| Android,iOS + +# fontStyle + +Font style + +> Default value is `normal` + +| Type | Required | Supported Platforms | +| ------------------------ | -------- | ------------------- | +| enum('normal', 'italic') | No | Android、iOS | + +# fontWeight + +Font weight + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/font-weight) + +| Type | Required| Supported Platforms +| ------ | -------- | --- | +| number \| string |No| Android,iOS + +# letterSpacing + +The horizontal spacing behavior between text characters + +[[MDN 文档]](//developer.mozilla.org/en-US/docs/Web/CSS/letter-spacing) + +!> hippy-vue should use @hippy/vue-css-loader `2.14.1` or above version + +!> On some Android devices, setting a negative value for letterSpacing has compatibility issues, which may cause line breaks at unexpected positions + +| Type | Required | Supported Platforms +| ------ | -------- | --- | +| number | No | Android、iOS + +# opacity + +| Type | Required| Supported Platforms +| ------ | -------- | --- | +| number |No| Android,iOS + +# textDecoration + +Same as `textDecorationLine` + +| Type | Required | Supported Platforms +|-------------------------------------------|----------| --------| +| enum('underline', 'line-through', 'none') | No | Android,iOS | + +# textDecorationColor + +[Hippy-React example](https://github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/src/components/Text/index.jsx) + +[Hippy-Vue example](https://github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/demos/demo-p.vue) + +Decoration line color for text + +| Type | Required| Supported Platforms| +| ------ | -------- | --------| +| [color](style/color.md) | No | iOS | + +# textDecorationLine + +[Hippy-React example](https://github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/src/components/Text/index.jsx) + +[Hippy-Vue example](https://github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/demos/demo-p.vue) + +Decoration line type for text + +| Type | Required| Supported Platforms| +| ------ | -------- | --------| +| enum('underline', 'line-through', 'none') |No| Android,iOS | + +# textDecorationStyle + +[Hippy-React example](https://github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/src/components/Text/index.jsx) + +[Hippy-Vue example](https://github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/demos/demo-p.vue) + +Decoration line style for text + +| Type | Required| Supported Platforms +| ------ | -------- | --------| +| enum('dotted', 'dashed', 'solid') |No| iOS | + +# textShadowColor + +> Minimum supported version 2.10.0 + +[Hippy-React example](https://github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/src/components/Text/index.jsx) + +[Hippy-Vue example](https://github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/demos/demo-p.vue) + +Text shadow color + +| Type | Required| Supported Platforms +| ------ | -------- | --------| +| [color](style/color.md) | No | Android,iOS | + +# textShadowOffset + +> Minimum supported version 2.10.0 + +[Hippy-React example](https://github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/src/components/Text/index.jsx) + +[Hippy-Vue example](https://github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/demos/demo-p.vue) + +Text shadow offset + +| Type | Required| Supported Platforms| +| ------ | -------- | --------| +| object: { x: number, y: number }| No| Android,iOS | + +# textShadowOffsetX + +* Minimum supported version 2.10.0 +* Note that the Hippy-Vue class style only supports the combined write format 'text shadow offset: 1px 1px', and does not support splitting + +[Hippy-React example](https://github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/src/components/Text/index.jsx) + +[Hippy-Vue example](https://github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/demos/demo-p.vue) + +Text shadow X-axis offset + +| Type | Required| Supported Platforms +| ------ | -------- | --------| +| number |No| Android,iOS | + +# textShadowOffsetY + +> * Minimum supported version 2.10.0 +>* Note that the Hippy-Vue class style only supports the combined write format `text-shadow-offset: 1px 1px`, and does not support splitting + +[Hippy-React example](https://github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/src/components/Text/index.jsx) + +[Hippy-Vue example](https://github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/demos/demo-p.vue) + +Text shadow Y-axis offset + +| Type | Required| Supported Platforms| +| ------ | -------- | --------| +| number |No| Android,iOS | + +# textShadowRadius + +> * Minimum supported version 2.10.0 + +[Hippy-React example](https://github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/src/components/Text/index.jsx) + +[Hippy-Vue example](https://github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/demos/demo-p.vue) + +Text shadow radius + +| Type | Required| Supported Platforms +| ------ | -------- | --------| +| number |No| Android,iOS | + +# tintColor + +[Hippy-React example](https://github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/src/components/Image/index.jsx) + +[Hippy-Vue example](https://github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/demos/demo-img.vue) + + +Tint the image (When tinting the non-solid color image with transparency, there is a difference in the default value of 'blendmode' between Android and iOS) + +| Type | Required| Supported Platforms +| ------------------ | -------- | --- | +| [color](style/color.md) |No| Android,iOS + +# visibility + +| Type | Required| Supported Platforms +|------------------------------------| -------- | --- | +| enum('visible'[default], 'hidden') | No | Android(2.14.5)、iOS(2.9.0) + diff --git a/docs/en-us/style/color.md b/docs/en-us/style/color.md new file mode 100644 index 00000000000..d06be19bc54 --- /dev/null +++ b/docs/en-us/style/color.md @@ -0,0 +1,180 @@ + + +# Color + +For the value of color attribute, you can query [color configuration of CSS](//developer.mozilla.org/en-US/docs/Web/CSS/color_value). + +--- + +# RGB + +Hippy supports`rgb()` and`rgba()`. + +* `'#f0f'` (#rgb) +* `'#ff00ff'` (#rrggbb) +* `'rgb(255, 0, 255)'` +* `'#f0ff'` (#rgba) +* `'#ff00ff00'` (#rrggbbaa) +* `'rgba(255, 255, 255, 1.0)'` + +# HSL + +Hippy also supports`hsl()` and`hsla()` + +* `'hsl(360, 100%, 100%)'` +* `'hsla(360, 100%, 100%, 1.0)'` + +# Transparency + + `rgba(0, 0, 0, 0)` has another abbreviated version: + +* `'transparent'` + +# Color name + +You can also use the following color configuration + +*
aliceblue (#f0f8ff)
+*
antiquewhite (#faebd7)
+*
aqua (#00ffff)
+*
aquamarine (#7fffd4)
+*
azure (#f0ffff)
+*
beige (#f5f5dc)
+*
bisque (#ffe4c4)
+*
black (#000000)
+*
blanchedalmond (#ffebcd)
+*
blue (#0000ff)
+*
blueviolet (#8a2be2)
+*
brown (#a52a2a)
+*
burlywood (#deb887)
+*
cadetblue (#5f9ea0)
+*
chartreuse (#7fff00)
+*
chocolate (#d2691e)
+*
coral (#ff7f50)
+*
cornflowerblue (#6495ed)
+*
cornsilk (#fff8dc)
+*
crimson (#dc143c)
+*
cyan (#00ffff)
+*
darkblue (#00008b)
+*
darkcyan (#008b8b)
+*
darkgoldenrod (#b8860b)
+*
darkgray (#a9a9a9)
+*
darkgreen (#006400)
+*
darkgrey (#a9a9a9)
+*
darkkhaki (#bdb76b)
+*
darkmagenta (#8b008b)
+*
darkolivegreen (#556b2f)
+*
darkorange (#ff8c00)
+*
darkorchid (#9932cc)
+*
darkred (#8b0000)
+*
darksalmon (#e9967a)
+*
darkseagreen (#8fbc8f)
+*
darkslateblue (#483d8b)
+*
darkslategrey (#2f4f4f)
+*
darkturquoise (#00ced1)
+*
darkviolet (#9400d3)
+*
deeppink (#ff1493)
+*
deepskyblue (#00bfff)
+*
dimgray (#696969)
+*
dodgerblue (#1e90ff)
+*
firebrick (#b22222)
+*
floralwhite (#fffaf0)
+*
forestgreen (#228b22)
+*
fuchsia (#ff00ff)
+*
gainsboro (#dcdcdc)
+*
ghostwhite (#f8f8ff)
+*
gold (#ffd700)
+*
goldenrod (#daa520)
+*
gray (#808080)
+*
green (#008000)
+*
greenyellow (#adff2f)
+*
grey (#808080)
+*
honeydew (#f0fff0)
+*
hotpink (#ff69b4)
+*
indianred (#cd5c5c)
+*
indigo (#4b0082)
+*
ivory (#fffff0)
+*
khaki (#f0e68c)
+*
lavender (#e6e6fa)
+*
lavenderblush (#fff0f5)
+*
lawngreen (#7cfc00)
+*
lemonchiffon (#fffacd)
+*
lightblue (#add8e6)
+*
lightcoral (#f08080)
+*
lightcyan (#e0ffff)
+*
lightgoldenrodyellow (#fafad2)
+*
lightgray (#d3d3d3)
+*
lightgreen (#90ee90)
+*
lightgrey (#d3d3d3)
+*
lightpink (#ffb6c1)
+*
lightsalmon (#ffa07a)
+*
lightseagreen (#20b2aa)
+*
lightskyblue (#87cefa)
+*
lightslategrey (#778899)
+*
lightsteelblue (#b0c4de)
+*
lightyellow (#ffffe0)
+*
lime (#00ff00)
+*
limegreen (#32cd32)
+*
linen (#faf0e6)
+*
magenta (#ff00ff)
+*
maroon (#800000)
+*
mediumaquamarine (#66cdaa)
+*
mediumblue (#0000cd)
+*
mediumorchid (#ba55d3)
+*
mediumpurple (#9370db)
+*
mediumseagreen (#3cb371)
+*
mediumslateblue (#7b68ee)
+*
mediumspringgreen (#00fa9a)
+*
mediumturquoise (#48d1cc)
+*
mediumvioletred (#c71585)
+*
midnightblue (#191970)
+*
mintcream (#f5fffa)
+*
mistyrose (#ffe4e1)
+*
moccasin (#ffe4b5)
+*
navajowhite (#ffdead)
+*
navy (#000080)
+*
oldlace (#fdf5e6)
+*
olive (#808000)
+*
olivedrab (#6b8e23)
+*
orange (#ffa500)
+*
orangered (#ff4500)
+*
orchid (#da70d6)
+*
palegoldenrod (#eee8aa)
+*
palegreen (#98fb98)
+*
paleturquoise (#afeeee)
+*
palevioletred (#db7093)
+*
papayawhip (#ffefd5)
+*
peachpuff (#ffdab9)
+*
peru (#cd853f)
+*
pink (#ffc0cb)
+*
plum (#dda0dd)
+*
powderblue (#b0e0e6)
+*
purple (#800080)
+*
rebeccapurple (#663399)
+*
red (#ff0000)
+*
rosybrown (#bc8f8f)
+*
royalblue (#4169e1)
+*
saddlebrown (#8b4513)
+*
salmon (#fa8072)
+*
sandybrown (#f4a460)
+*
seagreen (#2e8b57)
+*
seashell (#fff5ee)
+*
sienna (#a0522d)
+*
silver (#c0c0c0)
+*
skyblue (#87ceeb)
+*
slateblue (#6a5acd)
+*
slategray (#708090)
+*
snow (#fffafa)
+*
springgreen (#00ff7f)
+*
steelblue (#4682b4)
+*
tan (#d2b48c)
+*
teal (#008080)
+*
thistle (#d8bfd8)
+*
tomato (#ff6347)
+*
turquoise (#40e0d0)
+*
violet (#ee82ee)
+*
wheat (#f5deb3)
+*
white (#ffffff)
+*
whitesmoke (#f5f5f5)
+*
yellow (#ffff00)
+*
yellowgreen (#9acd32)
diff --git a/docs/en-us/style/layout.md b/docs/en-us/style/layout.md new file mode 100644 index 00000000000..582fe066068 --- /dev/null +++ b/docs/en-us/style/layout.md @@ -0,0 +1,442 @@ +# Layout + +Hippy's style layout uses Flex. It is worth noting that the `PercentFrame Layout` of web pages is not yet compatible. + +--- + + + +# alignItems + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/align-items) + +`alignItems` determines how the child elements are arranged in the direction of the secondary axis (this style is set on the parent element). For example, if the child elements are originally arranged in a vertical direction (i.e., The primary axis is vertical and the secondary axis is horizontal), the `alignItems` determines how they are arranged in the horizontal direction. Its behavior is consistent with that on CSS `align-items`(default is `stretch`). + +| Type | Required| +| --------------------------------------------------------------- | -------- | +| enum('flex-start', 'flex-end', 'center', 'stretch', 'baseline') |No | + +# alignSelf + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/align-self) + +`alignSelf` determines how elements are arranged in the direction of the secondary axis of the parent element (this style is set on the child element), and its value overrides the value of the parent element `alignItems`. Its behavior is consistent with that on CSS `align-self`(default is`auto`). + +| Type | Required| +| ----------------------------------------------------------------------- | -------- | +| enum('auto', 'flex-start', 'flex-end', 'center', 'stretch', 'baseline') |No | + + +# backgroundImage + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/background-image) + +`backgroundImage` value can be directly passed into the background image address or gradient. + +| Type | Required| +| --------------- | -------- | +| string |No | + +>after the version `2.8.1`, it supports the local image capability of the native and can be loaded through Webpack `file-loader`. + +>Gradient currently support `linear-gradient`(Minimum supported version 2.8.0) [[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient), and supports the use of `linear-gradient([ [ [ | to [top | bottom] || [left | right] ],]? [, ]+)` format. Among them, `angle` supports `deg`, `turn` and `rad` units, and `color-stop` supports setting multiple colors and percentages. DEMO: [Hippy-React](//github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/src/components/View/index.jsx) [Hippy-Vue](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/demos/demo-div.vue) +>
+>
+>Note: +> +>+ Android can not use percentages for `color-stop` if you use `to [top | bottom] || [left | right]` four top corners; +>+ iOS `color-stop` percentage can only be explicitly set from small to large, and can not be partially omitted, that is,`red 10%, yellow 20%, blue 50%` can not be`red 10%, yellow 20%, blue 10%` + +# backgroundPositionX + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/background-position) + +`backgroundPositionX` specifies the horizontal X-coordinate of the initial position of the background image. + +| Type | Required| +| --------------- | -------- | +| number |No | + +# backgroundPositionY + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/background-position) + +`backgroundPositionY` specifies the vertical Y-coordinate of the initial position of the background image. + +| Type | Required| +| --------------- | -------- | +| number |No | + +# backgroundSize + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/background-size) + +`backgroundSize` sets the background image size. + +| Type | Required| +|--------------------------| -------- | +| enum('cover', 'contain') |No | + +# collapsable + +In Android, if a `View` subcomponent is used only to layout it, it may be removed from the native layout tree for optimization, so the reference to the DOM of that node is lost`(for example, the size and location information cannot be obtained by calling measureInAppWindow)`. Set this property to `false` disable this optimization to ensure that the corresponding view exists in the native structure.(It can also be set as the `Attribute` attribute of `View`), default is `true`. + +| Type | Required| Supported Platforms| +| --------------- | -------- | ---- | +| enum('false', 'true'[default]) | No | Android| + +# display + +Hippy adopts Flex by default. Also, because it only supports Flex, it can be used without handwritten `display: flex`. + +| Type | Required| +| --------------- | -------- | +| enum('flex') |No | + +# flex + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/flex) + +There's a difference between flex and CSS in Hippy. Flex can only be an integer value in Hippy. + +| Type | Required| +| ------ | -------- | +| number |No | + +# flexBasis + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/flex-basis) + +`flex-basis` specifies the initial size of the flex element in the direction of the primary axis. + +| Type | Required| +| ------ | -------- | +| number |No | + +# flexDirection + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/flex-direction) + +`flexDirection` determines the direction in which the child elements of the container are arranged:`row` for horizontal arrangement and`column` for vertical arrangement. The other two parameters are reversed. +It's much like css's `flex-direction` definition, but CSS defaults to `row`, and Hippy defaults to `column`. + +| Type | Required| +| ------------------------------------------------------ | -------- | +| enum('row', 'row-reverse', 'column', 'column-reverse') |No | + +# flexGrow + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/flex-grow) + +`flexGrow` defines the extensibility of the flex project. It accepts a value without units as a scale. It is mainly used to determine how much space the remaining space of the telescopic container should be expanded proportionally. + +If `flex-grow` of all flex items is set to `1` then each flex item will be set to an equal amount of remaining space. If one of the flex items `flex-grow` set to `2`, the remaining space for that flex item is twice as large as the remaining space for the other flex items. Default is `0`. + +| Type | Required| +| ------ | -------- | +| number |No | + +# flexShrink + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/flex-shrink ) + +`flexShrink` property specifies the shrink rule for the flex element. Flex elements shrink only if the sum of the default widths is greater than the container, and the size of the shrink is based on the flex width shrink value. The default value of flexShrink in Hippy is `0`, which is different from the web standard `1`. + +| Type | Required| +| ------ | -------- | +| number |No | + +# flexWrap + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/flex-wrap) + +`flexWrap` defines how a child element performs line wrapping behavior when it touches the bottom of the parent container. Default value is `nowrap`. + +| Type | Required| +| ---------------------- | -------- | +| enum('wrap', 'nowrap') |No | + +# height + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/height) + +`height` defines the height of the container in pt + +| Type | Required| +| --------------- | -------- | +| number, |No | + +# justifyContent + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/justify-content) + +`justifyContent` defines how the browser allocates space between and around elastic elements that follow the main axis of the parent container. Default value is `flex-start`. + +| Type | Required| +| ----------------------------------------------------------------------------------------- | -------- | +| enum('flex-start', 'flex-end', 'center', 'space-between', 'space-around', 'space-evenly') |No | + +# left + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/left) + +`left` value refers to how many logical pixels the component is positioned to the left (the definition of the left depends on the position attribute). + +It behaves like `left` on CSS, but note that only logical pixel values (numerical units) can be used on Hippy, not percentages, em, or any other unit. + +| Type | Required| +| --------------- | -------- | +| number |No | + +# lineHeight + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/line-height) + +`lineHeight` property is used to set the amount of space for multiline elements, such as the spacing of multiline text. Hippy only supports setting specific values. + +| Type | Required| +| --------------- | -------- | +| number |No | + +# margin + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/margin) + +Setting `margin` has the same effect as setting the same values for `marginTop`, `marginLeft`, `marginBottom`, and `marginRight`. + +| Type | Required| +| --------------- | -------- | +| number |No | + +# marginBottom + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/margin-bottom) + +`marginBottom` similar to CSS `margin-bottom`. + +| Type | Required| +| --------------- | -------- | +| number |No | + +# marginHorizontal + +Setting `marginHorizontal` has the same effect as setting `marginLeft` and `marginRight` at the same time + +| Type | Required| +| --------------- | -------- | +| number |No | + +# marginLeft + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/margin-left) + +`marginLeft` similar to CSS `margin-left`. + +| Type | Required| +| --------------- | -------- | +| number |No | + +# marginRight + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/margin-right) + +`marginRight` similar to CSS `margin-right`. + +| Type | Required| +| --------------- | -------- | +| number |No | + +# marginTop + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/margin-top) + +`marginTop` similar to CSS `margin-top`. + +| Type | Required| +| --------------- | -------- | +| number |No | + +# marginVertical + +Setting `marginVertical` has the same effect as setting `marginTop` and `marginBottom` at the same time + +| Type | Required| +| --------------- | -------- | +| number |No | + +# maxHeight + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/max-height) + +| Type | Required| +| --------------- | -------- | +| number |No | + +# maxWidth + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/max-width) + +| Type | Required| +| --------------- | -------- | +| number |No | + +# minHeight + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/min-height) + +| Type | Required| +| --------------- | -------- | +| number |No | + +# minWidth + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/min-width) + +| Type | Required| +| --------------- | -------- | +| number |No | + +# overflow + + +`overflow` defines the display of the child element after it exceeds the width and height of the parent container. The condition of `overflow: hidden` will cause the child element to be cut by the parent container. The part beyond the display range will be displayed normally by the child container, even if it exceeds the display range of the parent container. + +!> For historical reasons, Android defaults to all elements `overflow: hidden` and iOS to`overflow: visible` + +| Type | Required| +| ----------------------------------- | -------- | +| enum('visible', 'hidden') |No | + +# padding + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/padding) + +Setting `padding` has the same effect as setting `paddingTop`, `paddingBottom`, `paddingLeft`, and `paddingRight` at the same time. + +| Type | Required| +| --------------- | -------- | +| numbe |No | + +# paddingBottom + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/padding-bottom) + +`paddingBottom` similar to CSS `padding-bottom`. + +| Type | Required| +| --------------- | -------- | +| number |No | + +# paddingHorizontal + +Setting `paddingHorizontal` has the same effect as setting `paddingLeft` and `paddingRight` at the same time. + +| Type | Required| +| --------------- | -------- | +| number |No | + +# paddingLeft + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/padding-left) + +`paddingLeft` similar to CSS `padding-left`. + +| Type | Required| +| --------------- | -------- | +| number |No | + +# paddingRight + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/padding-right) + +`paddingRight` similar to CSS `padding-right`. + +| Type | Required| +| --------------- | -------- | +| number |No | + +# paddingTop + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/padding-top) + +`paddingTop` similar to CSS `padding-top`. + +| Type | Required| +| --------------- | -------- | +| number |No | + +# paddingVertical + +Setting `paddingVertical` has the same effect as setting `paddingTop` and `paddingBottom` at the same time. + +| Type | Required| +| --------------- | -------- | +| number |No | + +# position + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/position) + + +The behaves of `position` in Hippy is basically the same as that in CSS, but it defaults to 'relative' all the time. Therefore, when the element is set to 'absolute', it can always ensure that only the parent element of the upper level is absolutely positioned. + +It is similar to the `position` attribute of CSS, but there are `position` only `absolute``relative` two attributes in Hippy. + +| Type | Required| +| ---------------------------- | -------- | +| enum('absolute', 'relative') |No | + +# right + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/right) + +`right` value refers to how many logical pixels the component is positioned to the right (the definition of the right depends on the position attribute). + +It behaves like `right` on CSS, but note that only logical pixel values (numerical units) can be used on Hippy, not percentages, em, or any other unit. + + +| Type | Required| +| --------------- | -------- | +| number |No | + +# textAlign + +[[MDN 文档]](//developer.mozilla.org/en-US/docs/Web/CSS/text-align) + +`textAlign` sets the horizontal alignment of the inline-level text inside a block element or table-cell box,default value is `left`. + +| Type | Required | +| --------------- | -------- | +| enum('left', 'center', 'right') | No | + +# top + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/top) + +`top` The value is how many logical pixels the component is positioned from the top (the definition of the top depends on the position property). + +It behaves like `top` on CSS, but note that only logical pixel values (numerical units) can be used on Hippy, not percentages, em, or any other unit. + +| Type | Required| +| --------------- | -------- | +| number |No | + +# width + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/width) + +`width` defines the width of the container + +| Type | Required| +| --------------- | -------- | +| number |No | + +# zIndex + +[[MDN Docs]](//developer.mozilla.org/en-US/docs/Web/CSS/z-index) + +`zIndex` determines the order in which the containers are arranged. Generally, you don't need to use `zIndex` directly. Container elements are rendered in the order of the node tree, with the following elements overwriting the previous elements (if any). zIndex levels can be used in situations where you need to manually specify drawing levels. + +| Type | Required| +| ------ | -------- | +| number |No | diff --git a/docs/en-us/style/setNativeProps.md b/docs/en-us/style/setNativeProps.md new file mode 100644 index 00000000000..fdfd7530e53 --- /dev/null +++ b/docs/en-us/style/setNativeProps.md @@ -0,0 +1,35 @@ +# SetNativeProps + +When doing some high-frequency animation effects, due to the need to re-render the JS layer for many times, the phenomenon of lag jam will occur.`setNativeProps` provides a "simple and crude" method under `ElementNode` to directly modify the style of the native components to optimize performance. At present, this method can only modify the `style`, and other attributes are not supported temporarily. P.S. Logical side effects such as data synchronization that may occur need to be solved by the developer + +--- + +# React + +[[Example: SetNativeProps.jsx scenario](//github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/src/externals/SetNativeProps/index.jsx) + +Except for element component (such as div), React does not directly expose ElementNode for developer use. You can use [React.forwardRef]( https://zh-hans.reactjs.org/docs/forwarding-refs.html )forward, or use the `UIManagerModule.getElementFromFiberRef` method provided by Hippy (this method will traverse the child nodes one by one from the current node until the 'ElementNode' element is found and returned, with a certain performance loss) obtains the first 'ElementNode'. The demo is as follows: + +```javascript + class BusinessComponent extends React.Component { + constructor(props) { + super(props); + this.customComRef = null; + } + componentDidMount() { + const customElementDom = UIManagerModule.getElementFromFiberRef(this.customComRef); + customElementDom.setNativeProps({ + style: { height: 100 } + }) + } + render() { + return this.customComRef = ref} /> + } + } +``` + +# Vue + +[[Example: demo-set-native-props.vue]](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/demos/demo-set-native-props.vue) + +If Vue is an HTML element, you can get DOM directly through `$refs`. For custom components, you can use `$refs.x.$el` get. `setNativeProps` is used in the same way as React. diff --git a/docs/en-us/style/transform.md b/docs/en-us/style/transform.md new file mode 100644 index 00000000000..8fd34114b77 --- /dev/null +++ b/docs/en-us/style/transform.md @@ -0,0 +1,19 @@ +# Transform + +This type of style can make some basic deformations to the component elements, such as scaling, rotating, twisting, and so on. + +--- + +# transform + +`transform` You can pass in multiple deformed parameter arrays to complete the transform of the original element. For example: + +```jsx +transform: [{ rotateX: '45deg' }, { rotateZ: '0.785398rad' }] +``` + +It is similar to the transform parameter of CSS, see the [mdn](//developer.mozilla.org/en-US/docs/Web/CSS/transform) for details. + +| Type | Required | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |----------| +| array of object: { perspective: number }, object: { rotate: string }, object: { rotateX: string }, object: { rotateY: string }, object: { rotateZ: string }, object: { scale: number }, object: { scaleX: number }, object: { scaleY: number }, object: { translateX: number }, object: {translateY: number}, object: { skewX: string }, object: { skewY: string }| false | diff --git a/docs/en-us/web/_sidebar.md b/docs/en-us/web/_sidebar.md new file mode 100755 index 00000000000..e24c994a318 --- /dev/null +++ b/docs/en-us/web/_sidebar.md @@ -0,0 +1,6 @@ + + +* [Web Integration](web/integration.md) +* [Web Events](web/event.md) +* [Custom UI Components](web/custom-component.md) +* [Custom Modules](web/custom-module.md) diff --git a/docs/en-us/web/custom-component.md b/docs/en-us/web/custom-component.md new file mode 100644 index 00000000000..051535c6977 --- /dev/null +++ b/docs/en-us/web/custom-component.md @@ -0,0 +1,140 @@ +# Custom UI Components + +In the development of using Hippy, it is possible that the builtin components won't meet the need of certain circumstances. In this case, you can augment the UI components by encapsulation or importing some third-party functionalities inside the components. + +--- + +# Component Extension + +Component extension mainly including: + +1. Extend the custom component from `HippyView` +2. Implement the class constuctor method +3. Set `tagName` for the custom component +4. Construct `dom` for the custom component +5. Implement `API` for the custom component +6. Implement the properties for the custom component + + +The HippyView class implements some interfaces and properties of HippyBaseView's interface. There are several essential properties in a custom component: + +* id: the unique identifier for every component's instance. This id will be assigned to the id property of component's dom by default. +* pId: the unique identifier for the parent of every component's instance. +* tagName: this is used to distinguish the type of components, as well as the value that needs to be passed into `nativeName` property when application uses this component, where `nativeName` will be used to map the key of the custom component. +* dom: the node that really mounts on the document. +* props: it carries the properties and styles passed from application. + + +## Example + +In the following example, we create a custom component, called `CustomView`, to display a video. + +* First, initialize the component: + +```javascript + +import { HippyView, HippyWebEngine, HippyWebModule } from '@hippy/web-renderer'; + +// Extends from HippyView +class CustomView extends HippyView { + // Implement the class constuctor method + constructor(context, id, pId) { + super(context, id, pId); + // Set the tagName as 'CustomView' + // Such that the JS application can set `nativeName="CustomView"` to create a mapping from application to this component. + this.tagName = 'CustomView'; + // Contruct the dom for the custom component. We create a video element and assign it to the class member 'dom'. Notice that the class member 'dom' needs to be set before the end of constructor method. + this.dom = document.createElement('video'); + } +} +``` + +* Second, implement the API and related properties for the custom component: + + We implement the getter and setter for property `src`. When JS application modifies the property `src`, the setter `set src()` will be triggered and retrieve the new `src` value. + + We also implement `play` and `pause` class methods. When JS application uses `callUIFunction(this.instance, 'play'/'pause', []);`, these two methods will be called respectively. + + In the `pasue()` method, we use `sendUiEvent` to emit a `onPause` event to JS application, those callbacks that subscribe to `onPause` event will be triggered. + + +```javascript + +import { HippyView, HippyWebEngine, HippyWebModule } from '@hippy/web-renderer'; + +class CustomView extends HippyView { + + set src(value) { + this.dom.src = value; + } + + get src() { + return this.props['src']; + } + + play() { + this.dom.play(); + } + + pause() { + this.dom.pause(); + this.context.sendUiEvent(this.id, 'onPause', {}); + } +} + +``` + +> About `props`: By default, the low-level implementation of `HippyWebRenderer` will store `props` that passed from application into the custom component's `props`, then triggering the setter for updated `props`, and the components will have timing for updating its states, such that executing some desired behaviors. +There is a `style` object inside the `props`, which will carry the styles passed from application. By default, this `style` object will also be assigned to the dom's style of custom component by `HippyWebRenderer`. However, since certain properties in `style` object from `props` are hippy-only, the `style` object needs to be translated before assigning it to the `dom`'s `style` property. + +> About `context`: A `context` object will be passed into the custom component when the custom component is constructing. It provides some key methods: + +```javascript +export interface ComponentContext { + sendEvent: (type: string, params: any) => void; // dispatch global event to the the application + sendUiEvent: (id: number, type: string, params: any) => void; // dispatch event to certain component's instance + sendGestureEvent: (e: HippyTransferData.NativeGestureEvent) => void; // dispatch gesture event + subscribe: (evt: string, callback: Function) => void; // subcribe to particular event + getModuleByName: (moduleName: string) => any; // retrieve module instance by module's name +} +``` + + +# Complicated Components + +Sometime we may want to provide a container to wrap the existing components. This container will have some particular forms or behaviors, such as managing its own node insertion and deleteion, modifying style or intercepting properties. In this kind of cases, we need to use more complicated ways to implement these custom components. + + +* As for the dom of child nodes or the default HippyWebRender components inserted and deleted, we will use the same methods as the web: + +```javascript +Node.insertBefore(node: T, child: Node | null): T; +Node.removeChild(child: T): T; +``` + +* If you don't want the default implementation, you can manage node insertion and deletion through insertChild and removeChild methods + +```javascript +class CustomView extends HippyView{ + insertChild (child: HippyBaseView, childPosition: number) { + // ... + } + removeChild (child: HippyBaseView) { + // ... + } +} +``` + +* To intercept the update of `props`, you need to implement the custom component's `updateProps` method. + +> In the example below, `data` parameter is the real data of updated `props`, and `defaultProcess()` is `HippyWebRenderer`'s default method to update the `props`. After intercepting the updating process, developers can update the property with default value, or use some custom methods to update it. + + +```javascript +class CustomView extends HippyView{ + + updateProps (data: UIProps, defaultProcess: (component: HippyBaseView, data: UIProps) => void) { + // ... + } +} +``` diff --git a/docs/en-us/web/custom-module.md b/docs/en-us/web/custom-module.md new file mode 100755 index 00000000000..f53ae0ac8a6 --- /dev/null +++ b/docs/en-us/web/custom-module.md @@ -0,0 +1,55 @@ +# Custom Modules + +In the development of using Hippy, it is possible that the builtin module won't meet the need of certain circumstances. In this case, you can augment the module by encapsulation or importing some third-party functionalities inside the module. + +--- + +# Module Extension + +Module extension mainly including: + +1. Extend from `HippyWebModule` class +2. Set the `name` property of the custom `Module` +3. Implement the `Module API` that needs to be exposed to Front-end + +The `HippyWebModule` class standardizes the useable modules of HippyWebRenderer, providing context of HippyWebRenderer. There are some important properties in a custom module: + +* name: define the name of the custom module, which is corresponding to the `moduleName` when JS application call `callNative(moduleName,methodName)`. +* context: provide a series of methods: + +```javascript +sendEvent(type: string, params: any); // dispatch an event +sendUiEvent(nodeId: number, type: string, params: any); // dispatch an UI-related event +sendGestureEvent(e: HippyTransferData.NativeGestureEvent); // dispatch an Gesture event +subscribe(evt: string, callback: Function); // listening to an event +getModuleByName(moduleName: string); // get module by module's name +``` + +## Example + +Take CustomModule as an example, let's introduce how to extend a custom Module from beginning + +```javascript +import { HippyWebModule } from '@hippy/web-renderer'; +// Extends from HippyWebModule +export class CustomModule extends HippyWebModule { + // Set the name for CustomModule + name = 'CustomModule'; + // Implement 'getBrowserInfo' and 'setBrowserTitle' APIs, which are used to retrieve the current browser infomation and set the title of browser repectively. + // When implements the custom module's APIs, the parameters will be like: `function name(arg1,arg2...argn,callBack)`, while all the parameters before the nth one will be the parameters that the application passes into, and the nth parameter will be the callback that passed from application to retrive the return from APIs. + getBrowserInfo(callBack) { + let data = {}; + ... + callBack.resolve(data); + } + + setBrowserTitle(title, callBack) { + if (title) { + window.document.title = title; + }; + ... + callBack.resolve(true); + // use callBack.reject(null) if something goes wrong; + } +} +``` diff --git a/docs/en-us/web/event.md b/docs/en-us/web/event.md new file mode 100755 index 00000000000..35f8393185d --- /dev/null +++ b/docs/en-us/web/event.md @@ -0,0 +1,46 @@ +# Web Event + +When the browser finishes loading or uses some third-party libraries to complete some functions, it needs to throw events to the application side. + +--- + +# Send event + +The WebRenderer calls the following code where the event needs to be sent: + +## In the module + +To send events, the `context` provided by the `HippyWebModule` base class provides the ability to send events to the application layer + +```javascript +const eventName = 'CustomName'; +const param = {}; +context.sendEvent(eventName, param); +``` + +## In the component + +To send events, the `context` of the `HippyWebView` base class provides the ability to send events to the application layer + +```javascript +const eventName = 'CustomName'; +const param = {}; +context.sendEvent(eventName, param); +``` + +## In the global + +To send events, the `context` of `Hippy.web.engine` provides the ability to send events to the application layer + +```javascript +const engine = Hippy.web.engine; +const eventName = 'CustomName'; +const param = {}; +engine.context.sendEvent(eventName, param); +``` + +# Front-end application listening events + +[hippy-react listening events](hippy-react/native-event.md?id=event-listener) + +[hippy-vue listening events](hippy-vue/native-event.md?id=event-listener) diff --git a/docs/en-us/web/integration.md b/docs/en-us/web/integration.md new file mode 100755 index 00000000000..8866a0046b8 --- /dev/null +++ b/docs/en-us/web/integration.md @@ -0,0 +1,155 @@ +# Web Integration + +This tutorial shows how to integrate Hippy into a Web page. + +> Different from @hippy/react-web and @hippy/vue-Web, this solution (Web Renderer) will not replace @hippy/react and @hippy/vue. Instead, the bundle running in the native environment will run intactly on the Web, which has advantages and disadvantages with Web translation solution. Application can adopt appropriate solution according to specific scenarios + +--- + +# Preparation + +- Template file: An HTML file is required as an entry for web working +- Entry file: WebRenderer is a running environment of the Hippy bundle, so it does not share the JS entry file, and should create an independent entry file for it + +# Experience the demo + +For a quick experience, you can base it directly on our [HippyReact Web Demo](https://github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo) and +[HippyVue Web Demo](https://github.com/Tencent/Hippy/tree/master/examples/hippy-vue-demo) + +## npm script + +In the Demo project, run the `web:dev` command to start the WebRenderer debugging service, and run the `web:build` to package. + +```json + "scripts": { + "web:dev": "npm run hippy:dev & node ./scripts/env-polyfill.js webpack serve --config ./scripts/hippy-webpack.web-renderer.dev.js", + "web:build": "node ./scripts/env-polyfill.js webpack --config ./scripts/hippy-webpack.web-renderer.js" + } +``` + +## Start debugging + +Run `npm run web:dev` to start WebRenderer debugging. According to the Webpack configuration of the demo, the WebRenderer web service runs on port `3000`, the browser accesses the page through `http://localhost:3000`. + +# Quick integration + +The execution of WebRenderer should comply with the following process: + +1. Import the WebRenderer: This stage will initialize the environment for the Hippy code to run +2. Load the application bundle: This bundle is consistent with the bundle package running on the native side +3. Start WebRenderer: This stage will load hippy built-in components and modules, or custom components and modules + +## Import WebRenderer + +### Use in CDN mode + +Add these code to the template file: + +```html + + + + + + Example + + +
+ + + + + + +``` + +### Use in NPM package mode + +```shell +npm install -S @hippy/web-renderer +``` + +Add to the entry file: + +```javascript +// 1. import web renderer +import { HippyWebEngine, HippyWebModule } from '@hippy/web-renderer'; + +// 2. Import the entry file of the application bundle after the web renderer import + +// 3. Create the web engine. If there are application custom modules and components, pass them in from here +``` + +## Load application bundle + +There are multiple ways to load the bundle package, which can be flexibly selected according to application needs. Just ensure that the import order is after the WebRenderer + +### Reference and load in the template file + +```html + + + + + +``` + +### Load dynamically in the entry file + +```javascript +import { HippyWebEngine } from '@hippy/web-renderer'; + +const engine = HippyWebEngine.create(); + + engine.load('https://xxxx.com/hippy-bundle/index.bundle.js').then(() => { + engine.start({ + id: 'root', + name: 'example', + }); +}); +``` + +### Import from source code + +```javascript +import { HippyCallBack, HippyWebEngine, HippyWebModule, View } from '@hippy/web-renderer'; +// Import the entry file of the application bundle after the web renderer import +import './main'; + + +const engine = HippyWebEngine.create(); +``` + +## Start the WebRenderer + +After the application bundle is loaded, the relevant API is invoked to create and start the WebRenderer + +```js +// Create the web engine. If there are application custom modules and components, pass them in from here +// If only official modules and components are used, directly use const engine = hippywebengine Create() +const engine = HippyWebEngine.create({ + modules: { + CustomCommonModule, + }, + components: { + CustomPageView, + }, +}); + +// Start the web renderer +engine.start({ + // Mounted dom id + id: 'root', + // Module name + name: 'module-name', + // The module startup parameters are customized by the service, + // hippy-react can be obtained from the props entry file, and hippy-vue can be obtained from app.$options.$superProps + params: { + path: '/home', + singleModule: true, + isSingleMode: true, + business: '', + data: { }, + }, +}); +``` diff --git a/docs/guide/_sidebar.md b/docs/guide/_sidebar.md index 2157d3bc46d..671f787f9cc 100644 --- a/docs/guide/_sidebar.md +++ b/docs/guide/_sidebar.md @@ -1,14 +1,17 @@ * [开始接入](guide/integration.md) -* [Android集成](android/integration.md) -* [iOS集成](ios/integration.md) -* [前端调试](guide/debug.md) -* [Core C++ 架构](core/introduction.md) +* [调试](guide/debug.md) +* [架构](structure/introduction.md) * [布局与单位](guide/layout.md) * [网络请求](guide/network-request.md) * [定时器](guide/timer.md) +* [动画](guide/animation.md) * [日志](guide/console.md) +* [异常捕获](guide/exception.md) * [自定义字体](guide/custom-font.md) * [动态加载](guide/dynamic-import.md) +* [性能监控](guide/performance.md) +* [JSI 模式](guide/jsi.md) * [技术支持](guide/support.md) +* [隐私政策](guide/privacy.md) diff --git a/docs/guide/animation.md b/docs/guide/animation.md new file mode 100644 index 00000000000..be8f6591949 --- /dev/null +++ b/docs/guide/animation.md @@ -0,0 +1,149 @@ +# 动画方案 + +--- + +# 原理 + +Hippy 的动画则是完全由前端传入动画参数,由终端控制每一帧的计算和排版更新,减少了前端与终端的通信次数,因此也大大减少动画的卡顿。 + +# 酷炫效果 + +* 关注动画 + +关注动画 + +* 点赞微笑动画 + +点赞微笑 + +* 进度条动画 + +PK进度条动画 + +# 如何使用 + +以 `Hippy-React` 为例,在 Hippy 上实现一个动画分为三个步骤: + +1. 通过 Animation 或 AnimationSet 定义动画 +2. 在 render() 时,将动画设置到需要产生动画效果的控件属性上 +3. 通过 Animation 的 start 方法启动动画,与通过 destroy 方法停止并销毁动画; + +## 示例代码 + +```js +import { Animation, StyleSheet } from "@hippy/react"; +import React, { Component } from "react"; + +export default class AnimationExample extends Component { + componentDidMount() { + // 动画参数的设置 + this.verticalAnimation = new Animation({ + startValue: 0, // 动画开始值 + toValue: 100, // 动画结束值 + duration: 500, // 动画持续时长 + delay: 360, // 至动画真正开始的延迟时间 + mode: "timing", // 动画模式,现在只支持timing + timingFunction: "linear", // 动画缓动函数 + }); + this.horizonAnimation = new Animation({ + startValue: 0, // 开始值 + toValue: 100, // 动画结束值 + duration: 500, // 动画持续时长 + delay: 360, // 至动画真正开始的延迟时间 + mode: "timing", // 动画模式,现在只支持timing + timingFunction: "linear", // 动画缓动函数 + }); + this.scaleAnimationSet = new AnimationSet({ + children: [ + { + animation: new Animation({ + startValue: 1, + toValue: 1.4, + duration: 200, + delay: 0, + mode: "timing", + timingFunction: "linear", + }), + follow: false, // 配置子动画的执行是否跟随执行 + }, + { + animation: new Animation({ + startValue: 1.4, + toValue: 0.2, + duration: 210, + delay: 200, + mode: "timing", + timingFunction: "linear", + }), + follow: true, + }, + ], + }); + } + + componentWillUnmount() { + // 如果动画没有销毁,需要在此处保证销毁动画,以免动画后台运行耗电 + this.scaleAnimationSet && this.scaleAnimationSet.destroy(); + this.horizonAnimation && this.horizonAnimation.destroy(); + this.verticalAnimation && this.verticalAnimation.destroy(); + } + + render() { + return ( + + + + + + { + this.verticalAnimation.start(); + }} + > + 水平位移动画 + + { + this.horizonAnimation.start(); + }} + > + 垂直位移动画 + + { + this.scaleAnimationSet.start(); + }} + > + 图形形变动画 + + + + ); + } +} + +// 样式代码省略 +``` + +## 更多使用说明 + +详细使用,可以参考 + +* [HippyReact Animation 模块](hippy-react/modules.md?id=animation) +* [HippyVue animation 组件](hippy-vue/external-components?id=animation) diff --git a/docs/guide/custom-font.md b/docs/guide/custom-font.md index 2e67ee2e018..183c542a989 100644 --- a/docs/guide/custom-font.md +++ b/docs/guide/custom-font.md @@ -2,6 +2,8 @@ # 自定义字体 +--- + # 前端 前端使用自定义字体非常简单,和浏览器一样,使用 [font-family](https://www.w3schools.com/cssref/pr_font_font-family.asp) 样式即可。 @@ -16,23 +18,23 @@ 打开 iOS 工程,新建一个 `fonts` 目录,并将字体文件拖动到该目录中。按照截图,建立字体引用即可,确保 Target 正确。 -![拷贝字体](https://puui.qpic.cn/vupload/0/1581839496811_kt5r2ikktko.png/0) +![拷贝字体](../assets/img/copy-font.png) 然后点击项目中的字体文件,并再次确认 Target 正确。 -![确认字体](https://puui.qpic.cn/vupload/0/1581839940042_5lhnniq1lxw.png/0) +![确认字体](../assets/img/confirm-font.png) ## 检查项目配置 确认项目的设置的 `Build Phases` 里字体文件正确整合。 -![项目设置](https://puui.qpic.cn/vupload/0/1581840059761_c4iuwaq27co.png/0) +![项目设置](../assets/img/font-project-setup.png) ## 将字体添加到 Info.plist 将准确的字体文件名加入 `Info.plist` 的 `Fonts provided by application` 字段,如果没有这行的话,需要手工 `Add row` 添加一行。 -![Info.plist](https://puui.qpic.cn/vupload/0/1581840249218_6ltk8mud643.png/0) +![Info.plist](../assets/img/info-plist.png) ## 验证字体正确性 @@ -53,7 +55,7 @@ for (NSString* family in [UIFont familyNames]) } ``` -![检查字体](https://puui.qpic.cn/vupload/0/1581840956463_08b8j5p8cp9.png/0) +![检查字体](../assets/img/check-font.png) # Android @@ -63,4 +65,13 @@ Android 只需要在静态资源 `assets` 目录中建立 `fonts` 目录,然 需要注意的是,字体文件名需要和 FontFamily 一致,因为 Android 虽然也可以做字体文件名映射,但是字体和文件名一致无疑是最简单的办法。 +如果有将自定义字体和`粗体 (fontWeight: 'bold')` 或`斜体 (fontStyle: 'italic')` 等属性一同使用的场景,SDK 会优先匹配在 `fonts` 目录下以字体名+后缀的形式存放的字体文件。如: + +- style={ { fontFamily: 'TTTGB' } } 对应 TTTGB.ttf 或 TTTGB.otf +- style={ { fontFamily: 'TTTGB', fontWeight: 'bold' } } 对应 TTTGB_bold.ttf 或 TTTGB_bold.otf +- style={ { fontFamily: 'TTTGB', fontStyle: 'italic' } } 对应 TTTGB_italic.ttf 或 TTTGB_italic.otf +- style={ { fontFamily: 'TTTGB', fontWeight: 'bold', fontStyle: 'italic' } } 对应 TTTGB_bold_italic.ttf 或 TTTGB_bold_italic.otf + +2.16.0版本后,如果匹配不到对应的带后缀的文件,SDK 会再尝试匹配不带后缀的自定义字体文件,结合 Android 的 [Typeface](https://developer.android.com/reference/android/graphics/Typeface) 的对应风格进行展示。 + > 官方 demo 的字体放在 [res/fonts](https://github.com/Tencent/Hippy/tree/master/examples/android-demo/res) 目录下,是因为编译脚本[将 `res` 目录下的文件直接拷贝到 `assets` 目录](https://github.com/Tencent/Hippy/blob/master/examples/android-demo/build.gradle#L35)下了,所以 `res/assets` 就变成 `assets/assets` 目录,为了让字体目录正确拷贝进 `assets` 静态资源目录,只能让它放在 `res` 下。 diff --git a/docs/guide/debug.md b/docs/guide/debug.md index a7cd860ffe3..84a0cded47f 100644 --- a/docs/guide/debug.md +++ b/docs/guide/debug.md @@ -1,27 +1,134 @@ -# 前端调试 +# 调试 + +--- # Hippy 调试原理 -Hippy 是直接运行于手机的 JS 引擎中的,在 Android 上使用 WebSocket 通过 [Chrome DevTools Protocol](//chromedevtools.github.io/devtools-protocol/) 与电脑上的 Chrome 进行通讯调试,而 iOS 上使用内置 的 [JavaScriptCore](//developer.apple.com/documentation/javascriptcore) 与 [Safari](//www.apple.com.cn/cn/safari/) 连接进行调试。 +Hippy 是直接运行于手机的 JS 引擎中的,在 Android 上使用 WebSocket 通过 [Chrome DevTools Protocol](//chromedevtools.github.io/devtools-protocol/) 与电脑上的 Chrome 进行通讯调试,而 iOS 上使用内置 的 [JavaScriptCore](//developer.apple.com/documentation/javascriptcore) 与 [Safari](//www.apple.com.cn/cn/safari/) 连接进行调试,在较新的 Hippy 版本 iOS 也可以使用 Chrome DevTools 进行调试。 -Hippy中运行的JS代码可以来源于本地文件(local file),或者远程服务地址(server)。 +Hippy 中运行的 JS 代码可以来源于本地文件(local file),或者远程服务地址(server)。 -[hippy-debug-server](//www.npmjs.com/package/hippy-debug-server) 就是为了解决调试模式下终端模式获取调试用 JS 文件,以及将 [Chrome DevTools Protocol](//chromedevtools.github.io/devtools-protocol/) 传输回调试器而诞生。 +[@hippy/debug-server-next](//www.npmjs.com/package/@hippy/debug-server-next) 就是为了解决调试模式下终端模式获取调试用 JS 文件,以及将 [Chrome DevTools Protocol](//chromedevtools.github.io/devtools-protocol/) 传输回调试器而诞生。 # 项目初始化 1. 运行 `git clone https://github.com/Tencent/Hippy.git` - !> Hippy 仓库使用 [git-lfs](https://git-lfs.github.com/) 来管理 so,gz,otf,png,jpg 文件, 请确保你已经安装 [git-lfs](https://git-lfs.github.com/)。 + !> Hippy 仓库使用 [git-lfs](https://git-lfs.github.com/) 来管理 so, gz, otf, png, jpg 文件, 请确保你已经安装 [git-lfs](https://git-lfs.github.com/)。 2. 项目根目录运行命令 `npm install` 安装前端依赖。 -3. 项目根目录运行命令 `lerna bootstrap` 安装前端每一个 package 依赖。(Hippy 采用 [Lerna](https://lerna.js.org/) 管理多JS仓库,如果出现 `lerna command is not found`, 先执行 `npm install lerna -g`) +3. 项目根目录运行命令 `npx lerna bootstrap` 安装前端每一个 package 依赖。(Hippy 采用 [Lerna](https://lerna.js.org/) 管理多JS仓库) 4. 项目根目录运行命令 `npm run build` 编译前端 SDK 包。 -5. 选择一个前端范例项目来进行编译,项目根目录运行 `npm run buildexample -- [hippy-react-demo|hippy-vue-demo]`。 +5. 选择一个前端范例项目来进行编译,项目根目录运行 `npm run buildexample [hippy-react-demo|hippy-vue-demo]`。 # 终端环境准备 -我们推荐在终端代码中留一个后门,通过一定条件触发后进入调试模式,具体代码可以参考 [iOS](//github.com/Tencent/Hippy/blob/master/examples/ios-demo/HippyDemo/TestModule.m#L36) 和 [Android](//github.com/Tencent/Hippy/blob/master/examples/android-demo/example/src/main/java/com/tencent/mtt/hippy/example/module/TestModule.java#L31),这里实现了一个 `TestModule`,当前端调用它的 `debug` 方法时就会进入调试模式,而终端可以通过其它方式进入。 +我们推荐在终端代码中留一个后门,通过一定条件触发后进入调试模式,具体代码可以参考 [iOS](//github.com/Tencent/Hippy/blob/master/examples/ios-demo/HippyDemo/TestModule.m#L60) 和 [Android](//github.com/Tencent/Hippy/blob/master/examples/android-demo/example/src/main/java/com/tencent/mtt/hippy/example/module/TestModule.java#L31),这里实现了一个 `TestModule`,当前端调用它的 `debug` 或 `remoteDebug` 方法时就会进入调试模式,而终端可以通过其它方式进入。终端打开 Hippy Debug 页面代码如下: + +1. **Android**: + + ```java + // 初始化 hippy 引擎 + HippyEngine.EngineInitParams initParams = new HippyEngine.EngineInitParams(); + // 可选:是否设置为 debug 模式,默认为 DebugMode.None。设置 DebugMode.Dev 为调试模式,所有 jsbundle 都将从 debug server 上下载 + initParams.debugMode = HippyEngine.DebugMode.Dev; // 2.16.0 以下版本 debugMode 使用 boolean 类型 => initParams.debugMode = true; + initParams.debugServerHost = "localhost:38989"; + // 可选参数 Hippy Server 的 jsbundle 名字,默认为 "index.bundle"。debugMode = DebugMode.Dev 时有效 + initParams.debugBundleName = "index.bundle"; + ``` + +2. **iOS**: + + ```objective-c + - (void)viewDidLoad { + // 开启调试 + NSDictionary *launchOptions = @{@"DebugMode": @(YES)}; + // 使用默认 http://localhost:38989/index.bundle + NSString *bundleStr = [HippyBundleURLProvider sharedInstance].bundleURLString; + NSURL *bundleUrl = [NSURL URLWithString:bundleStr]; + HippyBridge *bridge = [[HippyBridge alloc] initWithDelegate:self + bundleURL:bundleUrl + moduleProvider:nil + launchOptions:launchOptions + executorKey:@"Demo"]; + } + + + - (BOOL)shouldStartInspector:(HippyBridge *)bridge { + return bridge.debugMode; + } + + - (NSURL *)inspectorSourceURLForBridge:(HippyBridge *)bridge { + return bridge.bundleURL; + } + ``` + +# 前端环境准备 + +1. 安装新一代调试工具: `npm i -D @hippy/debug-server-next@latest` +2. 修改 Webpack 配置,添加调试服务地址,默认为 `http://localhost:38989` + + ```javascript + module.exports = { + devServer: { + // 调试服务地址 + remote: { + protocol: 'http', + // iOS 真机调试时配置为本地局域网 IP + // iOS 模拟器和安卓真机调试时配置为 localhost + // 远程调试时配置为远程服务器 IP 或域名 + host: '192.168.1.100', + // 调试服务端口 + port: 38989, + }, + // 默认为 false,设为 true 调试服务支持多个工程同时调试,彼此之间不会干扰 + multiple: false, + // 默认为 false,hippy vue 项目可以手动开启 + vueDevtools: false, + // 默认 hot, liveReload 都为 true,如果只想使用 live-reload 功能,请将 hot 设为 false,liveReload 设为 true + hot: true, + liveReload: true, + client: { + overlay: false, + }, + }, + // ... other config + } + ``` + +3. 修改 `package.json` 中的启动编译命令。如果业务通过自定义 cli 启动,参考 [打包编译 API](#debug-server-api) 进行配置 + + ```json + { + "scripts": { + // -c 或 --config 提供 webpack config 配置路径 + "hippy:dev": "node ./scripts/env-polyfill.js hippy-dev -c ./scripts/hippy-webpack.dev.js" + } + } + ``` + + !> Node 17+ 不再支持 `md4` hash,此处为了兼容 Webpack 的 hash 算法,暂时通过 `env-polyfill.js` 脚本判断环境来解决,若出现错误将 `node ./scripts/env-polyfill.js` 移除即可 + +4. 运行 `npm run hippy:dev` 启动编译并按需开启用于 `HMR` 和 `Live-Reload` 的 Dev Server,编译结束后打印出 bundleUrl 和调试首页地址 + + hippy dev 输出 + +5. 粘贴 bundleUrl 并点击开始按钮 + + iOS 远程调试配置 + +6. 使用调试器开始调试 + - Safari DevTools:在 Mac 上打开 Safari 的开发菜单(`预置` -> `高级` -> `显示开发菜单`),然后按下图指引开始调试。Safari 调试器支持 iOS 设备,支持 `HMR & Live-Reload, Log, Sources, Memory` 等能力。 + + Safari 调试器 + + - Chrome DevTools:访问第 4 步打印的调试首页地址开始调试。Chrome 调试器支持 Android & iOS 设备,支持 `HMR & Live-Reload, Elements, Log, Sources, Memory` 等能力。 + + Chrome 调试器 + + 如果不使用我们的调试主页,也可以主动在 `chrome://inspect` 打开 DevTools,首先确保 `Discover USB devices` 的复选框呈`未选中状态`,然后确保 `Discover network targets` 选中,并在右侧 `Configure` 按钮的弹窗中包含了 `localhost:38989` 调试服务地址,下方的 `Remote Target` 中应该会出现 `Hippy debug tools for V8` 字样,点击下方的 `inspect` 链接即可打开 Chrome 调试器。 + + ![Chrome inspect](../assets/img/chrome-inspect-process.png) # 调试 Javascript @@ -29,107 +136,554 @@ Hippy中运行的JS代码可以来源于本地文件(local file),或者远程 这里仅以官方范例为准,讲述如何进行调试。 -!> 需要注意的是:官方范例为测试最新功能,将 `@hippy/react` 和 `@hippy/vue` 做了个 [alias 到 packages 目录](https://github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/scripts/hippy-webpack.dev.js#L76),如果调试官方范例,需要先在 Hippy 项目根目录下使用 ```npm run build``` 编译前端 SDK;或者删除范例的 `scripts` 目录中对 packages 的 alias,Hippy-Vue 范例则需要将 `vue` 和 `vue-router` 分别映射到 `@hippy/vue` 和 `@hippy/vue-router` +!> 需要注意的是:官方范例为应用最新功能,将 `@hippy/react` 和 `@hippy/vue` 做了 [alias 到 packages 目录](https://github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/scripts/hippy-webpack.dev.js#L76),如果调试官方范例,需要先在 Hippy 项目根目录下运行 ```npm run build``` 编译前端 SDK;或者删除范例的 `scripts` 目录中对 packages 的 alias。 ## iOS -因为 Hippy 需要经过网络传输调试协议,我们建议使用 iOS 模拟器进行调试,真机上虽然也可以但会要求手机和开发机在同一个网络内,并且需要在手机中配置连接获取开发机上的调试服务。 +iOS 调试支持模拟器和真机两种方式,由于 JSBundle 和调试协议依赖网络传输,真机调试时需要确保手机与开发机在同一个局域网内,因此我们推荐使用模拟器调试。 -同时,本方法也可以用 Safari 进行内置包(不连接开发机上的调试 JS 包)的调试。 - -具体流程: +### 模拟器调试 1. 点击 [Xcode on Mac AppStore](//apps.apple.com/cn/app/xcode/id497799835?l=en&mt=12) 下载安装 Xcode。 2. 使用 Xcode 打开[Hippy iOS 范例工程](//github.com/Tencent/Hippy/tree/master/examples/ios-demo) 中的 `HippyDemo.xcodeproj` 工程文件,并点击运行,正常情况下应该可以启动模拟器,并运行之前内置的 Hippy 前端代码。 -3. 打开 `examples` 下的前端范例工程 [hippy-react-demo](//github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo) 或者 [hippy-vue-demo](//github.com/Tencent/Hippy/tree/master/examples/hippy-vue-demo),通过 `npm i` 安装完依赖之后,使用 `npm run hippy:dev` 启动编译,并另开一个终端窗口,运行 `npm run hippy:debug` 启动调试服务。 -4. 回到模拟器,点击前端工程中的调试按钮,即可进入调试状态。hippy-react 有一个单独的页面,hippy-vue 在右上角。以 hippy-react 为例: +3. 打开 `examples` 下的前端范例工程 [hippy-react-demo](//github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo) 或者 [hippy-vue-demo](//github.com/Tencent/Hippy/tree/master/examples/hippy-vue-demo),通过 `npm i` 安装完依赖之后,使用 `npm run hippy:dev` 启动编译和调试服务。 +4. 回到模拟器,[粘贴 bundleUrl](guide/debug.md#config-bundle) 并启动调试 +5. 当 JS 源码文件发生改动时,如已开启 HMR 或 Live-Reload,编译结束后会自动刷新;否则需要按 `Command + R` 或 `Command + D` 键调起 Reload 面板刷新 - ![iOS 模拟器](//puui.qpic.cn/vupload/0/1577796352672_tmjp70r3bma.png/0) +> 如果 `Command + D` 无法调起面板,可以点击 `Device` -> `Shake` 强制调起 Reload 面板 -5. 打开 Safari,首先确保 `预置` -> `高级` -> `显示开发菜单` 正常勾上。 -6. 然后按图打开 Safari 调试器即可开始调试工作。 - - Safari 调试器 +### 真机调试 -7. 当 JS 文件发生改动时,自动编译会执行,但是终端却无法获知 JS 文件已经发生改变,需要通过按 `Command + R`刷新 或者 `Command + D` 键调起 Reload 面板刷新 +1. **iOS 真机调试只支持 XCode 编译的 App,并且 iOS 设备上需要开启 JavaScript 调试和 Web 检查器选项** -> 如果 `Command + D` 无法调起面板,可以点击 `Device` -> `Shake` 强制调起 Reload 面板 + safari 调试设置 + +2. 确保 iOS 设备和调试服务处于同一局域网内 +3. 编译 App,[粘贴 bundleUrl](guide/debug.md#config-bundle) 并启动调试 -### 远程调试 - -默认情况下,iOS使用本地服务地址进行代码调试。Hippy客户端从服务器地址获取JS代码并运行Hippy业务。但是用户可能遇到真机调试的问题,这就需要真机连接远程地址。 -在`TestModuel.m`文件中,打开`REMOTEDEBUG`宏,并填入Hippy服务地址与业务名称,即可实现远程调试。 +!> 注意:真机调试时必须保证开发机和手机处于同一局域网内,否则会加载 JSBundle 失败。以下两种情况都不是同一局域网:
+  (a) 开发机和手机分别连接不同的网络环境;
+  (b) 开发机连接网线,手机连接 WiFi。 ## Android -Android 使用了 [adb](//developer.android.com/studio/command-line/adb) 的端口映射功能,解决了真机到开发机通讯问题,反而因为 ARM 模拟器运行效率问题,更加推荐使用真机进行调试。 +Android 使用了 [adb](//developer.android.com/studio/command-line/adb) 的端口映射功能,解决了真机到开发机的通讯问题。由于 ARM 模拟器运行效率问题,更加推荐使用真机进行调试。 具体流程: -1. 下载安装 [Android Studio](//developer.android.com/studio) (可能需要翻墙,也可以通过其它途径下载)。 -2. 通过 Android Studio 打开[Hippy Android 范例工程](//github.com/Tencent/Hippy/tree/master/examples/android-demo),当提示 ToolChain 需要更新时全部选择拒绝,安装好 SDK、NDK、和 cmake 3.6.4。 -3. 通过数据线插上 Android 手机,并在 Android Studio 中点击运行,正常情况下手机应该已经运行起 `Hippy Demo` app。*编译如果出现问题请参考 [#39](//github.com/Tencent/Hippy/issues/39)*。 +1. 下载安装 [Android Studio](//developer.android.com/studio)。 +2. 通过 Android Studio 直接打开 Hippy 项目根目录,即可加载 [Hippy Android 范例工程](//github.com/Tencent/Hippy/tree/master/examples/android-demo) +3. 通过数据线插上 Android 手机,并在 Android Studio 中点击运行,正常情况下手机应该已经运行起 `Hippy Demo` app。 4. 回到手机上,首先确保手机的 `USB 调试模式` 已经打开 -- 一般在关于手机页面里连续点击 `Build` 可以进入`开发者模式`,再进入`开发者模式`界面后打开 `USB 调试模式`。 5. 执行 `adb reverse --remove-all && adb reverse tcp:38989 tcp:38989` 确保 38389 端口不被占用。 -6. 打开前端范例工程 [hippy-react-demo](//github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo) 或者 [hippy-vue-demo](//github.com/Tencent/Hippy/tree/master/examples/hippy-vue-demo),通过 `npm i` 安装完依赖之后,使用 `npm run hippy:dev` 启动编译,并另开一个终端窗口,运行 `npm run hippy:debug` 启动调试服务。 -7. 回到手机上,点击前端工程中的调试按钮,即可进入调试状态。hippy-react 有一个单独的页面,hippy-vue 在右上角。以 hippy-react 为例: +6. 打开前端范例工程 [hippy-react-demo](//github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo) 或者 [hippy-vue-demo](//github.com/Tencent/Hippy/tree/master/examples/hippy-vue-demo),通过 `npm i` 安装完依赖之后,使用 `npm run hippy:dev` 启动编译和调试服务。 +7. 回到手机上,[粘贴 bundleUrl](guide/debug.md#config-bundle) 并启动调试 +8. 当 JS 源码文件发生改动时,如已开启 HMR 或 Live-Reload,编译结束后会自动刷新;否则需要按 `Command + R` 或 `Command + D` 键调起 Reload 面板刷新 + +# Elements 可视化审查 + +> Android SDK 最低支持版本 2.9.0
+> iOS SDK 最低支持版本 2.11.5 - Android 调试 +Hippy 实现了节点和属性从前端到终端的映射,可以在 Chrome DevTools 上进行 Elements 的可视化检查。 -8. 然后打开 [Chrome](//www.google.com/chrome/),输入 `chrome://inspect`,首先确保 `Discover USB devices` 的复选框呈未选中状态,然后确保 `Discover network targets` 选中,并在右侧 `Configure` 按钮的弹窗中包含了 `localhost:38989` 调试服务地址,下方的 `Remote Target` 中应该会出现 `Hippy debug tools for V8` 字样,点击下方的 `inspect` 链接即可打开 Chrome 调试器。 + +
+
- ![Chrome inspect](//puui.qpic.cn/vupload/0/1577798490075_9tezu60gzzo.png/0) +# 操作手机界面能力 -9. 当 JS 文件发生改动时,自动编译会执行,但是终端却无法获知 JS 文件已经发生改变,点击界面上的`小圆点`,选择弹出菜单中的 `Reload` 重新加载 JS 代码。 +> Android SDK 最低支持版本 2.16.0 -# Elements 可视化审查 +Hippy 实现了 `Chrome DevTools Protocol` 协议的 `Input` 接口,可以在 Chrome DevTools 上直接操作手机界面,调试时无需来回切换。 + + -> Android 最低支持版本 2.9.0 -Hippy 实现了节点和属性从前端到终端的映射,可以在 Chrome Inspector 上进行 Elements 的可视化检查。 +# HMR & Live-Reload 能力 -Inspect Elements +> 最低支持版本 2.12.0 + +[hippy-react-demo 配置脚本](//github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/scripts/hippy-webpack.dev.js) + +[hippy-vue-demo 配置脚本](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/scripts/hippy-webpack.dev.js) + +HMR preview

-# Live-Reload 能力 +!> 请勿在生产环境引入 HMR 相关的配置 + +当开发者修改了前端代码后,我们可以通过 `Hot Module Replacement (HMR)` 保留状态刷新组件视图,或通过 `live-reload` 重载业务实例,步骤如下: + +## Hippy-Vue + +1. 安装热更新依赖 + + ```bash + npm i @hippy/vue@^2.12.0 + npm i -D @hippy/debug-server-next@latest @hippy/vue-loader @hippy/vue-css-loader + ``` + +2. webpack 配置示例 + + ```javascript + const VueLoaderPlugin = require('@hippy/vue-loader/lib/plugin'); + const vueLoader = '@hippy/vue-loader'; + + module.exports = { + devServer: { + // 默认 hot, liveReload 都为 true,如果只想使用 live-reload 功能,请将 hot 设为 false,liveReload 设为 true + hot: true, + liveReload: true, + client: { + // 暂不支持错误提示蒙层 + overlay: false, + }, + }, + plugins: [ + new VueLoaderPlugin(), + // add other plugin ... + ], + module: { + rules: [ + { + test: /\.vue$/, + use: [ + vueLoader, + ], + }, + ], + // add other loaders ... + } + } + ``` + +3. package.json 配置: + + ```json + { + "scripts": { + // -c 或 --config 提供 webpack config 配置路径 + "hippy:dev": "node ./scripts/env-polyfill.js hippy-dev -c ./scripts/hippy-webpack.dev.js" + } + } + ``` + +4. 启动开发:`npm run hippy:dev` + +5. **如果安卓设备断连,需要手动用 adb 转发端口:**`adb reverse tcp:38989 tcp:38989`。 + +6. iOS 的热更新:iOS 设备需要代理到开发机上,或处于同一网段,才能使用 HMR 能力。Webpack 配置修改如下所示,对于模拟器,本就和开发机处于同一网段,IP 写 `localhost` 就能访问到。 + + ```javascript + module.exports = { + devServer: { + host: '', + }, + } + ``` + +## Hippy-React + +1. 安装热更新依赖 + + ```bash + npm i @hippy/react@^2.12.0 + npm i -D @hippy/debug-server-next@latest @hippy/hippy-react-refresh-webpack-plugin react-refresh + ``` + +2. webpack 配置示例 + + ```javascript + const ReactRefreshWebpackPlugin = require('@hippy/hippy-react-refresh-webpack-plugin'); + + module.exports = { + devServer: { + // 默认 hot, liveReload 都为 true,如果只想使用 live-reload 功能,请将 hot 设为 false,liveReload 设为 true + hot: true, + liveReload: true, + client: { + // 暂不支持错误提示蒙层 + overlay: false, + }, + }, + plugins: [ + new ReactRefreshWebpackPlugin({ + // 暂不支持错误提示蒙层 + overlay: false, + }), + ], + module: { + rules: [ + { + test: /\.(jsx?)$/, + use: [ + { + loader: 'babel-loader', + options: { + sourceType: 'unambiguous', + presets: [ + '@babel/preset-react', + [ + '@babel/preset-env', + { + targets: { + chrome: 57, + ios: 9, + }, + }, + ], + ], + plugins: [ + ['@babel/plugin-proposal-class-properties'], + ['@babel/plugin-proposal-decorators', { legacy: true }], + ['@babel/plugin-transform-runtime', { regenerator: true }], + // add react-refresh babel plugin + require.resolve('react-refresh/babel'), + ], + }, + }, + ], + }, + // other loader ... + ], + }, + }; + ``` + +3. package.json 配置: + + ```json + { + "scripts": { + // -c 或 --config 提供 webpack config 配置路径 + "hippy:dev": "node ./scripts/env-polyfill.js hippy-dev -c ./scripts/hippy-webpack.dev.js" + } + } + ``` + +4. 执行 `npm run hippy:dev` 命令。 + +5. **如果安卓设备断连,需要手动用adb转发端口:** `adb reverse tcp:38989 tcp:38989`。 + +6. iOS的热更新:iOS 设备需要代理到开发机上,或处于同一网段,才能使用 HMR 能力。Webpack 配置修改如下所示,对于模拟器,本就和开发机处于同一网段,IP 写 `localhost` 就能访问到。 + + ```javascript + module.exports = { + devServer: { + host: '', + }, + } + ``` + +# Vue Devtools + +> 最低支持版本 2.13.7 + +支持调试 Vue 组件树、组件状态、路由、store、以及事件性能等 + + + +使用配置: + +1. 安装 vue devtools 依赖: + + ```bash + npm i @hippy/vue@^2.13.7 @hippy/vue-router@^2.13.7 + npm i @hippy/debug-server-next@latest -D + ``` + +2. 开启 vue devtools + + ```js + module.exports = { + devServer: { + remote: { + protocol: 'https', + host: 'devtools.qq.com', + port: 443, + }, + // 默认为 false,开启后将通过 remote 字段指定的远程调试服务分发 vue 调试指令 + vueDevtools: true + }, + } + ``` + +!> Vue Devtools 的配置会在业务运行时中注入调试代码,可能会有一定的性能影响,请勿在生产环境引入。 + +# React Devtools + +> 客户端最低支持版本 2.13.7
+> 前端最低支持版本 2.14.0 + +支持调试 React 组件树、组件状态、路由以及性能等 + + + +使用配置: + +1. 安装 react devtools 依赖: + + ```bash + npm i @hippy/react@^2.14.0 + npm i @hippy/debug-server-next@latest -D + ``` + +2. 开启 react devtools + + ```js + module.exports = { + devServer: { + remote: { + protocol: 'https', + host: 'devtools.qq.com', + port: 443, + }, + // 默认为 false,开启后将通过 remote 字段指定的远程调试服务分发 react 调试指令 + reactDevtools: true + }, + module: { + rules: [ + { + test: /\.(jsx?)$/, + // 必须添加下面这一行,让 babel 忽略 react-devtools 插件 + exclude: /@hippy\/hippy-react-devtools-plugin/, + use: [ + { + loader: 'babel-loader', + options: { + sourceType: 'unambiguous', + presets: [ + '@babel/preset-react', + [ + '@babel/preset-env', + { + targets: { + chrome: 57, + ios: 9, + }, + }, + ], + ], + plugins: [ + ['@babel/plugin-proposal-class-properties'], + ['@babel/plugin-proposal-decorators', { legacy: true }], + ['@babel/plugin-transform-runtime', { regenerator: true }], + require.resolve('react-refresh/babel'), + ], + }, + }, + ], + }, + ], + }, + } + ``` + +!> React Devtools 的配置会在业务运行时中注入调试代码,可能会有一定的性能影响,请勿在生产环境引入。 + +# 打包编译 API + +`@hippy/debug-server-next` 除了提供 bin 命令 `hippy-debug` 和 `hippy-dev` 进行调试构建,还提供了接口供自定义的 CLI 工具封装时调用,使用方法如下: + +```javascript +const { webpack } = require('@hippy/debug-server-next'); + +// 开始 webpack 编译,支持 HMR 等能力 +webpack(webpackConfig, (err, stats) => { + // 处理 webpack 打包回调信息 +}); +``` -> 最低支持版本 2.9.0 +# 远程调试 -当开发者修改了前端代码后,我们可以通过 `live-reload` 能力帮助我们在代码变更时自动重载业务实例,步骤如下: +远程调试目前已支持两种模式,【开发环境远程调试】模式和【生产环境远程调试】模式 -+ webpack 配置文件在 entry 末端引入 `@hippy/hippy-live-reload-polyfill` +- 【开发环境远程调试】模式需要加载远程 bundle 包进行调试,适用于开发场景 +- 【生产环境远程调试】模式使用本地缓存 bundle 包替代远程 bundle 包,更适用于线上环境,无此需求的也可使用上述【开发环境远程调试】模式 -```javascript -module.exports = { - mode: 'development', - entry: { - index: ['regenerator-runtime', 'index.js', '@hippy/hippy-live-reload-polyfill'], +## 开发环境远程调试 + +本地调试存在两个痛点: + + 1. 无法覆盖所有机型,测试反馈的问题难以定位; + 2. 无法摆脱数据线的束缚。 + +那么这些场景我们可以考虑使用远程调试,效果预览: + + + +### 前端接入配置 + +1. 安装新一代调试工具: `npm i -D @hippy/debug-server-next@latest` + +2. 修改 Webpack 配置,添加 `remote` 字段来配置编译产物上传地址和调试服务地址(默认为 )。考虑到安全因素,官方不提供公网的远程调试服务,你需要自己[私有化部署](https://github.com/hippy-contrib/debug-server-next/blob/main/doc/deploy.md)。 + + ```js + module.exports = { + devServer: { + // 远程调试地址,需要配置为你的私有化调试服务地址 + remote: { + protocol: 'https', + host: 'devtools.qq.com', + port: 443, + // 配置宿主 App 扫码加载的 scheme,如无需扫码,可不配置 + qrcode: (bundleUrl) => { + // 必须指定业务 bundleName + const bundleName = 'QQGroupGameRank'; + return `mqqapi://hippy/remoteDebug?bundleName=${bundleName}&bundleUrl=${encodeURIComponent(bundleUrl)}`; + } + }, + client: { + overlay: false, + }, + }, + // other config ... + } + ``` + + !> 配置远程调试时编译产物将上传远端,并在调试结束后删除。为以防万一,请确保代码中不含敏感信息(如密钥等)。 + + !> 远程调试时,`publicPath` 将被设为 `${protocol}://${host}:${port}//`,以区分不同的业务。 + + !> [业务加载远程 js bundle 分包时](guide/dynamic-import.md#remote-bundle),如未配置 `customChunkPath`,将默认使用 `publicPath` 的地址,请确保远程分包也同时上传到调试服务器。 + +3. 启动编译:`npm run hippy:dev`,编译结束后将打印调试信息: + + + + 其中打印三个字段表示: + + - bundleUrl:远程调试的 JSBundle 地址,填入宿主 App 接入的 `remoteServerUrl` 字段中 + - debug page:PC 端调试首页 + - bundleUrl scheme:宿主 App 扫码的 scheme + +### 宿主 App 接入配置 + +宿主 App 设置 debugMode 为 DebugMode.Dev,并把前端 webpack 生成远程无线调试的 bundleUrl 传入,推荐宿主使用输入框或扫描二维码的方式传入。 + +1. **Android**: + + ```java + // 初始化 hippy 引擎 + HippyEngine.EngineInitParams initParams = new HippyEngine.EngineInitParams(); + initParams.debugMode = HippyEngine.DebugMode.Dev; // 2.16.0 以下版本 debugMode 使用 boolean 类型 => initParams.debugMode = true; + initParams.remoteServerUrl = ""; // 远程调试 bundleUrl + ``` + +2. **iOS**: + + ```objective-c + - (void)viewDidLoad { + // 开启调试 + NSDictionary *launchOptions = @{@"DebugMode": @(YES)}; + NSString *bundleStr = ""; // 远程调试 bundleUrl + NSURL *bundleUrl = [NSURL URLWithString:bundleStr]; + HippyBridge *bridge = [[HippyBridge alloc] initWithDelegate:self + bundleURL:bundleUrl + moduleProvider:nil + launchOptions:launchOptions + executorKey:@"Demo"]; + } + + - (BOOL)shouldStartInspector:(HippyBridge *)bridge { + return bridge.debugMode; + } + + - (NSURL *)inspectorSourceURLForBridge:(HippyBridge *)bridge { + return bridge.bundleURL; + } + ``` + +## 生产环境远程调试 + +生产环境下开发者可直接对本地 bundle 进行调试,还原线上用户真实运行的环境,提高问题定位的效率 + +### 前端接入 + +无相关调整 + +### 宿主 App 接入 + +1. **Android**: + +> 最低支持版本 2.16.0 + +安卓对于 debugMode 进行了改造,原本的 boolean 类型改为了枚举类型 + +```java + public enum DebugMode { + None, // 生产环境 + Dev, // 开发环境 + UserLocal, // 生产环境下开启远程调试,直接使用本地包发起调试 } -} ``` -+ 启动调试 server 增加 `--live` 参数,这时候会启动一个 `38999` 端口的 Websocket +- **第一步**: -```json -{ - "scripts": { - "hippy:debug": "hippy-debug --live" - } -} -``` + ```java + // 初始化 hippy 引擎 + HippyEngine.EngineInitParams initParams = new HippyEngine.EngineInitParams(); + initParams.debugMode = HippyEngine.DebugMode.UserLocal; // 外网调试选项开启 + ``` -+ 对于 Android 调试,打开 `Enable Live Reload` 开关,(`2.9.1` 版本后业务代码启动后会自动监听,无需再用该开关) +- **第二步**: + 由于不少业务场景下引擎启动与业务分离,因此我们将调试选项开启和 debug server 连接两个时机分开,使用上更加灵活,局限性小 -Android Debug -
+ ```java + // 在业务启动前后发起 debug server 连接 + mHippyEngine.getEngineContext().getBridgeManager().connectDebugUrl("ws://${ip}:${port}/debugger-proxy"); + mHippyView = mHippyEngine.loadModule(loadParams, new HippyEngine.ModuleListener() { + @Override + public void onLoadCompleted(ModuleLoadStatus statusCode, String msg, HippyRootView hippyRootView) { + if (statusCode != ModuleLoadStatus.STATUS_OK) { + LogUtils.e("MyActivity", "loadModule failed code:" + statusCode + ", msg=" + msg); + } + } + + @Override + public boolean onJsException(HippyJsException exception) { + return true; + } + }); + ``` + +1. **iOS** + +(待支持) + +## 远程调试支持能力列表 -+ 对于 iOS 调试,业务代码启动后会自动监听 +> 最低支持版本 2.13.1 + +| 平台 | HMR & Live-Reload | React Devtools | Vue Devtools | Elements | Log | Sources | Memory | +| :-----: | :---------------: | :------------: | :----------: | :------: | :---: | :-----: | :----: | +| Android | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| iOS | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | + +
+
# 框架日志输出 -无论是 hippy-react 还是 hippy-vue 都将和终端通讯的信息进行输出,包含了前终端的节点操作、事件收发。这些日志对于业务调试其实很有帮助,可以让开发了解到前端框架是如何将代码转译成终端可以理解的语法,当遇到问题时应先检查框架通信日志,基本可以定位到大部分问题。 +无论是 hippy-react 还是 hippy-vue 都将和终端通讯的信息进行输出,包含了前终端的节点操作、事件收发。这些日志对于业务调试其实很有帮助,可以让开发了解到前端框架是如何将代码转译成终端可以理解的语法。当遇到问题时应先检查框架通信日志,基本可以定位到大部分问题。 -如果需要关闭日志,可以在 hippy-react 的 new Hippy 启动参数中增加 `silent: true`,或者 hippy-vue 项目的入口文件中,开启 `Vue.config.silent = true;`。 +如果需要关闭日志,可以在 hippy-react 的 `new Hippy` 启动参数中增加 `silent: true`,或者 hippy-vue 项目的入口文件中,开启 `Vue.config.silent = true;`,或者在 hippy-vue-next 项目的 `createApp` 初始化参数中增加 `silent: true`。 -Communication Info +Communication Info diff --git a/docs/guide/dynamic-import.md b/docs/guide/dynamic-import.md index a9fd3913b60..227e44a0685 100644 --- a/docs/guide/dynamic-import.md +++ b/docs/guide/dynamic-import.md @@ -1,33 +1,32 @@ - - - # 动态加载 -## 介绍 +--- + +# 介绍 Hippy 2.2 版本之前只支持加载单个 js bundle 文件。随着业务越来越复杂,单个 js 文件体积愈发增加的体积会影响首屏启动速度。为了解决这个问题,Hippy 在 2.2 版本增添了动态加载能力,开发人员可以按需来动态引入子 js bundle 文件。 `Hippy 最低版本支持 2.2` -## 原理架构 +# 原理架构 -![Communication Info](//m4.publicimg.browser.qq.com/publicimg/nav/hippydoc/dynamic_import.png) +![Communication Info](../assets/img/dynamic_import.png) -## 范例 +# 范例 [[React 范例]](//github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/src/externals/DyanmicImport/index.jsx) [[Vue 范例]](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/demos/demo-dynamicimport.vue) -## 使用方法 +# 使用方法 -### 安装 +## 安装 `npm install -D @hippy/hippy-dynamic-import-plugin` -### 使用 +## 使用 -在 [webpack 打包脚本](https://github.com/Tencent/Hippy/tree/master/examples/hippy-vue-demo/scripts) 中引入插件 +在 [Webpack 打包脚本](https://github.com/Tencent/Hippy/tree/master/examples/hippy-vue-demo/scripts) 中引入插件 ```javascript const HippyDynamicImportPlugin = require('@hippy/hippy-dynamic-import-plugin'); @@ -48,7 +47,7 @@ module.exports = { }; ``` -### 降级方案 +## 降级方案 在终端 SDK 不支持 dynamic import 的版本,可以使用以下两种方法阻止分包。 @@ -74,7 +73,7 @@ plugins: [ ], ``` -## 支持同时配置本地/网络加载分包 +# 可同时配置本地/网络加载分包 `网络加载 hippy sdk 最低支持版本 2.5.5` @@ -82,11 +81,11 @@ plugins: [ 提供以下几种模式 -### 业务只加载 [本地] js bundle +## 业务只加载 [本地] js bundle 与原有动态加载能力一样,直接使用 `import()` 语法即可 -### 业务只加载 [远程] js bundle +## 业务只加载 [远程] js bundle + webpack打包脚本配置全局 `publicPath`(可选) @@ -94,22 +93,22 @@ plugins: [ // webpack output 配置 output: { ... - publicPath: 'https://static.res.qq.com/hippy/hippyVueDemo/', + publicPath: 'https://xxxx/hippy/hippyVueDemo/', }, ``` -+ 在业务代码引用分包的入口配置 `magic comment`的 `webpackChunkName`(必须) 和 `customChunkPath`(可选),如果没有配置`customChunkPath`,会默认使用全局 `publicPath`; ++ 在业务代码引用分包的入口配置 `magic comment`的 `webpackChunkName`(必须) 和 `customChunkPath`(可选),如果没有配置`customChunkPath`,会默认使用全局 `publicPath`; 以 Hippy-Vue 为例: ```javascript // Hippy-Vue 配置, - AsyncComponentFromHttp: () => import(/* customChunkPath: "https://static.res.qq.com/hippy/hippyVueDemo/", webpackChunkName: "asyncComponentFromHttp" */'./dynamicImport/async-component-http.vue') + AsyncComponentFromHttp: () => import(/* customChunkPath: "https://xxx/hippy/hippyVueDemo/", webpackChunkName: "asyncComponentFromHttp" */'./dynamicImport/async-component-http.vue') .then(res => res) .catch(err => console.error('import async remote component error', err)) ``` -### 业务同时加载 [本地 + 远程] js bundle +## 业务同时加载 [本地 + 远程] js bundle + 去除 webpack 全局配置的 `publicPath`(publicPath 会强制在所有 bundle 前加上配置的路径,影响本地 bundle 加载) @@ -117,7 +116,7 @@ plugins: [ ```javascript // Hippy-Vue 配置 - AsyncComponentFromHttp: () => import(/* customChunkPath: "https://static.res.qq.com/hippy/hippyVueDemo/", webpackChunkName: "asyncComponentFromHttp" */'./dynamicImport/async-component-http.vue') + AsyncComponentFromHttp: () => import(/* customChunkPath: "https://xxx/hippy/hippyVueDemo/", webpackChunkName: "asyncComponentFromHttp" */'./dynamicImport/async-component-http.vue') .then(res => res) .catch(err => console.error('import async remote component error', err)) diff --git a/docs/guide/exception.md b/docs/guide/exception.md new file mode 100644 index 00000000000..6656a93f280 --- /dev/null +++ b/docs/guide/exception.md @@ -0,0 +1,60 @@ +# 异常捕获 + +--- + +# uncaughtException + +Hippy 对于 JS 代码里没有处理的异常错误,可以通过监听 `uncaughtException` 事件进行捕获。 + +```javascript + global.Hippy.on('uncaughtException', (...args) => { + // 此处可以做错误上报 + report('[uncaughtException]', ...args); + }); +``` + +# unhandledRejection + +当 Promise 被 reject 且没有 reject 处理器的时候,会触发 `unhandledRejection` 事件 + +!> 当前只能通过 js polyfill 的方式捕获 iOS(JSCore) Promise 的 `unhandledRejection` 错误,Android(V8) 暂不支持。 + +## iOS + +iOS 的 Promise 目前是采用 polyfill 的方式实现的,因此可以对 `unhandledRejection` 异常直接进行捕获。 + +由于 `rejection-tracking` 有一定的性能损耗,业务在有需要的时候才引入该插件。 + +> 最低支持版本 `2.14.1` + ++ `npm install -D @hippy/rejection-tracking-polyfill` + ++ 引入 `polyfill` + +```javascript + +// 可以在 iOS webpack config 配置 +module.exports = { + entry: { + // 注入 polyfill 代码 + index: ['@hippy/rejection-tracking-polyfill', 'dist/dev/index.js'] + }, +} + +// 也可以在业务代码里直接引入,以 hippy-react 举例 +import { Hippy } from '@hippy/react'; +import '@hippy/rejection-tracking-polyfill'; + +new Hippy({ + appName: 'Demo', + entryPage: App, +}).start(); +``` + ++ 监听错误 + +```javascript +global.Hippy.on('unhandledRejection', (reason) => { + console.error('unhandledRejection', reason); +}); +``` diff --git a/docs/guide/integration.md b/docs/guide/integration.md index df9f5aa8691..b6754be312d 100644 --- a/docs/guide/integration.md +++ b/docs/guide/integration.md @@ -1,24 +1,218 @@ # 开始接入 -Hippy 已经提供了完整的[前端和终端范例](//github.com/Tencent/Hippy/tree/master/examples),可直接基于我们现有的范例开始 App 开发。若想快速体验 Hippy,可按照 [README 步骤](https://github.com/Tencent/Hippy/blob/master/README.zh_CN.md#-%E5%BC%80%E5%A7%8B) 将 DEMO 运行起来 。 +Hippy 采用 `monorepo` 进行代码管理,多仓库 SDK 统一版本,前端可以直接引入对应的 NPM 包,终端可通过发布分支源码接入或通过对应的包管理仓库引入。 -如果要在已有的 App 里整合 Hippy,请继续阅读下面的`终端集成`章节。需要提醒的是,在 Hippy 体系下,终端开发转变为`平台开发`,前端开发变成`业务开发`,`平台开发`提供通用的能力,供`业务开发`完成实际交互。 +Demo代码位于examples目录,如果你对阅读代码更感兴趣,可直接[进入github查看](https//github.com/Tencent/Hippy/tree/master/examples) -# 终端集成 +--- -如果要接入 Hippy 到现有终端项目,请参考 [Android](android/integration.md) 和 [iOS](ios/integration.md) 集成教程。 +# 快速体验 -# 前端接入 +## 环境准备 + +### macOS开发环境 + +可以在macOS上开发iOS,Android应用,请求根据需要进行环境配置。 + +首先,通过Homebrew包管理工具安装git, git-lfs, node(v16) and npm(v7) + +```shell +brew install git git-lfs node@16 +``` + +#### 编译iOS Demo环境准备 + +1. Xcode + + 通过Apple App Store安装[Xcode](https://apps.apple.com/cn/app/xcode/id497799835?l=en-GB&mt=12) + +2. 通过gem命令安装Cocoapods + + `sudo gem install cocoapods` + + +#### 编译Android Demo环境准备 + +1. Android Studio + + 通过android开发者平台下载安装[Android Studio](https://developer.android.com/studio) + +2. Android NDK + + 通过android开发者平台下载安装[NDK](https://developer.android.com/ndk?hl=en) + +### Windows开发环境 + +可以Windows上开发Android应用,请安装以下依赖。 + +1. Android Studio + + 通过android开发者平台下载安装[Android Studio](https://developer.android.com/studio) + +2. Android NDK + + 通过android开发者平台下载安装[NDK](https://developer.android.com/ndk?hl=en) + +3. Git for Windows + + 通过(https://gitforwindows.org)下载安装Git for Windows + +4. Node和NPM + + 通过指引安装[nodejs和npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm),建议使用Node v16,NPM v7版本 + + +## 代码拉取 + +```shell +git clone --branch master https://github.com/Tencent/Hippy.git +``` + +!> Hippy 2.0主干代码位于master分支,Hippy 3.0主干代码位于main分支,本文适用于2.0,因此分支指定为master + +## 编译运行Demo + +以下基于macOS平台,分别说明如何编译Android和iOS Demo。 + +### 编译运行iOS Demo + +```shell +# 进入Hippy源码目录 +cd ./Hippy/examples/ios-demo +# 通过Cocoapods生成workspace +pod install +# 打开workspace,编译运行即可 +open HippyDemo.xcworkspace +``` + +### 编译运行Android Demo + +1. 使用 Android Studio 打开根目录 `Android Project` 项目。 +2. 使用 USB 线连接 Android 设备,并确保设备 USB 调试模式已经开启(电脑 Terminal 执行 `adb devices` 检查手机连接状态)。 +3. Android Studio 执行项目构建,并安装 APK运行。 -Hippy 同时支持 React 和 Vue 两种语法框架,通过 [@hippy/react](//www.npmjs.com/package/@hippy/react) 和 [@hippy/vue](//www.npmjs.com/package/@hippy/vue) 两个包提供实现。 +Demo运行起来后,可见效果类似 -> 从工程上我们依然称呼它们为 `hippy-react` 和 `hippy-vue`。 +Demo效果 + +## 动手尝试 + +如果你不满足于简单把Demo跑起来,还可以动手尝试修改前端代码,可以按照以下指引进行。 + +### 了解Demo项目代码 + +Demo项目位于examples目录 + +```shell +steven@STEVEN-MC Hippy % ls examples +total 0 +drwxr-xr-x 8 steven staff 256 Oct 30 14:53 android-demo +drwxr-xr-x 8 steven staff 256 Oct 30 14:53 hippy-react-demo +drwxr-xr-x 9 steven staff 288 Oct 30 14:53 hippy-vue-demo +drwxr-xr-x 11 steven staff 352 Oct 30 14:53 hippy-vue-next-demo +drwxr-xr-x 12 steven staff 384 Dec 6 20:24 ios-demo + +``` + +其中前端hippy-react-demo、hippy-vue-demo、hippy-vue-next-demo这3者为前端项目Demo,分别演示基于hippy-react、hippy-vue、hippy-vue-next开发项目。 +android-demo为Android Native工程代码,ios-demo为iOS Native工程代码,这两个是运行demo前端项目的容器。 + +### 修改前端工程 + +以hippy-react-demo为例,打开hippy-react-demo/src/app.jsx,编辑render()函数 + +```jsx +render() { + return ( + + + Hello World! + + + ); +} +``` + +### 编译修改后的前端工程 + +以编译hippy-react-demo为例,在Hippy**根目录**执行命令 + +```shell +npm run init + +# 该命令由 `npm install && npx lerna bootstrap && npm run build` 组成,你也可以分别执行这几个命令。 +# +# npm install: 安装项目所需的脚本依赖。 +# +# `npx lerna bootstrap`: 安装每一个 JS 包的依赖。(Hippy 使用 [Lerna](https://lerna.js.org/) 管理多个 js 包) +# +# `npm run build`: 构建每一个 JS SDK 包。 + +# 编译hippy-react-demo +npm run buildexample hippy-react-demo + +# 如果上一条命令有异常,可以执行以下命令 +cd examples/hippy-react-demo +npm install --legacy-peer-deps +cd ../.. +npm run buildexample hippy-react-demo +``` + +执行完后,构建产物将会被打包放到examples/hippy-react-demo/dist目录中,目录内容类似 + +```shell +examples/hippy-react-demo/dist +├── android +│ ├── assets +│ │ ├── defaultSource.jpg +│ │ └── hippyLogoWhite.png +│ ├── asyncComponentFromHttp.android.js +│ ├── asyncComponentFromLocal.android.js +│ ├── index.android.js +│ ├── vendor-manifest.json +│ └── vendor.android.js +└── ios + ├── assets + │ ├── defaultSource.jpg + │ └── hippyLogoWhite.png + ├── asyncComponentFromHttp.ios.js + ├── asyncComponentFromLocal.ios.js + ├── index.ios.js + ├── vendor-manifest.json + └── vendor.ios.js + +5 directories, 14 files +``` + +### 运行前端编译产物 + +把examples/hippy-react-demo/dist/ios目录内容整体拷贝到ios demo的res目录,当用Android来跑时拷贝到Android对应的目录。 + +```shell +cp -R examples/hippy-react-demo/dist/ios/* examples/ios-demo/res/ +cp -R examples/hippy-react-demo/dist/android/* examples/android-demo/res/ +``` + +接下来,按照[编译运行Demo](#编译运行demo)一节运行Demo。 +效果如图所示 + +Demo效果 + +恭喜你完成了Hippy的初步体验,下一步将Hippy接入到你现有的工程吧。 + +# 终端接入 + +如果要接入 Hippy 到现有终端项目,请参考 [Android 集成](android/integration.md) 和 [iOS 集成](ios/integration.md) 教程。 + +# 前端接入 + +Hippy 同时支持 React 和 Vue 两种 UI 框架,通过 [@hippy/react](//www.npmjs.com/package/@hippy/react) 和 [@hippy/vue](//www.npmjs.com/package/@hippy/vue) 两个包提供实现。 ## hippy-react [[hippy-react 介绍]](hippy-react/introduction.md) [[范例工程]](//github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo) -hippy-react 工程暂时只能通过手工配置初始化(后期会提供基于 [yeoman](//yeoman.io/) 的脚手架),建议直接 clone 范例工程并基于它进行修改。 +hippy-react 工程暂时只能通过手工配置初始化,建议直接 clone 范例工程并基于它进行修改。 当然,也可以从头开始进行配置。 @@ -64,7 +258,7 @@ hippy-react 工程暂时只能通过手工配置初始化(后期会提供基 当前 hippy-react 采用 `Webpack 4`构建,配置全部放置于 [scripts](//github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo/scripts) 目录下,其实只是 [webpack](//webpack.js.org/) 的配置文件,建议先阅读 [webpack](//webpack.js.org/) 官网内容,具备一定基础后再进行修改。 -#### hippy-react 终端开发调试用编译配置 +#### hippy-react 开发调试编译配置 该配置展示了将 Hippy 运行于终端的最小化配置。 @@ -72,12 +266,12 @@ hippy-react 工程暂时只能通过手工配置初始化(后期会提供基 | ------------------------------------------------------------ | ---------- | | [hippy-webpack.dev.js](//github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/scripts/hippy-webpack.dev.js) | 调试用配置 | -#### 终端线上包配置 +#### hippy-react 生产环境编译配置 -线上包和开发调试用包主要有两个区别: +生产环境和开发调试的包主要有两个区别: -1. 开启了 production 模式,去掉调试信息,关闭了 `watch`(watch 模式下会监听文件变动并重新打包)。 -2. 终端内很可能不止运行一个 Hippy 业务,所以将共享的部分单独拆出来做成了 `vendor` 包,这样可以有效减少业务包体积,这里使用了 [DllPlugin](//webpack.js.org/plugins/dll-plugin/) 和 [DllReferencePlugin](//webpack.js.org/plugins/dll-plugin/#dllreferenceplugin) 来实现。需要说明的是生成的 `vendor` 包正常情况下是不需要特别更新的,但是如果更新了也要注意一下向上兼容性,不要因为分包导致业务崩溃。 +1. 生产环境开启了 production 模式,去掉调试信息,关闭了 `watch`(watch 模式下会监听文件变动并重新打包)。 +2. 终端内很可能不止运行一个 Hippy 业务,所以将共享的部分单独拆出来做成了 `vendor` 包,这样可以有效减小业务包体积,这里使用了 [DllPlugin](//webpack.js.org/plugins/dll-plugin/) 和 [DllReferencePlugin](//webpack.js.org/plugins/dll-plugin/#dllreferenceplugin) 来实现。 | 配置文件 | 说明 | | ------------------------------------------------------------ | ----------------------------- | @@ -128,14 +322,13 @@ export default function app() { ### hippy-react npm 脚本 -最后在 [package.json](//github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/package.json#L13) 中补上几个快速的 npm 脚本就可以了,这里以 `hippy:`开头做好了范例,这里顺道做了一个到 [hippy-debug-server](//www.npmjs.com/package/hippy-debug-server) 的快速启动命令。 +在 [package.json](//github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/package.json#L13) 中提供了几个以 `hippy:`开头的 npm 脚本,可用来启动 [@hippy/debug-server-next](//www.npmjs.com/package/@hippy/debug-server-next) 等调试工具。 ```json "scripts": { - "hippy:debug": "hippy-debug --live", // live 参数会开启 live-reload 监听 - "hippy:dev": "webpack --config ./scripts/hippy-webpack.dev.js", - "hippy:vendor": "webpack --config ./scripts/hippy-webpack.ios-vendor.js --config ./scripts/hippy-webpack.android-vendor.js", - "hippy:build": "webpack --config ./scripts/hippy-webpack.ios.js --config ./scripts/hippy-webpack.android.js" + "hippy:dev": "node ./scripts/env-polyfill.js hippy-dev --config ./scripts/hippy-webpack.dev.js", + "hippy:vendor": "node ./scripts/env-polyfill.js webpack --config ./scripts/hippy-webpack.ios-vendor.js --config ./scripts/hippy-webpack.android-vendor.js", + "hippy:build": "node ./scripts/env-polyfill.js webpack --config ./scripts/hippy-webpack.ios.js --config ./scripts/hippy-webpack.android.js" } ``` @@ -188,9 +381,9 @@ hippy-vue 相对简单很多,hippy-vue 只是 [Vue](//vuejs.org) 在终端上 ### hippy-vue 编译配置 -当前 hippy-vue 采用 `Webpack 4`构建(暂时不建议升级到`Weppack 5`),配置全部放置于 [scripts](//github.com/Tencent/Hippy/tree/master/examples/hippy-vue-demo/scripts) 目录下,其实只是 [webpack](//webpack.js.org/) 的配置文件,建议先阅读 [webpack](//webpack.js.org/) 官网内容,具备一定基础后再进行修改。 +当前 hippy-vue 采用 `Webpack 4`构建,配置全部放置于 [scripts](//github.com/Tencent/Hippy/tree/master/examples/hippy-vue-demo/scripts) 目录下,其实只是 [webpack](//webpack.js.org/) 的配置文件,建议先阅读 [webpack](//webpack.js.org/) 官网内容,具备一定基础后再进行修改。 -#### hippy-vue 终端开发调试用编译配置 +#### hippy-vue 开发调试编译配置 该配置展示了将 Hippy 运行于终端的最小化配置。 @@ -198,12 +391,12 @@ hippy-vue 相对简单很多,hippy-vue 只是 [Vue](//vuejs.org) 在终端上 | ------------------------------------------------------------ | ---------- | | [hippy-webpack.dev.js](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/scripts/hippy-webpack.dev.js) | 调试用配置 | -#### hippy-vue 终端线上包配置 +#### hippy-vue 生产环境编译配置 线上包和开发调试用包主要有两个区别: 1. 开启了 production 模式,去掉调试信息,关闭了 `watch`(watch 模式下会监听文件变动并重新打包)。 -2. 终端内很可能不止运行一个 Hippy 业务,所以将共享的部分单独拆出来做成了 `vendor` 包,这样可以有效减少业务包体积,这里使用了 [DllPlugin](//webpack.js.org/plugins/dll-plugin/) 和 [DllReferencePlugin](//webpack.js.org/plugins/dll-plugin/#dllreferenceplugin) 来实现。需要说明的是生成的 `vendor` 包正常情况下是不需要特别更新的,但是如果更新了也要注意一下向上兼容性,不要因为分包导致业务崩溃。 +2. 终端内很可能不止运行一个 Hippy 业务,所以将共享的部分单独拆出来做成了 `vendor` 包,这样可以有效减小业务包体积,这里使用了 [DllPlugin](//webpack.js.org/plugins/dll-plugin/) 和 [DllReferencePlugin](//webpack.js.org/plugins/dll-plugin/#dllreferenceplugin) 来实现。 | 配置文件 | 说明 | | ------------------------------------------------------------ | ----------------------------- | @@ -292,16 +485,15 @@ app.$start((/* app */) => { setApp(app); ``` -### hippy-vue npm script +### hippy-vue npm 脚本 -最后在 [package.json](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/package.json#L13) 中补上几个快速的 npm 脚本就可以了,这里以 `hippy:`开头做好了范例,这里顺道做了一个到 [hippy-debug-server](//www.npmjs.com/package/hippy-debug-server) 的快速启动命令。 +在 [package.json](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/package.json#L13) 中提供了几个以 `hippy:`开头的 npm 脚本,可用来启动 [@hippy/debug-server-next](//www.npmjs.com/package/@hippy/debug-server-next) 等调试工具。 ```json "scripts": { - "hippy:debug": "hippy-debug --live", // live 参数会开启 live-reload 监听 - "hippy:dev": "webpack --config ./scripts/hippy-webpack.dev.js", - "hippy:vendor": "webpack --config ./scripts/hippy-webpack.ios-vendor.js --config ./scripts/hippy-webpack.android-vendor.js", - "hippy:build": "webpack --config ./scripts/hippy-webpack.ios.js --config ./scripts/hippy-webpack.android.js" + "hippy:dev": "node ./scripts/env-polyfill.js hippy-dev --config ./scripts/hippy-webpack.dev.js", + "hippy:vendor": "node ./scripts/env-polyfill.js webpack --config ./scripts/hippy-webpack.ios-vendor.js --config ./scripts/hippy-webpack.android-vendor.js", + "hippy:build": "node ./scripts/env-polyfill.js webpack --config ./scripts/hippy-webpack.ios.js --config ./scripts/hippy-webpack.android.js" }, ``` @@ -309,6 +501,3 @@ setApp(app); `@hippy/vue-router` 完整支持 vue-router 中的跳转功能,具体请参考 [hippy-vue-router](hippy-vue/router.md) 文档。 -### hippy-vue 转 Web - -hippy-vue 项目基于 vue-cli 生成的 Web 项目,之前的 Web 能力可以直接使用,对于使用 vue-cli 生产的项目,可以参考[官方文档](//cli.vuejs.org/zh/guide/build-targets.html)。 diff --git a/docs/guide/jsi.md b/docs/guide/jsi.md new file mode 100644 index 00000000000..6a21d44a03a --- /dev/null +++ b/docs/guide/jsi.md @@ -0,0 +1,173 @@ +# JSI 模式 + +> 最低支持版本 2.11.0 + +JavaScript Interface(JSI) 模式提供了一种无需经历编解码(序列化)过程的跨 VM (同步)互调用解决方案,使得 js 可以和 native 直接通信。传统互调用所传递的对象会全部序列化,但并非所有成员都被访问,在特定场景下导致了不必要的开销与冗余。通过 JSI,js 侧可以获取 C++ 定义的对象(HostObject),并调用该对象上的方法。 + +--- + +# 架构图 + +
+jsi架构图 +
+
+ +# 不适用场景 + +JSI 并非适用于所有场景: + +* 所需读取的成员占比越少,JSI 表现出的性能越优异。 +* 随着所需读取的成员占比上升,JNI 调用次数的增加,所累计的耗时也随之上涨,反而不如编解码实现性能优异。 +* 同步调用简化了编码,耗时更稳定,但会阻塞 JS 执行,不适用于复杂逻辑。 + +# 接入说明 + +## Android + +* 通过设置引擎初始化参数开启JSI能力 + +```java + HippyEngine.EngineInitParams initParams = new HippyEngine.EngineInitParams(); + initParams.enableTurbo = true; +``` + +* 定义Module + +> 跟普通NativeModule类似,区别在于需要添加以下注解表明是同步调用 `@HippyMethod(isSync = true)` + +```java +@HippyNativeModule(name = "demoTurbo") +public class DemoJavaTurboModule extends HippyNativeModuleBase { + + ... + @HippyMethod(isSync = true) + public double getNum(double num) { + return num; + } + ... +} +``` + +> 支持的数据类型说明: + +
+数据类型 +
+
+ +更多示例可参考类[DemoJavaTurboModule](https://github.com/Tencent/Hippy/blob/master/examples/android-demo/example/src/main/java/com/tencent/mtt/hippy/example/module/turbo/DemoJavaTurboModule.java) + +* 注册TurboModule模块,跟NativeModule注册方法完全一致 + +```java +public class MyAPIProvider implements HippyAPIProvider { + + @Override + public Map, Provider> getNativeModules(final HippyEngineContext context) { + Map, Provider> modules = new HashMap<>(); + ... + modules.put(DemoJavaTurboModule.class, new Provider() { + @Override + public HippyNativeModuleBase get() { + return new DemoJavaTurboModule(context); + } + }); + ... + return modules; + } +``` + +## iOS + +* 通过设置引擎初始化参数开启JSI能力 +iOS有两种方式去打开关闭enableTurbo能力,如下: + +```objc +// 方式一:bridge初始化时通过配置参数设置生效 +NSDictionary *launchOptions = @{@"EnableTurbo": @(DEMO_ENABLE_TURBO)}; +HippyBridge *bridge = [[HippyBridge alloc] initWithDelegate:self + bundleURL:[NSURL fileURLWithPath:commonBundlePath] + moduleProvider:nil + launchOptions:launchOptions + executorKey:@"Demo"]; + +// 方式二:bridge初始化完成后,设置属性生效 +HippyRootView *rootView = [[HippyRootView alloc] initWithBridge:nil + businessURL:nil + moduleName:@"Demo" + initialProperties:@{@"isSimulator": @(isSimulator)} + launchOptions:nil + shareOptions:nil + debugMode:YES + delegate:nil]; +[rootView.bridge setTurboModuleEnabled:YES]; + +``` + +* 定义Module + +> 继承HippyOCTurboModule,实现协议HippyTurboModule。 + +目前iOS端仅支持继承关系来实现JSI能力,后续会考虑升级,只需实现协议`HippyTurboModule`就能实现能力。 + +具体使用与实现协议如下: + +```obj + +@implementation TurboConfig + +... + +// 注册模块 +HIPPY_EXPORT_TURBO_MODULE(TurboConfig) + +// 注册交互函数 +HIPPY_EXPORT_TURBO_METHOD(getInfo) { + return self.strInfo; +} +HIPPY_EXPORT_TURBO_METHOD(setInfo:(NSString *)string) { + self.strInfo = string; + return @(YES); +} + +... + +@end + +``` + +> 支持的数据类型说明: + +| Objec类型 | Js类型 | +|:----------|:----------| +| BOOL | Bool | +| NSInteger | Number | +| NSUInteger | Number | +| CGDouble | Number | +| CGFloat | Number | +| NSString | String | +| NSArray | Array | +| NSDictionary | Object | +| Promise | Function | +| NULL | null | + + + +更多示例可参考类[DemoIOSTurboModule](https://github.com/Tencent/Hippy/blob/master/examples/ios-demo/HippyDemo/turbomodule/TurboBaseModule.mm) + + +# 使用例子 + +[Android Demo](https://github.com/Tencent/Hippy/blob/master/examples/android-demo) + +[iOS Demo](https://github.com/Tencent/Hippy/blob/master/examples/ios-demo) + +[HippyReact Demo](https://github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/src/externals/Turbo/index.jsx) + +[HippyVue Demo](https://github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/demos/demo-turbo.vue) + + + + + diff --git a/docs/guide/layout.md b/docs/guide/layout.md index ab7dcc787cc..e4d01301b8f 100644 --- a/docs/guide/layout.md +++ b/docs/guide/layout.md @@ -1,3 +1,7 @@ +# 布局与单位 + +--- + # 盒模型 Hippy 为了方便前端开发便于理解,样式也采用了 CSS 的盒模型构建。当 Hippy 在进行布局的时候,渲染引擎会根据 CSS-Box 模型将所有元素表示为一个矩形盒子,样式配置决定这些盒子的大小,位置以及属性(颜色,背景,边框尺寸...). @@ -6,7 +10,7 @@ Hippy 为了方便前端开发便于理解,样式也采用了 CSS 的盒模型 > PS: Hippy 的盒模型布局对应的是CSS的 `box-sizing` 属性的 `border-box` 类型,具体表现与宽高边距计算可参考[MDN文档 box-sizing](//developer.mozilla.org/zh-CN/docs/Web/CSS/box-sizing)。 -![盒模型](//res.imtt.qq.com/hippydoc/img/border-box.png) +![盒模型](../assets/img/border-box.png) * [width](style/layout.md?id=width) * [height](style/layout.md?id=height) @@ -16,26 +20,26 @@ Hippy 为了方便前端开发便于理解,样式也采用了 CSS 的盒模型 # 布局(Flex) -Hippy 中,为了方便移动端编写布局,所以默认支持了现在移动端最流行的 `Flex` 布局。同时,因为仅支持 `Flex` 布局,所以不需要手写 `display: flex` 即可使用 -Flex 布局与 Web 的 Flex 类似,它们都旨在提供一个更加有效的方式制定、调整和分布一个容器里的项目布局,即使他们的大小是未知或者是动态的。Flex 规定了弹性元素如何伸长或缩短以适应flex容器中的可用空间。CSS 版教程文档可以参考[这篇](http://www.w3cplus.com/css3/a-visual-guide-to-css3-flexbox-properties.html) +Hippy 中,为了方便移动端编写布局,所以默认支持了现在移动端最流行的 `Flex` 布局。同时,因为仅支持 `Flex` 布局,所以不需要手写 `display: flex` 即可使用。 +Flex 布局与 Web 的 Flex 类似,它们都旨在提供一个更加有效的方式制定、调整和分布一个容器里的项目布局,即使他们的大小是未知或者是动态的。Flex 规定了弹性元素如何伸长或缩短,以适应flex容器中的可用空间。CSS 版教程文档可以参考[这篇](http://www.w3cplus.com/css3/a-visual-guide-to-css3-flexbox-properties.html) ## flexDirection flexDirection 属性指定了内部元素是如何在 flex 容器中布局的,定义了主轴的方向(水平或垂直)。 -> 注意:Hippy 的 flexDirection 与 Web的 flex-direction Web 默认为 `row`, Hippy 默认为 `column`。 +> 注意:Hippy 的 flexDirection 与 Web 的 flex-direction 默认值有区别, Web 默认为 `row`, Hippy 默认为 `column`。 -flexDirection +flexDirection

| 类型 | 必需 |默认| | ------ | -------- |---| -| enum('column', 'row') | 否 |'column'| +| enum('row', 'row-reverse', 'column', 'column-reverse')| 否 |'column'| ## alignItems -alignItems 定义了伸缩项目可以在伸缩容器的当前行的侧轴上对齐方式 +alignItems 定义了伸缩项目可以在伸缩容器次轴方向的排列方式 * flex-start(默认值):伸缩项目在侧轴起点边的外边距紧靠住该行在侧轴起始的边。 * flex-end:伸缩项目在侧轴终点边的外边距靠住该行在侧轴终点的边 。 @@ -54,22 +58,22 @@ justifyContent 定义了伸缩项目沿着主轴线的对齐方式。 * flex-start(默认值):伸缩项目向一行的起始位置靠齐。 * flex-end:伸缩项目向一行的结束位置靠齐。 * center:伸缩项目向一行的中间位置靠齐。 -* space-between:伸缩项目会平均地分布在行里。第一个伸缩项目一行中的最开始位置,最后一个伸缩项目在一行中最终点位置。 +* space-between:伸缩项目会平均地分布在行里。第一个伸缩项目在一行中的最开始位置,最后一个伸缩项目在一行中最终点位置。 * space-around:伸缩项目会平均地分布在行里,两端保留一半的空间。 | 类型 | 必需 |默认| | ------ | -------- |---| -| enum('flex-start', 'flex-end', 'center', 'baseline', 'stretch') | 否 |'flex-start'| +| enum('flex-start', 'flex-end', 'center', 'space-between', 'space-around', 'space-evenly') | 否 |'flex-start'| ## flex -flex 属性数值, 定义了 flex 容器的子节点项可以占用容器中剩余空间的大小。默认值为0,即不占用剩余空间。如果定义了 flex 数字且为正数的时候,则 +flex 属性数值, 定义了 flex 容器的子节点项可以占用容器中剩余空间的大小。默认值为 0,即不占用剩余空间。如果定义了 flex 数字且为正数的时候,则 ```text 每个元素占用的剩余空间=自己的 flex 数值 / 所有同一级子容器的 flex 数字之和 ``` -当 flex 设置为-1的时候,默认情况会显示正常宽高。然而, 如果剩余空间不足的话,此设置了`flex: -1`的容器将会收缩到其 minWidth 的宽度与 minHeight 的高度来显示。 +当 flex 设置为 -1 的时候,默认情况会显示正常宽高。然而, 如果剩余空间不足的话,此设置了 `flex: -1` 的容器将会收缩到其 minWidth 的宽度与 minHeight 的高度来显示。 | 类型 | 必需 |默认| | ------ | -------- |---| @@ -81,27 +85,27 @@ flexBasis 设置伸缩基准值,剩余的空间按比率进行伸缩,负值 | 类型 | 必需 |默认| | ------ | -------- |---| -| number, string| 否 |auto| +| number | 否 |auto| ## flexGrow -flexGrow 定义伸缩项目的扩展能力。它接受一个不带单位的值做为一个比例。主要用来决定伸缩容器剩余空间按比例应扩展多少空间。 +flexGrow 定义伸缩项目的扩展能力。它接受一个不带单位的值做为一个比例,主要用来决定伸缩容器剩余空间应按比例扩展多少空间。 如果所有伸缩项目的 flex-grow 设置了 1,那么每个伸缩项目将设置为一个大小相等的剩余空间。如果你给其中一个伸缩项目设置了 flex-grow 值为 2,那么这个伸缩项目所占的剩余空间是其他伸缩项目所占剩余空间的两倍。 | 类型 | 必需 |默认| | ------ | -------- |---| -| number| 否 |0| +| number | 否 |0| ## flexShrink flexShrink 定义伸缩项目收缩的能力。 -> 注意:Hippy 中 flexShrink 默认值为 0,与Web标准有差异 +> 注意:Hippy 中 flexShrink 默认值为 0,与 Web 标准有差异 | 类型 | 必需 |默认| | ------ | -------- |---| -| number| 否 |0| +| number | 否 |0| # 长度单位 diff --git a/docs/guide/network-request.md b/docs/guide/network-request.md index 619c0aa1c45..d95abd13416 100644 --- a/docs/guide/network-request.md +++ b/docs/guide/network-request.md @@ -4,6 +4,8 @@ Hippy 直接支持 W3C 标准的 fetch 和 WebSocket 接口,可以通过这两个方法对服务器进行访问。 +--- + # fetch Hippy 提供了跟 W3C 标准基本一致的 [fetch](//developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API) 方法,可以直接参考 [MDN](//developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API)。 @@ -20,6 +22,8 @@ fetch('//mywebsite.com/mydata.json'); fetch 函数也支持 HTTP 请求的配置。 +> 低版本仅支持 `method | headers | body` 参数设置,`2.14.0` 及以上版本支持所有自定义参数透传,如 `redirect: 'follow'` + ```javascript fetch('//mywebsite.com/endpoint/', { method: 'POST', @@ -31,6 +35,7 @@ fetch('//mywebsite.com/endpoint/', { firstParam: 'yourValue', secondParam: 'yourOtherValue', }), + redirect: 'follow', // `2.14.0` 及以上版本支持 }); ``` @@ -147,7 +152,7 @@ import { View } from "@hippy/react"; export default class WebSocketExpo extends React.Component { componentWillMount() { - this.webSocekt = new WebSocket("ws://websocket.test.qq.com/websocket"); + this.webSocekt = new WebSocket("ws://websocket.xx.com/websocket"); this.webSocekt.onopen = () => { this._webSocketOpened = true; console.log("WebSocket onOpen"); @@ -194,6 +199,6 @@ export default class WebSocketExpo extends React.Component { Hippy 在接收来自服务器的 `set-cookie` header 时,会自动种入 cookie,下次再请求同域名服务时,就自动带上之前种下的 cookie。 -但和浏览器不同,Hippy 内提供提供了 NetworkModule 提供了对 cookie 读取和修改,详情可以参考 hippy-react 的 [NetworkModule](hippy-react/modules.md?id=networkmodule),或者 hippy-vue 的 [Vue.Native.Cookie](/hippy-vue/vue-native.md?id=cookie) 文档。 +但和浏览器不同,Hippy 内提供了 `NetworkModule` 对 cookie 读取和修改,详情可以参考 hippy-react 的 [NetworkModule](hippy-react/modules.md?id=networkmodule),或者 hippy-vue 的 [Vue.Native.Cookie](/hippy-vue/vue-native.md?id=cookie) 文档。 > 浏览器中对 Cookie 的读写时通过 document 对象操作的,但是 Hippy 中暂时不能直接出现全局的 document,否则部分库会运行一些在浏览器中才有的 document 方法,但 Hippy 中并没有,会导致崩溃。 diff --git a/docs/guide/performance.md b/docs/guide/performance.md new file mode 100755 index 00000000000..983f4d6b376 --- /dev/null +++ b/docs/guide/performance.md @@ -0,0 +1,114 @@ +# 性能监控 + +--- + +## SDK启动加载性能指标 + +> 最低支持版本 `2.16.0` + +### 介绍 + +`Hippy` Native SDK的加载执行过程如下图所示: + +![hippy-launch-steps](../assets/img/hippy-launch-steps.png) + +对应上述各个阶段,`Hippy` Native SDK提供了对应的耗时等性能指标方便开发者获取,如下表所示: + +| 分类 | 指标 | 对应Key | +|:----------|:----------|:----------| +| JS引擎 | 初始化JS引擎耗时(仅Android) | hippyInitJsFramework | +| vendor包 | vendor包加载耗时 | hippyCommonLoadSource | +| vendor包 | vendor包执行耗时 | hippyCommonExecuteSource | +| 业务包 | 业务包加载耗时 | hippySecondaryLoadSource | +| 业务包 | 业务包执行耗时 | hippySecondaryExecuteSource | +| 整体 | Bridge启动耗时 | hippyBridgeStartup | +| 整体 | JS入口执行耗时 | hippyRunApplication | +| 整体 | 首帧耗时 | hippyFirstPaint | + + + + +### Native 获取性能数据 + +#### Android API 指引 + +##### 1. 注入`HippyEngineMonitorAdapter` + +```java +public class MyEngineMonitorAdapter extends DefaultEngineMonitorAdapter { + @Override + public void reportEngineLoadResult(int code, int loadTime, + List loadEvents, Throwable e) { + // 引擎创建完成回调 + } + + @Override + public void reportModuleLoadComplete(HippyRootView rootView, int loadTime, + List loadEvents) { + // 业务JS加载完成,首帧回调 + } + + @Override + public void reportCustomMonitorPoint(HippyRootView rootView, String eventName, + long timeMillis) { + // 自定义打点回调 + } + +} +``` + +```java +HippyEngine.EngineInitParams initParams = new HippyEngine.EngineInitParams(); +initParams.engineMonitor = new MyEngineMonitorAdapter(); +…… +HippyEngine engine = HippyEngine.create(initParams); +``` + +##### 2. 获取性能数据 + +推荐在reportModuleLoadComplete之后调用,以便获取完整的性能数据。 + +使用方法为 + +```java +TimeMonitor monitor = rootView == null ? null : rootView.getTimeMonitor(); +if (monitor != null) { + Map points = monitor.getAllPoints(); +} +``` + +`HippyEngineMonitorPoint`类中定义了每个性能指标对应的常量,命名规则为:`hippyXxx`对应`XXX_START`和`XXX_END`。 + +#### iOS API 指引 + +推荐在HippyRootView加载完成(即收到`HippyContentDidAppearNotification` 通知后)进行性能指标的获取。 + +使用方法为 + +```objc +int64_t duration = [bridge.performanceLogger durationForTag:HippyPLxxx]; +``` + +`HippyPLxxx`在`HippyPerformanceLogger`类中定义,为`HippyPLTag`枚举类型。 + +--- + +## Memory + +提供全局 `performance` 对象,用于获取性能数据。 + +`performance.memory` 返回 js 引擎中内存的统计数据(仅 Android 支持,iOS 将返回 `undefined` )。 +> 最低支持版本 `2.15.0` + +```javascript + +global.performance.memory = undefined || { + jsHeapSizeLimit: 4096, // 堆内存大小限制 + totalJSHeapSize: 2048, // 可使用的堆内存 + usedJSHeapSize: 1024, // 已使用的堆内存 + jsNumberOfNativeContexts: 1, // 当前活动的顶层上下文的数量(随着时间的推移,此数字的增加表示内存泄漏) + jsNumberOfDetachedContexts: 0, // 已分离但尚未回收垃圾的上下文数(该数字不为零表示潜在的内存泄漏) +} + +``` + diff --git a/docs/guide/privacy.md b/docs/guide/privacy.md new file mode 100644 index 00000000000..5e68f77b2e9 --- /dev/null +++ b/docs/guide/privacy.md @@ -0,0 +1,93 @@ +# Hippy SDK 个人信息保护规则 + +生效日期:2022年4月24日 + +## 引言 + +Hippy SDK (以下简称"SDK产品")由深圳市腾讯计算机系统有限公司(以下简称"我们"或称"腾讯")提供,注册地为深圳市南山区粤海街道麻岭社区科技中一路腾讯大厦35层。 + +《Hippy SDK 个人信息保护规则》(以下简称"本规则")主要向开发者及其终端用户("终端用户")告知,为了实现SDK产品的相关功能,SDK产品需收集、使用和处理终端用户个人信息的情况。 + +请开发者及终端用户认真阅读本规则。如您是开发者,请您确认充分了解并同意本规则后再集成SDK产品,如果您不同意本规则及按照本规则履行对应的用户个人信息保护义务,应立即停止接入及使用SDK产品;同时,您应仅在征得终端用户的同意后启动或使用SDK产品并处理终端用户的个人信息。 + +特别说明:
+如您是开发者,您应当: + +1. 遵守法律、法规收集、使用和处理终端用户的个人信息, 包括但不限于制定和公布有关个人信息保护的隐私政策等;
+2. 在启动或使用SDK产品前,告知终端用户SDK产品收集、使用和处理终端用户个人信息的情况,并依法征得终端用户同意;
+3. 在征得终端用户的同意前,除非法律法规另有规定,不应收集任何终端用户的个人信息;
+4. 向终端用户提供易于操作且满足法律法规要求的用户权利实现机制,并告知终端用户如何查阅、复制、修改、删除个人信息,撤回同意,以及限制个人信息处理、转移个人信息、获取个人信息副本和注销账号;
+5. 遵守本规则的要求。
+ +如开发者和终端用户对本规则内容有任何疑问或建议, 可随时通过本规则第八条提供的方式与我们联系。 + +## 一、我们收集的信息及我们如何使用信息 + +### (一)为实现 SDK 产品功能所需的权限 + +为实现SDK产品的相应功能所必须,我们会通过开发者的应用申请所需权限。 + +| 操作系统 | 权限名称 | 使用目的 | 是否可选 | +| ------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | -------- | +| iOS/Android | 剪切板 | 向终端用户或开发者提供访问剪切板的功能,帮助用户用户完成相关内容的复制粘贴 | 可选 + +请注意,在不同设备和系统中,权限显示方式及关闭方式会有所不同,需同时参考其使用的设备及操作系统开发方的说明或指引。当终端用户关闭权限即代表其取消了相应的授权,我们和开发者将不会继续收集和使用相关权限所对应的个人信息, 也无法为终端用户提供需要终端用户开启权限才能提供的对应的功能。 + +### (二)根据法律法规的规定,以下是征得用户同意的例外情形 + +1. 为订立、履行与终端用户的合同所必需; +2. 为履行我们的法定义务所必需; +3. 为应对突发公共卫生事件,或者紧急情况下为保护终端用户的生命健康和财产安全所必需; +4. 为公共利益实施新闻报道、舆论监督等行为, 在合理的范围内处理终端用户的个人信息; +5. 依照本法规定在合理的范围内处理终端用户自行公开或者其他已经合法公开的个人信息; +6. 法律行政法规规定的其他情形。 + +特别提示: 如我们收集的信息无法单独或结合其他信息识别到终端用户的个人身份,其不属于法律意义上的个人信息。 + +### (三)如何使用 Cookie + +为实现APP产品功能所必需,SDK按照Cookie规范为开发者提供基于业务需要使用Cookie技术的设置,修改、删除、存储能力。SDK不会收集、上报、使用通过Cookie收集的任何信息。如您是终端用户,您需要通过开发者提供的渠道管理和删除Cookie。 + +如您是开发者,您应当向终端用户告知与SDK产品相关的Cookie使用情况,包括但不限于Cookie的类型、收集的个人信息、使用目的、使用场景,征得终端用户的同意,并向终端用户提供管理和删除Cookie的机制。 + +## 二、终端用户如何管理自己的信息 + +我们非常重视终端用户对其个人信息管理的权利, 并竭力帮助终端用户管理个人信息,包括个人信息查阅、复制、删除、注销账号以及设置隐私功能等, 以保障终端用户的权利。如您是开发者,您应当为终端用户提供实现查阅、复制、修改、删除个人信息、撤回同意和注销账号的方式。 + +基于终端用户的同意而进行的个人信息处理活动, 终端用户有权撤回该同意。我们已向开发者提供关闭本SDK产品的能力,请开发者点击[此处](https://hippyjs.org/#/android/integration)查看操作指引。由于我们与终端用户无直接的交互对话界面,终端用户可以直接联系开发者停止使用本SDK产品。如您是终端用户,请您理解,特定的业务功能或服务需要您提供服务所需的信息才能得以完成,当您撤回同意后,我们无法继续为您提供对应的功能或服务,也不再处理您相应的个人信息。您撤回同意的决定,不会影响我们此前基于您的授权而开展的个人信息处理。 + +## 三、信息的存储 + +Hippy SDK不存储用户的任何信息。 + +## 四、信息安全 + +我们为终端用户的个人信息提供相应的安全保障,以防止信息的丢失、不当使用、未经授权访问或披露。 + +我们严格遵守法律法规保护终端用户的个人信息。 + +我们将在合理的安全水平内使用各种安全保护措施以保障信息的安全。例如,我们使用加密技术、匿名化处理等手段来保护终端用户的个人信息。 + +我们建立严谨的管理制度、流程和组织确保信息安全。例如,我们严格限制访问信息的人员范围,要求他们遵守保密义务,并进行审查。 + +若发生个人信息泄露等安全事件,我们会启动应急预案,阻止安全事件扩大,并以推送通知、公告等形式告知开发者。 + +## 五、变更 + +我们会适时修订本规则的内容。 + +如本规则的修订会导致终端用户在本规则项下权利的实质减损,我们将在变更生效前,通过网站公告等方式进行告知。如您是开发者,当更新后的本规则对处理终端用户的个人信息情况有变动的,您应当适时更新隐私政策,并以弹框形式通知终端用户并且征得其同意,如果终端用户不同意接受本规则, 请停止启用或使用SDK产品。 + +## 六、联系我们 + +我们设立了专门的个人信息保护团队和个人信息保护负责人, 如果开发者和/或终端用户对本规则或个人信息保护相关事宜有任何疑问或投诉、建议时, 可以通过以下方式与我们联系:
+(i)通过 与我们联系;
+(ii)将问题发送至
+(iii)邮寄信件至: 中国广东省深圳市南山区海天二路33号腾讯滨海大厦 数据隐私保护部(收)
+邮编: 518054。
+我们将尽快审核所涉问题, 并在15个工作日或法律法规规定的期限内予以反馈。 + + + + + diff --git a/docs/guide/support.md b/docs/guide/support.md index 94341f20778..5aa3bca6cc6 100644 --- a/docs/guide/support.md +++ b/docs/guide/support.md @@ -1,12 +1,14 @@ # 技术支持 +--- + # Github 我们鼓励在 [Issue](//github.com/Tencent/Hippy/issues) 里汇报使用问题。 或者在提交 [Pull Request](//github.com/Tencent/Hippy/pulls),一起来建设Hippy。 -# QQ 群 +# 企业微信群 -也可以加入 QQ 群 [784894901](//shang.qq.com/wpa/qunwpa?idkey=ce9cd2eb06fd6da26a1a63b70da82edd132964d22998e5154e533822f7b757cc) +使用微信或者企业微信扫描加入 -[![QQ群](//puui.qpic.cn/vupload/0/1577700402806_3qd3bjxmlq7.png/0)](//shang.qq.com/wpa/qunwpa?idkey=ce9cd2eb06fd6da26a1a63b70da82edd132964d22998e5154e533822f7b757cc) +![企业微信群二维码](../assets/img/wechat-group.jpeg) diff --git a/docs/guide/timer.md b/docs/guide/timer.md index c0d5235a6ef..670e031432f 100644 --- a/docs/guide/timer.md +++ b/docs/guide/timer.md @@ -4,7 +4,9 @@ Hippy 端定时器用法与 Web 端 Javascript 用法一致,可以直接使用。 -## 方法 +--- + +# 方法 * [setTimeout](//developer.mozilla.org/zh-CN/docs/Web/API/Window/setTimeout) * [clearTimeout](//developer.mozilla.org/zh-CN/docs/Web/API/WindowTimers/clearTimeout) diff --git a/docs/hippy-react/_sidebar.md b/docs/hippy-react/_sidebar.md index 18163a7e3b5..b271658d609 100644 --- a/docs/hippy-react/_sidebar.md +++ b/docs/hippy-react/_sidebar.md @@ -4,9 +4,7 @@ * [组件](hippy-react/components.md) * [模块](hippy-react/modules.md) * [样式](hippy-react/style.md) -* [终端事件](hippy-react/native-event.md) -* [动画方案](hippy-react/animation.md) +* [事件](hippy-react/native-event.md) * [手势系统](hippy-react/gesture.md) * [自定义组件和模块](hippy-react/customize.md) * [转 Web](hippy-react/web.md) -* [从 React Native 迁移](hippy-react/migrate-from-rn.md) diff --git a/docs/hippy-react/animation.md b/docs/hippy-react/animation.md deleted file mode 100644 index a281ff95723..00000000000 --- a/docs/hippy-react/animation.md +++ /dev/null @@ -1,194 +0,0 @@ -# 动画方案 - -## 原理 - -Hippy 的动画则是完全由前端传入动画参数,由终端控制每一帧的计算和排版更新,减少了前端端与终端的通信次数,因此也大大减少动画的卡顿。 - -## 酷炫的效果 - -* 关注动画 - -关注动画 - -* 点赞微笑动画 - -点赞微笑 - -* 进度条动画 - -PK进度条动画 - -## 让我们开始吧 - -在 Hippy 上实现一个动画分为三个步骤: - -1. 通过 Animation 或 AnimationSet 定义动画 -2. 在 render() 时,将动画设置到需要产生动画效果的控件属性上 -3. 通过 Animation 的 start 方法启动动画,与通过 destroy 方法停止并销毁动画; - -## 示例代码 - -```js -import { Animation, StyleSheet } from "@hippy/react"; -import React, { Component } from "react"; - -export default class AnimationExample extends Component { - componentDidMount() { - // 动画参数的设置 - this.verticalAnimation = new Animation({ - startValue: 0, // 动画开始值 - toValue: 100, // 动画结束值 - duration: 500, // 动画持续时长 - delay: 360, // 至动画真正开始的延迟时间 - mode: "timing", // 动画模式,现在只支持timing - timingFunction: "linear", // 动画缓动函数 - }); - this.horizonAnimation = new Animation({ - startValue: 0, // 开始值 - toValue: 100, // 动画结束值 - duration: 500, // 动画持续时长 - delay: 360, // 至动画真正开始的延迟时间 - mode: "timing", // 动画模式,现在只支持timing - timingFunction: "linear", // 动画缓动函数 - }); - this.scaleAnimationSet = new AnimationSet({ - children: [ - { - animation: new Animation({ - startValue: 1, - toValue: 1.4, - duration: 200, - delay: 0, - mode: "timing", - timingFunction: "linear", - }), - follow: false, // 配置子动画的执行是否跟随执行 - }, - { - animation: new Animation({ - startValue: 1.4, - toValue: 0.2, - duration: 210, - delay: 200, - mode: "timing", - timingFunction: "linear", - }), - follow: true, - }, - ], - }); - } - - componentWillUnmount() { - // 如果动画没有销毁,需要在此处保证销毁动画,以免动画后台运行耗电 - this.scaleAnimationSet && this.scaleAnimationSet.destroy(); - this.horizonAnimation && this.horizonAnimation.destroy(); - this.verticalAnimation && this.verticalAnimation.destroy(); - } - - render() { - return ( - - - - - - { - this.verticalAnimation.start(); - }} - > - 水平位移动画 - - { - this.horizonAnimation.start(); - }} - > - 垂直位移动画 - - { - this.scaleAnimationSet.start(); - }} - > - 图形形变动画 - - - - ); - } -} - -// 样式代码省略 -``` - -`Animation` 和 `AnimationSet` 都是赋予 hippy 组件的单个样式属性(如 width,height,left,right)动画能力的模块。 - -`Animation`与`AnimationSet`的不同点在于`Animation`只是单个动画模块,`AnimationSet`为多个`Animation`的动画模块组合,支持同步执行或顺序执行多个`Animation`动画 - -Hippy 的动画能力支持位移,变形,旋转等功能,且因为动画对应的是样式属性,与支持动画集合`AnimationSet`,所以可以更加灵活地制作出炫丽的动画效果~ - -## 属性 - -Animation 支持的动画配置包括: - -* mode :动画模式,当前仅支持“timing”模式,即随时间改变控件的属性,默认配置即为"timing"; -* delay :动画延迟开始的时间,单位为毫秒,默认为 0,即动画 start 之后立即执行; -* startValue :动画开始时的值,可为 Number 类型或一个 Animation 的对象,如果指定为一个 Animation 时,代表本动画的初始值为其指定的动画结束或中途 cancel 后的所处的动画值(这种场景通常用于 AnimationSet 中实现多个连续变化的动画); -* toValue :动画结束时候的值,类型只能为 Number; -* valueType :动画的开始和结束值的单位类型,默认为空,代表动画起止的单位是普通数值,另外可取值有: - - * “rad” :代表动画参数的起止值为弧度; - * “deg” :代表动画参数的起止值为度数; - * “color” :代表动画参数的起止值为颜色; - -* duration :动画的持续时间,单位为毫秒,默认为 0; - -* timingFunction :动画插值器类型,默认为“linear”,可选值包括: - - * “linear”:使用线性插值器,动画将匀速进行; - * “ease-in”:使用加速插值器,动画速度将随时间逐渐增加; - * “ease-out”:使用减速插值器,动画速度将随时间逐渐减小; - * “ease-in-out”:使用加减速插值器,动画速度前半段先随时间逐渐增加,后半段速度将逐渐减小; - * “cubic-bezier”:(最低支持版本 2.9.0)使用自定义贝塞尔曲线,与 [css transition-timing-function 的 cubic-bezier](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function) 一致; - -* repeatCount :动画的重复次数,默认为 0,即不重复播放,为"loop"时代表无限循环播放; - -AnimationSet 为实现动画集合添加了 3 个属性 - -* children :接收一个 Array,用于指定子动画,该 Array 的每个元素包括: - * animation:子动画对应的 Animation 对象; - * follow:配置子动画的执行是否跟随执行,为 true,代表该子动画会等待上一个子动画执行完成后在开始,为 false 则代表和上一个子动画同时开始,默认为 false; - -## 方法 - -除了动画配置,Animation 与 AnimationSet 都提供了一系列控制和监听动画过程的方法: - -* start() :启动动画,注意,如果调用该方法前,动画尚未经过 render 赋值给相应控件或者该动画已经 destroy,那 start 将不会生效; -* destroy():停止并销毁一个动画; -* updateAnimation( newConfig ) :修改动画的配置参数,注意,如果动画已经 start 或 destroy,更新操作将不会生效,该方法接收的 newConfig 参数结构与 Animation 构造函数中动画配置参数一致; -* removeEventListener():撤销所有注册的动画监听; - -## 回调 - -* onAnimationStart(callback):注册一个动画的监听回调,在动画开始时将会回调 callback; -* onAnimationEnd(callback):注册一个动画的监听回调,在动画结束时将会回调 callback; -* onAnimationCancel(callback):注册一个动画的监听回调,在动画被取消时将会回调 callback,取消的情况包括:尚未 start 或尚未结束的动画被 destroy 时; -* onAnimationRepeat(callback):注册一个动画的监听回调,当动画开始下一次重复播放时 callback 将被回调; diff --git a/docs/hippy-react/components.md b/docs/hippy-react/components.md index 808d81358e1..24226ef3b26 100644 --- a/docs/hippy-react/components.md +++ b/docs/hippy-react/components.md @@ -2,7 +2,7 @@ # 组件 -hippy-react 的组件接近终端,语法上接近 React Native。 +Hippy-React SDK 提供了常用的 UI 组件,语法上接近终端 --- @@ -12,8 +12,8 @@ hippy-react 的组件接近终端,语法上接近 React Native。 图片组件,用于显示单张图片。 -> * 注意: 必须指定样式中的宽度和高度,否则无法工作。 -> * 注意: Android 端默认会带上灰底色用于图片占位,可以加上 `backgroundColor: transparent` 样式改为透明背景。 +> * 必须指定样式中的宽度和高度,否则无法工作。 +> * Android 端默认会带上灰底色用于图片占位,可以加上 `backgroundColor: transparent` 样式改为透明背景。(`2.14.1` 版本后 Image 默认背景色修改成 `transparent`) ## 基本用法 @@ -22,7 +22,7 @@ hippy-react 的组件接近终端,语法上接近 React Native。 ```jsx ; ``` @@ -44,22 +44,23 @@ import icon from './qb_icon_new.png'; ## 参数 -| 参数 | 描述 | 类型 | 支持平台 | -| ------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | -------- | -| onLayout | 当元素挂载或者布局改变的时候调用,参数为: `{ nativeEvent: { layout: { x, y, width, height } } }`。 | `Function` | `ALL` | -| onLoad | 加载成功完成时调用此回调函数。 | `Function` | `ALL` | -| onLoadStart | 加载开始时调用。 例如, `onLoadStart={() => this.setState({ loading: true })}` | `Function` | `ALL` | -| onLoadEnd | 加载结束后,不论成功还是失败,调用此回调函数。 | `Function` | `ALL` | -| resizeMode | 决定当组件尺寸和图片尺寸不成比例的时候如何调整图片的大小。 | `enum`(cover, contain, stretch, repeat, center) | `ALL` | -| source | uri是一个表示图片的资源标识的字符串,需要用http路径。 现在支持的图片格式有 `png` , `jpg` , `jpeg` , `bmp` , `gif` 。 | `{ uri: string }` | `ALL` | -| defaultSource | 指定当 `Image` 组件还没加载出来 `source` 属性指定的图片的占位符图片,当 `source` 属性指定的图片加载失败时, `Image` 组件会显示 `defaultSource` 属性指定的图片 | `string`:图片 base64 字符串 | `ALL` | -| onError | 当加载错误的时候调用此回调函数,参数为 `{ nativeEvent: { error } }` | `Function` | `ALL` | -| capInsets | 当调整 `Image` 大小的时候,由 `capInsets` 指定的边角尺寸会被固定而不进行缩放,而中间和边上其他的部分则会被拉伸。这在制作一些可变大小的圆角按钮、阴影、以及其它资源的时候非常有用。 | `{ top: number, left: number, bottom: number, right: number }` | `ALL` | -| onProgress | 在加载过程中不断调用,参数为 `{ nativeEvent: { loaded, total } }` | `Function` | `ALL` | -| onTouchDown | 当用户开始触屏控件时(即用户在该控件上按下手指时),将回调此函数,并将触屏点信息作为参数传递进来; 参数为 `{ nativeEvent: { name, page_x, page_y, id } }` | `Function` | `ALL` | -| onTouchMove | 当用户在控件移动手指时,此函数会持续收到回调,并通过event参数告知控件的触屏点信息;参数为 `{ nativeEvent: { name, page_x, page_y, id } }` | `Function` | `ALL` | -| onTouchEnd | 当触屏操作结束,用户在该控件上抬起手指时,此函数将被回调,event参数也会通知当前的触屏点信息;参数为 `{ nativeEvent: { name, page_x, page_y, id } }` | `Function` | `ALL` | -| onTouchCancel | 当用户触屏过程中,某个系统事件中断了触屏,例如电话呼入、组件变化(如设置为hidden),此函数会收到回调,触屏点信息也会通过event参数告知前端;参数为 `{ nativeEvent: { name, page_x, page_y, id } }` | `Function` | `ALL` | +| 参数 | 描述 | 类型 | 支持平台 | +| ------------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ------------------------------------------------------------ | -------- | +| capInsets | 当调整 `Image` 大小的时候,由 `capInsets` 指定的边角尺寸会被固定而不进行缩放,而中间和边上其他的部分则会被拉伸。这在制作一些可变大小的圆角按钮、阴影、以及其它资源的时候非常有用。 | `{ top: number, left: number, bottom: number, right: number }` | `Android、iOS` | +| defaultSource | 指定当 `Image` 组件还没加载出来 `source` 属性指定的图片的占位符图片,当 `source` 属性指定的图片加载失败时, `Image` 组件会显示 `defaultSource` 属性指定的图片 | `string`: 图片 base64 字符串 | `Android、iOS、hippy-react-web、Web-Renderer` | +| source | uri 是一个表示图片的资源标识的字符串。 现在支持的图片格式有 `png` , `jpg` , `jpeg` , `bmp` , `gif` 。 | `{ uri: string }` | `Android、iOS、hippy-react-web、Web-Renderer` | +| tintColor | 对图片进行染色(对非纯色图片进行有透明度的染色时,Android 和 iOS 的 `blendMode` 默认值有差异)。 | [color](style/color.md) | `Android、iOS、Web-Renderer`| +| onLayout | 当元素挂载或者布局改变的时候调用,参数为: `nativeEvent: { layout: { x, y, width, height } }`,其中 `x` 和 `y` 为相对父元素的坐标位置 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onLoad | 加载成功完成时调用此回调函数,`2.16.0`后支持参数返回,参数为:`nativeEvent: { width: number, height: number, url: string }` | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onLoadStart | 加载开始时调用。 例如, `onLoadStart={() => this.setState({ loading: true })}` | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onLoadEnd | 加载结束后,不论成功还是失败,调用此回调函数。 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| resizeMode | 决定当组件尺寸和图片尺寸不成比例的时候如何调整图片的大小。`注意:hippy-react-web、Web-Renderer 不支持 repeat` | `enum (cover, contain, stretch, repeat, center)` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onError | 当加载错误的时候调用此回调函数,参数为 `nativeEvent: { error }` | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onProgress | 在加载过程中不断调用,参数为 `nativeEvent: { loaded: number, total: number }`, `loaded` 表示加载中的图片大小, `total` 表示图片总大小 | `Function` | `iOS` | +| onTouchDown | 当用户开始在控件上按下手指时,将回调此函数,并将触屏点信息作为参数传递进来; 参数为 `nativeEvent: { name, page_x, page_y, id }`, `page_x` 和 `page_y` 分别表示点击在屏幕内的绝对位置 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onTouchMove | 当用户在控件移动手指时,此函数会持续收到回调,并通过event参数告知控件的触屏点信息;参数为 `nativeEvent: { name, page_x, page_y, id }`,`page_x` 和 `page_y` 分别表示点击在屏幕内的绝对位置 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onTouchEnd | 当触屏操作结束,用户在该控件上抬起手指时,此函数将被回调,event参数也会通知当前的触屏点信息;参数为 `nativeEvent: { name, page_x, page_y, id }`,`page_x` 和 `page_y` 分别表示点击在屏幕内的绝对位置 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onTouchCancel | 当用户触屏过程中,某个系统事件中断了触屏,例如电话呼入、组件变化(如设置为hidden)、其他组件的滑动手势,此函数会收到回调,触屏点信息也会通过event参数告知前端;参数为 `nativeEvent: { name, page_x, page_y, id }`,`page_x` 和 `page_y` 分别表示点击在屏幕内的绝对位置 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | ## 方法 @@ -67,7 +68,7 @@ import icon from './qb_icon_new.png'; `(uri: string, success: (width: number, height: number) => void, failure?: ErrorFunction) => void` 在显示图片前获取图片的宽高(以像素为单位)。如果图片地址不正确或下载失败, 此方法也会失败。 -要获取图片的尺寸, 首先需要加载或下载图片(同时会被缓存起来)。这意味着理论上你可以用这个方法来预加载图片,虽然此方法并没有针对这一用法进行优化,而且将来可能会换一些实现方案使得并不需要完整下载图片即可获取尺寸。所以更好的预加载方案是使用下面那个专门的预加载方法。 +要获取图片的尺寸, 首先需要加载或下载图片(同时会被缓存起来)。这意味着理论上可以用这个方法来预加载图片,但是更好的预加载方案是使用下面 `prefetch` 预加载方法。 *不适用于静态图片资源。* @@ -77,7 +78,7 @@ import icon from './qb_icon_new.png'; ### prefetch -`(url: string) => void` 预加载一个远程图片,将其下载到本地磁盘缓存。 +`(url: string) => void` 预加载一张远程图片,将其下载到本地磁盘缓存。 > * `uri`: string - 图片的地址 @@ -89,45 +90,66 @@ import icon from './qb_icon_new.png'; 可复用垂直列表功能,尤其适合大量条目的数据渲染。 +> Android `2.14.0` 版本后会采用 `RecyclerView` 替换原有 `ListView` + ## 参数 | 参数 | 描述 | 类型 | 支持平台 | | --------------------- | ------------------------------------------------------------ | ----------------------------------------------------------- | -------- | -| horizontal | 指定 `ListView` 是否采用横向布局。`default: undefined` | `any` | `Android` | -| initialListSize | 指定在组件刚挂载的时候渲染多少行数据。用这个属性来确保首屏显示合适数量的数据,而不是花费太多帧时间逐步显示出来。 | `number` | `ALL` | -| initialContentOffset | 初始位移值 -- 在列表初始化时即可指定滚动距离,避免初始化后再通过 scrollTo 系列方法产生的闪动。Android 在 `2.8.0` 版本后支持 | `number` | `ALL` | -| bounces | 是否开启回弹效果,默认 `true` | `boolean` | `iOS` | -| overScrollEnabled | 是否开启回弹效果,默认 `true` | `boolean` | `Android` | -| renderRow | 这里的入参是当前row 的index,在这里可以凭借index 获取到具体这一行单元格的数据,从而决定如何渲染这个单元格。 | `(index: number) => Node` | `ALL` | -| getRowStyle | 设置`ListViewItem`容器的样式。当设置了 `horizontal=true` 启用横向 `ListView` 时,需显式设置 `ListViewItem` 宽度 | `(index: number) => styleObject` | `ALL` | -| getRowType | 指定一个函数,在其中返回对应条目的类型(返回Number类型的自然数,默认是0),List 将对同类型条目进行复用,所以合理的类型拆分,可以很好地提升list 性能。 | `(index: number) => number` | `ALL` | -| getRowKey | 指定一个函数,在其中返回对应条目的 Key 值,详见 [React 官文](//reactjs.org/docs/lists-and-keys.html) | `(index: number) => any` | `ALL` | -| preloadItemNumber | 指定当列表滚动至倒数第几行时触发 `onEndReached` 回调。 | `number` | `ALL` | -| onAppear | 当有`ListViewItem`滑动进入屏幕时(曝光)触发,入参返回曝光的`ListViewItem`对应索引值。 | `(index) => any` | `ALL` | -| onDisappear | 当有`ListViewItem`滑动离开屏幕时触发,入参返回离开的`ListViewItem`对应索引值。 | `(index) => any` | `ALL` | -| onWillAppear | 当有`ListViewItem`至少一个像素进入屏幕时(曝光)触发,入参返回曝光的`ListViewItem`对应索引值。 `最低支持版本2.3.0` | `(index) => any` | `ALL` | -| onWillDisappear | 当有`ListViewItem`至少一个像素滑动离开屏幕时触发,入参返回离开的`ListViewItem`对应索引值。 `最低支持版本2.3.0`| `(index) => any` | `ALL` | -| onEndReached | 当所有的数据都已经渲染过,并且列表被滚动到最后一条时,将触发 `onEndReached` 回调。 | `Function` | `ALL` | -| onMomentumScrollBegin | 在 `ListView` 开始滑动的时候调起 | `Function` | `ALL` | -| onMomentumScrollEnd | 在 `ListView` 结束滑动的时候调起 | `Function` | `ALL` | -| onScroll | 当触发 `ListView` 的滑动事件时回调,在 `ListView` 滑动时回调,因此调用会非常频繁,请使用 `scrollEventThrottle` 进行频率控制。 注意:ListView 在滚动时会进行组件回收,不要在滚动时对 renderRow() 生成的 ListItemView 做任何 ref 节点级的操作(例如:所有 callUIFunction 和 measureInAppWindow 方法),回收后的节点将无法再进行操作而报错。横向ListView时,Android在 `2.8.0` 版本后支持 | `(obj: { contentOffset: { x: number, y: number } }) => any` | `ALL` | -| onScrollBeginDrag | 当用户开始拖拽 `ListView` 时调用。 | `Function` | `ALL` | -| onScrollEndDrag | 当用户停止拖拽 `ListView` 或者放手让 `ListView` 开始滑动的时候调用 | `Function` | `ALL` | -| scrollEventThrottle | 指定滑动事件的回调频率,传入数值指定了多少毫秒(ms)组件会调用一次 `onScroll` 回调事件 | `number` | `ALL` | -| rowShouldSticky | 在回调函数,根据传入参数index(ListView单元格的index)返回true或false指定对应的item是否需要使用悬停效果(滚动到顶部时,会悬停在List顶部,不会滚出屏幕)。 | `(index: number) => boolean` | `ALL` | -| showScrollIndicator | 是否显示垂直滚动条。 因为目前 ListView 其实仅有垂直滚动一种方向,水平滚动会导致 `onEndReached` 等一堆问题暂不建议使用,所以 `showScrollIndicator` 也仅用来控制是否显示垂直滚动条。 | `boolean` | `ALL` | -| renderPullHeader | 设置列表下拉头部(刷新条),配合`onHeaderReleased`、`onHeaderPulling` 和 `collapsePullHeader`使用, 参考 [DEMO](//github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo/src/components/PullHeader/index.jsx)。 | `() => View` | `ALL` | -| onHeaderPulling | 下拉过程中触发, 事件会通过 contentOffset 参数返回拖拽高度,可以根据下拉偏移量做相应的逻辑。 | `(obj: { contentOffset: number }) => any` | `ALL` | -| onHeaderReleased | 下拉超过内容高度,松手后触发。 | `() => any` | `ALL` | +| bounces | 是否开启回弹效果,默认 `true`, Android `2.14.1` 版本后支持该属性,老版本使用 `overScrollEnabled` | `boolean` | `Android`、`iOS` | +| overScrollEnabled | 是否开启回弹效果,默认 `true`,3.0 版本后即将废弃 | `boolean` | `Android` | +| getRowKey | 指定一个函数,在其中返回对应条目的 Key 值,详见 [React 官文](//reactjs.org/docs/lists-and-keys.html) | `(index: number) => any` | `Android、iOS、hippy-react-web、Web-Renderer` | +| getRowStyle | 设置 `ListViewItem` 容器的样式。当设置了 `horizontal=true` 启用横向 `ListView` 时,需显式设置 `ListViewItem` 宽度 | `(index: number) => styleObject` | `Android、iOS、hippy-react-web、Web-Renderer` | +| getHeaderStyle | 设置 `PullHeader` 容器的样式。当设置了 `horizontal=true` 启用横向 `ListView` 时,需显式设置 `PullHeader` 宽度。`最低支持版本2.14.1` | `() => styleObject` | `Android、iOS` | +| getFooterStyle | 设置 `PullFooter` 容器的样式。当设置了 `horizontal=true` 启用横向 `ListView` 时,需显式设置 `PullFooter` 宽度。`最低支持版本2.14.1` | `() => styleObject` | `Android、iOS` | +| getRowType | 指定一个函数,在其中返回对应条目的类型(返回Number类型的自然数,默认是0),List 将对同类型条目进行复用,所以合理的类型拆分,可以很好地提升 List 性能。`注意:同一 type 的 item 组件由于复用可能不会走完整组件创建生命周期` | `(index: number) => number` | `Android、iOS、hippy-react-web、Web-Renderer` | +| horizontal | 指定 `ListView` 是否采用横向布局。`default: undefined` 纵向布局,Android `2.14.1` 版本后可设置 `false` 显式固定纵向布局;iOS 暂不支持横向 `ListView`| `boolean \| undefined` | `Android、hippy-react-web` | +| initialListSize | 指定在组件刚挂载的时候渲染多少行数据。用这个属性来确保首屏显示合适数量的数据,而不是花费太多帧时间逐步显示出来。 | `number` | `Android、iOS、Web-Renderer` | +| initialContentOffset | 初始位移值。在列表初始化时即可指定滚动距离,避免初始化后再通过 scrollTo 系列方法产生的闪动。Android 在 `2.8.0` 版本后支持 | `number` | `Android、iOS、Web-Renderer` | +| onAppear | 当有`ListViewItem`滑动进入屏幕时(曝光)触发,入参返回曝光的`ListViewItem`对应索引值。 | `(index) => void` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onDisappear | 当有`ListViewItem`滑动离开屏幕时触发,入参返回离开的`ListViewItem`对应索引值。 | `(index) => void` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onWillAppear | 当有`ListViewItem`至少一个像素进入屏幕时(曝光)触发,入参返回曝光的`ListViewItem`对应索引值。 `最低支持版本2.3.0` | `(index) => void` | `Android、iOS` | +| onWillDisappear | 当有`ListViewItem`至少一个像素滑动离开屏幕时触发,入参返回离开的`ListViewItem`对应索引值。 `最低支持版本2.3.0`| `(index) => void` | `Android、iOS` | +| onEndReached | 当所有的数据都已经渲染过,并且列表被滚动到最后一条时,将触发 `onEndReached` 回调。 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onMomentumScrollBegin | 在 `ListView` 开始滑动的时候触发。 | `(obj: { contentOffset: { x: number, y: number } }) => any` | `Android、iOS、Web-Renderer` | +| onMomentumScrollEnd | 在 `ListView` 结束滑动的时候触发。 | `(obj: { contentOffset: { x: number, y: number } }) => any` | `Android、iOS、Web-Renderer` | +| onScroll | 在 `ListView` 滑动时回调。调用频率可能较高,可使用 `scrollEventThrottle` 进行频率控制。 注意:ListView 在滚动时会进行组件回收,不要在滚动时对 renderRow() 生成的 ListItemView 做任何 ref 节点级的操作(例如:所有 callUIFunction 和 measureInAppWindow 方法),回收后的节点将无法再进行操作而报错。横向 ListView Android 在 `2.8.0` 版本后支持 | `(obj: { contentOffset: { x: number, y: number } }) => any` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onScrollBeginDrag | 当用户开始拖拽 `ListView` 时调用。 | `(obj: { contentOffset: { x: number, y: number } }) => any` | `Android、iOS、Web-Renderer` | +| onScrollEndDrag | 当用户停止拖拽 `ListView` 或者放手让 `ListView` 开始滑动时调用 | `(obj: { contentOffset: { x: number, y: number } }) => any` | `Android、iOS、Web-Renderer` | +| preloadItemNumber | 指定当列表滚动至倒数第几行时触发 `onEndReached` 回调。 | `number` | `Android、iOS、Web-Renderer` | +| renderRow | 这里的入参是当前行的索引 index,需返回一个用于构造 `ListViewItem` 内容的 Node 节点。在这里可以凭借 index 获取到具体这一行单元格的数据,从而决定如何渲染这个单元格。 | `(index: number) => Node` | `Android、iOS、hippy-react-web、Web-Renderer` | +| rowShouldSticky | 在回调函数,根据传入参数index(ListView单元格的index)返回 true 或 false 指定对应的 item 是否需要使用悬停效果(滚动到顶部时,会悬停在List顶部,不会滚出屏幕)。 | `(index: number) => boolean` | `Android、iOS、hippy-react-web、Web-Renderer` | +| scrollEventThrottle | 指定滑动事件的回调频率,传入数值指定了多少毫秒(ms)组件会调用一次 `onScroll` 事件 | `number` | `Android、iOS、hippy-react-web、Web-Renderer` | +| scrollEnabled | 滑动是否开启。`default: true` | `boolean` | `Android、iOS、hippy-react-web、Web-Renderer` | +| showScrollIndicator | 是否显示滚动条。`default: true` | `boolean` | `iOS、hippy-react-web` | +| renderPullHeader | 设置列表下拉头部(刷新条),配合`onHeaderReleased`、`onHeaderPulling` 和 `collapsePullHeader`使用, 参考 [DEMO](//github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo/src/components/PullHeaderFooter/index.jsx)。 | `() => View` | `Android、iOS、hippy-react-web` | +| onHeaderPulling | 下拉过程中触发, 事件会通过 contentOffset 参数返回拖拽高度,可以根据下拉偏移量做相应的逻辑。 | `(obj: { contentOffset: number }) => any` | `Android、iOS、hippy-react-web` | +| onHeaderReleased | 下拉超过内容高度,松手后触发。 | `() => any` | `Android、iOS、hippy-react-web` | +| renderPullFooter | 最低支持版本`2.14.0`, 设置列表底部上拉刷新条,配合 `onFooterReleased`、`onFooterPulling` 和 `collapsePullFooter` 使用, 参考 [DEMO](//github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo/src/components/PullHeaderFooter/index.jsx)。 | `() => View` | `Android、iOS、hippy-react-web` | +| onFooterPulling | 最低支持版本`2.14.0`,上拉过程中触发, 事件会通过 contentOffset 参数返回拖拽高度,可以根据上拉偏移量做相应的逻辑。 | `(obj: { contentOffset: number }) => any` | `Android、iOS、hippy-react-web` | +| onFooterReleased | 最低支持版本`2.14.0`,上拉超出一定距离,松手后触发。 | `() => any` | `Android、iOS、hippy-react-web` | | editable | 是否可编辑,开启侧滑删除时需要设置为 `true`。`最低支持版本2.9.0` | `boolean` | `iOS` | | delText | 侧滑删除文本。`最低支持版本2.9.0` | `string` | `iOS` | -| onDelete | 在列表项侧滑删除时调起。`最低支持版本2.9.0` | `( nativeEvent: { index: number} ) => void` | `iOS` | +| onDelete | 在列表项侧滑删除时调起。`最低支持版本2.9.0` | `(nativeEvent: { index: number}) => void` | `iOS` | +| nestedScrollPriority* | 嵌套滚动事件处理优先级,`default:self`。相当于同时设置 `nestedScrollLeftPriority`、 `nestedScrollTopPriority`、 `nestedScrollRightPriority`、 `nestedScrollBottomPriority`。 `最低支持版本 2.16.0` | `enum(self,parent,none)` | `Android` | +| nestedScrollLeftPriority | 嵌套时**从右往左**滚动事件的处理优先级,会覆盖 `nestedScrollPriority` 对应方向的值。`最低支持版本 2.16.0` | `enum(self,parent,none)` | `Android` | +| nestedScrollTopPriority | 嵌套时**从下往上**滚动事件的处理优先级,会覆盖 `nestedScrollPriority` 对应方向的值。`最低支持版本 2.16.0` | `enum(self,parent,none)` | `Android` | +| nestedScrollRightPriority | 嵌套时**从左往右**滚动事件的处理优先级,会覆盖 `nestedScrollPriority` 对应方向的值。`最低支持版本 2.16.0` | `enum(self,parent,none)` | `Android` | +| nestedScrollBottomPriority | 嵌套时**从上往下**滚动事件的处理优先级,会覆盖 `nestedScrollPriority` 对应方向的值。`最低支持版本 2.16.0` | `enum(self,parent,none)` | `Android` | + +* nestedScrollPriority 的参数含义: + + * `self`(默认值):当前组件优先,滚动事件将先由当前组件消费,剩余部分传递给父组件消费; + + * `parent`:父组件优先,滚动事件将先由父组件消费,剩余部分再由当前组件消费; + + * `none`:不允许嵌套滚动,滚动事件将不会传递给父组件。 ## 方法 ### scrollToContentOffset -`(xOffset: number, yOffset: number: animated: boolean) => void` 通知 ListView 滑动到某个具体坐标偏移值(offset)的位置。 +`(xOffset: number, yOffset: number, animated: boolean) => void` 通知 ListView 滑动到某个具体坐标偏移值(offset)的位置。 > * `xOffset`: number - 滑动到 X 方向的 offset > * `yOffset`: number - 滑动到 Y 方向的 offset @@ -135,7 +157,7 @@ import icon from './qb_icon_new.png'; ### scrollToIndex -`(xIndex: number, yIndex: number: animated: boolean) => void` 通知 ListView 滑动到第几个 item。 +`(xIndex: number, yIndex: number, animated: boolean) => void` 通知 ListView 滑动到第几个 item。 > * `xIndex`: number - 滑动到 X 方向的第 xIndex 个 item > * `yIndex`: number - 滑动到 Y 方向的 yIndex 个 item @@ -143,7 +165,21 @@ import icon from './qb_icon_new.png'; ### collapsePullHeader -`() => void` 收起刷新条 PullHeader。当设置了`renderPullHeader`后,每当下拉刷新结束需要主动调用该方法收回 PullHeader。 +`(otions: { time: number }) => void` 收起刷新条 PullHeader。当设置了`renderPullHeader`后,每当下拉刷新结束需要主动调用该方法收回 PullHeader。 + +> options 参数,最低支持版本 `2.14.0` +> +>* time: number: 可指定延迟多久后收起 PullHeader,单位ms + +### expandPullHeader + +`() => void` 展开刷新条 PullHeader。刷新结束后需要主动调用`collapsePullHeader`方法收起 PullHeader。 + +### collapsePullFooter + +> 最低支持版本 `2.14.0` + +`() => void` 收起底部上拉刷新条 PullFooter。当设置了`renderPullFooter`后,每当上拉刷新结束需要主动调用该方法收回 PullFooter。 --- @@ -157,18 +193,16 @@ import icon from './qb_icon_new.png'; | 参数 | 描述 | 类型 | 支持平台 | | --------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | -------- | -| animated | 弹出时是否需要带动画 | `boolean` | `ALL` | -| animationType | 动画效果 | `enum`(none, slide, fade, slide_fade) | `ALL` | -| supportedOrientations | 支持屏幕翻转方向 | `enum`(portrait, portrait-upside-down, landscape, landscape-left, landscape-right)[] | `ALL` | -| immersionStatusBar | 是否是沉浸式状态栏。 | `boolean` | `ALL` | -| darkStatusBarText | 是否是亮色主体文字,默认字体是黑色的,改成 true 后会认为 Modal 背景为暗色调,字体就会改成白色。 | `boolean` | `ALL` | -| onShow | 在`Modal`显示时会执行此回调函数。 | `Function` | `ALL` | -| onOrientationChange | 屏幕旋转方向改变时执行会回调 | `Function` | `ALL` | -| onRequestClose | 在`Modal`请求关闭时会执行此回调函数,一般时在 Android 系统里按下硬件返回按钮时触发,一般要在里面处理关闭弹窗。 | `Function` | `ALL` | -| primaryKey | - | `string` | `iOS` | -| onDismiss | - | `Function` | `iOS` | -| transparent | 背景是否是透明的 | `boolean` | `ALL` | -| visible | 是否显示 | `boolean` | `ALL` | +| animated | 弹出时是否需要带动画 | `boolean` | `Android、iOS、hippy-react-web、Web-Renderer` | +| animationType | 动画效果 | `enum (none, slide, fade, slide_fade)` | `Android、iOS、hippy-react-web、Web-Renderer` | +| supportedOrientations | 支持屏幕翻转方向 | `enum (portrait, portrait-upside-down, landscape, landscape-left, landscape-right)[]` | `iOS` | +| immersionStatusBar | 是否是沉浸式状态栏。`default: false` | `boolean` | `Android` | +| darkStatusBarText | 是否是亮色主体文字,默认字体是黑色的,改成 true 后会认为 Modal 背景为暗色调,字体就会改成白色。 | `boolean` | `Android、iOS` | +| onShow | 在`Modal`显示时会执行此回调函数。 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onOrientationChange | 屏幕旋转方向改变时执行会回调 | `Function` | `Android、iOS` | +| onRequestClose | 在 `Modal` 请求关闭时会执行此回调函数,一般时在 Android 系统里按下硬件返回按钮时触发,一般要在里面处理关闭弹窗。 | `Function` | `Android、hippy-react-web` | +| transparent | 背景是否是透明的。`default: true` | `boolean` | `Android、iOS、hippy-react-web、Web-Renderer` | +| visible | 是否显示。`default: true` | `boolean` | `Android、iOS、hippy-react-web、Web-Renderer` | --- @@ -176,17 +210,20 @@ import icon from './qb_icon_new.png'; [[RefreshWrapper 范例]](//github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo/src/components/RefreshWrapper) -包裹住 `ListView` 提供下滑刷新功能的组件. - -> `RefreshWrapper` 现在只支持包裹一个 `ListView` 组件,暂不支持别的组件的下滑刷新功能。 +包裹住 `ListView` 或 `ViewPager` 提供滑动刷新功能的组件. ## 参数 | 参数 | 描述 | 类型 | 支持平台 | | ---------- | ---------------------------------------------------- | ---------- | -------- | -| onRefresh | 当`RefreshWrapper`执行刷新操作时,会触发到此回调函数 | `Function` | `ALL` | -| getRefresh | 定义刷新栏的视图表现,返回 `View`, `Text` 等组件。 | `Function` | `ALL` | -| bounceTime | 指定刷新条收回动画的时长,单位为ms | `number` | `ALL` | +| onRefresh | 当`RefreshWrapper`的`Refresh Header`执行刷新操作时,会触发到此回调函数 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| getRefresh | 定义`Refresh Header`刷新栏的视图表现,返回 `View`, `Text` 等组件。 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| bounceTime | 指定刷新条收回动画的时长,单位为ms | `number` | `Android、iOS、Web-Renderer` | + +| hiddenHeader | 是否显示`RefreshWrapper`的`Refresh Header`,`default: false`,`最低支持版本 2.17.6` | `boolean` | `Android、iOS` | +| showFooter | 是否显示`RefreshWrapper`的`Refresh Footer`,`default: false`,`最低支持版本 2.17.6` | `boolean` | `Android、iOS` | +| onFooterRefresh | 当`RefreshWrapper`的`Refresh Footer`执行刷新操作时,会触发到此回调函数。 `最低支持版本 2.17.6` | `Function` | `Android、iOS` | +| getFooterRefresh | 定义`Refresh Footer`刷新栏的视图表现,返回 `View`, `Text` 等组件。`最低支持版本 2.17.6` | `Function` | `Android、iOS` | ## 方法 @@ -213,22 +250,36 @@ import icon from './qb_icon_new.png'; ## 参数 -| 参数 | 描述 | 类型 | 支持平台 | -| ------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | -------- | -| bounces | 是否开启回弹效果,默认 `true` | `boolean` | `iOS` | -| contentContainerStyle | 这些样式会应用到一个内层的内容容器上,所有的子视图都会包裹在内容容器内。 | `StyleSheet` | `ALL` | -| onMomentumScrollBegin | 在 `ScrollView` 滑动开始的时候调起。 | `Function` | `ALL` | -| onMomentumScrollEnd | 在 `ScrollView` 滑动结束的时候调起。 | `Function` | `ALL` | -| onScroll | 在滚动的过程中,每帧最多调用一次此回调函数。 | `Function` | `ALL` | -| onScrollBeginDrag | 当用户开始拖拽 `ScrollView` 时调用。 | `Function` | `ALL` | -| onScrollEndDrag | 当用户停止拖拽 `ScrollView` 或者放手让 `ScrollView` 开始滑动的时候调用。 | `Function` | `ALL` | -| scrollEventThrottle | 指定滑动事件的回调频率,传入数值指定了多少毫秒(ms)组件会调用一次 `onScroll` 回调事件。 | `number` | `ALL` | -| scrollIndicatorInsets | 决定滚动条距离视图边缘的坐标。这个值应该和contentInset一样。 | `{ top: number, left: number, bottom: number, right: number }` | `ALL` | -| pagingEnabled | 当值为 `true` 时,滚动条会停在滚动视图的尺寸的整数倍位置。这个可以用在水平分页上。`default: false` | `boolean` | `ALL` | -| scrollEnabled | 当值为 `false` 的时候,内容不能滚动。`default: true` | `boolean` | `ALL` | -| horizontal | 当此属性为 `true` 的时候,所有的子视图会在水平方向上排成一行,而不是默认的在垂直方向上排成一列 | `boolean` | `ALL` | -| showsHorizontalScrollIndicator | 当此值设为 `false` 的时候,`ScrollView` 会隐藏水平的滚动条。`default: true` | `boolean` | `iOS` | -| showsVerticalScrollIndicator | 当此值设为 `false` 的时候,`ScrollView` 会隐藏垂直的滚动条。 `default: true` | `boolean` | `iOS` | +| 参数 | 描述 | 类型 | 支持平台 | +| ------------------------------ |----------------------------------------------------------------------------------------------------------------------------------------------------------------------| ------------------------------------------------------------ | -------- | +| bounces | 是否开启回弹效果,默认 `true` | `boolean` | `iOS` | +| contentContainerStyle | 这些样式会应用到一个内层的内容容器上,所有的子视图都会包裹在内容容器内。 | `StyleSheet` | `Android、iOS、hippy-react-web、Web-Renderer` | +| horizontal | 当此属性为 `true` 的时候,所有的子视图会在水平方向上排成一行,而不是默认的在垂直方向上排成一列 | `boolean` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onMomentumScrollBegin | 在 `ScrollView` 滑动开始的时候调起。 | `(obj: { contentOffset: { x: number, y: number } }) => any` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onMomentumScrollEnd | 在 `ScrollView` 滑动结束的时候调起。 | `(obj: { contentOffset: { x: number, y: number } }) => any` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onScroll | 在滚动的过程中,每帧最多调用一次此回调函数。 | `(obj: { contentOffset: { x: number, y: number } }) => any` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onScrollBeginDrag | 当用户开始拖拽 `ScrollView` 时调用。 | `(obj: { contentOffset: { x: number, y: number } }) => any` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onScrollEndDrag | 当用户停止拖拽 `ScrollView` 或者放手让 `ScrollView` 开始滑动的时候调用。 | `(obj: { contentOffset: { x: number, y: number } }) => any` | `Android、iOS、hippy-react-web、Web-Renderer` | +| pagingEnabled | 当值为 `true` 时,滚动条会停在滚动视图的尺寸的整数倍位置。这个可以用在水平分页上。`default: false` | `boolean` | `Android、iOS、hippy-react-web、Web-Renderer` | +| scrollEventThrottle | 指定滑动事件的回调频率,传入数值指定了多少毫秒(ms)组件会调用一次 `onScroll` 回调事件。 | `number` | `Android、iOS、hippy-react-web、Web-Renderer` | +| scrollIndicatorInsets | 决定滚动条距离视图边缘的坐标。这个值应该和contentInset一样。 | `{ top: number, left: number, bottom: number, right: number }` | `Android、iOS` | +| scrollEnabled | 当值为 `false` 的时候,内容不能滚动。`default: true` | `boolean` | `Android、iOS、hippy-react-web、Web-Renderer` | +| showScrollIndicator | 是否显示滚动条。 `default: false` | `boolean` | `Android、hippy-react-web` | +| showsHorizontalScrollIndicator | 当此值设为 `false` 的时候,`ScrollView` 会隐藏水平的滚动条。`default: true` | `boolean` | `iOS` | +| showsVerticalScrollIndicator | 当此值设为 `false` 的时候,`ScrollView` 会隐藏垂直的滚动条。 `default: true` | `boolean` | `iOS` | +| nestedScrollPriority* | 嵌套滚动事件处理优先级,`default:self`。相当于同时设置 `nestedScrollLeftPriority`、 `nestedScrollTopPriority`、 `nestedScrollRightPriority`、 `nestedScrollBottomPriority`。 `最低支持版本 2.16.0` | `enum(self,parent,none)` | `Android` | +| nestedScrollLeftPriority | 嵌套时**从右往左**滚动事件的处理优先级,会覆盖 `nestedScrollPriority` 对应方向的值。`最低支持版本 2.16.0` | `enum(self,parent,none)` | `Android` | +| nestedScrollTopPriority | 嵌套时**从下往上**滚动事件的处理优先级,会覆盖 `nestedScrollPriority` 对应方向的值。`最低支持版本 2.16.0` | `enum(self,parent,none)` | `Android` | +| nestedScrollRightPriority | 嵌套时**从左往右**滚动事件的处理优先级,会覆盖 `nestedScrollPriority` 对应方向的值。`最低支持版本 2.16.0` | `enum(self,parent,none)` | `Android` | +| nestedScrollBottomPriority | 嵌套时**从上往下**滚动事件的处理优先级,会覆盖 `nestedScrollPriority` 对应方向的值。`最低支持版本 2.16.0` | `enum(self,parent,none)` | `Android` | + +* nestedScrollPriority 的参数含义: + + * `self`(默认值):当前组件优先,滚动事件将先由当前组件消费,剩余部分传递给父组件消费; + + * `parent`:父组件优先,滚动事件将先由父组件消费,剩余部分再由当前组件消费; + + * `none`:不允许嵌套滚动,滚动事件将不会传递给父组件。 ## 方法 @@ -246,7 +297,7 @@ import icon from './qb_icon_new.png'; > * x: number - X 偏移值 > * y: number - Y 偏移值 -> * duration: number - 毫秒为单位的滚动时间 +> * duration: number - 毫秒为单位的滚动时间,默认 1000ms --- @@ -303,25 +354,34 @@ import icon from './qb_icon_new.png'; | 参数 | 描述 | 类型 | 支持平台 | | --------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | --------- | -| defaultValue | 提供一个文本框中的初始值。当用户开始输入的时候,值就可以改变。 在一些简单的使用情形下,如果你不想用监听消息然后更新 value 属性的方法来保持属性和状态同步的时候,就可以用 defaultValue 来代替。 | `string` | `ALL` | -| editable | 如果为 false,文本框是不可编辑的。 | `boolean` | `ALL` | -| keyboardType | 决定弹出的何种软键盘的。 注意,`password`仅在属性 `multiline=false` 单行文本框时生效。 | `enum`(default, numeric, password, email, phone-pad) | `ALL` | -| maxLength | 限制文本框中最多的字符数。使用这个属性而不用JS 逻辑去实现,可以避免闪烁的现象。 | `numbers` | `ALL` | -| multiline | 如果为 `true` ,文本框中可以输入多行文字。 由于终端特性。 | `boolean` | `ALL` | -| numberOfLines | 设置 `TextInput` 的最大行数,在使用的时候必需同时设置 `multiline` 参数为 `true`。 | `number` | `ALL` | -| onBlur | 当文本框失去焦点的时候调用此回调函数。 | `Function` | `ALL` | -| onChangeText | 当文本框内容变化时调用此回调函数。改变后的文字内容会作为参数传递。 | `Function` | `ALL` | -| onKeyboardWillShow | 在弹出输入法键盘时候会触发此回调函数,返回值包含键盘高度 `keyboardHeight`,样式如 `{ keyboardHeight: 260}`。仅在 `iOS` 可用,`Android` 输入法不会遮挡App画面 | `Function` | `iOS` | -| onEndEditing | 当文本输入结束后调用此回调函数。 | `Function` | `ALL` | -| onLayout | 当组件挂载或者布局变化的时候调用,参数为`{ x, y, width, height }`。 | `Function` | `ALL` | -| onSelectionChange | 当输入框选择文字的范围被改变时调用。返回参数的样式如 `{ nativeEvent: { selection: { start, end } } }`。 | `Function` | `ALL` | -| placeholder | 如果没有任何文字输入,会显示此字符串。 | `string` | `ALL` | -| placeholderTextColor | 占位字符串显示的文字颜色。 | [`color`](style/color.md) | `ALL` | -| placeholderTextColors | - | [`color`](style/color.md) | `ALL` | -| returnKeyType | 指定软键盘的回车键显示的样式。 | `enum`(done, go, next, search, send) | `ALL` | -| underlineColorAndroid | `TextInput` 下底线的颜色。 可以设置为'transparent'来去掉下底线。 | `string` | `Android` | -| value | 指定 `TextInput` 组件的值。 | `string` | `ALL` | -| autoFocus | 组件渲染时自动获得焦点。 | `boolean` | `ALL` | +| caretColor | 输入光标颜色。(也可设置为 Style 属性) `最低支持版本2.11.5` | [`color`](style/color.md) | `Android、hippy-react-web` | +| defaultValue | 提供一个文本框中的初始值。当用户开始输入的时候,值就可以改变。 在一些简单的使用情形下,如果你不想用监听消息然后更新 value 属性的方法来保持属性和状态同步的时候,就可以用 defaultValue 来代替。 | `string` | `Android、iOS、hippy-react-web、Web-Renderer` | +| editable | 如果为 false,文本框是不可编辑的。 `default: true` | `boolean` | `Android、iOS、hippy-react-web、Web-Renderer` | +| keyboardType | 决定弹出的何种软键盘的。 注意,`password`仅在属性 `multiline=false` 单行文本框时生效。 | `enum (default, numeric, password, email, phone-pad)` | `Android、iOS、hippy-react-web、Web-Renderer` | +| maxLength | 限制文本框中最多的字符数。使用这个属性而不用JS 逻辑去实现,可以避免闪烁的现象。 | `number` | `Android、iOS、hippy-react-web、Web-Renderer` | +| multiline | 如果为 `true` ,文本框中可以输入多行文字。 | `boolean` | `Android、iOS、hippy-react-web、Web-Renderer` | +| numberOfLines | 设置 `TextInput` 最大显示行数,如果 `TextInput` 没有显式设置高度,会根据 `numberOfLines` 来计算高度撑开。在使用的时候必需同时设置 `multiline` 参数为 `true`。 | `number` | `Android、hippy-react-web、Web-Renderer` | +| onBlur | 当文本框失去焦点的时候调用此回调函数。 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onFocus | 当文本框获得焦点的时候调用此回调函数。 | `Function` | `Android、iOS` | +| onChangeText | 当文本框内容变化时调用此回调函数。改变后的文字内容会作为参数传递。 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onKeyboardWillShow | 在弹出输入法键盘时候会触发此回调函数,返回值包含键盘高度 `keyboardHeight`,样式如 `{ keyboardHeight: 260 }`。 | `Function` | `Android、iOS、hippy-react-web` | +| onKeyboardWillHide | 在隐藏输入法键盘时候会触发此回调函数 `iOS最低支持版本2.16.0` | `Function` | `Android、iOS` | +| onKeyboardHeightChanged | 在输入法键盘高度改变时触发此回调函数,返回值包含键盘高度 `keyboardHeight`,样式如 `{ keyboardHeight: 260 }`, `最低支持版本2.14.0`。 | `Function` | `iOS` | +| onEndEditing | 当文本输入结束后调用此回调函数。 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onLayout | 当组件挂载或者布局变化的时候调用,参数为`nativeEvent: { layout: { x, y, width, height } }`,其中 `x` 和 `y` 为相对父元素的坐标位置。 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onSelectionChange | 当输入框选择文字的范围被改变时调用。返回参数的样式如 `nativeEvent: { selection: { start, end } }`。 | `Function` | `Android、iOS、Web-Renderer` | +| placeholder | 如果没有任何文字输入,会显示此字符串。 | `string` | `Android、iOS、hippy-react-web、Web-Renderer` | +| placeholderTextColor | 占位字符串显示的文字颜色。(也可设置为 Style 属性)`最低支持版本2.13.4` | [`color`](style/color.md) | `Android、iOS、Web-Renderer` | +| returnKeyType | 指定软键盘的回车键显示的样式。 | `enum (done, go, next, search, send)` | `Android、iOS、Web-Renderer` | +| underlineColorAndroid | `TextInput` 下底线的颜色。 可以设置为 'transparent' 来去掉下底线。(也可设置为 Style 属性) | [`color`](style/color.md) | `Android` | +| value | 指定 `TextInput` 组件的值。 | `string` | `Android、iOS、hippy-react-web、Web-Renderer` | +| autoFocus | 组件渲染时自动获得焦点。 | `boolean` | `Android、iOS、hippy-react-web、Web-Renderer` | +| breakStrategy* | 设置Android API 23及以上系统的文本折行策略。`default: simple` | `enum(simple, high_quality, balanced)` | `Android(版本 2.14.2以上)` | + +* breakStrategy 的参数含义: + * `simple`(默认值):简单折行,每一行显示尽可能多的字符,直到这一行不能显示更多字符时才进行换行,这种策略下不会自动折断单词(当一行只有一个单词并且宽度显示不下的情况下才会折断); + * `high_quality`:高质量折行,针对整段文本的折行进行布局优化,必要时会自动折断单词,比其他两种策略略微影响性能,通常比较适合只读文本; + * `balanced`:平衡折行,尽可能保证一个段落的每一行的宽度相同,必要时会折断单词。 ## 方法 @@ -339,11 +399,7 @@ import icon from './qb_icon_new.png'; ### getValue -`() => Promise` 获得文本框中的内容。 - -### hideInputMethod - -`() => void` 隐藏软键盘。 +`() => Promise` 获得文本框中的内容。注意,由于是异步回调,收到回调时值可能已经改变。 ### setValue @@ -351,9 +407,11 @@ import icon from './qb_icon_new.png'; > * value: string - 文本框内容 -### showInputMethod +### isFocused -`() => void` 显示软键盘。 +`最低支持版本 2.14.1。hippy-react-web 不支持。` + +`() => Promise` 获得文本框的焦点状态。注意,由于是异步回调,收到回调时值可能已经改变。 --- @@ -363,41 +421,37 @@ import icon from './qb_icon_new.png'; 文本组件。 -## 注意事项 - -需要注意的是,在 `` 中拼接字符串时,推荐使用 ES6 的[模板字符串](//developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals): - -```jsx -{ `现在时间是 ${new Date().toString()}` } // ✅ -``` - -而不是 - -```jsx -现在时间是 { new Date().toString() } // ❌ -``` - -后者有可能在数据更新时不会更新界面。 - ## 属性 | 参数 | 描述 | 类型 | 支持平台 | | ------------- | ------------------------------------------------------------ | ----------------------------------------- | -------- | -| numberOfLines | 用来当文本过长的时候裁剪文本。包括折叠产生的换行在内,总的行数不会超过这个属性的限制。 | `number` | `ALL` | -| opacity | 配置 `View` 的透明度,同时会影响子节点的透明度。 | `number` | `ALL` | -| onLayout | 当元素挂载或者布局改变的时候调用,参数为: `{ nativeEvent: { layout: { x, y, width, height } } }`。 | `Function` | `ALL` | -| onClick | 当文本被点击以后调用此回调函数。 例如, `onClick={() => console.log('onClick') }` | `Function` | `ALL` | -| ellipsizeMode* | 当设定了 `numberOfLines` 值后,这个参数指定了字符串如何被截断。所以在使用 `ellipsizeMode` 时,必须得同时指定 `numberOfLines` 数值。 | `enum`(head, middle, tail, clip)| `Android 仅支持 tail 属性,iOS 全支持` | -| onTouchDown | 当用户开始触屏控件时(即用户在该控件上按下手指时),将回调此函数,并将触屏点信息作为参数传递进来; 参数为 `{ nativeEvent: { name, page_x, page_y, id } }` | `Function` | `ALL` | -| onTouchMove | 当用户在控件移动手指时,此函数会持续收到回调,并通过event参数告知控件的触屏点信息;参数为 `{ nativeEvent: { name, page_x, page_y, id } }` | `Function` | `ALL` | -| onTouchEnd | 当触屏操作结束,用户在该控件上抬起手指时,此函数将被回调,event参数也会通知当前的触屏点信息;参数为 `{ nativeEvent: { name, page_x, page_y, id } }` | `Function` | `ALL` | -| onTouchCancel | 当用户触屏过程中,某个系统事件中断了触屏,例如电话呼入、组件变化(如设置为hidden),此函数会收到回调,触屏点信息也会通过event参数告知前端;参数为 `{ nativeEvent: { name, page_x, page_y, id } }` | `Function` | `ALL` | +| numberOfLines | 用来当文本过长的时候裁剪文本。包括折叠产生的换行在内,总的行数不会超过这个属性的限制。 | `number` | `Android、iOS、hippy-react-web、Web-Renderer` | +| opacity | 配置 `View` 的透明度,同时会影响子节点的透明度。 | `number` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onLayout | 当元素挂载或者布局改变的时候调用,参数为: `nativeEvent: { layout: { x, y, width, height } }`,其中 `x` 和 `y` 为相对父元素的坐标位置。 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onClick | 当文本被点击以后调用此回调函数。 例如, `onClick={() => console.log('onClick') }` | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| ellipsizeMode* | 当设定了 `numberOfLines` 值后,这个参数指定了字符串如何被截断。所以在使用 `ellipsizeMode` 时,必须得同时指定 `numberOfLines` 数值。 `default: tail` | `enum(head, middle, tail, clip)` | `Android 仅支持 tail 属性,iOS 全支持、hippy-react-web(clip、ellipsis)、Web-Renderer(clip、ellipsis)` | +| onTouchDown | 当用户开始在控件上按下手指时,将回调此函数,并将触屏点信息作为参数传递进来; 参数为 `nativeEvent: { name, page_x, page_y, id }`, `page_x` 和 `page_y` 分别表示点击在屏幕内的绝对位置| `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onTouchMove | 当用户在控件移动手指时,此函数会持续收到回调,并通过event参数告知控件的触屏点信息;参数为 `nativeEvent: { name, page_x, page_y, id }`,`page_x` 和 `page_y` 分别表示点击在屏幕内的绝对位置 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onTouchEnd | 当触屏操作结束,用户在该控件上抬起手指时,此函数将被回调,event参数也会通知当前的触屏点信息;参数为 `nativeEvent: { name, page_x, page_y, id }`,`page_x` 和 `page_y` 分别表示点击在屏幕内的绝对位置 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onTouchCancel | 当用户触屏过程中,某个系统事件中断了触屏,例如电话呼入、组件变化(如设置为hidden)、其他组件的滑动手势,此函数会收到回调,触屏点信息也会通过event参数告知前端;参数为 `nativeEvent: { name, page_x, page_y, id }`,`page_x` 和 `page_y` 分别表示点击在屏幕内的绝对位置 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| breakStrategy* | 设置Android API 23及以上系统的文本折行策略。`default: simple` | `enum(simple, high_quality, balanced)` | `Android(版本 2.14.2以上)` | +| verticalAlign* | 设置文本组件内嵌套文本组件或文本组件内嵌套图片组件时的对齐策略。`default: baseline` | `enum(top, middle, baseline, bottom)` | `Android、iOS(版本2.16.0以上)` | +| forbidUnicodeToChar | 是否禁止文本组件内嵌套文本转换成真实的字符。`default: false` | `boolean` | `hippy-react` | * ellipsizeMode 的参数含义: - * `clip` - 超过指定行数的文字会被直接截断,不显示“...”;(仅iOS支持) - * `head` - 文字将会从头开始截断,保证字符串的最后的文字可以正常显示在 `Text` 组件的最后,而从开头给截断的文字,将以 “...” 代替,例如 “...wxyz”;(仅iOS支持) - * `middle` - "文字将会从中间开始截断,保证字符串的最后与最前的文字可以正常显示在Text组件的响应位置,而中间给截断的文字,将以 “...” 代替,例如 “ab...yz”;(仅iOS支持) - * `tail` - 文字将会从最后开始截断,保证字符串的最前的文字可以正常显示在 Text 组件的最前,而从最后给截断的文字,将以 “...” 代替,例如 “abcd...”; + * `clip` - 超过指定行数的文字会被直接截断,不显示“...”;(Android 2.14.1以上、iOS) + * `head` - 文字将会从头开始截断,保证字符串的最后的文字可以正常显示在 `Text` 组件的最后,而从开头给截断的文字,将以 “...” 代替,例如 “...wxyz”;(Android 2.14.1 以上、iOS全支持) + * `middle` - "文字将会从中间开始截断,保证字符串的最后与最前的文字可以正常显示在Text组件的响应位置,而中间给截断的文字,将以 “...” 代替,例如 “ab...yz”;(Android 2.14.1 以上、iOS全支持) + * `tail`(默认值) - 文字将会从最后开始截断,保证字符串的最前的文字可以正常显示在 Text 组件的最前,而从最后给截断的文字,将以 “...” 代替,例如 “abcd...”; +* breakStrategy 的参数含义: + * `simple`(默认值):简单折行,每一行显示尽可能多的字符,直到这一行不能显示更多字符时才进行换行,这种策略下不会自动折断单词(当一行只有一个单词并且宽度显示不下的情况下才会折断); + * `high_quality`:高质量折行,针对整段文本的折行进行布局优化,必要时会自动折断单词,比其他两种策略略微影响性能,通常比较适合只读文本; + * `balanced`:平衡折行,尽可能保证一个段落的每一行的宽度相同,必要时会折断单词。 +* verticalAlign 的参数含义: + * `top`: 行顶部对齐 + * `middle`: 居中对齐 + * `baseline`: 基线对齐 + * `bottom`: 行底部对齐 --- @@ -407,27 +461,46 @@ import icon from './qb_icon_new.png'; 最基础的容器组件,它是一个支持Flexbox布局、样式、一些触摸处理、和一些无障碍功能的容器,并且它可以放到其它的视图里,也可以有任意多个任意类型的子视图。不论在什么平台上,`View` 都会直接对应一个平台的原生视图。 +!> Android 具有节点优化的特性,请注意 `collapsable` 属性的使用 + ## 属性 | 参数 | 描述 | 类型 | 支持平台 | | ------------------ | ------------------------------------------------------------ | ------------------------------------ | --------- | -| accessible | 当此属性为 `true` 时,表示此视图时一个启用了无障碍功能的元素。启用无障碍的其他属性时,必须优先设置 `accessible` 为 `true`。 | `boolean` | `ALL` | -| accessibilityLabel | 设置当用户与此元素交互时,“读屏器”(对视力障碍人士的辅助功能)阅读的文字。默认情况下,这个文字会通过遍历所有的子元素并累加所有的文本标签来构建。 | `node` | `ALL` | -| style | - | [`View Styles`](style/layout.md) | `ALL` | -| opacity | 配置 `View` 的透明度,同时会影响子节点的透明度 | `number` | `ALL` | -| overflow | 指定当子节点内容溢出其父级 `View` 容器时, 是否剪辑内容 | `enum`(visible, hidden) | `ALL` | -| onLayout | 这个事件会在布局计算完成后立即调用一次,不过收到此事件时新的布局可能还没有在屏幕上呈现,尤其是一个布局动画正在进行中的时候。 | `Function` | `ALL` | -| onAttachedToWindow | 这个事件会在节点已经渲染并且添加到容器组件中触发,因为 Hippy 的渲染是异步的,这是很稳妥的执行后续操作的事件。 | `Function` | `ALL` | -| onTouchDown | 当用户开始触屏控件时(即用户在该控件上按下手指时),将回调此函数,并将触屏点信息作为参数传递进来; 参数为 `{ nativeEvent: { name, page_x, page_y, id } }` | `Function` | `ALL` | -| onTouchMove | 当用户在控件移动手指时,此函数会持续收到回调,并通过event参数告知控件的触屏点信息;参数为 `{ nativeEvent: { name, page_x, page_y, id } }` | `Function` | `ALL` | -| onTouchEnd | 当触屏操作结束,用户在该控件上抬起手指时,此函数将被回调,event参数也会通知当前的触屏点信息;参数为 `{ nativeEvent: { name, page_x, page_y, id } }` | `Function` | `ALL` | -| onTouchCancel | 当用户触屏过程中,某个系统事件中断了触屏,例如电话呼入、组件变化(如设置为hidden),此函数会收到回调,触屏点信息也会通过event参数告知前端;参数为 `{ nativeEvent: { name, page_x, page_y, id } }` | `Function` | `ALL` | - -## 样式内特殊属性 +| accessible | 当此属性为 `true` 时,表示此视图时一个启用了无障碍功能的元素。启用无障碍的其他属性时,必须优先设置 `accessible` 为 `true`。 | `boolean` | `Android、iOS、hippy-react-web` | +| accessibilityLabel | 设置当用户与此元素交互时,“读屏器”(对视力障碍人士的辅助功能)阅读的文字。默认情况下,这个文字会通过遍历所有的子元素并累加所有的文本标签来构建。 | `string` | `Android、iOS、hippy-react-web` | +| collapsable | Android 里如果一个 `View` 只用于布局它的子组件,则它可能会为了优化而从原生布局树中移除,因此该节点 DOM 的引用会丢失 `(比如调用 measureInAppWindow 无法获取到大小和位置信息)`。 把此属性设为 `false` 可以禁用这个优化,以确保对应视图在原生结构中存在。`(Android 2.14.1 版本后支持在 Attribute 设置,以前版本请在 Style 属性里设置)` | `boolean` | `Android` | +| style | - | [`View Styles`](style/layout.md) | `Android、iOS、hippy-react-web、Web-Renderer` | +| opacity | 配置 `View` 的透明度,同时会影响子节点的透明度 | `number` | `Android、iOS、hippy-react-web、Web-Renderer` | +| overflow | 指定当子节点内容溢出其父级 `View` 容器时, 是否剪辑内容 | `enum(visible, hidden)` | `Android、iOS、hippy-react-web、Web-Renderer` | +| nativeBackgroundAndroid | 配置水波纹效果,`最低支持版本 2.13.1`;配置项为 `{ borderless: boolean, color: Color, rippleRadius: number }`; `borderless` 表示波纹是否有边界,默认 false;`color` 波纹颜色;`rippleRadius` 波纹半径,若不设置,默认容器边框为边界; `注意:设置水波纹后默认不显示,需要在对应触摸事件中调用 setPressed 和 setHotspot 方法进行水波纹展示,详情参考相关`[demo](//github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo/src/components/RippleViewAndroid/index.jsx) | `Object`| `Android` | +| onLayout | 当元素挂载或者布局改变的时候调用,参数为: `nativeEvent: { layout: { x, y, width, height } }`,其中 `x` 和 `y` 为相对父元素的坐标位置。 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onAttachedToWindow | 这个事件会在节点已经渲染并且添加到容器组件中触发,因为 Hippy 的渲染是异步的,这是很稳妥的执行后续操作的事件。 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onTouchDown | 当用户开始在控件上按下手指时,将回调此函数,并将触屏点信息作为参数传递进来; 参数为 `nativeEvent: { name, page_x, page_y, id }`, `page_x` 和 `page_y` 分别表示点击在屏幕内的绝对位置| `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onTouchMove | 当用户在控件移动手指时,此函数会持续收到回调,并通过event参数告知控件的触屏点信息;参数为 `nativeEvent: { name, page_x, page_y, id }`,`page_x` 和 `page_y` 分别表示点击在屏幕内的绝对位置 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onTouchEnd | 当触屏操作结束,用户在该控件上抬起手指时,此函数将被回调,event参数也会通知当前的触屏点信息;参数为 `nativeEvent: { name, page_x, page_y, id }`,`page_x` 和 `page_y` 分别表示点击在屏幕内的绝对位置 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onTouchCancel | 当用户触屏过程中,某个系统事件中断了触屏,例如电话呼入、组件变化(如设置为hidden)、其他组件的滑动手势,此函数会收到回调,触屏点信息也会通过event参数告知前端;参数为 `nativeEvent: { name, page_x, page_y, id }`,`page_x` 和 `page_y` 分别表示点击在屏幕内的绝对位置 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer` | -| 参数 | 描述 | 类型 | 支持平台 | -| ------------------ | ------------------------------------------------------------ | ------------------------------------ | --------- | -| collapsable | Android 里如果一个 `View` 只用于布局它的子组件,则它可能会为了优化而从原生布局树中移除,因此该节点 DOM 的引用会丢失。 把此属性设为 `false` 可以禁用这个优化,以确保对应视图在原生结构中存在。 | `boolean` | `Android` | + +## 方法 + +### setPressed + +[[setPressed 范例]](//github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo/src/components/RippleViewAndroid/RippleViewAndroid.jsx) + +`最低支持版本 2.13.1。hippy-react-web、Web-Renderer 不支持` + +`(pressed: boolean) => void` 通过传入一个布尔值,通知终端当前是否需要显示水波纹效果 + +> * pressed: boolean - true 显示水波纹,false 收起水波纹 + +### setHotspot + +[[setHotspot 范例]](//github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo/src/components/RippleViewAndroid/RippleViewAndroid.jsx) + +`最低支持版本 2.13.1 hippy-react-web、Web-Renderer 不支持` + +`(x: number, y: number) => void` 通过传入一个 `x, y` 坐标值,通知终端设置当前波纹中心位置 --- @@ -441,12 +514,13 @@ import icon from './qb_icon_new.png'; | 参数 | 描述 | 类型 | 支持平台 | | ------------------------ | ------------------------------------------------------------ | -------------------------------------------- | -------- | -| initialPage | 指定一个数字,用于决定初始化后默认显示的页面index,默认不指定的时候是0 | `number` | `ALL` | -| scrollEnabled | 指定ViewPager是否可以滑动,默认为true | `boolean` | `ALL` | -| onPageSelected | 指定一个函数,当page被选中时进行回调,回调参数是一个对象event,包括position值 回调参数: `position`: number -被选中即将滑到的目标page的index | `(obj: {position: number}) => void` | `ALL` | -| onPageScroll | 指定一个函数,当page被滑动时进行回调,回调参数是一个对象event,包括position值与offset值 回调参数: `position`: number -即将滑到的目标page的index `offset`: number -当前被选中的page的相对位移,取值范围-1到1 | `(obj: {position: number, offset: number}) => void` | `ALL` | -| onPageScrollStateChanged | 指定一个函数,当page的滑动状态改变时进行回调 回调参数: `pageScrollState`: string -改变后的状态,idle表示停止,dragging表示用户用手拖拽,settling表示page正在滑动 | `(pageScrollState: string) => void` | `ALL` | -| direction | 设置viewPager滚动方向,不设置默认横向滚动,设置 `vertical` 为竖向滚动 | `string` | `Android` | +| bounces | 是否开启回弹效果,默认 `true` | `boolean` | `iOS` | +| initialPage | 指定一个数字,用于决定初始化后默认显示的页面 index,默认不指定的时候是0 | `number` | `Android、iOS、hippy-react-web、Web-Renderer` | +| scrollEnabled | 指定 ViewPager 是否可以滑动,默认为 `true` | `boolean` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onPageSelected | 指定一个函数,当 page 被选中时进行回调。回调参数是一个 event 对象,回调参数: `position: number` - 表示即将滑到的目标 page 的索引 | `(obj: {position: number}) => void` | `Android、iOS、hippy-react-web、Web-Renderer` | +| onPageScroll | 指定一个函数,当 page 被滑动时进行回调。回调参数是一个 event 对象,回调参数 `position: number` - 表示即将滑到的目标 page 的索引,`offset: number` - 当前被选中的 page 的相对位移,取值范围 -1 到 1 | `(obj: {position: number, offset: number}) => void` | `Android、iOS、Web-Renderer` | +| onPageScrollStateChanged | 指定一个函数,当 page 的滑动状态改变时进行回调。回调参数: `pageScrollState: string` - 改变后的状态,`idle` 表示停止,`dragging` 表示用户用手拖拽,`settling` 表示 page 正在滑动 | `(pageScrollState: string) => void` | `Android、iOS、hippy-react-web、Web-Renderer` | +| direction | 设置 viewPager 滚动方向,不设置默认横向滚动,设置 `vertical` 为竖向滚动 | `string` | `Android、hippy-react-web` | ## 方法 @@ -466,7 +540,7 @@ import icon from './qb_icon_new.png'; # WaterfallView -> 最低支持版本 2.9.0 +> 最低支持版本 2.9.0。hippy-react-web 不支持 [[WaterfallView 范例]](//github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo/src/components/WaterfallView) @@ -474,25 +548,25 @@ import icon from './qb_icon_new.png'; ## 参数 -| 参数 | 描述 | 类型 | 支持平台 | -| --------------------- | ------------------------------------------------------------ | ----------------------------------------------------------- | -------- | -| numberOfColumns | 瀑布流列数量 , Default: 2 | `number` | `ALL` | -| numberOfItems | 瀑布流 item 总个数 | `number` | `ALL`| -| columnSpacing | 瀑布流每列之前的水平间距 | `number` | `ALL` | -| interItemSpacing | item 间的垂直间距 | `number` | `ALL` | -| contentInset | 内容缩进 ,默认值 `{ top:0, left:0, bottom:0, right:0 }` | `Object` | `ALL` | -| renderItem | 这里的入参是当前 item 的 index,在这里可以凭借 index 获取到瀑布流一个具体单元格的数据,从而决定如何渲染这个单元格。 | `(index: number) => React.ReactElement` | `ALL` | -| renderBanner | 如何渲染 Banner。 | `() => React.ReactElement` | `iOS` -| getItemStyle | 设置`WaterfallItem`容器的样式。 | `(index: number) => styleObject` | `ALL` | -| getItemType | 指定一个函数,在其中返回对应条目的类型(返回Number类型的自然数,默认是0),List 将对同类型条目进行复用,所以合理的类型拆分,可以很好地提升list 性能。 | `(index: number) => number` | `ALL` | -| getItemKey | 指定一个函数,在其中返回对应条目的 Key 值,详见 [React 官文](//reactjs.org/docs/lists-and-keys.html) | `(index: number) => any` | `ALL` | -| preloadItemNumber | 滑动到瀑布流底部前提前预加载的 item 数量 | `number` | `ALL` | -| onEndReached | 当所有的数据都已经渲染过,并且列表被滚动到最后一条时,将触发 `onEndReached` 回调。 | `Function` | `ALL` | -| containPullHeader | 是否包含`PullHeader`组件,默认 `false` ;`Android` 暂不支持,可暂时用 `RefreshWrapper` 组件替代 | `boolean` | `iOS` | -| renderPullHeader | 如何渲染 `PullHeader`,此时 `containPullHeader` 默认设置成 `true` | `() => React.ReactElement` | `iOS` | -| containPullFooter | 是否包含`PullFooter`组件,默认 `false` | `boolean` | `ALL` | -| renderPullFooter | 如何渲染 `PullFooter`,此时 `containPullFooter` 默认设置成 `true` | `() => React.ReactElement` | `ALL` | -| onScroll | 当触发 `WaterFall` 的滑动事件时回调。`startEdgePos`表示距离 List 顶部边缘滚动偏移量;`endEdgePos`表示距离 List 底部边缘滚动偏移量;`firstVisibleRowIndex`表示当前可见区域内第一个元素的索引;`lastVisibleRowIndex`表示当前可见区域内最后一个元素的索引;`visibleRowFrames`表示当前可见区域内所有 item 的信息(x,y,width,height) | `{ nativeEvent: { startEdgePos: number, endEdgePos: number, firstVisibleRowIndex: number, lastVisibleRowIndex: number, visibleRowFrames: Object[] } }` | `ALL` +| 参数 | 描述 | 类型 | 支持平台 | +|-------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------|---------------| +| numberOfColumns | 瀑布流列数量 , `Default: 2` | `number` | `Android、iOS` | +| numberOfItems | 瀑布流 item 总个数 | `number` | `Android、iOS` | +| columnSpacing | 瀑布流每列之前的水平间距 | `number` | `Android、iOS` | +| interItemSpacing | item 间的垂直间距 | `number` | `Android、iOS` | +| contentInset | 内容缩进 ,默认值 `{ top:0, left:0, bottom:0, right:0 }` | `Object` | `Android、iOS` | +| renderItem | 这里的入参是当前 item 的 index,在这里可以凭借 index 获取到瀑布流一个具体单元格的数据,从而决定如何渲染这个单元格。 | `(index: number) => React.ReactElement` | `Android、iOS` | +| renderBanner | 如何渲染 Banner。(`Android` 最低支持版本 `2.15.0`) | `() => React.ReactElement` | `Android、iOS` | +| getItemStyle | 设置`WaterfallItem`容器的样式。 | `(index: number) => styleObject` | `Android、iOS` | +| getItemType | 指定一个函数,在其中返回对应条目的类型(返回Number类型的自然数,默认是0),List 将对同类型条目进行复用,所以合理的类型拆分,可以很好地提升list 性能。 | `(index: number) => number` | `Android、iOS` | +| getItemKey | 指定一个函数,在其中返回对应条目的 Key 值,详见 [React 官文](//reactjs.org/docs/lists-and-keys.html) | `(index: number) => any` | `Android、iOS` | +| preloadItemNumber | 滑动到瀑布流底部前提前预加载的 item 数量 | `number` | `Android、iOS` | +| onEndReached | 当所有的数据都已经渲染过,并且列表被滚动到最后一条时,将触发 `onEndReached` 回调。 | `Function` | `Android、iOS` | +| containPullHeader | 是否包含`PullHeader`组件,默认 `false` ;`Android` 暂不支持,可暂时用 `RefreshWrapper` 组件替代 | `boolean` | `iOS` | +| renderPullHeader | 如何渲染 `PullHeader`,此时 `containPullHeader` 默认设置成 `true` | `() => React.ReactElement` | `iOS` | +| containPullFooter | 是否包含`PullFooter`组件,默认 `false` | `boolean` | `Android、iOS` | +| renderPullFooter | 如何渲染 `PullFooter`,此时 `containPullFooter` 默认设置成 `true` | `() => React.ReactElement` | `Android、iOS` | +| onScroll | 当触发 `WaterFall` 的滑动事件时回调。`startEdgePos`表示距离 List 顶部边缘滚动偏移量;`endEdgePos`表示距离 List 底部边缘滚动偏移量;`firstVisibleRowIndex`表示当前可见区域内第一个元素的索引;`lastVisibleRowIndex`表示当前可见区域内最后一个元素的索引;`visibleRowFrames`表示当前可见区域内所有 item 的信息(x,y,width,height) | `nativeEvent: { startEdgePos: number, endEdgePos: number, firstVisibleRowIndex: number, lastVisibleRowIndex: number, visibleRowFrames: Object[] }` | `Android、iOS` | ## 方法 @@ -523,10 +597,10 @@ WebView组件。 | 参数 | 描述 | 类型 | 支持平台 | | --------------------- | ------------------------------------------------------------ | ----------------------------------------------------------- | -------- | -| source | Webview 内嵌地址 | `{ uri: string }` | `ALL` | -| userAgent | Webview userAgent | `string` | `ALL`| -| method | 请求方式, `get`、`post` | `string` | `ALL` | -| onLoadStart | 网页开始加载时触发 | `(object: { url:string }) => void` | `ALL` | -| onLoad | 网页加载时触发 | `(object: { url:string }) => void` | `ALL` | -| onLoadEnd | 网页加载结束时触发 | `(object: { url:string }) => void` | `ALL` | -| style | Webview 容器样式 | `Object` | `ALL` | +| source | Webview 内嵌地址 | `{ uri: string }` | `Android、iOS、hippy-react-web、Web-Renderer` | +| userAgent | Webview userAgent | `string` | `Android、iOS`| +| method | 请求方式, `get`、`post` | `string` | `Android、iOS` | +| onLoadStart | 网页开始加载时触发 | `(object: { url: string }) => void` | `Android、iOS、Web-Renderer` | +| onLoad | 网页加载时触发 | `(object: { url: string }) => void` | `Android、iOS、Web-Renderer` | +| onLoadEnd | 网页加载结束时触发 (`success`与`error`参数仅`Android`、`iOS`上可用,最低支持版本`2.15.3`) | `(object: { url: string, success: boolean, error: string }) => void` | `Android、iOS、hippy-react-web、Web-Renderer` | +| style | Webview 容器样式 | `Object` | `Android、iOS、hippy-react-web、Web-Renderer` | diff --git a/docs/hippy-react/customize.md b/docs/hippy-react/customize.md index b84f4e882f7..ed30972d492 100644 --- a/docs/hippy-react/customize.md +++ b/docs/hippy-react/customize.md @@ -1,5 +1,7 @@ # 自定义组件和模块 +--- + # 自定义组件 写个 React 组件,在需要渲染的地方通过 `nativeName` 指定到终端组件名称即可,以终端范例中的 `MyView` 为例: diff --git a/docs/hippy-react/gesture.md b/docs/hippy-react/gesture.md index 8ee2ab4707f..04d150ae370 100644 --- a/docs/hippy-react/gesture.md +++ b/docs/hippy-react/gesture.md @@ -2,7 +2,9 @@ Hippy 的手势系统使用起来相对更加便捷,主要区别就在不需要再依赖其它事件组件,所有组件,包括 View、Text、Image 或各种自定义控件等都可以设置点击、触屏事件监听; -## 点击事件 +--- + +# 点击事件 点击事件包括长按、点击、按下、抬手 4 种类型,分别由以下 4 种接口通知: @@ -11,38 +13,41 @@ Hippy 的手势系统使用起来相对更加便捷,主要区别就在不需 3. onPressOut:在长按或点击时,用户结束触屏(即用户抬起手指时)该控件时,此函数会被调用; 4. onLongClick:当控件被长按时,此函数会被调用; -### 范例 +## 范例 通过配合使用 onPressIn 和 onPressOut 可以实现点击态的效果,例如下面的示例代码,实现了点击时背景变色的功能: ```jsx -render() { - let bgColor = "#FFFFFF"; //非点击状态下背景为白色 - if (this.state.pressedIn) { - bgColor = "#000000"; //点击状态下背景为黑色 - } - - return ( - { this.setState({pressedIn: true}) }} - onPressOut={() => { this.setState({pressedIn: false}) }} - > - 点击按钮 - - ); +render() +{ + let bgColor = "#FFFFFF"; //非点击状态下背景为白色 + if (this.state.pressedIn) { + bgColor = "#000000"; //点击状态下背景为黑色 + } + + return ( + { + this.setState({pressedIn: true}) + }} + onPressOut={() => { + this.setState({pressedIn: false}) + }} + > + 点击按钮 + + ); } ``` -## 触屏事件 +# 触屏事件 触屏事件的处理与点击事件类似,可以再任何 React 组件上使用,touch 事件主要由以下几个回调函数组成: -1. onTouchDown(event):当用户开始触屏控件时(即用户在该控件上按下手指时),将回调此函数,并将触屏点信息作为参数传递进来; +1. onTouchDown(event):当用户开始在控件上按下手指时,将回调此函数,并将触屏点信息作为参数传递进来; 2. onTouchMove(event):当用户在控件移动手指时,此函数会持续收到回调,并通过 event 参数告知控件的触屏点信息; 3. onTouchEnd(event):当触屏操作结束,用户在该控件上抬起手指时,此函数将被回调,event 参数也会通知当前的触屏点信息; -4. onTouchCancel(event):当用户触屏过程中,某个系统事件中断了触屏,例如电话呼入、组件变化(如设置为 hidden),此函数会收到回调,触屏点信息也会通过 event 参数告知前端; - -注意:若 onTouchCancel 被触发,则 onTouchEnd 不会被触发 +4. onTouchCancel(event):当用户触屏过程中,某个系统事件中断了触屏,例如电话呼入、组件变化(如设置为 hidden)、其他组件的滑动手势,此函数会收到回调,触屏点信息也会通过 event 参数告知前端; `注意:若 onTouchCancel 被触发,则 onTouchEnd 不会被触发` 以上回调函数均带有一个参数 event,该数据包含以下结构: @@ -51,7 +56,7 @@ render() { - page_x:触屏点相对于根元素的横坐标; - page_y:触屏点相对于根元素的纵坐标; -以上结构中的 x 和 y 坐标已经经过转换,与屏幕分辨率无关的单位,例如 onTouchDonw 回调的 event 参数结构如下: +以上结构中的 x 和 y 坐标已经经过转换,与屏幕分辨率无关的单位,例如 onTouchDown 回调的 event 参数结构如下: ```json { @@ -62,49 +67,69 @@ render() { } ``` -## 事件冒泡 +# 事件冒泡 + +[[事件冒泡范例]](//github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo/src/components/ListView) 点击事件和触屏事件均可以在回调函数中定义是否需要冒泡该事件到上层组件,点击或触屏事件发生时,终端会寻找该触屏点下声明了要处理该事件的最小控件: !> HippyReact 默认不冒泡 -1. 返回 true 或没有返回值:控件处理完事件后,将不再继续冒泡,整个手势事件处理结束; -2. 返回 false:控件处理完事件后,事件将继续往上一层冒泡,如果找到某个父控件也设置了对应事件处理函数,则会调用改该回调函数,并再次根据其返回值决定是否继续冒泡。如果再向上冒泡的过程中达到了根节点,则事件冒泡结束; +1. 返回 `true` 或 `没有返回值`:控件处理完事件后,将不再继续冒泡,整个手势事件处理结束; +2. 返回 `false`:控件处理完事件后,事件将继续往上一层冒泡,如果找到某个父控件也设置了对应事件处理函数,则会调用改该回调函数,并再次根据其返回值决定是否继续冒泡。如果再向上冒泡的过程中达到了根节点,则事件冒泡结束; + +`2.11.2 版本` 开始,系统 `onClick` 、`onTouchEvent` 事件回调函数添加了 `Event` 实例参数,包含了 `target` 属性(事件的真正发出节点)、`currentTarget` +属性(监听事件的节点)、`stopPropagation` 方法。`stopPropagation` 在开启全局冒泡后能阻止冒泡,优先级高于回调函数 `return 返回值`,`return 返回值` 后面逐渐废弃。 我们通过以下示例进一步说明事件冒泡的机制: ```js -render() { - return ( - { console.log("根节点 点击"); }} - > - console.log("点击按钮1 点击")} - > - 点击按钮1 - - { - console.log("父控件 点击"); - return true; - }} - > - { - console.log("点击按钮2 点击"); - return false; - }} +render() +{ + return ( + { + console.log("根节点 点击"); + }} > - 点击按钮2 - - - - ); + console.log("按钮1 点击")} + > + 点击按钮1 + + { + console.log("父控件 点击"); + // 不再向上冒泡到跟节点 + return true; + }} + > + { + console.log("按钮2 点击"); + // 向上冒泡到父控件 + return false; + }} + > + 点击按钮2 + + { + console.log("按钮2 点击", event.target.nodeId, event.currentTarget.nodeId); + event.stopPropagation(); + // 调用了 stopPropagation 后,即使 return false,按钮2点击事件也不会向上冒泡到父节点 + return false; + }} + > + 点击按钮3 + + + + ); } ``` -> 2.10.1 版本开始支持在 Hippy 初始化时通过 `bubbles` 参数设置默认冒泡(即事件处理return没有返回值,也会向上传递事件),默认 `false` +> 2.10.1 版本开始支持在 Hippy 初始化时通过 `bubbles` 参数设置默认冒泡(即如果事件处理 return 没有返回值,也会向上传递事件),默认 `false` ```js new Hippy({ @@ -115,42 +140,101 @@ new Hippy({ }).start(); ```` -## 事件的拦截 +# 事件捕获 + +> 最低支持版本 2.11.5 + +[[事件捕获范例]](//github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo/src/components/ListView) + +点击事件和触屏事件支持事件捕获,如需注册捕获阶段的事件处理函数,则应在目标元素事件名添加 `Capture` 后缀,如 `onClickCapture`、`onTouchDownCapture`。 + +Hippy为了做更好的性能优化,如果目标元素没有 `Capture` 事件处理函数,默认不开启捕获,全局冒泡配置 `bubbles: false` 不会影响捕获开启。事件捕获设计与 Web 标准一致,当在任意一个捕获函数内调用 `stopPropagation` 时,会同时阻止剩余的捕获阶段、目标节点阶段和冒泡阶段执行。 -某些场景下,父控件又需要优先拦截到子控件的手势事件,因此 Hippy 也提供了手势事件拦截机制,手势拦截由父控件的两个属性控制 `onInterceptTouchEvent` 和`onInterceptPullUpEvent`,这两个属性仅对能容纳子控件的组件生效,如 `` 这种控件就不支持这两个属性: +!> 事件捕获会有一定性能损耗,如非必要尽量不开启。 -- onInterceptTouchEvent:父控件是否拦截所有子控件的手势事件,true 为拦截,false 为不拦截(默认为 false)。当父控件设置该属性为 true 时,所有其子控件将无法收到任何 touch 事件和点击事件的回调,不管是否有设置事件处理函数,在该父控件区域内按下、移动、抬起手指以及点击和长按发生时,终端将默认把事件发送给该父控件进行处理。如果父控件在设置 onInterceptTouchEvent 为 true 之前,子控件已经在处理 touch 事件,那么子控件将收到一次 onTouchCancel 回调(如果子控件有注册该函数); -- onInterceptPullUpEvent:该属性的作用与 onInterceptTouchEvent 类似,只是决定父控件是否拦截的条件稍有不同。为 true 时,如果用户在当前父控件区域内发生了手指上滑的动作,后续所有的触屏事件将被该父控件拦截处理,所有其子控件将无法收到任何 touch 事件回调,不管是否有设置 touch 事件处理函数;如果拦截生效之前子控件已经在处理 touch 事件,子控件将收到一次 onTouchCancel 回调。为 false 时,父控件将不会拦截事件,默认为 false; +例子如下: + +```js +render() +{ + return ( + { + console.log("根节点 点击"); + }} + onClickCapture={(event) => { + // 如果根节点调用 stopPropagation,则按钮2的 onClickCapture 和按钮1的 onClick 都不会触发 + // event.stopPropagation(); + console.log("根节点 捕获点击") + }} + > + { + // 点击按钮1不会触发根节点捕获点击 + console.log("按钮1 点击") + }} + > + 点击按钮1 + + + { + // 点击按钮2会触发根节点捕获点击 + console.log("按钮2 点击"); + }} + > + 点击按钮2 + + + + ); +} +``` + +# 事件拦截 + +某些场景下,父控件又需要优先拦截到子控件的手势事件,因此 Hippy 也提供了手势事件拦截机制,手势拦截由父控件的两个属性控制 `onInterceptTouchEvent` 和`onInterceptPullUpEvent` +,这两个属性仅对能容纳子控件的组件生效,如 `` 这种控件就不支持这两个属性: + +- onInterceptTouchEvent:父控件是否拦截所有子控件的手势事件,true 为拦截,false 为不拦截(默认为 false)。当父控件设置该属性为 true 时,所有其子控件将无法收到任何 touch + 事件和点击事件的回调,不管是否有设置事件处理函数,在该父控件区域内按下、移动、抬起手指以及点击和长按发生时,终端将默认把事件发送给该父控件进行处理。如果父控件在设置 onInterceptTouchEvent 为 true + 之前,子控件已经在处理 touch 事件,那么子控件将收到一次 onTouchCancel 回调(如果子控件有注册该函数); +- onInterceptPullUpEvent:该属性的作用与 onInterceptTouchEvent 类似,只是决定父控件是否拦截的条件稍有不同。为 true + 时,如果用户在当前父控件区域内发生了手指上滑的动作,后续所有的触屏事件将被该父控件拦截处理,所有其子控件将无法收到任何 touch 事件回调,不管是否有设置 touch 事件处理函数;如果拦截生效之前子控件已经在处理 touch + 事件,子控件将收到一次 onTouchCancel 回调。为 false 时,父控件将不会拦截事件,默认为 false; 注意,由于这两种标记拦截条件不同,onInterceptTouchEvent 标记设置为 true 之后,子控件的所有触屏事件都将失效,而 onInterceptPullUpEvent 则不会影响子控件的点击事件。 还是以代码为例: ```js -render() { - return ( - { console.log("根节点 TouchMove:" + JSON.stringify(event)); }} - > - console.log("红色区域 TouchMove:" + JSON.stringify(event)) } - onTouchDown={(event) => { - console.log("红色区域 onTouchDown:" + JSON.stringify(event)); - }}/> - { - console.log("绿色区域 TouchMove:" + JSON.stringify(event)); - return false; - }} - onInterceptTouchEvent={true} - > - { - console.log("蓝色区域 TouchMove:" + JSON.stringify(event)); - return false; - }}/> - - - ); +render() +{ + return ( + { + console.log("根节点 TouchMove:" + JSON.stringify(event)); + }} + > + console.log("红色区域 TouchMove:" + JSON.stringify(event))} + onTouchDown={(event) => { + console.log("红色区域 onTouchDown:" + JSON.stringify(event)); + }}/> + { + console.log("绿色区域 TouchMove:" + JSON.stringify(event)); + return false; + }} + onInterceptTouchEvent={true} + > + { + console.log("蓝色区域 TouchMove:" + JSON.stringify(event)); + return false; + }}/> + + + ); } ``` diff --git a/docs/hippy-react/introduction.md b/docs/hippy-react/introduction.md index 482f77011ca..e28d604febe 100644 --- a/docs/hippy-react/introduction.md +++ b/docs/hippy-react/introduction.md @@ -1,11 +1,43 @@ # hippy-react 介绍 -hippy-react 是基于 Facebook React 的官方自定义渲染器 [react-reconciler](//www.npmjs.com/package/react-reconciler) 重新开发的 React 到终端的渲染层,可以使用 React 的全部特性。 +hippy-react 是基于 React 的官方自定义渲染器 [react-reconciler](//www.npmjs.com/package/react-reconciler) 重新开发的 React 到终端的渲染层,可以使用 React 的全部特性,当前采用 `React17`。 -在语法上 hippy-react 更加接近底层终端,使用了类似 [React Native](//facebook.github.io/react-native/) 的语法。 +--- # 架构图 -hippy-react 架构图 +hippy-react 架构图

+ +# 初始化 + +```javascript +import { Hippy, View } from '@hippy/react'; +import React, { Component } from 'react'; + +new Hippy({ + appName: 'Demo', + entryPage: App, + // set global bubbles, default is false + bubbles: false, + // set log output, default is false + silent: false, +}).start(); + +class App extends Component { + constructor(props) { + // 终端给前端的初始化参数,终端可以将一些启动需要的自定义属性放到入口文件props里 + super(props); + } + + render() { + const { __instanceId__: instanceId } = this.props; + console.log('instanceId', instanceId); + return ( + + ); + } +} + +``` diff --git a/docs/hippy-react/migrate-from-rn.md b/docs/hippy-react/migrate-from-rn.md deleted file mode 100644 index d2081ca6d71..00000000000 --- a/docs/hippy-react/migrate-from-rn.md +++ /dev/null @@ -1,40 +0,0 @@ -# 从 React Native 迁移 - -Hippy React 基本兼容 React Native 语法,但相对 React Native 提供的组件,Hippy 更加内聚,除了部分组件可能需要通过前端重新实现,主要还有以下三个区别: - -# 触屏 Touchable 系列组件 - -Hippy 的触屏系列事件可以直接绑定到 View 上,之前 RN 上的 `Touchable` 系列组件其实可以简单迁移过来。以 `TouchableWithoutFeedback` 为例: - -```jsx -import React from 'react'; - -function TouchableWithoutFeedback(props) { - const child = React.Children.only(props.children); - const { onClick, onPressIn, onPressOut } = props; - const { children, ...nativeProps } = child.props; - // 透传事件 - if (typeof onClick === 'function') { - nativeProps.onClick = onClick; - } - if (typeof onPressIn === 'function') { - nativeProps.onPressIn = onPressIn; - } - if (typeof onPressOut === 'function') { - nativeProps.onPressOut = onPressOut; - } - return React.cloneElement(child, nativeProps, children); -} -``` - -# 动画系统 - -Hippy 的动画机制和 React Native 机制有所不同,React Native 的动画模块其实是由前端通过定时器驱动,存在大量前终端通讯,而 Hippy 通过将动画方案一次性下发给终端实现了更好的动画性能。 - -请参考 [动画方案的最佳实践](hippy-react/animation.md)。 - -# 手势系统 - -和 React Native 的 PanResponder 不同,Hippy 的手势事件可以应用于任何一个组件上,更加接近浏览器的实现。 - -请参考 [手势系统的最佳实践](hippy-react/gesture.md)。 diff --git a/docs/hippy-react/modules.md b/docs/hippy-react/modules.md index 1df441cdb90..11bd1fdd2ae 100644 --- a/docs/hippy-react/modules.md +++ b/docs/hippy-react/modules.md @@ -1,5 +1,4 @@ - # 模块 @@ -15,51 +14,41 @@ - 在 render 时,将动画设置到需要产生动画效果的控件属性上; - 通过 Animation 的 start 接口启动动画,或是通过 destroy 停止并销毁动画。 -> 注意,转 Web 需要用 setRef 方法手动传入 ref 才可以正常运行动画 +> 注意,转 Web 需要用 setRef 方法手动传入 ref 才可以正常运行动画,hippy-react-web 不支持颜色渐变动画。 +> +> 注意,2.17.1版本对iOS动画进行了较大升级,修复了历史版本与Android端动画表现不一致的问题,升级时请关注兼容性。 ## 构造参数 | 参数 | 类型 | 必需 | 默认值 | 描述 | | ---------------- | ------------------ | ---- | ------ | ------------------------------------------------------------------------------------------------------------------------- | -| mode | `string` | 是 | timing | 动画时间轴模式 | -| delay | `number` | 是 | - | 动画延迟开始的时间,单位为毫秒,默认为 0,即动画 start 之后立即执行;指定列表的行数,一般直接传入数据源条数 `length` 即可 | -| startValue | `number`, `string` | 是 | - | 动画开始时的值,可为 Number 类型 String 类型,如果为颜色值参考 [color](style/color.md) | -| toValue | `number`, `string` | 是 | - | 动画结束时候的值;如果为颜色值参考 [color](style/color.md) | -| valueType\* | `number`, `string` | 否 | null | 动画的开始和结束值的类型,默认为空,代表动画起止的单位是普通 Number。 PS: Web 平台此接口只支持 number 类型传参 | -| duration | `number` | 否 | - | 动画时长,单位为毫秒(ms) | -| timingFunction\* | `string` | 否 | linear | 动画插值器类型, 支持 `linear`,`ease-in`, `ease-out`,`ease-in-out`,`cubic-bezier` | -| repeatCount | `number`, `loop` | 否 | - | 动画的重复次数,默认为 0,即只播放一次,为"loop"时代表无限循环播放; repeatCount 设为 n 时,则动画会播放 n 次 | - -- valueType 的参数选项: - - - `rad`:代表动画参数的起止值为弧度; +| mode | `string` | 是 | timing | 动画时间轴模式,当前仅支持 `timing` 模式,即随时间改变控件的属性,默认配置即为 `timing` | +| delay | `number` | 是 | - | 动画延迟开始的时间,单位为毫秒,默认为 0,即动画 start 之后立即执行 | +| startValue | `number`, `string`, [color](style/color.md) | 是 | - | 动画开始时的值,可为 Number 类型、String 类型,颜色值 [color](style/color.md) 类型 | +| toValue | `number`, `string`, [color](style/color.md) | 是 | - | 动画结束时候的值;如果为颜色值参考 [color](style/color.md) | +| valueType\* | `enum(undefined,rad,deg,color)` | 否 | undefined `(rotate 动画默认单位为 rad)` | 动画的开始和结束值的类型,默认为空,代表动画起止的单位是普通数值。 PS: Web 平台此接口只支持 number 类型传参 | +| duration | `number` | 否 | 0 | 动画时长,单位为毫秒(ms) | +| timingFunction\* | `string` | 否 | linear | 动画插值器类型, 支持 `linear`,`ease-in`, `ease-out`,`ease-in-out`,`cubic-bezier` | +| repeatCount | `number`, `loop` | 否 | - | 动画的重复次数,默认为 0,即只播放一次;为 -1 或者 "loop" 时代表无限循环播放; repeatCount 设为 n 时,则动画会播放 n 次 | + +- valueType 的额外参数选项: + + - `rad`:代表动画参数的起止值为弧度, `这是 rotate 动画的默认单位`; - `deg`:代表动画参数的起止值为度数; - - `color`:代表动画参数的起止值为颜色值,可修饰背景色 `backgroundColor` 和文字颜色 `color`(仅 Android 支持),参考 [例子](//github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/src/modules/Animation/index.jsx) `最低支持版本2.6.0` + - `color`:代表动画参数的起止值为颜色值,可修饰背景色 `backgroundColor` 和文字颜色 `color`(iOS 2.17.1版本开始支持),参考 [例子](//github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/src/modules/Animation/index.jsx) `最低支持版本2.6.0` - timingFunction 的参数选项: - `linear`:使用线性插值器,动画将匀速进行; - `ease-in`:使用加速插值器,动画速度将随时间逐渐增加; - `ease-out`:使用减速插值器,动画速度将随时间逐渐减小; - `ease-in-out`:使用加减速插值器,动画速度前半段先随时间逐渐增加,后半段速度将逐渐减小; - - `cubic-bezier`:(最低支持版本 2.9.0)使用自定义贝塞尔曲线,与 [css transition-timing-function 的 cubic-bezier](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function) 一致; + - `cubic-bezier`:使用自定义贝塞尔曲线,与 [css transition-timing-function 的 cubic-bezier](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function) 一致;`最低支持版本2.9.0` ## 方法 ### destroy -`() => void` 停止并销毁一个动画集。建议在组件销毁的生命周期执行此方法,避免动画在后台运行耗。 - -### onAnimationEnd - -`(callback: () => void) => void` 注册一个动画的监听回调,在动画结束时将会回调 callback。 - -### onAnimationRepeat(仅 Android 支持) - -`(callback: () => void) => void` 注册一个动画的监听回调,当动画开始下一次重复播放时 callback 将被回调。 - -### onAnimationStart - -`(callback: () => void) => void` 注册一个动画的监听回调,在动画开始时将会回调 callback。 +`() => void` 停止并销毁一个动画集。建议在组件销毁的生命周期执行此方法,避免动画在后台运行耗电。 ### pause @@ -75,10 +64,26 @@ ### updateAnimation -`(options: Object) => void` 修改动画的配置参数,只需要填入需要修改的配置项即可,不需要重复填入所有的动画参数 +`(options: Object) => void` 修改动画的配置参数,只需要填入需要修改的配置项即可,不需要重复填入所有的动画参数。注意,如果动画已经 start 或 destroy,更新操作将不会生效 > - options: Object: 实例化参数 +### onAnimationCancel + +`(callback: () => void) => void` 注册一个动画的监听回调,在动画被取消时将会回调 callback。 + +### onAnimationEnd + +`(callback: () => void) => void` 注册一个动画的监听回调,在动画结束时将会回调 callback。 + +### onAnimationRepeat(iOS 2.17.1版本开始支持) + +`(callback: () => void) => void` 注册一个动画的监听回调,当动画开始下一次重复播放时 callback 将被回调。 + +### onAnimationStart + +`(callback: () => void) => void` 注册一个动画的监听回调,在动画开始时将会回调 callback。 + --- # AnimationSet @@ -96,7 +101,7 @@ | 参数 | 类型 | 必需 | 默认值 | 描述 | | ----------- | ------------------------------------------- | ---- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | children | `{ children: Animation, follow = false }[]` | 是 | - | 接收一个 Array,用于指定子动画,该 Array 的每个元素包括: + animation:子动画对应的 Animation 对象; + follow:配置子动画的执行是否跟随执行,为 true,代表该子动画会等待上一个子动画执行完成后在开始,为 false 则代表和上一个子动画同时开始,默认为 false。 | -| repeatCount | `number` | 否 | - | 动画 Set 的重复次数,默认为 0,即不重复播放,为'loop'时代表无限循环播放; `repeatCount` 设为 n 时,则动画会播放 n 次。 | +| repeatCount | `number`, `loop` | 否 | - | 动画 Set 的重复次数,默认为 0,即不重复播放,为 `loop` 时代表无限循环播放; `repeatCount` 设为 n 时,则动画会播放 n 次。 | ## 方法 @@ -104,18 +109,6 @@ `() => void` 停止并销毁一个动画集。建议在组件销毁的生命周期执行此方法,避免动画在后台运行耗。 -### onAnimationEnd - -`(callback: () => void) => void` 注册一个动画的监听回调,在动画结束时将会回调 callback。 - -### onAnimationRepeat - -`(callback: () => void) => void` 注册一个动画的监听回调,当动画开始下一次重复播放时 callback 将被回调。 - -### onAnimationStart - -`(callback: () => void) => void` 注册一个动画的监听回调,在动画开始时将会回调 callback。 - ### pause `() => void` 暂停运行中的动画。 @@ -128,9 +121,21 @@ `() => void` 启动动画。注意:如果调用该方法前,动画尚未经过 render 赋值给相应控件, 或该动画已经 destroy 的话,那 start 将不会生效; -### updateAnimation +### onAnimationCancel + +`(callback: () => void) => void` 注册一个动画的监听回调,在动画被取消时将会回调 callback。 + +### onAnimationEnd + +`(callback: () => void) => void` 注册一个动画的监听回调,在动画结束时将会回调 callback。 + +### onAnimationRepeat -`(options: Object) => void` 修改动画的配置参数,只需要填入需要修改的配置项即可,不需要重复填入所有的动画参数 +`(callback: () => void) => void` 注册一个动画的监听回调,当动画开始下一次重复播放时 callback 将被回调。 + +### onAnimationStart + +`(callback: () => void) => void` 注册一个动画的监听回调,在动画开始时将会回调 callback。 --- @@ -191,7 +196,7 @@ AsyncStorage 是一个简单的、异步的、持久化的 Key-Value 存储系 [[BackAndroid 范例]](//github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/src/pages/gallery.jsx#L171) -可以监听 Android 实体键的回退,在退出前做操作或拦截实体键的回退。 +可以监听 Android 实体键的回退,在退出前做操作或拦截实体键的回退。 `hippy-react-web` 不支持。 > 注意:该方法需要终端拦截实体返回按钮的事件,可以参考 [android-demo 的 onBackPressed 方法](//github.com/Tencent/Hippy/blob/master/examples/android-demo/example/src/main/java/com/tencent/mtt/hippy/example/MyActivity.java) @@ -215,26 +220,6 @@ AsyncStorage 是一个简单的、异步的、持久化的 Key-Value 存储系 --- -# Clipboard - -[[Clipboard 范例]](//github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo/src/modules/Clipboard) - -模块提供了 iOS/Android 双端的剪贴板能力,开发者可使用其来读取或写入剪贴板,目前仅支持字符串作为存取类型。 - -## 方法 - -### Clipboard.getString - -`() => string` 获取剪贴板的内容 - -### Clipboard.setString - -`(value: string) => void` 设置剪贴板的内容 - -> - value: string - 需要设置到剪贴板中的内容。 - ---- - # ConsoleModule 提供了将前端日志输出到 iOS 终端日志和 [Android logcat](//developer.android.com/studio/command-line/logcat) 的能力 @@ -302,7 +287,7 @@ AsyncStorage 是一个简单的、异步的、持久化的 Key-Value 存储系 [[NetInfo 范例]](//github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo/src/modules/NetInfo) -通过该接口可以获得当前设备的网络状态,也可以注册一个监听器,当系统网络切换的时候,得到一个通知。 +通过该接口可以获得当前设备的网络状态;也可以注册一个监听器,当系统网络切换的时候,得到网络变化通知。 安卓的开发者,在请求网络状态之前,你需要在 app 的 `AndroidManifest.xml` 加入以下配置 : @@ -310,6 +295,8 @@ AsyncStorage 是一个简单的、异步的、持久化的 Key-Value 存储系 ``` +`hippy-react-web` 使用了实验属性 NetworkInformation,详情参考 https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation + ## 网络状态 以异步的方式判断设备是否联网,以及是否使用了移动数据网络。 @@ -330,7 +317,7 @@ AsyncStorage 是一个简单的、异步的、持久化的 Key-Value 存储系 ### NetInfo.fetch -`() => Promise` 用于获取当前的网络状态。 +`() => Promise` 用于获取当前的网络状态。 ### NetInfo.removeEventListener @@ -345,21 +332,24 @@ AsyncStorage 是一个简单的、异步的、持久化的 Key-Value 存储系 普通的网络请求请参考: [起步 - 网络请求](guide/network-request.md) +`hippy-react-web` 获取 cookie 和 设置 cookie 有域名限制,具体参考 https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_where_cookies_are_sent + ## 方法 ### NetworkModule.getCookies -`(url: string) => Promise` 获取指定 url 的所有 cookie +`(url: string) => Promise` 获取指定 url 下的所有 cookies > - url: string - 需要获取 cookie 的目标 url +> - 返回值:`Prmoise`,获取到诸如 `name=hippy;network=mobile` 的字符串,`2.14.0` 版本后过期的 Cookies 将不再返回。 ### NetworkModule.setCookie -`(url: string, keyValue: string, expires?: string) => Promise` 设置 Cookie +`(url: string, keyValue: string, expires?: Date) => Promise` 设置 Cookie -> - url: string - 需要获取 cookie 的目标 url -> - keyValue: string - 需要设置的键值对 -> - expires?: string - 设置 Cookie 的超市时间 +> - url: string - 需要设置 cookie 的目标 url +> - keyValue: string - 需要设置的键值对,如 `name=hippy;network=mobile`,`2.14.0` 版本后设置 `空字符串` 会强制清除(过期)指定域名下的所有 Cookies。 +> - expires?: Date - 设置 Cookie 的过期时间,默认为空,会通过 `toUTCString` 转成 `String` 传给客户端 --- @@ -404,8 +394,8 @@ AsyncStorage 是一个简单的、异步的、持久化的 Key-Value 存储系 | 参数 | 描述 | 类型 | 支持平台 | | ------------ | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------- | -| OS | 用来判断是在 iOS 或者 Android 下 | `string` | `ALL` | -| Localization | 输出国际化相关信息, `最低支持版本 2.8.0` | `object: { country: string , language: string, direction: number }`, 其中 `direction` 为 0 表示 LTR 方向,1 表示 RTL 方向 | `ALL` | +| OS | 用来判断是在 iOS 或者 Android 下 | `string` | `Android、iOS` | +| Localization | 输出国际化相关信息, `最低支持版本 2.8.0` | `object: { country: string , language: string, direction: number }`, 其中 `direction` 为 0 表示 LTR 方向,1 表示 RTL 方向 | `Android、iOS、hippy-react-web(不支持 country 信息)` | --- @@ -417,7 +407,7 @@ AsyncStorage 是一个简单的、异步的、持久化的 Key-Value 存储系 | 参数 | 描述 | 类型 | 支持平台 | | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------- | -| hairlineWidth | 这一常量定义了当前平台上的最细的宽度。可以用作边框或是两个元素间的分隔线。然而,你不应该信任它作为一个衡量长度的单位,因为在不同机器与不同分辨率,hairlineWidth 可能会表现不同。 | `number` | `ALL` | +| hairlineWidth | 这一常量定义了当前平台上的最细的宽度。可以用作边框或是两个元素间的分隔线。然而,你不应该信任它作为一个衡量长度的单位,因为在不同机器与不同分辨率,hairlineWidth 可能会表现不同。 | `number` | `Android、iOS` | ## 方法 @@ -447,7 +437,7 @@ AsyncStorage 是一个简单的、异步的、持久化的 Key-Value 存储系 ### UIManagerModule.getElementFromFiberRef -获取元素 Ref 对应的 Element(类似DOM) +获取元素 Ref 对应的 Element(类似DOM)。`hippy-react-web` 不支持。 `getElementFromFiberRef(instance: ref): ElementNode` @@ -456,8 +446,22 @@ AsyncStorage 是一个简单的、异步的、持久化的 Key-Value 存储系 ### UIManagerModule.measureInAppWindow -测量在 App 窗口范围内某个组件的尺寸和位置,如果出错 callback 参数可能为字符串或者 -1 +测量在 App 窗口范围内某个组件的尺寸和位置,如果出错 callback 参数可能为字符串或者 -1,注意需要保证节点实例真正上屏后(onLayout事件后)才能调用该方法。 `(ref, callback: Function) => Promise` -> - callback: ({ x, y, width, height } | string | -1) => void - 回调函数, 参数可以获取到引用组件在 App 窗口范围内的坐标值和宽高,如果出错可能返回 -1 或者 `this view is null` 字符串 +> - callback: ({ x, y, width, height } | string | -1) => void - 回调函数, 参数可以获取到引用组件在 App 窗口范围内的坐标值和宽高,如果出错或者 [节点被优化(仅在Android)](style/layout?id=collapsable)可能返回 -1 或者 `this view is null` 字符串 + +### UIManagerModule.getBoundingClientRect + +[[getBoundingClientRect 范例]](//github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo/src/modules/UIManagerModule/index.jsx) + +> 最低支持版本 `2.15.3`,原有 `measureInWindow` 和 `measureInAppWindow` 将逐渐废弃 + +测量元素在宿主容器(RootView) 或 App 窗口(屏幕)范围内的尺寸和位置。 + +`(instance: ref, options: { relToContainer: boolean }) => Promise` + +> - instance: 元素或组件的引用 Ref。 +> - options: 可选参数,`relToContainer` 表示是否相对宿主容器(RootView)进行测量,默认 `false` 相对 App 窗口或屏幕进行测量。当对宿主容器(RootView)进行测量时,`iOS` 包含顶部状态栏高度,`Android` 不包含。 +> - DOMRect: 与 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getBoundingClientRect) 一致的返回参数, 可以获取元素相应的位置信息和尺寸,如果出错或者 [节点被优化(仅在Android)](style/layout?id=collapsable),会触发 `Promise.reject`。 diff --git a/docs/hippy-react/native-event.md b/docs/hippy-react/native-event.md index 7dc1c1fd05f..559f169b8b0 100644 --- a/docs/hippy-react/native-event.md +++ b/docs/hippy-react/native-event.md @@ -1,10 +1,20 @@ -# 终端事件 +# 事件 有一些事件不是发给单个 UI,而是发给整个业务的,例如屏幕的翻转、网络的变化等等,我们称之它为 `终端事件`。 +Hippy 提供了两种方式来管理全局事件: + ++ `Hippy.on`、`Hippy.off`、`Hippy.emit` 是框架无关的全局事件监听器,主要用来监听如 `dealloc`、`destroyInstance` 等特殊 C++ 底层事件,也可以手动定制 JS 内的全局事件。 + ++ `HippyEventEmitter` 和 `EventBus`(2.15.0后支持) 是 HippyReact 定制的 EventBus,除了可以手动定制 JS 内的全局事件外,所有全局 `NativeEvent` 都由其来分发,如 `rotate` 事件等。 + +--- + # 事件监听器 -这里是向前端发送一个名叫 rotate 的事件,里面有个参数是 result,这样就发送到前端去了。 +这里终端向前端发送一个名叫 `rotate` 的事件,里面包含参数 result,前端通过 `HippyEventEmitter.addListener` 监听事件。 + +> 注意:`HippyEventEmitter` 无需反复实例化,建议全局只初始化一次来复用。 ```jsx import { HippyEventEmitter } from '@hippy/react'; @@ -21,11 +31,112 @@ this.call = hippyEventEmitter.addListener('rotate', evt => console.log(evt.resul this.call.remove() ``` -# 实例销毁事件 +!> `2.15.0` 版本后,增加 `EventBus` 全局事件对象,推荐采用该对象管理全局事件 + +## EventBus + +最低支持版本 `2.15.0` + +### on + +`(events: string | string[], callback: (data?: any) => void) => EventBus` 用于监听全局事件,返回 `EventBus` 对象可用于链式调用。 + +> + events: string | string[] - 指定事件名称,可以有两种类型,传入字符串时用于绑定单个事件,传入数组时用于同时绑定多个事件。 +> + callback: (data?: any) => void) - 指定回调函数,该回调函数可以作为 `EventBus.off` 的第二个参数。 + +```js +import { EventBus } from '@hippy/react'; +const rotateCallback = (data) => { + console.log('rotate data', data && data.orientation); +} +const accountChanged = (data) => { + console.log('accountChanged data', data && data.user); +} +// 链式调用注册事件 +EventBus + .on('rotate', rotateCallback) + .on('accountChanged', accountChanged); +/* + 可以通过数组同时注册两个事件 + EventBus.on(['rotate1', 'rotate2'], rotateCallback) + */ +``` + +### off + +`(events: string | string[], callback?: (data?: any) => void) => EventBus` 用于移除全局绑定的事件,返回 `EventBus` 对象可用于链式调用。 +这里有两种使用方法,当只提供了事件名称,则移除对应事件的所有回调函数;当同时提供了事件名称和回调函数,则只移除事件上指定的回调函数。 + +> + events: string | string[] - 指定事件名称,可以有两种类型,传入字符串时用于移除单个事件,传入数组时用于同时移除多个事件。 +> + callback?: (data?: any) => void - 可选参数,与 `EventBus.on` 第二个参数对应的回调函数,当 `callback` 为空时,移除对应事件的所有监听器。 + +```js +import { EventBus } from '@hippy/react'; +const rotateCallback = (data) => { + console.log('rotate data', data && data.orientation); +} +EventBus.on('rotate', rotateCallback); +// 只移除事件上指定的回调函数 +EventBus.off('rotate', rotateCallback); +// 移除对应事件的所有回调函数 +EventBus.off('rotate'); +``` + +### emit + +`(event: string, ...param: any) => EventBus` 用于触发对应事件,返回 `EventBus` 对象可用于链式调用。 + +> + event: string - 指定事件名称,只能传单个事件。 +> + ...param: any - 可选,支持发送多个参数,用作回调函数的参数。 + + +```js +import { EventBus } from '@hippy/react'; +const rotateCallback = (data1, data2) => { + console.log('rotate data', data1, data2); +} +EventBus.on('rotate', rotateCallback); +// 触发 rotate 事件,并携带参数 +EventBus.emit('rotate', { orientation: 'vertical' }, { degree: '90' }); +``` + +### sizeOf + +`(event: string) => number` 用于获取对应事件所绑定的回调函数数量。 + +> + event: string - 指定事件名称。 + +```js +import { EventBus } from '@hippy/react'; +const rotateCallback1 = (data) => { + console.log('rotate data', data && data.orientation); +} +const rotateCallback2 = (data) => { + console.log('rotate data', data && data.orientation); +} +EventBus.on('rotate', rotateCallback1); +EventBus.on('rotate', rotateCallback2); +// 获取 rotate 事件所绑定的回调函数数量 +console.log(EventBus.sizeOf('rotate')); // => 2; +``` + +# JS 引擎销毁事件 `最低支持版本 2.3.4` -当 hippy js 引擎或者 context 被销毁时会触发该事件,hippy业务可以通过监听 `destroyInstance` 事件做一些离开时的操作,但回调函数不能使用 `async` +当 hippy js 引擎被销毁前会触发该事件,能够保证回调函数里的最后一句 js 代码被执行到,hippy 业务可以通过监听 `dealloc` 事件做一些离开时的操作,但回调函数不能使用 `async` + +```jsx +Hippy.on('dealloc', () => { + // do something +}); +``` + +# 界面节点销毁事件 + +`最低支持版本 2.3.4` + +当 RootView 被卸载时调用该事件,与 `dealloc` 不同的是该事件早于 `dealloc` 触发,但不会阻塞 JS 线程。 ```jsx Hippy.on('destroyInstance', () => { @@ -35,7 +146,7 @@ Hippy.on('destroyInstance', () => { # 容器大小改变事件 -`只有 Android 支持` +`Android 全版本支持,iOS 最低支持版本 2.16.0` 当容器大小改变时,如屏幕旋转、折叠屏切换等,会触发该事件 @@ -47,3 +158,18 @@ hippyEventEmitter.addListener('onSizeChanged', ({ oldWidth, oldHeight, width, he console.log('size', oldWidth, oldHeight, width, height); }); ``` + +# 系统夜间模式改变事件 + +`仅iOS支持,最低支持版本 2.16.6,(注:Android修改夜间模式时页面将被重新创建)` + +在当系统夜间模式发生改变时,会触发该事件 + +```jsx +import { HippyEventEmitter } from '@hippy/react'; +const hippyEventEmitter = new HippyEventEmitter(); +hippyEventEmitter.addListener('onNightModeChanged', ({ NightMode, RootViewTag }) => { + // NightMode: 当前是否夜间模式,取值0或1;RootViewTag: 发送事件的HippyRootView的Tag + console.log(`onDarkModeChanged: ${NightMode}, rootViewTag: ${RootViewTag}`); +}); +``` diff --git a/docs/hippy-react/style.md b/docs/hippy-react/style.md index 2557098ddbc..a639054a9f1 100644 --- a/docs/hippy-react/style.md +++ b/docs/hippy-react/style.md @@ -2,6 +2,8 @@ Hippy 的所有样式支持由终端直接提供,基本和浏览器一致,但暂不支持百分比布局,但可以使用最新的 Flex 弹性布局。 +--- + # 内联样式 最简单的方式,我们可以用内联样式,直接定义容器如`View`,`Text`等的样式,用双括号包裹,示例代码如下: diff --git a/docs/hippy-react/web.md b/docs/hippy-react/web.md index 432493e83c1..fedd96d76ae 100644 --- a/docs/hippy-react/web.md +++ b/docs/hippy-react/web.md @@ -1,8 +1,12 @@ + + # 转 Web hippy-react 通过 [@hippy/react-web](//www.npmjs.com/package/@hippy/react-web) 库来将 Hippy 应用转译、运行在浏览器中。 -> 该项目仍在开发中,有不完善的地方,欢迎 PR。 +> @hippy/react-web 2.14.0 开始支持较为完整的转 Web 能力 + +--- # 安装运行时依赖 @@ -10,9 +14,14 @@ hippy-react 通过 [@hippy/react-web](//www.npmjs.com/package/@hippy/react-web) | 包名 | 说明 | | --------------- | --------------------------------- | -| bezier-easing | hippy-react 动画在 Web 运行时需要 | -| hippy-react-web | hippy-react 转 Web 适配器 | -| react-dom | react 的 Web 的渲染器 | +| react | react 版本 >= v16.8.0 | +| hippy-react-web | hippy-react 转 Web 适配器 | +| react-dom | react 的 Web 的渲染器 | +| animated-scroll-to | scroll 的时候添加动画 | +| swiper | ViewPager 需要 | +| @hippy/rmc-list-view | ListView 需要 | +| @hippy/rmc-pull-to-refresh | ListView PullHeader 需要 | + # 编译时依赖 @@ -39,7 +48,7 @@ hippy-react 通过 [@hippy/react-web](//www.npmjs.com/package/@hippy/react-web) hippy-react-web 和 hippy-react 的启动参数一致,可以共享同一个 `main.js` 入口文件。 -# npm script +# NPM 脚本 hippy-react-web 使用了 [webpack-dev-server](//webpack.js.org/configuration/dev-server/) 来启动调试,可以支持全部的 Web 调试特性,而同时使用同一份配置文件换而使用 webpack 进行打包。 @@ -55,3 +64,7 @@ hippy-react-web 使用了 [webpack-dev-server](//webpack.js.org/configuration/de # 启动调试 执行 `npm run serve` 后就会启动 Web 调试,但要注意默认生成的 HTML 文件名是从 `package.json` 的 `name` 字段定义,而不是默认的 `index.html`,所以对于官方范例,需要使用 `http://localhost:8080/hippy-react-demo.html` 来访问调试用页面。 + +# WebRenderer 方案 + +Hippy 全新 [`WebRenderer`](web/integration.md) 方案,增加基于公共通信协议的转换层,业务开发者可以使用同一套 Hippy 语法开发的业务代码,映射成 JS 实现的组件和模块,上层无论使用 React,Vue 或者其他第三方框架,都可以实现兼容,可参考。 diff --git a/docs/hippy-vue/_sidebar.md b/docs/hippy-vue/_sidebar.md index 5e4aab0d3eb..f01e76d285f 100644 --- a/docs/hippy-vue/_sidebar.md +++ b/docs/hippy-vue/_sidebar.md @@ -1,10 +1,13 @@ -* Hippy-Vue - * [介绍](hippy-vue/introduction.md) - * [核心组件](hippy-vue/components.md) - * [扩展组件](hippy-vue/external-components.md) - * [终端能力](hippy-vue/vue-native.md) - * [终端事件](hippy-vue/native-event.md) - * [自定义组件和模块](hippy-vue/customize.md) - * [路由](hippy-vue/router.md) +* [介绍](hippy-vue/introduction.md) +* [核心组件](hippy-vue/components.md) +* [扩展组件](hippy-vue/external-components.md) +* [模块](hippy-vue/vue-native.md) +* [样式](hippy-vue/style.md) +* [事件](hippy-vue/native-event.md) +* [手势系统](hippy-vue/gesture.md) +* [自定义组件和模块](hippy-vue/customize.md) +* [路由](hippy-vue/router.md) +* [转 Web](hippy-vue/web.md) +* [Vue 3.x](hippy-vue/vue3.md) diff --git a/docs/hippy-vue/components.md b/docs/hippy-vue/components.md index b65fa01ce58..f3fe2a6ec41 100644 --- a/docs/hippy-vue/components.md +++ b/docs/hippy-vue/components.md @@ -8,16 +8,16 @@ # a -该组件目前映射到 Text,目前主要用于在 hippy-vue-router 中进行页面跳转。 一切同 [p](hippy-vue/components.md?id=p)。 +该组件目前映射到终端 Text 组件,目前主要用于在 hippy-vue-router 中进行页面跳转。 一切同 [p](hippy-vue/components.md?id=p)。 ## 事件 | 事件名称 | 描述 | 类型 | 支持平台 | | ------------- | ------------------------------------------------------------ | ----------------------------------------- | -------- | -| touchstart | 触屏开始事件,最低支持版本 2.6.2 | `Function` | `ALL` | -| touchmove | 触屏移动事件,最低支持版本 2.6.2 | `Function` | `ALL` | -| touchend | 触屏结束事件,最低支持版本 2.6.2 | `Function` | `ALL` | -| touchcancel | 触屏取消事件,最低支持版本 2.6.2 | `Function` | `ALL` | +| touchstart | 触屏开始事件,最低支持版本 2.6.2,参数为 `evt: { touches: [{ clientX: number, clientY: number }] }`,`clientX` 和 `clientY` 分别表示点击在屏幕内的绝对位置 | `Function` | `Android、iOS、Web-Renderer` | +| touchmove | 触屏移动事件,最低支持版本 2.6.2,参数为 `evt: { touches: [{ clientX: number, clientY: number }] }`,`clientX` 和 `clientY` 分别表示点击在屏幕内的绝对位置 | `Function` | `Android、iOS、Web-Renderer` | +| touchend | 触屏结束事件,最低支持版本 2.6.2,参数为 `evt: { touches: [{ clientX: number, clientY: number }] }`,`clientX` 和 `clientY` 分别表示点击在屏幕内的绝对位置 | `Function` | `Android、iOS、Web-Renderer` | +| touchcancel | 触屏取消事件,当用户触屏过程中,某个系统事件中断了触屏,例如电话呼入、组件变化(如设置为hidden)、其他组件的滑动手势,此函数会收到回调,最低支持版本 2.6.2,参数为 `evt: { touches: [{ clientX: number, clientY: number }] }`,`clientX` 和 `clientY` 分别表示点击在屏幕内的绝对位置 | `Function` | `Android、iOS、Web-Renderer` | --- @@ -25,18 +25,18 @@ [[范例:demo-button.vue]](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/demos/demo-button.vue) -该组件映射到 View 上是因为它是一个可点击的容器,容器里面可以放图片、也可以放文本。但是因为 View 不能包裹文本,所以需要在 `
@@ -65,6 +98,7 @@ export default { #demo-img { overflow-y: scroll; flex: 1; + margin: 7px; } #demo-img #demo-img-container { @@ -77,6 +111,17 @@ export default { height: 180px; margin: 30px; border-width: 1px; + border-style: solid; + border-color: #40b883; +} + +#demo-img .img-result { + width: 300px; + height: 150px; + margin-top: -30px; + margin-horizontal: 30px; + border-width: 1px; + border-style: solid; border-color: #40b883; } @@ -91,4 +136,8 @@ export default { #demo-img .center { resize-mode: center; } + +#demo-img .tint-color { + tint-color: #40b88399 +} diff --git a/examples/hippy-vue-demo/src/components/demos/demo-input.vue b/examples/hippy-vue-demo/src/components/demos/demo-input.vue index 85646d0bc47..d355d902ac5 100644 --- a/examples/hippy-vue-demo/src/components/demos/demo-input.vue +++ b/examples/hippy-vue-demo/src/components/demos/demo-input.vue @@ -9,14 +9,22 @@ ref="input" v-model="text" placeholder="Text" + caret-color="yellow" + underline-color-android="grey" + placeholder-text-color="#40b883" + :editable="true" class="input" @click="stopPropagation" @keyboardWillShow="onKeyboardWillShow" + @keyboardWillHide="onKeyboardWillHide" + @blur="onBlur" + @focus="onFocus" >
文本内容为: {{ text }}
+
{{ `事件: ${event} | isFocused: ${isFocused}` }}