diff --git a/.github/workflows/pod_spec_lint.yml b/.github/workflows/pod_spec_lint.yml new file mode 100644 index 00000000000..a169f9dee17 --- /dev/null +++ b/.github/workflows/pod_spec_lint.yml @@ -0,0 +1,32 @@ +name: '[pods] pod spec lint' + +on: + pull_request: + branches: + - main + paths: + - 'framework/ios/**' + - 'framework/examples/ios-demo/**' + - 'driver/js/src/**' + - 'driver/js/include/**' + - 'dom/include/**' + - 'dom/src/**' + - 'layout/engine/**' + - 'modules/ios/**' + - 'modules/footstone/**' + - 'modules/vfs/ios/**' + - 'modules/vfs/native/**' + - 'renderer/native/ios/**' + - 'devtools/devtools-backend/**' + +jobs: + pod_spec_lint: + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + lfs: true + - name: Pod Spec Lint + run: | + pod spec lint hippy.podspec --allow-warnings --use-libraries --verbose --skip-import-validation diff --git a/.github/workflows/pod_spec_lint_bypass.yml b/.github/workflows/pod_spec_lint_bypass.yml new file mode 100644 index 00000000000..4d7b056cba8 --- /dev/null +++ b/.github/workflows/pod_spec_lint_bypass.yml @@ -0,0 +1,32 @@ +name: '[pods] pod spec lint' + +on: + pull_request: + branches: + - main + paths-ignore: + - 'framework/ios/**' + - 'framework/examples/ios-demo/**' + - 'driver/js/src/**' + - 'driver/js/include/**' + - 'dom/include/**' + - 'dom/src/**' + - 'layout/engine/**' + - 'modules/ios/**' + - 'modules/footstone/**' + - 'modules/vfs/ios/**' + - 'modules/vfs/native/**' + - 'renderer/native/ios/**' + - 'devtools/devtools-backend/**' + +jobs: + pod_spec_lint: + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + lfs: true + - name: Pod_Spec_Lint_Bypass + run: | + echo "No Pod Spec Lint 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/voltron_build_tests.yml b/.github/workflows/voltron_build_tests.yml index 268d1650460..3a7d28fb876 100644 --- a/.github/workflows/voltron_build_tests.yml +++ b/.github/workflows/voltron_build_tests.yml @@ -91,7 +91,7 @@ jobs: working-directory: framework/voltron voltron_ios_flutter_build: - runs-on: macos-latest + runs-on: macos-13 steps: - uses: actions/checkout@v3 - uses: subosito/flutter-action@v2 @@ -103,3 +103,5 @@ jobs: run: flutter pub get - name: Build for iOS run: flutter build ios --release --no-codesign + env: + DEVELOPER_DIR: /Applications/Xcode_14.3.1.app/Contents/Developer diff --git a/docs/development/web-integration.md b/docs/development/web-integration.md index 4d373acddcb..3d77a392597 100644 --- a/docs/development/web-integration.md +++ b/docs/development/web-integration.md @@ -1,7 +1,7 @@ # 前端接入 -Hippy 同时支持 React 和 Vue 两种 UI 框架,通过 [@hippy/react](//www.npmjs.com/package/@hippy/react) 和 [@hippy/vue](//www.npmjs.com/package/@hippy/vue) 两个包提供实现。 +Hippy 同时支持 React 和 Vue 两种 UI 框架,通过 [@hippy/react](//www.npmjs.com/package/@hippy/react) 和 [@hippy/vue](//www.npmjs.com/package/@hippy/vue) 及 [@hippy/vue-next](//www.npmjs.com/package/@hippy/vue-next) 三个包提供实现。 # hippy-react @@ -97,20 +97,20 @@ new Hippy({ // P.S. entryPage需要通过单节点包裹,不能用数组的形式,例如 import React from 'react'; import { - View, - Text, + View, + Text, } from '@hippy/react'; export default function app() { - // 入口文件不要使用这种形式,非入口文件可以使用 - return [ - , - test test - ]; - // 修改成通过单节点包裹 - return ( - , - test test - ); + // 入口文件不要使用这种形式,非入口文件可以使用 + return [ + , + test test + ]; + // 修改成通过单节点包裹 + return ( + , + test test + ); } ``` @@ -121,10 +121,10 @@ export default function app() { ```json "scripts": { - "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" - } +"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" +} ``` ## hippy-react 转 Web @@ -133,6 +133,8 @@ export default function app() { # hippy-vue +>注意:因vue2.x版本将于年底停止更新,建议用户升级至使用vue3.x版本的@hippy/vue-next + [[hippy-vue 介绍]](api/hippy-vue/introduction.md) [[范例工程]](//github.com/Tencent/Hippy/tree/master/examples/hippy-vue-demo) hippy-vue 相对简单很多,hippy-vue 只是 [Vue](//vuejs.org) 在终端上的渲染层,组件也基本和浏览器保持一致。可以通过 [vue-cli](//cli.vuejs.org/) 先[创建一个 Web 项目](//cli.vuejs.org/zh/guide/creating-a-project.html),然后加上一些 hippy-vue 的内容就可以直接将网页渲染到终端了。 @@ -284,6 +286,186 @@ setApp(app); 在 [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: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" +}, +``` + +## 路由 + +`@hippy/vue-router` 完整支持 vue-router 中的跳转功能,具体请参考 [hippy-vue-router](api/hippy-vue/router.md) 文档。 + +# hippy-vue-next + +[[hippy-vue-next 介绍]](api/hippy-vue/vue3) [[范例工程]](//github.com/Tencent/Hippy/tree/master/examples/hippy-vue-next-demo) + +hippy-vue-next 是 [Vue](//cn.vuejs.org) 在终端上的渲染层,组件也基本和浏览器保持一致。可以通过脚手架 [vue-cli](//github.com/vuejs/vue-cli) 先[创建一个 Web 项目](//cli.vuejs.org/zh/guide/creating-a-project.html#vue-create),然后加上一些 hippy-vue-next 的内容就可以直接将网页渲染到终端了。也可以参考我们的范例项目来初始化你的项目。 +>注意这里使用vue-cli创建项目时构建工具要选择webpack,并且Router和Typescript需要勾选,我们的hippy-vue-next默认都是基于Typescript开发的 + +## 准备 hippy-vue-next 运行时依赖 + +请使用 `npm i` 安装以下 npm 包,保证运行时正常。 + +| 包名 | 说明 | +|---------------|-------------------------------------| +| @hippy/vue-next | hippy-vue-next 运行时核心 | + +## hippy-vue-next 编译时依赖 + +以官方提供的 [范例工程](//github.com/Tencent/Hippy/tree/master/examples/hippy-vue-next-demo) 范例工程为例,需要使用 `npm i -D` 准备好以下依赖,当然开发者可以根据需要自行选择: + +必须的: + +| 包名 | 说明 | +|------------------------------|------------------------------------| +| @hippy/debug-server-next | Hippy 前终端调试服务 | +| @hippy/vue-css-loader | hippy-vue-next 的 CSS 文本到 JS 语法树转换 | +| @hippy/vue-next-style-parser | hippy-vue-next 的样式 parser | +| @babel/preset-env | Babel 插件 - 根据所设置的环境选择 polyfill | +| @babel/core | Babel - 高版本 ES 转换为 ES6 和 ES5 的转译程序 | +| babel-loader | Webpack 插件 - 加载 Babel 转译后的代码 | +| webpack | Webpack 打包程序 | +| webpack-cli | Webpack 命令行 | + +可选的: + +| 包名 | 说明 | +|-------------------------------------|-----------------------------------------------------------------------| +| case-sensitive-paths-webpack-plugin | Webpack 插件,对 import 文件进行大小写检查 | +| @hippy/hippy-live-reload-polyfill | live-reload 必备脚本 - 会在调试模式编译时注入代码到工程里 | +| @hippy/hippy-dynamic-import-plugin | 动态加载插件 - 拆分出子包用于按需加载 | +| @hippy/vue-router-next-history | 支持按安卓物理返回键回退路由 | +| @babel/plugin-x | Babel 其余相关插件,如 `@babel/plugin-proposal-nullish-coalescing-operator` 等 | +| file-loader | 静态文件加载 | +| url-loader | 静态文件以 Base64 形式加载 | +| esbuild & esbuild-loader | 开发环境webpack支持使用esbuild构建,性能比babel更好 | + +## hippy-vue-next 编译配置 + +当前 hippy-vue-next 支持 `Webpack 4 或 Webpack 5`构建,配置全部放置于 [scripts](//github.com/Tencent/Hippy/tree/master/examples/hippy-vue-next-demo/scripts) 目录下,其实只是 [webpack](//webpack.js.org/) 的配置文件,建议先阅读 [webpack](//webpack.js.org/) 官网内容,具备一定基础后再进行修改。 + +### hippy-vue-next 开发调试编译配置 + +该配置展示了将 Hippy 运行于终端的最小化配置。 + +| 配置文件 | 说明 | +|--------------------------------------------------------------------------------------------------------------------------| ---------- | +| [hippy-webpack.dev.js](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-next-demo/scripts/hippy-webpack.dev.js) | 调试用配置 | + +### hippy-vue-next 生产环境编译配置 + +线上包和开发调试用包主要有两个区别: + +1. 开启了 production 模式,去掉调试信息,关闭了 `watch`(watch 模式下会监听文件变动并重新打包)。 +2. 终端内很可能不止运行一个 Hippy 业务,所以将共享的部分单独拆出来做成了 `vendor` 包,这样可以有效减小业务包体积,这里使用了 [DllPlugin](//webpack.js.org/plugins/dll-plugin/) 和 [DllReferencePlugin](//webpack.js.org/plugins/dll-plugin/#dllreferenceplugin) 来实现。 + +| 配置文件 | 说明 | +|-------------------------------------------------------------------------------------------------------------------------------------------| ----------------------------- | +| [vendor.js](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-next-demo/scripts/vendor.js) | vendor 包中需要包含的共享部分 | +| [hippy-webpack.ios.js](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-next-demo/scripts/hippy-webpack.ios.js) | iOS 业务包配置 | +| [hippy-webpack.ios-vendor.js](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-next-demo/scripts/hippy-webpack.ios-vendor.js) | iOS Vendor 包配置 | +| [hippy-webpack.android.js](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-next-demo/scripts/hippy-webpack.android.js) | Android 业务包配置 | +| [hippy-webpack.android-vendor.js](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-next-demo/scripts/hippy-webpack.android-vendor.js) | Android Vendor 包配置 | + +如果仔细观察 webpack 配置,可以看出 iOS 和 Android 配置相差不大,但因为 iOS 上受苹果政策影响只能使用 [JavaScriptCore](//developer.apple.com/documentation/javascriptcore)(以下简称 JSC)作为运行环境,而 JSC 是跟随 iOS 操作系统的,无法进行独立升级,低版本 iOS 带的 JSC 甚至无法完整支持 ES6,所以需要输出一份 ES5 版本的 JS 代码。而 Android 下可以使用独立升级的 [X5](//x5.tencent.com/) 中的 V8 作为运行环境,就可以直接使用 ES6 代码了。 + +!> **特别说明:** JS 可以使用的语法受到 iOS 覆盖的最低版本的影响,绝大多数能力可以通过 `@babel/preset-env` 自动安装 polyfill,但是部分特性不行,例如要使用 [Proxy](//caniuse.com/#feat=proxy),就无法覆盖 iOS 10 以下版本,而hippy-vue-next是基于vue-next的,因此使用hippy-vue-next iOS版本必须要10及以上。 + +## hippy-vue-next 入口文件 + +因为 hippy-vue-next 的启动参数与 web 页面不一样,所以我们需要专门的 [终端入口文件](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-next-demo/src/main-native.ts)来加载一些终端上用到的模块,并作为项目的入口文件 + +```ts +// 首先导入所需模块 +import { + createApp, + type HippyApp, + EventBus, + setScreenSize, + BackAndroid, +} from '@hippy/vue-next'; + +import App from './app.vue'; +import { createRouter } from './routes'; +import { setGlobalInitProps } from './util'; + +// 创建 hippy app 实例 +const app: HippyApp = createApp(App, { + // hippy native module name + appName: 'Demo', + iPhone: { + // config of statusBar + statusBar: { + // disable status bar autofill + // disabled: true, + + // Status bar background color, if not set, it will use 4282431619, as #40b883, Vue default green + // hippy-vue-css-loader/src/compiler/style/color-parser.js + backgroundColor: 4283416717, + + // 状态栏背景图,要注意这个会根据容器尺寸拉伸。 + // backgroundImage: 'https://user-images.githubusercontent.com/12878546/148737148-d0b227cb-69c8-4b21-bf92-739fb0c3f3aa.png', + }, + }, + // do not print trace info when set to true + // silent: true, + /** + * whether to trim whitespace on text element, + * default is true, if set false, it will follow vue-loader compilerOptions whitespace setting + */ + trimWhitespace: true, +}); +// create router +const router = createRouter(); +app.use(router); + +// init callback +const initCallback = ({ superProps, rootViewId }) => { + setGlobalInitProps({ + superProps, + rootViewId, + }); + /** + * 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. + * On the browser, it is matched by vue-router according to location.href, and the default push root path '/' + */ + router.push('/'); + + // listen android native back press, must before router back press inject + BackAndroid.addListener(() => { + console.log('backAndroid'); + // set true interrupts native back + return true; + }); + + // mount first, you can do something before mount + app.mount('#root'); + + /** + * You can also mount the app after the route is ready, However, + * it is recommended to mount first, because it can render content on the screen as soon as possible + */ + // router.isReady().then(() => { + // // mount app + // app.mount('#root'); + // }); +}; + +// start hippy app +app.$start().then(initCallback); + +// you can also use callback to start app like @hippy/vue before +// app.$start(initCallback); +``` + +## hippy-vue-next npm 脚本 + +在 [package.json](//github.com/Tencent/Hippy/blob/master/examples/hippy-vue-next-demo/package.json#L13) 中提供了几个以 `hippy:`开头的 npm 脚本,可用来启动 [@hippy/debug-server-next](//www.npmjs.com/package/@hippy/debug-server-next) 等调试工具。 + ```json "scripts": { "hippy:dev": "node ./scripts/env-polyfill.js hippy-dev --config ./scripts/hippy-webpack.dev.js", @@ -294,5 +476,4 @@ setApp(app); ## 路由 -`@hippy/vue-router` 完整支持 vue-router 中的跳转功能,具体请参考 [hippy-vue-router](api/hippy-vue/router.md) 文档。 - +`@hippy/vue-next` 无需侵入式修改vue-router,直接使用官方 vue-router 即可,如果需要支持安卓物理健回退时路由历史回退,则可以安装@hippy/vue-router-next-history模块。 diff --git a/driver/js/include/driver/performance/performance.h b/driver/js/include/driver/performance/performance.h index 093a1329d23..971138f507f 100644 --- a/driver/js/include/driver/performance/performance.h +++ b/driver/js/include/driver/performance/performance.h @@ -28,6 +28,8 @@ #include "footstone/string_view.h" #include "driver/performance/performance_entry.h" #include "driver/performance/performance_resource_timing.h" +#include "driver/performance/performance_navigation_timing.h" +#include "driver/performance/performance_paint_timing.h" namespace hippy { inline namespace driver { @@ -54,6 +56,10 @@ class Performance { return time_origin_; } + std::shared_ptr PerformanceNavigation(const string_view& name); + std::shared_ptr PerformancePaint(const PerformancePaintTiming::Type& type); + std::shared_ptr PerformanceResource(const string_view& name); + void Mark(const string_view& name); void ClearMarks(const string_view& name); void ClearMarks(); @@ -67,8 +73,8 @@ class Performance { void ClearResourceTimings(); std::vector> GetEntries(const PerformanceEntryFilterOptions& options); std::vector> GetEntries(); - std::shared_ptr GetEntriesByName(const string_view& name); - std::shared_ptr GetEntriesByName(const string_view& name, PerformanceEntry::Type type); + std::vector> GetEntriesByName(const string_view& name); + std::vector> GetEntriesByName(const string_view& name, PerformanceEntry::Type type); std::vector> GetEntriesByType(PerformanceEntry::Type type); string_view ToJSON(); diff --git a/driver/js/include/driver/performance/performance_entry.h b/driver/js/include/driver/performance/performance_entry.h index cd1e8612153..182cdeccbbb 100644 --- a/driver/js/include/driver/performance/performance_entry.h +++ b/driver/js/include/driver/performance/performance_entry.h @@ -55,6 +55,12 @@ class PerformanceEntry { start_time_(start_time), duration_(duration) {} + PerformanceEntry(const string_view& name, + SubType sub_type, + Type type) : name_(name), + sub_type_(sub_type), + type_(type) {} + virtual ~PerformanceEntry() = default; inline auto GetName() const { diff --git a/driver/js/include/driver/performance/performance_navigation_timing.h b/driver/js/include/driver/performance/performance_navigation_timing.h index 618b565c2e1..4db214968f7 100644 --- a/driver/js/include/driver/performance/performance_navigation_timing.h +++ b/driver/js/include/driver/performance/performance_navigation_timing.h @@ -33,62 +33,48 @@ inline namespace performance { class PerformanceNavigationTiming : public PerformanceEntry { public: struct BundleInfo { - string_view bundle_url; - TimePoint start; - TimePoint end; + string_view url_; + TimePoint execute_source_start_; + TimePoint execute_source_end_; }; - PerformanceNavigationTiming(const string_view& name, const TimePoint& start, - const TimePoint& engine_initialization_start, const TimePoint& engine_initialization_end, - std::vector bundle_info, - const TimePoint& load_instance_start, const TimePoint& load_instance_end, - const TimePoint& first_frame); + PerformanceNavigationTiming(const string_view& name); - inline auto GetEngineInitializationStart() const { - return engine_initialization_start_; +#define DEFINE_SET_AND_GET_METHOD(method_name, member_type, member) \ + void Set##method_name(member_type t) { \ + member = t; \ + } \ + inline auto Get##method_name() const { \ + return member; \ } + DEFINE_SET_AND_GET_METHOD(HippyNativeInitStart, TimePoint, hippy_native_init_start_) + DEFINE_SET_AND_GET_METHOD(HippyNativeInitEnd, TimePoint, hippy_native_init_end_) + DEFINE_SET_AND_GET_METHOD(HippyJsEngineInitStart, TimePoint, hippy_js_engine_init_start_) + DEFINE_SET_AND_GET_METHOD(HippyJsEngineInitEnd, TimePoint, hippy_js_engine_init_end_) + DEFINE_SET_AND_GET_METHOD(HippyRunApplicationStart, TimePoint, hippy_run_application_start_) + DEFINE_SET_AND_GET_METHOD(HippyRunApplicationEnd, TimePoint, hippy_run_application_end_) + DEFINE_SET_AND_GET_METHOD(HippyFirstFrameStart, TimePoint, hippy_first_frame_start_) + DEFINE_SET_AND_GET_METHOD(HippyFirstFrameEnd, TimePoint, hippy_first_frame_end_) +#undef DEFINE_SET_AND_GET_METHOD - inline auto GetEngineInitializationEnd() const { - return engine_initialization_end_; + inline const std::vector& GetBundleInfoArray() const { + return bundle_info_array_; } - inline auto GetBundleInfo() const { - return bundle_info_; - } - - inline auto GetLoadInstanceStart() const { - return load_instance_start_; - } - - inline auto GetLoadInstanceEnd() const { - return load_instance_end_; - } - - inline auto GetFirstFrame() const { - return first_frame_; - } + BundleInfo& BundleInfoOfUrl(const string_view& url); virtual string_view ToJSON() override; private: -// TimePoint dom_complete_; -// TimePoint dom_content_loaded_event_end_; -// TimePoint dom_content_loaded_event_start_; -// TimePoint dom_interactive_; -// TimePoint load_event_end_; -// TimePoint load_event_start_; -// uint32_t redirect_count_; -// TimePoint request_start_; -// TimePoint response_start_; -// string_view type_; // navigate, reload, back_forward or prerender -// TimePoint unload_event_end_; -// TimePoint unload_event_start_; - TimePoint engine_initialization_start_; - TimePoint engine_initialization_end_; - std::vector bundle_info_; - TimePoint load_instance_start_; - TimePoint load_instance_end_; - TimePoint first_frame_; + TimePoint hippy_native_init_start_; + TimePoint hippy_native_init_end_; + TimePoint hippy_js_engine_init_start_; + TimePoint hippy_js_engine_init_end_; + std::vector bundle_info_array_; + TimePoint hippy_run_application_start_; + TimePoint hippy_run_application_end_; + TimePoint hippy_first_frame_start_; + TimePoint hippy_first_frame_end_; }; } diff --git a/driver/js/include/driver/performance/performance_paint_timing.h b/driver/js/include/driver/performance/performance_paint_timing.h index f59331f842f..ae9e74e2230 100644 --- a/driver/js/include/driver/performance/performance_paint_timing.h +++ b/driver/js/include/driver/performance/performance_paint_timing.h @@ -36,6 +36,7 @@ class PerformancePaintTiming : public PerformanceEntry { }; PerformancePaintTiming(Type type, const TimePoint& start_time); + PerformancePaintTiming(Type type); virtual string_view ToJSON() override; diff --git a/driver/js/include/driver/performance/performance_resource_timing.h b/driver/js/include/driver/performance/performance_resource_timing.h index f36a60cd0cf..33c9d0e7bb8 100644 --- a/driver/js/include/driver/performance/performance_resource_timing.h +++ b/driver/js/include/driver/performance/performance_resource_timing.h @@ -31,109 +31,32 @@ inline namespace performance { class PerformanceResourceTiming: public PerformanceEntry { public: enum class InitiatorType { - AUDIO, BEACON, BODY, CSS, EARLY_HINT, EMBED, FETCH, FRAME, IFRAME, ICON, IMAGE, IMG, INPUT, LINK, NAVIGATION, OBJECT, + OTHER, AUDIO, BEACON, BODY, CSS, EARLY_HINT, EMBED, FETCH, FRAME, IFRAME, ICON, IMAGE, IMG, INPUT, LINK, NAVIGATION, OBJECT, PING, SCRIPT, TRACK, VIDEO, XMLHTTPREQUEST }; - PerformanceResourceTiming(const string_view& name, TimePoint start_time, TimeDelta duration, - const InitiatorType& initiator_type, const string_view& next_hop_protocol, TimePoint worker_start, - TimePoint redirect_start, TimePoint redirect_end, TimePoint fetch_start, - TimePoint domain_lookup_start, TimePoint domain_lookup_end, TimePoint connect_start, - TimePoint connect_end, TimePoint secure_connection_start, TimePoint request_start_, - TimePoint response_start, TimePoint response_end, uint64_t transfer_size, - uint64_t encoded_body_size, uint64_t decoded_body_size); + PerformanceResourceTiming(const string_view& name); - inline InitiatorType GetInitiatorType() { - return initiator_type_; - } - - inline string_view GetNextHopProtocol() { - return next_hop_protocol_; - } - - inline TimePoint GetWorkerStart() { - return worker_start_; - } - - inline TimePoint GetRedirectStart() { - return redirect_start_; - } - - inline TimePoint GetRedirectEnd() { - return redirect_end_; - } - - inline TimePoint GetFetchStart() { - return fetch_start_; - } - - inline TimePoint GetDomainLookupStart() { - return domain_lookup_start_; - } - - inline TimePoint GetDomainLookupEnd() { - return domain_lookup_end_; - } - - inline TimePoint GetConnectStart() { - return connect_start_; - } - - inline TimePoint GetConnectEnd() { - return connect_end_; - } - - inline TimePoint GetSecureConnectionStart() { - return secure_connection_start_; - } - - inline TimePoint GetRequestStart() { - return request_start_; - } - - inline TimePoint GetResponseStart() { - return response_start_; - } - - inline TimePoint GetResponseEnd() { - return response_end_; - } - - inline uint64_t GetTransferSize() { - return transfer_size_; - } - - inline uint64_t GetEncodedBodySize() { - return encoded_body_size_; - } - - inline uint64_t GetDecodedBodySize() { - return decoded_body_size_; +#define DEFINE_SET_AND_GET_METHOD(method_name, member_type, member) \ + void Set##method_name(member_type t) { \ + member = t; \ + } \ + inline auto Get##method_name() const { \ + return member; \ } + DEFINE_SET_AND_GET_METHOD(InitiatorType, InitiatorType, initiator_type_) + DEFINE_SET_AND_GET_METHOD(LoadSourceStart, TimePoint, load_source_start_) + DEFINE_SET_AND_GET_METHOD(LoadSourceEnd, TimePoint, load_source_end_) +#undef DEFINE_SET_AND_GET_METHOD virtual string_view ToJSON() override; static string_view GetInitiatorString(InitiatorType type); private: - InitiatorType initiator_type_; - string_view next_hop_protocol_; - TimePoint worker_start_; - TimePoint redirect_start_; - TimePoint redirect_end_; - TimePoint fetch_start_; - TimePoint domain_lookup_start_; - TimePoint domain_lookup_end_; - TimePoint connect_start_; - TimePoint connect_end_; - TimePoint secure_connection_start_; - TimePoint request_start_; - TimePoint response_start_; - TimePoint response_end_; - uint64_t transfer_size_; - uint64_t encoded_body_size_; - uint64_t decoded_body_size_; - // std::vector<> server_timing_{}; + InitiatorType initiator_type_ = InitiatorType::OTHER; + TimePoint load_source_start_; + TimePoint load_source_end_; }; } diff --git a/driver/js/include/driver/scope.h b/driver/js/include/driver/scope.h index f23e7503acf..55dff530563 100644 --- a/driver/js/include/driver/scope.h +++ b/driver/js/include/driver/scope.h @@ -136,6 +136,7 @@ class Scope : public std::enable_shared_from_this { using Encoding = hippy::napi::Encoding; using TaskRunner = footstone::runner::TaskRunner; using Task = footstone::Task; + using TimePoint = footstone::TimePoint; #ifdef ENABLE_INSPECTOR using DevtoolsDataSource = hippy::devtools::DevtoolsDataSource; @@ -241,6 +242,7 @@ class Scope : public std::enable_shared_from_this { uint64_t GetListenerId(const EventListenerInfo& event_listener_info); void RunJS(const string_view& js, + const string_view& uri, const string_view& name, bool is_copy = true); @@ -270,6 +272,22 @@ class Scope : public std::enable_shared_from_this { inline void SetUriLoader(std::weak_ptr loader) { loader_ = loader; + auto the_loader = loader_.lock(); + if (the_loader) { + the_loader->SetRequestTimePerformanceCallback([WEAK_THIS](const string_view& uri, const TimePoint& start, const TimePoint& end) { + DEFINE_AND_CHECK_SELF(Scope) + auto runner = self->GetTaskRunner(); + if (runner) { + auto task = [weak_this, uri, start, end]() { + DEFINE_AND_CHECK_SELF(Scope) + auto entry = self->GetPerformance()->PerformanceResource(uri); + entry->SetLoadSourceStart(start); + entry->SetLoadSourceEnd(end); + }; + runner->PostTask(std::move(task)); + } + }); + } } inline std::weak_ptr GetUriLoader() { return loader_; } diff --git a/driver/js/include/driver/vm/js_vm.h b/driver/js/include/driver/vm/js_vm.h index 07ea1e5b09b..318b029f4ef 100644 --- a/driver/js/include/driver/vm/js_vm.h +++ b/driver/js/include/driver/vm/js_vm.h @@ -27,11 +27,12 @@ #include "driver/napi/js_ctx.h" #include "footstone/logging.h" +namespace hippy { #ifdef ENABLE_INSPECTOR -#include "devtools/devtools_data_source.h" +namespace devtools { +class DevtoolsDataSource; +} #endif - -namespace hippy { inline namespace driver { inline namespace vm { diff --git a/driver/js/src/js_driver_utils.cc b/driver/js/src/js_driver_utils.cc index 25ef8c837ac..dd42b720484 100644 --- a/driver/js/src/js_driver_utils.cc +++ b/driver/js/src/js_driver_utils.cc @@ -320,6 +320,11 @@ bool JsDriverUtils::RunScript(const std::shared_ptr& scope, << ", script content empty, uri = " << uri; return false; } + + // perfromance start time + auto entry = scope->GetPerformance()->PerformanceNavigation("hippyInit"); + entry->BundleInfoOfUrl(uri).execute_source_start_ = footstone::TimePoint::SystemNow(); + #ifdef JS_V8 auto ret = std::static_pointer_cast(scope->GetContext())->RunScript( script_content, file_name, is_use_code_cache,&code_cache_content, true); @@ -357,8 +362,12 @@ bool JsDriverUtils::RunScript(const std::shared_ptr& scope, auto ret = scope->GetContext()->RunScript(script_content, file_name); #endif + // perfromance end time + entry->BundleInfoOfUrl(uri).execute_source_end_ = footstone::TimePoint::SystemNow(); + auto flag = (ret != nullptr); FOOTSTONE_LOG(INFO) << "runScript end, flag = " << flag; + return flag; } diff --git a/driver/js/src/modules/contextify_module.cc b/driver/js/src/modules/contextify_module.cc index 8e4f1e60f49..bcef9834c2a 100644 --- a/driver/js/src/modules/contextify_module.cc +++ b/driver/js/src/modules/contextify_module.cc @@ -175,7 +175,7 @@ void ContextifyModule::LoadUntrustedContent(CallbackInfo& info, void* data) { auto try_catch = CreateTryCatchScope(true, scope->GetContext()); try_catch->SetVerbose(true); string_view view_code(reinterpret_cast(move_code.c_str()), move_code.length()); - scope->RunJS(view_code, file_name); + scope->RunJS(view_code, uri, file_name); ctx->SetProperty(global_object, cur_dir_key, last_dir_str_obj, hippy::napi::PropertyAttribute::ReadOnly); if (try_catch->HasCaught()) { error = try_catch->Exception(); diff --git a/driver/js/src/modules/performance/performance_entry_module.cc b/driver/js/src/modules/performance/performance_entry_module.cc index 6ba57ee48db..c7175992f12 100644 --- a/driver/js/src/modules/performance/performance_entry_module.cc +++ b/driver/js/src/modules/performance/performance_entry_module.cc @@ -63,12 +63,12 @@ std::shared_ptr> RegisterPerformanceEntry(const return nullptr; } - auto entry = scope->GetPerformance()->GetEntriesByName(name, static_cast(type)); - if (!entry) { + auto entries = scope->GetPerformance()->GetEntriesByName(name, static_cast(type)); + if (entries.empty()) { exception = context->CreateException("entry not found"); return nullptr; } - return entry; + return entries.back(); }; return std::make_shared>(std::move(class_template)); } diff --git a/driver/js/src/modules/performance/performance_frame_timing_module.cc b/driver/js/src/modules/performance/performance_frame_timing_module.cc index df389365d31..991fb9b9be9 100644 --- a/driver/js/src/modules/performance/performance_frame_timing_module.cc +++ b/driver/js/src/modules/performance/performance_frame_timing_module.cc @@ -63,12 +63,12 @@ std::shared_ptr> RegisterPerformanceFrameT return nullptr; } - auto entry = scope->GetPerformance()->GetEntriesByName(name, static_cast(type)); - if (!entry) { + auto entries = scope->GetPerformance()->GetEntriesByName(name, static_cast(type)); + if (entries.empty()) { exception = context->CreateException("entry not found"); return nullptr; } - return std::static_pointer_cast(entry); + return std::static_pointer_cast(entries.back()); }; return std::make_shared>(std::move(class_template)); diff --git a/driver/js/src/modules/performance/performance_module.cc b/driver/js/src/modules/performance/performance_module.cc index e8f1309a3fd..1dc69cd7f1a 100644 --- a/driver/js/src/modules/performance/performance_module.cc +++ b/driver/js/src/modules/performance/performance_module.cc @@ -184,6 +184,7 @@ std::shared_ptr> RegisterPerformance(const std::weak_ exception = context->CreateException("measure startMark not found"); return nullptr; } + return nullptr; } string_view end_mark; flag = context->GetValueString(arguments[2], &end_mark); @@ -250,11 +251,16 @@ std::shared_ptr> RegisterPerformance(const std::weak_ return nullptr; } if (argument_count == 1) { - auto entry = performance->GetEntriesByName(name); - auto javascript_class = scope->GetJavascriptClass(PerformanceEntry::GetSubTypeString(entry->GetSubType())); - std::shared_ptr argv[] = { context->CreateString(entry->GetName()), - context->CreateNumber(static_cast(entry->GetSubType())) }; - return context->NewInstance(javascript_class, 2, argv, entry.get()); + auto entries = performance->GetEntriesByName(name); + std::shared_ptr instances[entries.size()]; + for (size_t i = 0; i < entries.size(); ++i) { + auto entry = entries[i]; + auto javascript_class = scope->GetJavascriptClass(PerformanceEntry::GetSubTypeString(entry->GetSubType())); + std::shared_ptr argv[] = { context->CreateString(entry->GetName()), + context->CreateNumber(static_cast(entry->GetType())) }; + instances[i] = context->NewInstance(javascript_class, 2, argv, entry.get()); + } + return context->CreateArray(entries.size(), instances); } string_view type; flag = context->GetValueString(arguments[1], &type); @@ -267,14 +273,16 @@ std::shared_ptr> RegisterPerformance(const std::weak_ exception = context->CreateException("entry_type error"); return nullptr; } - auto entry = performance->GetEntriesByName(name, entry_type); - if (!entry) { - return nullptr; + auto entries = performance->GetEntriesByName(name, entry_type); + std::shared_ptr instances[entries.size()]; + for (size_t i = 0; i < entries.size(); ++i) { + auto entry = entries[i]; + auto javascript_class = scope->GetJavascriptClass(PerformanceEntry::GetSubTypeString(entry->GetSubType())); + std::shared_ptr argv[] = { context->CreateString(entry->GetName()), + context->CreateNumber(static_cast(entry->GetType())) }; + instances[i] = context->NewInstance(javascript_class, 2, argv, entry.get()); } - auto javascript_class = scope->GetJavascriptClass(PerformanceEntry::GetSubTypeString(entry->GetSubType())); - std::shared_ptr argv[] = { context->CreateString(entry->GetName()), - context->CreateNumber(static_cast(entry->GetSubType())) }; - return context->NewInstance(javascript_class, 2, argv, entry.get()); + return context->CreateArray(entries.size(), instances); }; class_template.functions.emplace_back(std::move(get_entries_by_name_function_define)); @@ -311,7 +319,7 @@ std::shared_ptr> RegisterPerformance(const std::weak_ auto entry = entries[i]; auto javascript_class = scope->GetJavascriptClass(PerformanceEntry::GetSubTypeString(entry->GetSubType())); std::shared_ptr argv[] = { context->CreateString(entry->GetName()), - context->CreateNumber(static_cast(entry->GetSubType())) }; + context->CreateNumber(static_cast(entry->GetType())) }; instances[i] = context->NewInstance(javascript_class, 2, argv, entry.get()); } return context->CreateArray(entries.size(), instances); @@ -384,7 +392,7 @@ std::shared_ptr> RegisterPerformance(const std::weak_ auto entry = entries[i]; auto javascript_class = scope->GetJavascriptClass(PerformanceEntry::GetSubTypeString(entry->GetSubType())); std::shared_ptr argv[] = { context->CreateString(entry->GetName()), - context->CreateNumber(static_cast(entry->GetSubType())) }; + context->CreateNumber(static_cast(entry->GetType())) }; instances[i] = context->NewInstance(javascript_class, 2, argv, entry.get()); } return context->CreateArray(entries.size(), instances); diff --git a/driver/js/src/modules/performance/performance_navigation_timing_module.cc b/driver/js/src/modules/performance/performance_navigation_timing_module.cc index 6458f1b36a2..3231d4f7985 100644 --- a/driver/js/src/modules/performance/performance_navigation_timing_module.cc +++ b/driver/js/src/modules/performance/performance_navigation_timing_module.cc @@ -33,8 +33,8 @@ inline namespace driver { inline namespace module { constexpr char kBundleInfoUrlKey[] = "url"; -constexpr char kBundleInfoStartKey[] = "start"; -constexpr char kBundleInfoEndKey[] = "end"; +constexpr char kBundleInfoStartKey[] = "executeSourceStart"; +constexpr char kBundleInfoEndKey[] = "executeSourceEnd"; std::shared_ptr> RegisterPerformanceNavigationTiming(const std::weak_ptr& weak_scope) { ClassTemplate class_template; @@ -67,102 +67,64 @@ std::shared_ptr> RegisterPerformanceN return nullptr; } - auto entry = scope->GetPerformance()->GetEntriesByName(name, static_cast(type)); - if (!entry) { + auto entries = scope->GetPerformance()->GetEntriesByName(name, static_cast(type)); + if (entries.empty()) { exception = context->CreateException("entry not found"); return nullptr; } - return std::static_pointer_cast(entry); + return std::static_pointer_cast(entries.back()); }; - PropertyDefine engine_initialization_start; - engine_initialization_start.name = "engineInitializationStart"; - engine_initialization_start.getter = [weak_scope](PerformanceNavigationTiming* thiz, - std::shared_ptr& exception) -> std::shared_ptr { - auto scope = weak_scope.lock(); - if (scope) { - return nullptr; - } - auto context = scope->GetContext(); - return context->CreateNumber(thiz->GetEngineInitializationStart().ToEpochDelta().ToMillisecondsF()); - }; - class_template.properties.push_back(std::move(engine_initialization_start)); +#define ADD_PROPERTY(prop_var, prop_name, get_prop_method) \ + PropertyDefine prop_var; \ + prop_var.name = prop_name; \ + prop_var.getter = [weak_scope](PerformanceNavigationTiming* thiz, \ + std::shared_ptr& exception) -> std::shared_ptr { \ + auto scope = weak_scope.lock(); \ + if (!scope) { \ + return nullptr; \ + } \ + auto context = scope->GetContext(); \ + return context->CreateNumber(thiz->get_prop_method().ToEpochDelta().ToMillisecondsF()); \ + }; \ + class_template.properties.push_back(std::move(prop_var)); - PropertyDefine engine_initialization_end; - engine_initialization_end.name = "engineInitializationEnd"; - engine_initialization_end.getter = [weak_scope](PerformanceNavigationTiming* thiz, - std::shared_ptr& exception) -> std::shared_ptr { - auto scope = weak_scope.lock(); - if (scope) { - return nullptr; - } - auto context = scope->GetContext(); - return context->CreateNumber(thiz->GetEngineInitializationEnd().ToEpochDelta().ToMillisecondsF()); - }; - class_template.properties.push_back(std::move(engine_initialization_end)); + ADD_PROPERTY(hippy_native_init_start, "hippyNativeInitStart", GetHippyNativeInitStart) + ADD_PROPERTY(hippy_native_init_end, "hippyNativeInitEnd", GetHippyNativeInitEnd) + ADD_PROPERTY(hippy_js_engine_init_start, "hippyJsEngineInitStart", GetHippyJsEngineInitStart) + ADD_PROPERTY(hippy_js_engine_init_end, "hippyJsEngineInitEnd", GetHippyJsEngineInitEnd) + ADD_PROPERTY(hippy_run_application_start, "hippyRunApplicationStart", GetHippyRunApplicationStart) + ADD_PROPERTY(hippy_run_application_end, "hippyRunApplicationEnd", GetHippyRunApplicationEnd) + ADD_PROPERTY(hippy_first_frame_start, "hippyFirstFrameStart", GetHippyFirstFrameStart) + ADD_PROPERTY(hippy_first_frame_end, "hippyFirstFrameEnd", GetHippyFirstFrameEnd) +#undef ADD_PROPERTY PropertyDefine bundle_info; bundle_info.name = "bundleInfo"; bundle_info.getter = [weak_scope](PerformanceNavigationTiming* thiz, - std::shared_ptr& exception) -> std::shared_ptr { + std::shared_ptr& exception) -> std::shared_ptr { auto scope = weak_scope.lock(); - if (scope) { + if (!scope) { return nullptr; } auto context = scope->GetContext(); - auto bundle_info = thiz->GetBundleInfo(); - std::shared_ptr array[bundle_info.size()]; - for (const auto& info: bundle_info) { + auto bundle_info_array = thiz->GetBundleInfoArray(); + std::shared_ptr array[bundle_info_array.size()]; + for (size_t i = 0; i < bundle_info_array.size(); ++i) { + auto& info = bundle_info_array[i]; auto object = context->CreateObject(); - context->SetProperty(object, context->CreateString(kBundleInfoUrlKey), context->CreateString(info.bundle_url)); + context->SetProperty(object, context->CreateString(kBundleInfoUrlKey), + context->CreateString(info.url_)); context->SetProperty(object, context->CreateString(kBundleInfoStartKey), - context->CreateNumber(info.start.ToEpochDelta().ToMillisecondsF())); + context->CreateNumber(info.execute_source_start_.ToEpochDelta().ToMillisecondsF())); context->SetProperty(object, context->CreateString(kBundleInfoEndKey), - context->CreateNumber(info.end.ToEpochDelta().ToMillisecondsF())); + context->CreateNumber(info.execute_source_end_.ToEpochDelta().ToMillisecondsF())); + array[i] = object; } - return context->CreateArray(bundle_info.size(), array); + return context->CreateArray(bundle_info_array.size(), array); }; class_template.properties.push_back(std::move(bundle_info)); - PropertyDefine load_instance_start; - load_instance_start.name = "loadInstanceStart"; - load_instance_start.getter = [weak_scope](PerformanceNavigationTiming* thiz, - std::shared_ptr& exception) -> std::shared_ptr { - auto scope = weak_scope.lock(); - if (scope) { - return nullptr; - } - auto context = scope->GetContext(); - return context->CreateNumber(thiz->GetLoadInstanceStart().ToEpochDelta().ToMillisecondsF()); - }; - class_template.properties.push_back(std::move(load_instance_start)); - - PropertyDefine load_instance_end; - load_instance_end.name = "loadInstanceEnd"; - load_instance_end.getter = [weak_scope](PerformanceNavigationTiming* thiz, - std::shared_ptr& exception) -> std::shared_ptr { - auto scope = weak_scope.lock(); - if (scope) { - return nullptr; - } - auto context = scope->GetContext(); - return context->CreateNumber(thiz->GetLoadInstanceEnd().ToEpochDelta().ToMillisecondsF()); - }; - class_template.properties.push_back(std::move(load_instance_end)); - - PropertyDefine first_frame; - first_frame.name = "firstFrame"; - first_frame.getter = [weak_scope](PerformanceNavigationTiming* thiz, - std::shared_ptr& exception) -> std::shared_ptr { - auto scope = weak_scope.lock(); - if (scope) { - return nullptr; - } - auto context = scope->GetContext(); - return context->CreateNumber(thiz->GetFirstFrame().ToEpochDelta().ToMillisecondsF()); - }; - class_template.properties.push_back(std::move(first_frame)); - return std::make_shared>(std::move(class_template)); } diff --git a/driver/js/src/modules/performance/performance_paint_timing_module.cc b/driver/js/src/modules/performance/performance_paint_timing_module.cc index c37a596185d..1ad81689616 100644 --- a/driver/js/src/modules/performance/performance_paint_timing_module.cc +++ b/driver/js/src/modules/performance/performance_paint_timing_module.cc @@ -61,12 +61,12 @@ std::shared_ptr> RegisterPerformancePaintT return nullptr; } - auto entry = scope->GetPerformance()->GetEntriesByName(name, static_cast(type)); - if (!entry) { + auto entries = scope->GetPerformance()->GetEntriesByName(name, static_cast(type)); + if (entries.empty()) { exception = context->CreateException("entry not found"); return nullptr; } - return std::static_pointer_cast(entry); + return std::static_pointer_cast(entries.back()); }; return std::make_shared>(std::move(class_template)); diff --git a/driver/js/src/modules/performance/performance_resource_timing_module.cc b/driver/js/src/modules/performance/performance_resource_timing_module.cc index b401950f899..394b417d2cc 100644 --- a/driver/js/src/modules/performance/performance_resource_timing_module.cc +++ b/driver/js/src/modules/performance/performance_resource_timing_module.cc @@ -64,12 +64,12 @@ std::shared_ptr> RegisterPerformanceRes return nullptr; } - auto entry = scope->GetPerformance()->GetEntriesByName(name, static_cast(type)); - if (!entry) { + auto entries = scope->GetPerformance()->GetEntriesByName(name, static_cast(type)); + if (entries.empty()) { exception = context->CreateException("entry not found"); return nullptr; } - return std::static_pointer_cast(entry); + return std::static_pointer_cast(entries.back()); }; PropertyDefine initiator_type; @@ -77,7 +77,7 @@ std::shared_ptr> RegisterPerformanceRes initiator_type.getter = [weak_scope](PerformanceResourceTiming* thiz, std::shared_ptr& exception) -> std::shared_ptr { auto scope = weak_scope.lock(); - if (scope) { + if (!scope) { return nullptr; } auto context = scope->GetContext(); @@ -85,200 +85,23 @@ std::shared_ptr> RegisterPerformanceRes }; class_template.properties.push_back(std::move(initiator_type)); - PropertyDefine next_hop_protocol; - next_hop_protocol.name = "nextHopProtocol"; - next_hop_protocol.getter = [weak_scope](PerformanceResourceTiming* thiz, - std::shared_ptr& exception) -> std::shared_ptr { - auto scope = weak_scope.lock(); - if (scope) { - return nullptr; - } - auto context = scope->GetContext(); - return context->CreateString(thiz->GetNextHopProtocol()); - }; - class_template.properties.push_back(std::move(next_hop_protocol)); - - PropertyDefine redirect_start; - redirect_start.name = "redirectStart"; - redirect_start.getter = [weak_scope](PerformanceResourceTiming* thiz, - std::shared_ptr& exception) -> std::shared_ptr { - auto scope = weak_scope.lock(); - if (scope) { - return nullptr; - } - auto context = scope->GetContext(); - return context->CreateNumber(thiz->GetRedirectStart().ToEpochDelta().ToMillisecondsF()); - }; - class_template.properties.push_back(std::move(redirect_start)); - - PropertyDefine redirect_end; - redirect_end.name = "redirectEnd"; - redirect_end.getter = [weak_scope](PerformanceResourceTiming* thiz, - std::shared_ptr& exception) -> std::shared_ptr { - auto scope = weak_scope.lock(); - if (scope) { - return nullptr; - } - auto context = scope->GetContext(); - return context->CreateNumber(thiz->GetRedirectEnd().ToEpochDelta().ToMillisecondsF()); - }; - class_template.properties.push_back(std::move(redirect_end)); - - PropertyDefine fetch_start; - fetch_start.name = "fetchStart"; - fetch_start.getter = [weak_scope](PerformanceResourceTiming* thiz, - std::shared_ptr& exception) -> std::shared_ptr { - auto scope = weak_scope.lock(); - if (scope) { - return nullptr; - } - auto context = scope->GetContext(); - return context->CreateNumber(thiz->GetFetchStart().ToEpochDelta().ToMillisecondsF()); - }; - class_template.properties.push_back(std::move(fetch_start)); - - PropertyDefine domain_lookup_start; - domain_lookup_start.name = "domainLookupStart"; - domain_lookup_start.getter = [weak_scope](PerformanceResourceTiming* thiz, - std::shared_ptr& exception) -> std::shared_ptr { - auto scope = weak_scope.lock(); - if (scope) { - return nullptr; - } - auto context = scope->GetContext(); - return context->CreateNumber(thiz->GetDomainLookupStart().ToEpochDelta().ToMillisecondsF()); - }; - class_template.properties.push_back(std::move(domain_lookup_start)); - - PropertyDefine domain_lookup_end; - domain_lookup_end.name = "domainLookupEnd"; - domain_lookup_end.getter = [weak_scope](PerformanceResourceTiming* thiz, - std::shared_ptr& exception) -> std::shared_ptr { - auto scope = weak_scope.lock(); - if (scope) { - return nullptr; - } - auto context = scope->GetContext(); - return context->CreateNumber(thiz->GetDomainLookupEnd().ToEpochDelta().ToMillisecondsF()); - }; - class_template.properties.push_back(std::move(domain_lookup_end)); - - PropertyDefine connect_start; - connect_start.name = "connectStart"; - connect_start.getter = [weak_scope](PerformanceResourceTiming* thiz, - std::shared_ptr& exception) -> std::shared_ptr { - auto scope = weak_scope.lock(); - if (scope) { - return nullptr; - } - auto context = scope->GetContext(); - return context->CreateNumber(thiz->GetConnectStart().ToEpochDelta().ToMillisecondsF()); - }; - class_template.properties.push_back(std::move(connect_start)); - - PropertyDefine connect_end; - connect_end.name = "connectEnd"; - connect_end.getter = [weak_scope](PerformanceResourceTiming* thiz, - std::shared_ptr& exception) -> std::shared_ptr { - auto scope = weak_scope.lock(); - if (scope) { - return nullptr; - } - auto context = scope->GetContext(); - return context->CreateNumber(thiz->GetConnectEnd().ToEpochDelta().ToMillisecondsF()); - }; - class_template.properties.push_back(std::move(connect_end)); - - PropertyDefine secure_connection_start; - secure_connection_start.name = "secureConnectionStart"; - secure_connection_start.getter = [weak_scope](PerformanceResourceTiming* thiz, - std::shared_ptr& exception) -> std::shared_ptr { - auto scope = weak_scope.lock(); - if (scope) { - return nullptr; - } - auto context = scope->GetContext(); - return context->CreateNumber(thiz->GetSecureConnectionStart().ToEpochDelta().ToMillisecondsF()); - }; - class_template.properties.push_back(std::move(secure_connection_start)); - - PropertyDefine request_start; - request_start.name = "requestStart"; - request_start.getter = [weak_scope](PerformanceResourceTiming* thiz, - std::shared_ptr& exception) -> std::shared_ptr { - auto scope = weak_scope.lock(); - if (scope) { - return nullptr; - } - auto context = scope->GetContext(); - return context->CreateNumber(thiz->GetRequestStart().ToEpochDelta().ToMillisecondsF()); - }; - class_template.properties.push_back(std::move(request_start)); - - PropertyDefine response_start; - response_start.name = "responseStart"; - response_start.getter = [weak_scope](PerformanceResourceTiming* thiz, - std::shared_ptr& exception) -> std::shared_ptr { - auto scope = weak_scope.lock(); - if (scope) { - return nullptr; - } - auto context = scope->GetContext(); - return context->CreateNumber(thiz->GetResponseStart().ToEpochDelta().ToMillisecondsF()); - }; - class_template.properties.push_back(std::move(response_start)); - - PropertyDefine response_end; - response_end.name = "responseEnd"; - response_end.getter = [weak_scope](PerformanceResourceTiming* thiz, - std::shared_ptr& exception) -> std::shared_ptr { - auto scope = weak_scope.lock(); - if (scope) { - return nullptr; - } - auto context = scope->GetContext(); - return context->CreateNumber(thiz->GetResponseEnd().ToEpochDelta().ToMillisecondsF()); - }; - class_template.properties.push_back(std::move(response_end)); - - PropertyDefine transfer_size; - transfer_size.name = "transferSize"; - transfer_size.getter = [weak_scope](PerformanceResourceTiming* thiz, - std::shared_ptr& exception) -> std::shared_ptr { - auto scope = weak_scope.lock(); - if (scope) { - return nullptr; - } - auto context = scope->GetContext(); - return context->CreateNumber(footstone::checked_numeric_cast(thiz->GetTransferSize())); - }; - class_template.properties.push_back(std::move(transfer_size)); - - PropertyDefine encoded_body_size; - encoded_body_size.name = "encodedBodySize"; - encoded_body_size.getter = [weak_scope](PerformanceResourceTiming* thiz, - std::shared_ptr& exception) -> std::shared_ptr { - auto scope = weak_scope.lock(); - if (scope) { - return nullptr; - } - auto context = scope->GetContext(); - return context->CreateNumber(footstone::checked_numeric_cast(thiz->GetEncodedBodySize())); - }; - class_template.properties.push_back(std::move(encoded_body_size)); - - PropertyDefine decoded_body_size; - decoded_body_size.name = "decodedBodySize"; - decoded_body_size.getter = [weak_scope](PerformanceResourceTiming* thiz, - std::shared_ptr& exception) -> std::shared_ptr { - auto scope = weak_scope.lock(); - if (scope) { - return nullptr; - } - auto context = scope->GetContext(); - return context->CreateNumber(footstone::checked_numeric_cast(thiz->GetDecodedBodySize())); - }; - class_template.properties.push_back(std::move(decoded_body_size)); +#define ADD_PROPERTY(prop_var, prop_name, get_prop_method) \ + PropertyDefine prop_var; \ + prop_var.name = prop_name; \ + prop_var.getter = [weak_scope](PerformanceResourceTiming* thiz, \ + std::shared_ptr& exception) -> std::shared_ptr { \ + auto scope = weak_scope.lock(); \ + if (!scope) { \ + return nullptr; \ + } \ + auto context = scope->GetContext(); \ + return context->CreateNumber(thiz->get_prop_method().ToEpochDelta().ToMillisecondsF()); \ + }; \ + class_template.properties.push_back(std::move(prop_var)); + + ADD_PROPERTY(load_source_start, "loadSourceStart", GetLoadSourceStart) + ADD_PROPERTY(load_source_end, "loadSourceEnd", GetLoadSourceEnd) +#undef ADD_PROPERTY return std::make_shared>(std::move(class_template)); } diff --git a/driver/js/src/napi/jsc/jsc_ctx.cc b/driver/js/src/napi/jsc/jsc_ctx.cc index 3855538fb21..0c79151d32e 100644 --- a/driver/js/src/napi/jsc/jsc_ctx.cc +++ b/driver/js/src/napi/jsc/jsc_ctx.cc @@ -90,7 +90,7 @@ JSValueRef InvokeJsCallback(JSContextRef ctx, JSObjectRef global_object = JSContextGetGlobalObject(ctx); auto global_external_data = JSObjectGetPrivate(global_object); cb_info.SetSlot(global_external_data); - auto context = const_cast(ctx); + auto context = JSContextGetGlobalContext(ctx); cb_info.SetReceiver(std::make_shared(context, object)); if (object != global_object) { auto object_private_data = JSObjectGetPrivate(object); @@ -141,7 +141,7 @@ JSObjectRef InvokeConstructorCallback(JSContextRef ctx, JSObjectRef global_obj = JSContextGetGlobalObject(ctx); auto global_external_data = JSObjectGetPrivate(global_obj); cb_info.SetSlot(global_external_data); - auto context = const_cast(ctx); + auto context = JSContextGetGlobalContext(ctx); auto proto = std::static_pointer_cast(constructor_data->prototype)->value_; JSObjectSetPrototype(ctx, instance, proto); cb_info.SetReceiver(std::make_shared(context, instance)); @@ -209,7 +209,7 @@ static JSValueRef JSObjectGetPropertyCallback(JSContextRef ctx, JSValueRef *exception_ref) { auto data = JSObjectGetPrivate(object); - auto context = const_cast(ctx); + auto context = JSContextGetGlobalContext(ctx); auto constructor_data = reinterpret_cast(data); auto function_wrapper = reinterpret_cast(constructor_data->function_wrapper); auto js_cb = function_wrapper->callback; diff --git a/driver/js/src/performance/performance.cc b/driver/js/src/performance/performance.cc index 02f607f8be0..ffc0d04d6fd 100644 --- a/driver/js/src/performance/performance.cc +++ b/driver/js/src/performance/performance.cc @@ -28,6 +28,7 @@ #include "driver/performance/performance_measure.h" #include "footstone/check.h" #include "footstone/logging.h" +#include "footstone/string_view_utils.h" namespace hippy { inline namespace driver { @@ -39,6 +40,61 @@ Performance::Performance(): resource_timing_current_buffer_size_(0), resource_timing_max_buffer_size_(kMaxSize), time_origin_(TimePoint::Now()) {} +std::shared_ptr Performance::PerformanceNavigation(const string_view& name) { + auto u16n = footstone::StringViewUtils::ConvertEncoding(name, string_view::Encoding::Utf16); + auto name_iterator = name_map_.find(u16n); + if (name_iterator != name_map_.end()) { + for (auto& entry : name_iterator->second) { + if (entry->GetType() == PerformanceEntry::Type::kNavigation) { + return std::static_pointer_cast(entry); + } + } + } + + auto entry = std::make_shared(name); + if (InsertEntry(entry)) { + return entry; + } + return nullptr; +} + +std::shared_ptr Performance::PerformancePaint(const PerformancePaintTiming::Type& type) { + auto name = (type == PerformancePaintTiming::Type::kFirstPaint ? "first-paint" : "first-contentful-paint"); + auto u16n = footstone::StringViewUtils::ConvertEncoding(name, string_view::Encoding::Utf16); + auto name_iterator = name_map_.find(u16n); + if (name_iterator != name_map_.end()) { + for (auto& entry : name_iterator->second) { + if (entry->GetType() == PerformanceEntry::Type::kPaint) { + return std::static_pointer_cast(entry); + } + } + } + + auto entry = std::make_shared(type); + if (InsertEntry(entry)) { + return entry; + } + return nullptr; +} + +std::shared_ptr Performance::PerformanceResource(const string_view& name) { + auto u16n = footstone::StringViewUtils::ConvertEncoding(name, string_view::Encoding::Utf16); + auto name_iterator = name_map_.find(u16n); + if (name_iterator != name_map_.end()) { + for (auto& entry : name_iterator->second) { + if (entry->GetType() == PerformanceEntry::Type::kResource) { + return std::static_pointer_cast(entry); + } + } + } + + auto entry = std::make_shared(name); + if (InsertEntry(entry)) { + return entry; + } + return nullptr; +} + void Performance::Mark(const Performance::string_view& name) { auto entry = std::make_shared( name, TimePoint::Now(), nullptr); @@ -61,24 +117,36 @@ bool Performance::Measure(const Performance::string_view &name) { bool Performance::Measure(const Performance::string_view &name, const Performance::string_view &start_mark) { - auto start_mark_entry = GetEntriesByName(start_mark, PerformanceEntry::Type::kMark); + auto entries = GetEntriesByName(start_mark, PerformanceEntry::Type::kMark); + if (entries.empty()) { + return false; + } + auto start_mark_entry = entries.back(); if (!start_mark_entry) { return false; } - auto entry = std::make_shared( - name, PerformanceEntry::SubType::kPerformanceMeasure, PerformanceEntry::Type::kMeasure, start_mark_entry->GetStartTime(), - Now() - start_mark_entry->GetStartTime()); + auto entry = std::make_shared( + name, start_mark_entry->GetStartTime(), + Now() - start_mark_entry->GetStartTime(), nullptr); return InsertEntry(entry); } bool Performance::Measure(const Performance::string_view& name, const Performance::string_view& start_mark, const Performance::string_view& end_mark) { - auto start_mark_entry = GetEntriesByName(start_mark, PerformanceEntry::Type::kMark); + auto start_entries = GetEntriesByName(start_mark, PerformanceEntry::Type::kMark); + if (start_entries.empty()) { + return false; + } + auto start_mark_entry = start_entries.back(); if (!start_mark_entry) { return false; } - auto end_mark_entry = GetEntriesByName(end_mark, PerformanceEntry::Type::kMark); + auto end_entries = GetEntriesByName(end_mark, PerformanceEntry::Type::kMark); + if (end_entries.empty()) { + return false; + } + auto end_mark_entry = end_entries.back(); if (!end_mark_entry) { return false; } @@ -87,15 +155,19 @@ bool Performance::Measure(const Performance::string_view& name, } bool Performance::InsertEntry(const std::shared_ptr& entry) { - if (entry->GetType() == PerformanceEntry::Type::kResource && resource_timing_current_buffer_size_ >= resource_timing_max_buffer_size_) { - return false; + if (entry->GetType() == PerformanceEntry::Type::kResource) { + if (resource_timing_current_buffer_size_ >= resource_timing_max_buffer_size_) { + return false; + } + ++resource_timing_current_buffer_size_; } auto name = entry->GetName(); - auto name_iterator = name_map_.find(name); + auto u16n = footstone::StringViewUtils::ConvertEncoding(name, string_view::Encoding::Utf16); + auto name_iterator = name_map_.find(u16n); if (name_iterator == name_map_.end()) { - name_map_[name] = { entry }; + name_map_[u16n] = { entry }; } else { - name_map_[name].push_back(entry); + name_map_[u16n].push_back(entry); } auto type = entry->GetType(); auto type_iterator = type_map_.find(type); @@ -111,35 +183,37 @@ bool Performance::Measure(const Performance::string_view& name, const std::shared_ptr& start_mark, const std::shared_ptr& end_mark) { FOOTSTONE_CHECK(start_mark && end_mark); - auto entry = std::make_shared( - name, PerformanceEntry::SubType::kPerformanceMeasure, PerformanceEntry::Type::kMeasure, start_mark->GetStartTime(), - end_mark->GetStartTime() - end_mark->GetStartTime()); + auto entry = std::make_shared( + name, start_mark->GetStartTime(), + end_mark->GetStartTime() - start_mark->GetStartTime(), nullptr); return InsertEntry(entry); } -std::shared_ptr Performance::GetEntriesByName(const Performance::string_view& name) { - auto iterator = name_map_.find(name); +std::vector> Performance::GetEntriesByName(const Performance::string_view& name) { + auto u16n = footstone::StringViewUtils::ConvertEncoding(name, string_view::Encoding::Utf16); + auto iterator = name_map_.find(u16n); if (iterator == name_map_.end()) { - return nullptr; + return {}; } - return iterator->second.back(); + return iterator->second; } -std::shared_ptr Performance::GetEntriesByName(const Performance::string_view& name, - PerformanceEntry::Type type) { - auto iterator = name_map_.find(name); +std::vector> Performance::GetEntriesByName(const Performance::string_view& name, + PerformanceEntry::Type type) { + auto u16n = footstone::StringViewUtils::ConvertEncoding(name, string_view::Encoding::Utf16); + auto iterator = name_map_.find(u16n); if (iterator == name_map_.end()) { - return nullptr; + return {}; } auto array = iterator->second; - auto array_iterator = std::find_if(array.rbegin(), array.rend(), + auto array_iterator = std::find_if(array.begin(), array.end(), [type](const std::shared_ptr& entry) { return entry->GetType() == type; }); - if (array_iterator == array.rend()) { - return nullptr; + if (array_iterator == array.end()) { + return {}; } - return *array_iterator; + return std::vector>(array_iterator, array.end()); } std::vector> Performance::GetEntriesByType(PerformanceEntry::Type type) { @@ -151,7 +225,8 @@ std::vector> Performance::GetEntriesByType(Per } void Performance::RemoveEntry(const Performance::string_view& name) { - auto name_iterator = name_map_.find(name); + auto u16n = footstone::StringViewUtils::ConvertEncoding(name, string_view::Encoding::Utf16); + auto name_iterator = name_map_.find(u16n); if (name_iterator == name_map_.end()) { return; } @@ -160,8 +235,9 @@ void Performance::RemoveEntry(const Performance::string_view& name) { FOOTSTONE_CHECK(type_iterator != type_map_.end()); auto array = type_iterator->second; auto erase_iterator = std::remove_if(array.begin(), array.end(), - [name](const std::shared_ptr& item) { - return item->GetName() == name; + [u16n](const std::shared_ptr& item) { + auto u16n2 = footstone::StringViewUtils::ConvertEncoding(item->GetName(), string_view::Encoding::Utf16); + return u16n2 == u16n; }); if (entry->GetType() == PerformanceEntry::Type::kResource) { auto count = std::distance(erase_iterator, array.end()); @@ -179,7 +255,8 @@ void Performance::RemoveEntry(PerformanceEntry::Type type) { return; } for (const auto& entry: type_iterator->second) { - auto name_iterator = name_map_.find(entry->GetName()); + auto u16n = footstone::StringViewUtils::ConvertEncoding(entry->GetName(), string_view::Encoding::Utf16); + auto name_iterator = name_map_.find(u16n); FOOTSTONE_CHECK(name_iterator != name_map_.end()); auto array = name_iterator->second; array.erase(std::remove_if(array.begin(), array.end(), @@ -194,14 +271,15 @@ void Performance::RemoveEntry(PerformanceEntry::Type type) { } void Performance::RemoveEntry(const Performance::string_view& name, PerformanceEntry::Type type) { - auto name_iterator = name_map_.find(name); + auto u16n = footstone::StringViewUtils::ConvertEncoding(name, string_view::Encoding::Utf16); + auto name_iterator = name_map_.find(u16n); if (name_iterator == name_map_.end()) { return; } auto name_array = name_iterator->second; name_array.erase(std::remove_if(name_array.begin(), name_array.end(), - [&type_map = type_map_, &size = resource_timing_current_buffer_size_, type]( + [&type_map = type_map_, &size = resource_timing_current_buffer_size_, u16n, type]( const std::shared_ptr& entry) { if (entry->GetType() != type) { return false; @@ -210,8 +288,9 @@ void Performance::RemoveEntry(const Performance::string_view& name, PerformanceE FOOTSTONE_CHECK(type_iterator != type_map.end()); auto type_array = type_iterator->second; auto erase_iterator = std::remove_if(type_array.begin(), type_array.end(), - [name = entry->GetName()](const std::shared_ptr& item) { - return item->GetName() == name; + [u16n](const std::shared_ptr& item) { + auto u16n2 = footstone::StringViewUtils::ConvertEncoding(item->GetName(), string_view::Encoding::Utf16); + return u16n2 == u16n; }); if (type == PerformanceEntry::Type::kResource) { auto count = std::distance(erase_iterator, type_array.end()); @@ -243,15 +322,15 @@ std::vector> Performance::GetEntries() { std::vector> ret; for (auto [ key, value ]: type_map_) { if (!ret.empty()) { - auto size = ret.size() + value.size(); - ret.resize(size); - std::merge(ret.begin(), ret.end(), value.begin(), value.end(), ret.begin(), + std::vector> merged_ret; + std::merge(ret.begin(), ret.end(), value.begin(), value.end(), std::back_inserter(merged_ret), [](const std::shared_ptr& lhs, const std::shared_ptr& rhs) { if (lhs && rhs) { return lhs->GetStartTime() < rhs->GetStartTime(); } return true; }); + ret = merged_ret; } else { ret = value; } diff --git a/driver/js/src/performance/performance_navigation_timing.cc b/driver/js/src/performance/performance_navigation_timing.cc index 0dd9f1232eb..f2584cddeac 100644 --- a/driver/js/src/performance/performance_navigation_timing.cc +++ b/driver/js/src/performance/performance_navigation_timing.cc @@ -21,27 +21,35 @@ */ #include "driver/performance/performance_navigation_timing.h" - +#include "footstone/string_view_utils.h" #include namespace hippy { inline namespace driver { inline namespace performance { -PerformanceNavigationTiming::PerformanceNavigationTiming( - const string_view& name, const TimePoint& start, - const TimePoint& engine_initialization_start, const TimePoint& engine_initialization_end, - std::vector bundle_info, - const TimePoint& load_instance_start, const TimePoint& load_instance_end, - const TimePoint& first_frame): PerformanceEntry( - name, SubType::kPerformanceNavigationTiming, Type::kNavigation, start, TimePoint::Now() - start), - engine_initialization_start_(engine_initialization_start), engine_initialization_end_(engine_initialization_end), - bundle_info_(std::move(bundle_info)), load_instance_start_(load_instance_start), load_instance_end_(load_instance_end) {} +PerformanceNavigationTiming::PerformanceNavigationTiming(const string_view& name) +: PerformanceEntry(name, SubType::kPerformanceNavigationTiming, Type::kNavigation) {} PerformanceEntry::string_view PerformanceNavigationTiming::ToJSON() { return PerformanceEntry::ToJSON(); } +PerformanceNavigationTiming::BundleInfo& PerformanceNavigationTiming::BundleInfoOfUrl(const string_view& url) { + auto u16n = footstone::StringViewUtils::ConvertEncoding(url, string_view::Encoding::Utf16); + for (auto& info : bundle_info_array_) { + auto u16n2 = footstone::StringViewUtils::ConvertEncoding(info.url_, string_view::Encoding::Utf16); + if (u16n2 == u16n) { + return info; + } + } + + PerformanceNavigationTiming::BundleInfo info; + info.url_ = url; + bundle_info_array_.emplace_back(info); + return bundle_info_array_.back(); +} + } } } diff --git a/driver/js/src/performance/performance_paint_timing.cc b/driver/js/src/performance/performance_paint_timing.cc index 249273f63a0..479d5338c67 100644 --- a/driver/js/src/performance/performance_paint_timing.cc +++ b/driver/js/src/performance/performance_paint_timing.cc @@ -29,7 +29,7 @@ inline namespace performance { PerformancePaintTiming::PerformancePaintTiming(PerformancePaintTiming::Type type, const TimePoint& start_time) : PerformanceEntry( - type == Type::kFirstContentfulPaint ? "first-paint" : "first-contentful-paint", + type == Type::kFirstPaint ? "first-paint" : "first-contentful-paint", SubType::kPerformancePaintTiming, PerformanceEntry::Type::kPaint, start_time, @@ -37,6 +37,12 @@ PerformancePaintTiming::PerformancePaintTiming(PerformancePaintTiming::Type type } +PerformancePaintTiming::PerformancePaintTiming(Type type) +: PerformanceEntry( + type == Type::kFirstPaint ? "first-paint" : "first-contentful-paint", + SubType::kPerformancePaintTiming, + PerformanceEntry::Type::kPaint) {} + PerformanceEntry::string_view PerformancePaintTiming::ToJSON() { return PerformanceEntry::ToJSON(); } diff --git a/driver/js/src/performance/performance_resource_timing.cc b/driver/js/src/performance/performance_resource_timing.cc index 39f196e48b4..5572d94ea7e 100644 --- a/driver/js/src/performance/performance_resource_timing.cc +++ b/driver/js/src/performance/performance_resource_timing.cc @@ -33,45 +33,8 @@ namespace hippy { inline namespace driver { inline namespace performance { -PerformanceResourceTiming::PerformanceResourceTiming( - const string_view& name, - TimePoint start_time, - TimeDelta duration, - const InitiatorType& initiator_type, - const string_view& next_hop_protocol, - TimePoint worker_start, - TimePoint redirect_start, - TimePoint redirect_end, - TimePoint fetch_start, - TimePoint domain_lookup_start, - TimePoint domain_lookup_end, - TimePoint connect_start, - TimePoint connect_end, - TimePoint secure_connection_start, - TimePoint request_start, - TimePoint response_start, - TimePoint response_end, - uint64_t transfer_size, - uint64_t encoded_body_size, - uint64_t decoded_body_size) : - PerformanceEntry(name, SubType::kPerformanceResourceTiming, Type::kResource, start_time, duration), - initiator_type_(initiator_type), - next_hop_protocol_(next_hop_protocol), - worker_start_(worker_start), - redirect_start_(redirect_start), - redirect_end_(redirect_end), - fetch_start_(fetch_start), - domain_lookup_start_(domain_lookup_start), - domain_lookup_end_(domain_lookup_end), - connect_start_(connect_start), - connect_end_(connect_end), - secure_connection_start_(secure_connection_start), - request_start_(request_start), - response_start_(response_start), - response_end_(response_end), - transfer_size_(transfer_size), - encoded_body_size_(encoded_body_size), - decoded_body_size_(decoded_body_size) {} +PerformanceResourceTiming::PerformanceResourceTiming(const string_view& name) +: PerformanceEntry(name, SubType::kPerformanceResourceTiming, Type::kResource) {} string_view PerformanceResourceTiming::ToJSON() { return ""; @@ -79,7 +42,10 @@ string_view PerformanceResourceTiming::ToJSON() { string_view PerformanceResourceTiming::GetInitiatorString(InitiatorType type) { switch (type) { - case InitiatorType::AUDIO:{ + case InitiatorType::OTHER: { + return "other"; + } + case InitiatorType::AUDIO: { return "audio"; } case InitiatorType::BEACON: { diff --git a/driver/js/src/scope.cc b/driver/js/src/scope.cc index d066e1019ba..86f82d28e69 100644 --- a/driver/js/src/scope.cc +++ b/driver/js/src/scope.cc @@ -481,10 +481,16 @@ uint64_t Scope::GetListenerId(const EventListenerInfo& event_listener_info) { } void Scope::RunJS(const string_view& data, + const string_view& uri, const string_view& name, bool is_copy) { std::weak_ptr weak_context = context_; - auto callback = [data, name, is_copy, weak_context] { + auto callback = [WEAK_THIS, data, uri, name, is_copy, weak_context] { + DEFINE_AND_CHECK_SELF(Scope) + // perfromance start time + auto entry = self->GetPerformance()->PerformanceNavigation("hippyInit"); + entry->BundleInfoOfUrl(uri).execute_source_start_ = footstone::TimePoint::SystemNow(); + #ifdef JS_V8 auto context = std::static_pointer_cast(weak_context.lock()); if (context) { @@ -496,6 +502,9 @@ void Scope::RunJS(const string_view& data, context->RunScript(data, name); } #endif + + // perfromance end time + entry->BundleInfoOfUrl(uri).execute_source_end_ = footstone::TimePoint::SystemNow(); }; auto runner = GetTaskRunner(); @@ -510,10 +519,16 @@ void Scope::LoadInstance(const std::shared_ptr& value) { std::weak_ptr weak_context = context_; #ifdef ENABLE_INSPECTOR std::weak_ptr weak_data_source = devtools_data_source_; - auto cb = [weak_context, value, weak_data_source]() mutable { + auto cb = [WEAK_THIS, weak_context, value, weak_data_source]() mutable { #else - auto cb = [weak_context, value]() mutable { + auto cb = [WEAK_THIS, weak_context, value]() mutable { #endif + DEFINE_AND_CHECK_SELF(Scope) + // perfromance start time + auto entry = self->GetPerformance()->PerformanceNavigation("hippyInit"); + entry->SetHippyFirstFrameStart(footstone::TimePoint::SystemNow()); + entry->SetHippyRunApplicationStart(footstone::TimePoint::SystemNow()); + std::shared_ptr context = weak_context.lock(); if (context) { auto global_object = context->GetGlobalObject(); @@ -544,6 +559,9 @@ void Scope::LoadInstance(const std::shared_ptr& value) { context->ThrowException("Application entry not found"); } } + + // perfromance end time + entry->SetHippyRunApplicationEnd(footstone::TimePoint::SystemNow()); }; auto runner = GetTaskRunner(); if (footstone::Worker::IsTaskRunning() && runner == footstone::runner::TaskRunner::GetCurrentTaskRunner()) { diff --git a/framework/android/connector/driver/js/src/main/cpp/include/connector/js_driver_jni.h b/framework/android/connector/driver/js/src/main/cpp/include/connector/js_driver_jni.h index b3f8e22aee0..522e7d28632 100644 --- a/framework/android/connector/driver/js/src/main/cpp/include/connector/js_driver_jni.h +++ b/framework/android/connector/driver/js/src/main/cpp/include/connector/js_driver_jni.h @@ -84,6 +84,12 @@ void SetDomManager(JNIEnv* j_env, jint j_runtime_id, jint j_dom_manager_id); +void OnNativeInitEnd(JNIEnv* j_env, jobject j_object, jint j_scope_id, jlong startTime, jlong endTime); + +void OnFirstFrameEnd(JNIEnv* j_env, jobject j_object, jint j_scope_id, jlong time); + +void OnResourceLoadEnd(JNIEnv* j_env, jobject j_object, jint j_scope_id, jstring j_uri, jlong j_start_time, jlong j_end_time); + } } } diff --git a/framework/android/connector/driver/js/src/main/cpp/src/js_driver_jni.cc b/framework/android/connector/driver/js/src/main/cpp/src/js_driver_jni.cc index bdaf428d088..c7ddb148e6e 100644 --- a/framework/android/connector/driver/js/src/main/cpp/src/js_driver_jni.cc +++ b/framework/android/connector/driver/js/src/main/cpp/src/js_driver_jni.cc @@ -102,6 +102,21 @@ REGISTER_JNI("com/openhippy/connector/JsDriver", // NOLINT(cert-err58-cpp) "(II)V", SetDomManager) +REGISTER_JNI("com/openhippy/connector/JsDriver", // NOLINT(cert-err58-cpp) + "onNativeInitEnd", + "(IJJ)V", + OnNativeInitEnd) + +REGISTER_JNI("com/openhippy/connector/JsDriver", // NOLINT(cert-err58-cpp) + "onFirstFrameEnd", + "(IJ)V", + OnFirstFrameEnd) + +REGISTER_JNI("com/openhippy/connector/JsDriver", // NOLINT(cert-err58-cpp) + "onResourceLoadEnd", + "(ILjava/lang/String;JJ)V", + OnResourceLoadEnd) + using string_view = footstone::stringview::string_view; using u8string = footstone::string_view::u8string; using StringViewUtils = footstone::stringview::StringViewUtils; @@ -134,6 +149,60 @@ std::shared_ptr GetScope(jint j_scope_id) { return std::any_cast>(scope_object); } +void OnNativeInitEnd(JNIEnv* j_env, jobject j_object, jint j_scope_id, jlong startTime, jlong endTime) { + auto scope = GetScope(j_scope_id); + auto runner = scope->GetEngine().lock()->GetJsTaskRunner(); + if (runner) { + std::weak_ptr weak_scope = scope; + auto task = [weak_scope, startTime, endTime]() { + auto scope = weak_scope.lock(); + if (scope) { + auto entry = scope->GetPerformance()->PerformanceNavigation("hippyInit"); + entry->SetHippyNativeInitStart(footstone::TimePoint::FromEpochDelta(footstone::TimeDelta::FromMilliseconds(startTime))); + entry->SetHippyNativeInitEnd(footstone::TimePoint::FromEpochDelta(footstone::TimeDelta::FromMilliseconds(endTime))); + } + }; + runner->PostTask(std::move(task)); + } +} + +void OnFirstFrameEnd(JNIEnv* j_env, jobject j_object, jint j_scope_id, jlong time) { + auto scope = GetScope(j_scope_id); + auto runner = scope->GetEngine().lock()->GetJsTaskRunner(); + if (runner) { + std::weak_ptr weak_scope = scope; + auto task = [weak_scope, time]() { + auto scope = weak_scope.lock(); + if (scope) { + auto entry = scope->GetPerformance()->PerformanceNavigation("hippyInit"); + entry->SetHippyFirstFrameEnd(footstone::TimePoint::FromEpochDelta(footstone::TimeDelta::FromMilliseconds(time))); + } + }; + runner->PostTask(std::move(task)); + } +} + +void OnResourceLoadEnd(JNIEnv* j_env, jobject j_object, jint j_scope_id, jstring j_uri, jlong j_start_time, jlong j_end_time) { + if (!j_uri) { + return; + } + auto uri = JniUtils::ToStrView(j_env, j_uri); + auto scope = GetScope(j_scope_id); + auto runner = scope->GetEngine().lock()->GetJsTaskRunner(); + if (runner) { + std::weak_ptr weak_scope = scope; + auto task = [weak_scope, uri, j_start_time, j_end_time]() { + auto scope = weak_scope.lock(); + if (scope) { + auto entry = scope->GetPerformance()->PerformanceResource(uri); + entry->SetLoadSourceStart(footstone::TimePoint::FromEpochDelta(footstone::TimeDelta::FromMilliseconds(j_start_time))); + entry->SetLoadSourceEnd(footstone::TimePoint::FromEpochDelta(footstone::TimeDelta::FromMilliseconds(j_end_time))); + } + }; + runner->PostTask(std::move(task)); + } +} + jint CreateJsDriver(JNIEnv* j_env, jobject j_object, jbyteArray j_global_config, @@ -153,6 +222,10 @@ jint CreateJsDriver(JNIEnv* j_env, << ", j_is_dev_module = " << static_cast(j_is_dev_module) << ", j_group_id = " << j_group_id; + + // perfromance start time + auto perf_start_time = footstone::TimePoint::SystemNow(); + auto global_config = JniUtils::JByteArrayToStrView(j_env, j_global_config); auto java_callback = std::make_shared(j_env, j_callback); @@ -196,10 +269,16 @@ jint CreateJsDriver(JNIEnv* j_env, auto dom_task_runner = dom_manager_object->GetTaskRunner(); auto bridge = std::make_shared(j_env, j_object); auto scope_id = hippy::global_data_holder_key.fetch_add(1); - auto scope_initialized_callback = [ + auto scope_initialized_callback = [perf_start_time, scope_id, java_callback, bridge, &holder = hippy::global_data_holder](std::shared_ptr scope) { scope->SetBridge(bridge); holder.Insert(scope_id, scope); + + // perfromance end time + auto entry = scope->GetPerformance()->PerformanceNavigation("hippyInit"); + entry->SetHippyJsEngineInitStart(perf_start_time); + entry->SetHippyJsEngineInitEnd(footstone::TimePoint::SystemNow()); + FOOTSTONE_LOG(INFO) << "run scope cb"; hippy::bridge::CallJavaMethod(java_callback->GetObj(), INIT_CB_STATE::SUCCESS); }; diff --git a/framework/android/connector/driver/js/src/main/java/com/openhippy/connector/JsDriver.java b/framework/android/connector/driver/js/src/main/java/com/openhippy/connector/JsDriver.java index 74abcdecc33..560633b5f86 100644 --- a/framework/android/connector/driver/js/src/main/java/com/openhippy/connector/JsDriver.java +++ b/framework/android/connector/driver/js/src/main/java/com/openhippy/connector/JsDriver.java @@ -69,6 +69,18 @@ public void reportException(String message, String stackTrace) { } } + public void recordNativeInitEndTime(long startTime, long endTime) { + onNativeInitEnd(mInstanceId, startTime, endTime); + } + + public void recordFirstFrameEndTime(long time) { + onFirstFrameEnd(mInstanceId, time); + } + + public void recordResourceLoadEndTime(@NonNull String uri, long startTime, long endTime) { + onResourceLoadEnd(mInstanceId, uri, startTime, endTime); + } + public void onResourceReady(ByteBuffer output, long resId) { onResourceReady(mInstanceId, output, resId); } @@ -148,4 +160,10 @@ private native void callFunction(int instanceId, String action, NativeCallback c byte[] buffer, int offset, int length); private native void onResourceReady(int instanceId, ByteBuffer output, long resId); + + private native void onNativeInitEnd(int instanceId, long startTime, long endTime); + + private native void onFirstFrameEnd(int instanceId, long time); + + private native void onResourceLoadEnd(int instanceId, String uri, long startTime, long endTime); } diff --git a/framework/android/src/main/java/com/tencent/mtt/hippy/HippyEngineContext.java b/framework/android/src/main/java/com/tencent/mtt/hippy/HippyEngineContext.java index 25a1d549476..5065a6eef75 100644 --- a/framework/android/src/main/java/com/tencent/mtt/hippy/HippyEngineContext.java +++ b/framework/android/src/main/java/com/tencent/mtt/hippy/HippyEngineContext.java @@ -21,6 +21,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.openhippy.connector.JsDriver; import com.tencent.devtools.DevtoolsManager; import com.tencent.mtt.hippy.HippyEngine.ModuleLoadStatus; import com.tencent.mtt.hippy.bridge.HippyBridgeManager; @@ -41,6 +42,9 @@ public interface HippyEngineContext { @NonNull VfsManager getVfsManager(); + @NonNull + JsDriver getJsDriver(); + @NonNull TimeMonitor getMonitor(); diff --git a/framework/android/src/main/java/com/tencent/mtt/hippy/HippyEngineManagerImpl.java b/framework/android/src/main/java/com/tencent/mtt/hippy/HippyEngineManagerImpl.java index 39df6b0672a..1054d6a194f 100644 --- a/framework/android/src/main/java/com/tencent/mtt/hippy/HippyEngineManagerImpl.java +++ b/framework/android/src/main/java/com/tencent/mtt/hippy/HippyEngineManagerImpl.java @@ -112,6 +112,7 @@ public abstract class HippyEngineManagerImpl extends HippyEngineManager implemen private final String mRemoteServerUrl; private ViewGroup mRootView; final boolean enableV8Serialization; + private long mInitStartTime = 0; private final TimeMonitor mMonitor; private final HippyThirdPartyAdapter mThirdPartyAdapter; private final V8InitParams v8InitParams; @@ -151,6 +152,7 @@ public void initEngine(EngineListener listener) throws IllegalStateException { throw new IllegalStateException( "Cannot repeatedly call engine initialization, current state=" + mCurrentState); } + mInitStartTime = System.currentTimeMillis(); mCurrentState = EngineState.INITING; if (listener != null) { mEventListeners.add(listener); @@ -217,6 +219,7 @@ protected void onDestroyEngine() { @Override public void onFirstViewAdded() { + mEngineContext.getJsDriver().recordFirstFrameEndTime(System.currentTimeMillis()); MonitorGroup monitorGroup = mEngineContext.getMonitor() .endGroup(MonitorGroupType.LOAD_INSTANCE); if (monitorGroup != null) { @@ -549,6 +552,7 @@ void notifyEngineInitialized(final EngineInitStatus statusCode, final Throwable } private void onEngineInitialized(EngineInitStatus statusCode, Throwable error) { + mEngineContext.getJsDriver().recordNativeInitEndTime(mInitStartTime, System.currentTimeMillis()); MonitorGroup monitorGroup = mEngineContext.getMonitor() .endGroup(MonitorGroupType.ENGINE_INITIALIZE); if (monitorGroup != null) { @@ -727,8 +731,10 @@ public class HippyEngineContextImpl implements HippyEngineContext, public HippyEngineContextImpl(@Nullable DomManager domManager) throws RuntimeException { mVfsManager = (mProcessors != null) ? new VfsManager(mProcessors) : new VfsManager(); mVfsManager.setId(onCreateVfs(mVfsManager)); - DefaultProcessor processor = new DefaultProcessor(new HippyResourceLoader(this)); - mVfsManager.addProcessorAtLast(processor); + DefaultProcessor defaultProcessor = new DefaultProcessor(new HippyResourceLoader(this)); + PerformanceProcessor performanceProcessor = new PerformanceProcessor(this); + mVfsManager.addProcessorAtFirst(performanceProcessor); + mVfsManager.addProcessorAtLast(defaultProcessor); if (mDebugMode) { mDevtoolsManager = new DevtoolsManager(true); String localCachePath = getGlobalConfigs().getContext().getCacheDir() @@ -792,6 +798,12 @@ public void onRuntimeInitialized() { } } + @Override + @NonNull + public JsDriver getJsDriver() { + return mJsDriver; + } + @NonNull DomManager getDomManager() { return mDomManager; diff --git a/framework/android/src/main/java/com/tencent/mtt/hippy/PerformanceProcessor.java b/framework/android/src/main/java/com/tencent/mtt/hippy/PerformanceProcessor.java new file mode 100644 index 00000000000..f2bb7ce0de8 --- /dev/null +++ b/framework/android/src/main/java/com/tencent/mtt/hippy/PerformanceProcessor.java @@ -0,0 +1,80 @@ +/* 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; + +import androidx.annotation.NonNull; +import com.tencent.vfs.Processor; +import com.tencent.vfs.ResourceDataHolder; +import com.tencent.vfs.ResourceDataHolder.RequestFrom; +import com.tencent.vfs.UrlUtils; +import com.tencent.vfs.VfsManager.ProcessorCallback; +import java.lang.ref.WeakReference; + +/** + * For performance API features, we need to add this processor at the head of the vfs access chain + * for recording the start and end of resource loading. + */ +public class PerformanceProcessor extends Processor { + + private final WeakReference mEngineContextRef; + + public PerformanceProcessor(@NonNull HippyEngineContext engineContext) { + mEngineContextRef = new WeakReference<>(engineContext); + } + + @Override + public void handleRequestAsync(@NonNull ResourceDataHolder holder, + @NonNull ProcessorCallback callback) { + if (holder.requestFrom == RequestFrom.LOCAL) { + holder.loadStartTime = System.currentTimeMillis(); + } + super.handleRequestAsync(holder, callback); + } + + @Override + public boolean handleRequestSync(@NonNull ResourceDataHolder holder) { + if (holder.requestFrom == RequestFrom.LOCAL) { + holder.loadStartTime = System.currentTimeMillis(); + } + return super.handleRequestSync(holder); + } + + @Override + public void handleResponseAsync(@NonNull ResourceDataHolder holder, + @NonNull ProcessorCallback callback) { + HippyEngineContext engineContext = mEngineContextRef.get(); + if (shouldDoRecord(holder) && engineContext != null) { + engineContext.getJsDriver().recordResourceLoadEndTime(holder.uri, holder.loadStartTime, + System.currentTimeMillis()); + } + super.handleResponseAsync(holder, callback); + } + + @Override + public void handleResponseSync(@NonNull ResourceDataHolder holder) { + HippyEngineContext engineContext = mEngineContextRef.get(); + if (shouldDoRecord(holder) && engineContext != null) { + engineContext.getJsDriver().recordResourceLoadEndTime(holder.uri, holder.loadStartTime, + System.currentTimeMillis()); + } + super.handleResponseSync(holder); + } + + private boolean shouldDoRecord(@NonNull ResourceDataHolder holder) { + return holder.requestFrom != RequestFrom.NATIVE && !UrlUtils.isBase64Url(holder.uri); + } +} diff --git a/framework/ios/base/HippyPerformanceLogger.h b/framework/ios/base/HippyPerformanceLogger.h deleted file mode 100644 index b041f4d8f80..00000000000 --- a/framework/ios/base/HippyPerformanceLogger.h +++ /dev/null @@ -1,83 +0,0 @@ -/*! - * iOS SDK - * - * 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. - */ - -#import - -typedef NS_ENUM(NSUInteger, HippyPLTag) { - HippyPLScriptDownload = 0, //bundle download tag - HippyExecuteSource, //bundle execution tag - HippyPLBundleSize, //bundle size tag - - HippyPLNativeModuleInit, //native module initialization tag - HippyPLJSExecutorSetup, //js executor setup tag - HippyPLBridgeStartup, //bridge set up tag - HippyPLSize -}; - -@interface HippyPerformanceLogger : NSObject - -/** - * Starts measuring a metric with the given tag. - * Overrides previous value if the measurement has been already started. - * If HippyProfile is enabled it also begins appropriate async event. - * All work is scheduled on the background queue so this doesn't block current thread. - */ -- (void)markStartForTag:(HippyPLTag)tag forKey:(NSString *)key; - -/** - * Stops measuring a metric with given tag. - * Checks if HippyPerformanceLoggerStart() has been called before - * and doesn't do anything and log a message if it hasn't. - * If HippyProfile is enabled it also ends appropriate async event. - * All work is scheduled on the background queue so this doesn't block current thread. - */ -- (void)markStopForTag:(HippyPLTag)tag forKey:(NSString *)key; - -/** - * Sets given value for a metric with given tag. - * All work is scheduled on the background queue so this doesn't block current thread. - */ -- (void)setValue:(int64_t)value forTag:(HippyPLTag)tag forKey:(NSString *)key; - -/** - * Adds given value to the current value for a metric with given tag. - * All work is scheduled on the background queue so this doesn't block current thread. - */ -- (void)addValue:(int64_t)value forTag:(HippyPLTag)tag forKey:(NSString *)key; - -/** - * Returns a duration in ms (stop_time - start_time) for given HippyPLTag. - */ -- (int64_t)durationForTag:(HippyPLTag)tag forKey:(NSString *)key; - -/** - * Returns a value for given HippyPLTag. - */ -- (int64_t)valueForTag:(HippyPLTag)tag forKey:(NSString *)key; - -/** - * Returns an array with values for all tags. - * Use HippyPLTag to go over the array. - */ -- (NSArray *)labelsForTags; - -@end diff --git a/framework/ios/base/HippyPerformanceLogger.mm b/framework/ios/base/HippyPerformanceLogger.mm deleted file mode 100644 index dcc056e59e6..00000000000 --- a/framework/ios/base/HippyPerformanceLogger.mm +++ /dev/null @@ -1,119 +0,0 @@ -/*! - * iOS SDK - * - * 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. - */ - -#import - -#import "HippyPerformanceLogger.h" -#import "HPLog.h" - -#include - -struct PerformanceValue { - int64_t startTime; - int64_t endTime; - PerformanceValue():startTime(0), endTime(0) {} -}; - -struct NSStringHash { - std::size_t operator()(const NSString* str) const { - return [str hash]; - } -}; - -struct NSStringEqual { - bool operator()(const NSString* lhs, const NSString* rhs) const { - return 0 == std::strcmp([lhs UTF8String], [rhs UTF8String]); - } -}; - -using PerformanceTagValue = std::unordered_map; -using PerformanceMap = std::unordered_map; -@interface HippyPerformanceLogger () { - PerformanceMap _data; -} - -@property (nonatomic, copy) NSArray *labelsForTags; - -@end - -@implementation HippyPerformanceLogger - -- (instancetype)init { - if (self = [super init]) { - _labelsForTags = @[ - @"ScriptDownload", - @"ScriptExecution", - @"NativeModuleInit", - @"JSExecutorSetup", - @"BridgeStartup", - @"BundleSize", - @"ExecuteSource", - ]; - _data.reserve(HippyPLSize); - } - return self; -} - -- (PerformanceValue &)getPLValue:(HippyPLTag)tag forKey:(NSString *)key { - NSString *finalKey = key ?: [NSString stringWithFormat:@"%p", self]; - PerformanceTagValue &tagValue = _data[tag]; - PerformanceValue &value = tagValue[finalKey]; - return value; -} - -- (void)markStartForTag:(HippyPLTag)tag forKey:(NSString *)key { - PerformanceValue &value = [self getPLValue:tag forKey:key]; - value.startTime = CACurrentMediaTime() * 1000.f; -} - -- (void)markStopForTag:(HippyPLTag)tag forKey:(NSString *)key { - PerformanceValue &value = [self getPLValue:tag forKey:key]; - if (value.startTime != 0 && value.endTime == 0) { - value.endTime = CACurrentMediaTime() * 1000.f; - } - else { - HPLogInfo(@"[Hippy_OC_Log][Performance],Unbalanced calls start/end for tag %li", (unsigned long)tag); - } -} - -- (void)setValue:(int64_t)value forTag:(HippyPLTag)tag forKey:(NSString *)key { - PerformanceValue &pvalue = [self getPLValue:tag forKey:key]; - pvalue.startTime = 0; - pvalue.endTime = value; -} - -- (void)addValue:(int64_t)value forTag:(HippyPLTag)tag forKey:(NSString *)key { - PerformanceValue &pvalue = [self getPLValue:tag forKey:key]; - pvalue.startTime = pvalue.startTime + value; -} - -- (int64_t)durationForTag:(HippyPLTag)tag forKey:(NSString *)key { - PerformanceValue &value = [self getPLValue:tag forKey:key]; - return value.endTime - value.startTime; -} - -- (int64_t)valueForTag:(HippyPLTag)tag forKey:(NSString *)key { - PerformanceValue &value = [self getPLValue:tag forKey:key]; - return value.endTime; -} - -@end diff --git a/framework/ios/base/bridge/HippyBridge.h b/framework/ios/base/bridge/HippyBridge.h index 578706d431e..c9024cf72b9 100644 --- a/framework/ios/base/bridge/HippyBridge.h +++ b/framework/ios/base/bridge/HippyBridge.h @@ -32,7 +32,6 @@ #include -@class HippyPerformanceLogger; @class HippyJSExecutor; @class HippyModuleData; @@ -42,6 +41,7 @@ namespace hippy { inline namespace dom { class DomManager; class RootNode; +class RenderManager; }; }; @@ -90,6 +90,8 @@ HP_EXTERN NSString *HippyBridgeModuleNameForClass(Class bridgeModuleClass); @property (nonatomic, copy, readonly) NSDictionary *launchOptions; +@property (nonatomic, assign) std::weak_ptr renderManager; + /** * Create A HippyBridge instance * @@ -251,11 +253,6 @@ HP_EXTERN NSString *HippyBridgeModuleNameForClass(Class bridgeModuleClass); */ - (void)requestReload; -/** - * Link to the Performance Logger that logs Hippy Native perf events. - */ -@property (nonatomic, readonly, strong) HippyPerformanceLogger *performanceLogger; - @property (nonatomic, assign) BOOL debugMode; @property (nonatomic, strong) NSString *appVerson; // diff --git a/framework/ios/base/bridge/HippyBridge.mm b/framework/ios/base/bridge/HippyBridge.mm index d14e7b249cf..7c31e7a8355 100644 --- a/framework/ios/base/bridge/HippyBridge.mm +++ b/framework/ios/base/bridge/HippyBridge.mm @@ -36,11 +36,9 @@ #import "HippyModuleMethod.h" #import "HippyTurboModuleManager.h" #import "HippyOCTurboModule.h" -#import "HippyPerformanceLogger.h" #import "HippyRedBox.h" #import "HippyTurboModule.h" #import "HippyUtils.h" - #import "HPAsserts.h" #import "HPConvert.h" #import "HPDefaultImageProvider.h" @@ -49,6 +47,7 @@ #import "HPLog.h" #import "HPOCToHippyValue.h" #import "HPToolUtils.h" +#import "NSObject+Render.h" #import "TypeConverter.h" #import "VFSUriLoader.h" @@ -61,6 +60,7 @@ #include "dom/scene.h" #include "dom/render_manager.h" #include "driver/scope.h" +#include "driver/performance/performance.h" #include "footstone/worker_manager.h" #include "vfs/uri_loader.h" #include "VFSUriHandler.h" @@ -97,6 +97,8 @@ @interface HippyBridge() { NSMutableArray *_bundleURLs; NSURL *_sandboxDirectory; std::weak_ptr _uriLoader; + std::weak_ptr _renderManager; + footstone::TimePoint _startTime; } @property(readwrite, strong) dispatch_semaphore_t moduleSemaphore; @@ -142,6 +144,8 @@ - (instancetype)initWithDelegate:(id)delegate _invalidateReason = HPInvalidateReasonDealloc; _valid = YES; _bundlesQueue = [[HippyBundleOperationQueue alloc] init]; + _startTime = footstone::TimePoint::SystemNow(); + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(rootViewContentDidAppear:) name:kRootViewDidAddContent object:nil]; [self setUp]; HPExecuteOnMainThread(^{ [self bindKeys]; @@ -151,6 +155,20 @@ - (instancetype)initWithDelegate:(id)delegate return self; } +- (void)rootViewContentDidAppear:(NSNotification *)noti { + UIView *rootView = [[noti userInfo] objectForKey:kRootViewKey]; + if (rootView) { + auto domManager = _javaScriptExecutor.pScope->GetDomManager().lock(); + if (domManager) { + auto viewRenderManager = [rootView renderManager]; + if (_renderManager.lock() == viewRenderManager.lock()) { + auto entry = _javaScriptExecutor.pScope->GetPerformance()->PerformanceNavigation("hippyInit"); + entry->SetHippyFirstFrameEnd(footstone::TimePoint::SystemNow()); + } + } + } +} + - (void)dealloc { /** * This runs only on the main thread, but crashes the subclass @@ -240,14 +258,11 @@ - (void)requestReload { } - (void)setUp { - _performanceLogger = [HippyPerformanceLogger new]; - [_performanceLogger markStartForTag:HippyPLBridgeStartup forKey:nil]; _valid = YES; self.moduleSemaphore = dispatch_semaphore_create(0); @try { __weak HippyBridge *weakSelf = self; _moduleSetup = [[HippyModulesSetup alloc] initWithBridge:self extraProviderModulesBlock:_moduleProvider]; - [_performanceLogger markStartForTag:HippyPLJSExecutorSetup forKey:nil]; _javaScriptExecutor = [[HippyJSExecutor alloc] initWithEngineKey:_engineKey bridge:self]; _javaScriptExecutor.contextCreatedBlock = ^(id ctxWrapper){ HippyBridge *strongSelf = weakSelf; @@ -255,7 +270,6 @@ - (void)setUp { dispatch_semaphore_wait(strongSelf.moduleSemaphore, DISPATCH_TIME_FOREVER); NSString *moduleConfig = [strongSelf moduleConfig]; [ctxWrapper createGlobalObject:@"__hpBatchedBridgeConfig" withJsonValue:moduleConfig]; - [strongSelf->_performanceLogger markStopForTag:HippyPLJSExecutorSetup forKey:nil]; #if HP_DEV //default is yes when debug mode [strongSelf setInspectable:YES]; @@ -270,17 +284,21 @@ - (void)setUp { //The caller may attempt to look up a module immediately after creating the HippyBridge, //therefore the initialization of all modules cannot be placed in a sub-thread // dispatch_async(HippyBridgeQueue(), ^{ - [self initWithModulesCompletion:^{ - HippyBridge *strongSelf = weakSelf; - if (strongSelf) { - dispatch_semaphore_signal(strongSelf.moduleSemaphore); - } - }]; + [self initWithModulesCompletion:^{ + HippyBridge *strongSelf = weakSelf; + if (strongSelf) { + dispatch_semaphore_signal(strongSelf.moduleSemaphore); + footstone::TimePoint endTime = footstone::TimePoint::SystemNow(); + auto enty = + strongSelf.javaScriptExecutor.pScope->GetPerformance()->PerformanceNavigation("hippyInit"); + enty->SetHippyNativeInitStart(strongSelf->_startTime); + enty->SetHippyNativeInitEnd(endTime); + } + }]; // }); } @catch (NSException *exception) { HippyBridgeHandleException(exception, self); } - [_performanceLogger markStopForTag:HippyPLBridgeStartup forKey:nil]; } - (void)loadBundleURL:(NSURL *)bundleURL @@ -302,9 +320,7 @@ - (void)loadBundleURL:(NSURL *)bundleURL - (void)initWithModulesCompletion:(dispatch_block_t)completion { - [_performanceLogger markStartForTag:HippyPLNativeModuleInit forKey:nil]; [_moduleSetup setupModulesCompletion:completion]; - [_performanceLogger markStopForTag:HippyPLNativeModuleInit forKey:nil]; } - (void)beginLoadingBundle:(NSURL *)bundleURL @@ -437,13 +453,11 @@ - (void)executeJSCode:(NSData *)script return; } HPAssert(self.javaScriptExecutor, @"js executor must not be null"); - [_performanceLogger markStartForTag:HippyExecuteSource forKey:[sourceURL absoluteString]]; __weak HippyBridge *weakSelf = self; [self.javaScriptExecutor executeApplicationScript:script sourceURL:sourceURL onComplete:^(id result ,NSError *error) { HippyBridge *strongSelf = weakSelf; if (!strongSelf || ![strongSelf isValid]) { completion(result, error); - [strongSelf->_performanceLogger markStopForTag:HippyExecuteSource forKey:[sourceURL absoluteString]]; return; } if (error) { @@ -457,7 +471,6 @@ - (void)executeJSCode:(NSData *)script userInfo:userInfo]; }); } - [strongSelf->_performanceLogger markStopForTag:HippyExecuteSource forKey:[sourceURL absoluteString]]; completion(result, error); }]; } @@ -820,6 +833,7 @@ - (void)invalidate { _displayLink = nil; _javaScriptExecutor = nil; _moduleSetup = nil; + _startTime = footstone::TimePoint::SystemNow(); self.moduleSemaphore = nil; dispatch_group_notify(group, dispatch_get_main_queue(), ^{ [jsExecutor executeBlockOnJavaScriptQueue:^{ diff --git a/framework/ios/base/bridge/HippyConvenientBridge.mm b/framework/ios/base/bridge/HippyConvenientBridge.mm index 8d2d68a67f2..2d4dbfa8f2f 100644 --- a/framework/ios/base/bridge/HippyConvenientBridge.mm +++ b/framework/ios/base/bridge/HippyConvenientBridge.mm @@ -77,12 +77,14 @@ - (void)setUpNativeRenderManager { auto domManager = engineResource->GetDomManager(); //Create NativeRenderManager _nativeRenderManager = std::make_shared(); + _nativeRenderManager->Initialize(); //set dom manager _nativeRenderManager->SetDomManager(domManager); //set image provider for native render manager _nativeRenderManager->AddImageProviderClass([HPDefaultImageProvider class]); _nativeRenderManager->RegisterExtraComponent(_extraComponents); _nativeRenderManager->SetVFSUriLoader([self URILoader]); + _bridge.renderManager = _nativeRenderManager; } - (std::shared_ptr)URILoader { @@ -155,7 +157,9 @@ - (void)setRootView:(UIView *)rootView { _rootNode->SetRootSize(rootView.frame.size.width, rootView.frame.size.height); //set rendermanager for dommanager - domManager->SetRenderManager(_nativeRenderManager); + if (!domManager->GetRenderManager().lock()) { + domManager->SetRenderManager(_nativeRenderManager); + } //bind rootview and root node _nativeRenderManager->RegisterRootView(rootView, _rootNode); diff --git a/framework/ios/base/bundleoperations/HippyBundleLoadOperation.mm b/framework/ios/base/bundleoperations/HippyBundleLoadOperation.mm index 636aaba6f14..7a8f876b9ea 100644 --- a/framework/ios/base/bundleoperations/HippyBundleLoadOperation.mm +++ b/framework/ios/base/bundleoperations/HippyBundleLoadOperation.mm @@ -22,7 +22,6 @@ #import "HippyBundleLoadOperation.h" #import "HippyBridge+VFSLoader.h" -#import "HippyPerformanceLogger.h" #include @@ -71,8 +70,6 @@ - (void)main { self.executing = YES; HippyBridge *bridge = _bridge; NSString *bundleURL = [_bundleURL absoluteString]; - HippyPerformanceLogger *performanceLogger = bridge?bridge.performanceLogger:nil; - [performanceLogger markStartForTag:HippyPLScriptDownload forKey:bundleURL]; __weak HippyBundleLoadOperation *weakSelf = self; [bridge loadContentsAsynchronouslyFromUrl:bundleURL method:@"get" @@ -87,9 +84,6 @@ - (void)main { strongSelf.executing = NO; return; } - int64_t sourceLength = [data length]; - [performanceLogger markStopForTag:HippyPLScriptDownload forKey:bundleURL]; - [performanceLogger setValue:sourceLength forTag:HippyPLBundleSize forKey:bundleURL]; if (strongSelf.onLoad) { strongSelf.onLoad(data, error); } diff --git a/framework/ios/base/executors/HippyJSExecutor.mm b/framework/ios/base/executors/HippyJSExecutor.mm index beb4e42e096..e2c77f31b3a 100644 --- a/framework/ios/base/executors/HippyJSExecutor.mm +++ b/framework/ios/base/executors/HippyJSExecutor.mm @@ -31,7 +31,6 @@ #import "HippyJSEnginesMapper.h" #import "HippyJSExecutor.h" #import "HippyOCTurboModule+Inner.h" -#import "HippyPerformanceLogger.h" #import "HippyRedBox.h" #import "HippyUtils.h" #import "HippyTurboModuleManager.h" @@ -78,7 +77,6 @@ @interface HippyJSExecutor () { // Set at setUp time: - HippyPerformanceLogger *_performanceLogger; id _contextWrapper; NSMutableArray *_pendingCalls; __weak HippyBridge *_bridge; @@ -95,7 +93,6 @@ @implementation HippyJSExecutor - (void)setBridge:(HippyBridge *)bridge { _bridge = bridge; - _performanceLogger = [bridge performanceLogger]; } - (HippyBridge *)bridge { @@ -105,9 +102,11 @@ - (HippyBridge *)bridge { - (void)setup { auto engine = [[HippyJSEnginesMapper defaultInstance] createJSEngineResourceForKey:self.enginekey]; const char *pName = [self.enginekey UTF8String] ?: ""; + footstone::TimePoint startPoint = footstone::TimePoint::SystemNow(); auto scope = engine->GetEngine()->CreateScope(pName); + dispatch_semaphore_t scopeSemaphore = dispatch_semaphore_create(0); __weak HippyJSExecutor *weakSelf = self; - engine->GetEngine()->GetJsTaskRunner()->PostTask([weakSelf](){ + engine->GetEngine()->GetJsTaskRunner()->PostTask([weakSelf, scopeSemaphore, startPoint](){ @autoreleasepool { HippyJSExecutor *strongSelf = weakSelf; if (!strongSelf) { @@ -117,6 +116,7 @@ - (void)setup { if (!bridge) { return; } + dispatch_semaphore_wait(scopeSemaphore, DISPATCH_TIME_FOREVER); auto scope = strongSelf->_pScope; scope->CreateContext(); auto context = scope->GetContext(); @@ -214,9 +214,13 @@ - (void)setup { [strongSelf executeBlockOnJavaScriptQueue:obj]; }]; [strongSelf->_pendingCalls removeAllObjects]; + auto entry = scope->GetPerformance()->PerformanceNavigation("hippyInit"); + entry->SetHippyJsEngineInitStart(startPoint); + entry->SetHippyJsEngineInitEnd(footstone::TimePoint::SystemNow()); } }); self.pScope = scope; + dispatch_semaphore_signal(scopeSemaphore); #ifdef ENABLE_INSPECTOR HippyBridge *bridge = self.bridge; if (bridge && bridge.debugMode) { @@ -522,10 +526,8 @@ - (void)_executeJSCall:(NSString *)method - (void)executeApplicationScript:(NSData *)script sourceURL:(NSURL *)sourceURL onComplete:(HippyJavaScriptCallback)onComplete { HPAssertParam(script); HPAssertParam(sourceURL); - // HippyProfileBeginFlowEvent(); __weak HippyJSExecutor* weakSelf = self; [self executeBlockOnJavaScriptQueue:^{ - // HippyProfileEndFlowEvent(); @autoreleasepool { HippyJSExecutor *strongSelf = weakSelf; if (!strongSelf || !strongSelf.isValid) { @@ -533,7 +535,7 @@ - (void)executeApplicationScript:(NSData *)script sourceURL:(NSURL *)sourceURL o return; } NSError *error = nil; - id result = executeApplicationScript(script, sourceURL, strongSelf->_performanceLogger, strongSelf.pScope->GetContext(), &error); + id result = executeApplicationScript(script, sourceURL, strongSelf.pScope->GetContext(), &error); if (onComplete) { onComplete(result, error); } @@ -550,7 +552,7 @@ - (void)executeApplicationScript:(NSData *)script sourceURL:(NSURL *)sourceURL o return lock; } -static NSError *executeApplicationScript(NSData *script, NSURL *sourceURL, HippyPerformanceLogger *performanceLogger, SharedCtxPtr context, NSError **error) { +static NSError *executeApplicationScript(NSData *script, NSURL *sourceURL, SharedCtxPtr context, NSError **error) { @autoreleasepool { const char *scriptBytes = reinterpret_cast([script bytes]); string_view view = string_view::new_from_utf8(scriptBytes, [script length]); diff --git a/framework/voltron/core/src/bridge/ios/VoltronJSCExecutor.mm b/framework/voltron/core/src/bridge/ios/VoltronJSCExecutor.mm index 8109813d0a8..905d6fd8c93 100644 --- a/framework/voltron/core/src/bridge/ios/VoltronJSCExecutor.mm +++ b/framework/voltron/core/src/bridge/ios/VoltronJSCExecutor.mm @@ -199,11 +199,11 @@ - (void)setup { std::shared_ptr context = std::static_pointer_cast(scope->GetContext()); JSContext *jsContext = [JSContext contextWithJSGlobalContextRef:context->GetCtxRef()]; -// #if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_16_2 -// if (@available(iOS 16.4, *)) { -// jsContext.inspectable = true; -// } -// #endif + #if defined(__IPHONE_16_4) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_16_4 + if (@available(iOS 16.4, *)) { + jsContext.inspectable = true; + } + #endif auto global_object = context->GetGlobalObject(); auto user_global_object_key = context->CreateString(kGlobalKey); context->SetProperty(global_object, user_global_object_key, global_object); diff --git a/hippy.podspec b/hippy.podspec index e84385191e8..0058da1d5b0 100644 --- a/hippy.podspec +++ b/hippy.podspec @@ -69,6 +69,10 @@ Pod::Spec.new do |s| 'GCC_ENABLE_CPP_EXCEPTIONS' => false, 'GCC_ENABLE_CPP_RTTI' => false, } + framework.dependency 'hippy/Base' + framework.dependency 'hippy/JSDriver' + framework.dependency 'hippy/Image' + framework.dependency 'hippy/NativeRenderer' puts 'hippy subspec \'framework\' read end' end @@ -110,6 +114,7 @@ Pod::Spec.new do |s| 'GCC_ENABLE_CPP_RTTI' => false, } footstoneutils.dependency 'hippy/Footstone' + footstoneutils.dependency 'hippy/Base' puts 'hippy subspec \'footstoneutils\' read end' end @@ -127,6 +132,7 @@ Pod::Spec.new do |s| base.libraries = 'c++' base.source_files = ['modules/ios/base/*.{h,m,mm}', 'modules/ios/logutils/*.{h,mm}'] base.public_header_files = ['modules/ios/base/*.h', 'modules/ios/logutils/*.h'] + base.dependency 'hippy/Footstone' puts 'hippy subspec \'base\' read end' end @@ -151,6 +157,7 @@ Pod::Spec.new do |s| 'HEADER_SEARCH_PATHS' => header_search_paths } vfs.preserve_path = 'modules/vfs/native' + vfs.dependency 'hippy/Footstone' puts 'hippy subspec \'vfs\' read end' end @@ -165,6 +172,8 @@ Pod::Spec.new do |s| 'GCC_ENABLE_CPP_RTTI' => false, } iosvfs.dependency 'hippy/VFS' + iosvfs.dependency 'hippy/Footstone' + iosvfs.dependency 'hippy/FootstoneUtils' puts 'hippy subspec \'iosvfs\' read end' end @@ -225,6 +234,9 @@ Pod::Spec.new do |s| driver.user_target_xcconfig = { 'HEADER_SEARCH_PATHS' => header_search_paths, } + driver.dependency 'hippy/Footstone' + driver.dependency 'hippy/Dom' + driver.dependency 'hippy/iOSVFS' driver.preserve_path = 'driver/js' puts 'hippy subspec \'driver\' read end' end @@ -234,6 +246,10 @@ Pod::Spec.new do |s| renderer.libraries = 'c++' renderer.source_files = 'renderer/native/ios/**/*.{h,m,mm}' renderer.public_header_files = 'renderer/native/ios/**/*.h' + renderer.dependency 'hippy/Base' + renderer.dependency 'hippy/DomUtils' + renderer.dependency 'hippy/Image' + renderer.dependency 'hippy/iOSVFS' puts 'hippy subspec \'nativerenderer\' read end' end @@ -291,6 +307,8 @@ Pod::Spec.new do |s| 'GCC_ENABLE_CPP_RTTI' => false, } domutils.dependency 'hippy/Dom' + domutils.dependency 'hippy/FootstoneUtils' + domutils.dependency 'hippy/Base' puts 'hippy subspec \'domutils\' read end' end diff --git a/modules/footstone/include/footstone/time_point.h b/modules/footstone/include/footstone/time_point.h index a25fa484354..9f038bc7fab 100644 --- a/modules/footstone/include/footstone/time_point.h +++ b/modules/footstone/include/footstone/time_point.h @@ -42,6 +42,12 @@ class TimePoint { .count()); } + static TimePoint SystemNow() { + return TimePoint(std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count()); + } + static constexpr TimePoint Min() { return TimePoint(std::numeric_limits::min()); } static constexpr TimePoint Max() { return TimePoint(std::numeric_limits::max()); } diff --git a/modules/ios/base/MacroDefines.h b/modules/ios/base/MacroDefines.h index 423181d3dda..6be51b67366 100644 --- a/modules/ios/base/MacroDefines.h +++ b/modules/ios/base/MacroDefines.h @@ -67,3 +67,6 @@ */ #define HP_CONCAT2(A, B) A##B #define HP_CONCAT(A, B) HP_CONCAT2(A, B) + +#define kRootViewDidAddContent @"RootViewDidAddContent" +#define kRootViewKey @"RootViewKey" diff --git a/modules/ios/base/NSObject+Render.h b/modules/ios/base/NSObject+Render.h new file mode 100644 index 00000000000..de33d40859e --- /dev/null +++ b/modules/ios/base/NSObject+Render.h @@ -0,0 +1,41 @@ +/*! + * iOS SDK + * + * 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. + */ + +#import + +#include + +NS_ASSUME_NONNULL_BEGIN + +namespace hippy { +inline namespace dom { +class RenderManager; +}; +}; + +@interface NSObject (Render) + +@property(nonatomic, assign) std::weak_ptr renderManager; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/ios/base/NSObject+Render.mm b/modules/ios/base/NSObject+Render.mm new file mode 100644 index 00000000000..d6f5fa6dd49 --- /dev/null +++ b/modules/ios/base/NSObject+Render.mm @@ -0,0 +1,51 @@ +/*! + * iOS SDK + * + * 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. + */ + +#import "NSObject+Render.h" +#import "objc/runtime.h" + +#include "dom/render_manager.h" + +@interface RenderManagerWrapper : NSObject + +@property(nonatomic, assign) std::weak_ptr renderManager; + +@end + +@implementation RenderManagerWrapper + +@end + +@implementation NSObject (Render) + +- (void)setRenderManager:(std::weak_ptr)renderManager { + RenderManagerWrapper *wrapper = [[RenderManagerWrapper alloc] init]; + wrapper.renderManager = renderManager; + objc_setAssociatedObject(self, @selector(renderManager), wrapper, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (std::weak_ptr)renderManager { + RenderManagerWrapper *wrapper = objc_getAssociatedObject(self, _cmd); + return wrapper.renderManager; +} + +@end diff --git a/modules/vfs/android/src/main/java/com/tencent/vfs/ResourceDataHolder.java b/modules/vfs/android/src/main/java/com/tencent/vfs/ResourceDataHolder.java index 6a1b6c958c5..57cdb91c0b7 100644 --- a/modules/vfs/android/src/main/java/com/tencent/vfs/ResourceDataHolder.java +++ b/modules/vfs/android/src/main/java/com/tencent/vfs/ResourceDataHolder.java @@ -59,6 +59,7 @@ public enum TransferType { public RequestFrom requestFrom; public int nativeRequestId; public int index = -1; + public long loadStartTime = 0; // The resource loading error code is defined by the processor itself, // a value other than 0 indicates failure, and a value of 0 indicates success. public int resultCode = -1; @@ -106,6 +107,7 @@ public void recycle() { callback = null; errorMessage = null; processorTag = null; + loadStartTime = 0; index = -1; resultCode = -1; transferType = TransferType.NORMAL; diff --git a/modules/vfs/ios/VFSUriLoader.mm b/modules/vfs/ios/VFSUriLoader.mm index cb9d8a7dcbe..438be059332 100644 --- a/modules/vfs/ios/VFSUriLoader.mm +++ b/modules/vfs/ios/VFSUriLoader.mm @@ -141,7 +141,16 @@ auto &cur_convenient = (*cur_convenient_it); //check if convenient loader exists, or forward to cpp loader if (cur_convenient) { - cur_convenient->RequestUntrustedContent(request, operationQueue, progress, completion, block); + auto startPoint = footstone::TimePoint::SystemNow(); + VFSHandlerCompletionBlock callback = ^(NSData *data, NSURLResponse *response, NSError *error) { + auto endPoint = footstone::TimePoint::SystemNow(); + string_view uri(NSStringToU16StringView([[response URL] absoluteString])); + DoRequestTimePerformanceCallback(uri, startPoint, endPoint); + if (completion) { + completion(data, response, error); + } + }; + cur_convenient->RequestUntrustedContent(request, operationQueue, progress, callback, block); } else { string_view uri = NSStringToU8StringView([requestURL absoluteString]); diff --git a/modules/vfs/native/include/vfs/uri_loader.h b/modules/vfs/native/include/vfs/uri_loader.h index 93d539bd85e..4d784b5ad5d 100644 --- a/modules/vfs/native/include/vfs/uri_loader.h +++ b/modules/vfs/native/include/vfs/uri_loader.h @@ -38,9 +38,11 @@ inline namespace vfs { class UriLoader: public std::enable_shared_from_this { public: using string_view = footstone::string_view; + using TimePoint = footstone::TimePoint; using WorkerManager = footstone::WorkerManager; using bytes = vfs::UriHandler::bytes; using RetCode = vfs::JobResponse::RetCode; + using RequestTimePerformanceCallback = std::function; UriLoader(); virtual ~UriLoader() = default; @@ -73,6 +75,11 @@ class UriLoader: public std::enable_shared_from_this { void Terminate(); + void SetRequestTimePerformanceCallback(const RequestTimePerformanceCallback& cb) { on_request_time_performance_ = cb; } + + protected: + void DoRequestTimePerformanceCallback(const string_view& uri, const TimePoint& start, const TimePoint& end); + private: std::shared_ptr GetNextHandler(std::list>::iterator& cur, const std::list>::iterator& end); @@ -86,6 +93,8 @@ class UriLoader: public std::enable_shared_from_this { std::list> default_handler_list_; std::list> interceptor_; std::mutex mutex_; + + RequestTimePerformanceCallback on_request_time_performance_; }; } diff --git a/modules/vfs/native/src/uri_loader.cc b/modules/vfs/native/src/uri_loader.cc index 43a38255c88..fa2cae7121e 100644 --- a/modules/vfs/native/src/uri_loader.cc +++ b/modules/vfs/native/src/uri_loader.cc @@ -83,6 +83,9 @@ void UriLoader::RequestUntrustedContent(const string_view& uri, } void UriLoader::RequestUntrustedContent(const std::shared_ptr& request, std::shared_ptr response) { + // performance start time + auto start_time = TimePoint::SystemNow(); + auto uri = request->GetUri(); auto scheme = GetScheme(uri); std::list>::iterator cur_it; @@ -105,10 +108,17 @@ void UriLoader::RequestUntrustedContent(const std::shared_ptr& reque return this->GetNextHandler(cur_it, end_it); }; (*cur_it)->RequestUntrustedContent(request, std::move(response), next); + + // performance end time + auto end_time = TimePoint::SystemNow(); + DoRequestTimePerformanceCallback(request->GetUri(), start_time, end_time); } void UriLoader::RequestUntrustedContent(const std::shared_ptr& request, const std::function)>& cb) { + // performance start time + auto start_time = TimePoint::SystemNow(); + auto uri = request->GetUri(); auto scheme = GetScheme(uri); std::shared_ptr>::iterator> cur_it; @@ -135,7 +145,20 @@ void UriLoader::RequestUntrustedContent(const std::shared_ptr& reque } return self->GetNextHandler(*cur_it, *end_it); }; - (**cur_it)->RequestUntrustedContent(request, cb, next); + auto new_cb = [WEAK_THIS, request, start_time, orig_cb = cb](std::shared_ptr response) { + DEFINE_SELF(UriLoader) + if (!self) { + orig_cb(response); + return; + } + + // performance end time + auto end_time = TimePoint::SystemNow(); + self->DoRequestTimePerformanceCallback(request->GetUri(), start_time, end_time); + + orig_cb(response); + }; + (**cur_it)->RequestUntrustedContent(request, new_cb, next); } std::shared_ptr UriLoader::GetNextHandler(std::list>::iterator& cur, @@ -159,5 +182,11 @@ std::string UriLoader::GetScheme(const UriLoader::string_view& uri) { return {}; } +void UriLoader::DoRequestTimePerformanceCallback(const string_view& uri, const TimePoint& start, const TimePoint& end) { + if (on_request_time_performance_ != nullptr) { + on_request_time_performance_(uri, start, end); + } +} + } } diff --git a/renderer/native/ios/renderer/NativeRenderImpl.h b/renderer/native/ios/renderer/NativeRenderImpl.h index 20a4ab415a4..c0b69cad856 100644 --- a/renderer/native/ios/renderer/NativeRenderImpl.h +++ b/renderer/native/ios/renderer/NativeRenderImpl.h @@ -35,6 +35,7 @@ class VFSUriLoader; namespace hippy { inline namespace dom { +class RenderManager; class DomManager; class DomArgument; class RootNode; @@ -57,9 +58,14 @@ class HippyValue; */ @interface NativeRenderImpl : NSObject +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithRenderManager:(std::weak_ptr)renderManager NS_DESIGNATED_INITIALIZER; + @property(nonatomic, assign) BOOL uiCreationLazilyEnabled; @property(nonatomic, assign) std::weak_ptr VFSUriLoader; +@property(nonatomic, assign) std::weak_ptr renderManager; @property(nonatomic, readonly) std::weak_ptr domManager; @property(nonatomic, readonly) NativeRenderComponentMap *viewRegistry; diff --git a/renderer/native/ios/renderer/NativeRenderImpl.mm b/renderer/native/ios/renderer/NativeRenderImpl.mm index fd90dfa7ef4..7e817e13c1d 100644 --- a/renderer/native/ios/renderer/NativeRenderImpl.mm +++ b/renderer/native/ios/renderer/NativeRenderImpl.mm @@ -40,6 +40,7 @@ #import "UIView+DomEvent.h" #import "UIView+NativeRender.h" #import "UIView+Render.h" +#import "NSObject+Render.h" #include @@ -180,6 +181,7 @@ @interface NativeRenderImpl() { std::mutex _imageProviderMutex; std::function _rootViewSizeChangedCb; + std::weak_ptr _renderManager; } @end @@ -190,9 +192,10 @@ @implementation NativeRenderImpl #pragma mark Life cycle -- (instancetype)init { +- (instancetype)initWithRenderManager:(std::weak_ptr)renderManager { self = [super init]; if (self) { + _renderManager = renderManager; [self initContext]; } return self; @@ -282,6 +285,10 @@ - (NativeRenderObjectView *)renderObjectForcomponentTag:(NSNumber *)componentTag return [_renderObjectRegistry componentForTag:componentTag onRootTag:rootTag]; } +- (std::weak_ptr)renderManager { + return _renderManager; +} + - (std::mutex &)renderQueueLock { return _renderQueueLock; } @@ -317,7 +324,7 @@ - (void)registerRootView:(UIView *)rootView asRootNode:(std::weak_ptr) [_viewRegistry addRootComponent:rootView rootNode:rootNode forTag:componentTag]; [rootView addObserver:self forKeyPath:@"frame" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; - + rootView.renderManager = [self renderManager]; CGRect frame = rootView.frame; UIColor *backgroundColor = [rootView backgroundColor]; @@ -487,7 +494,7 @@ - (UIView *)createViewFromRenderObject:(NativeRenderObjectView *)renderObject { rootTag:renderObject.rootTag properties:renderObject.props viewName:renderObject.viewName]; - view.renderImpl = self; + view.renderManager = [self renderManager]; [view nativeRenderSetFrame:renderObject.frame]; const std::vector &eventNames = [renderObject allEventNames]; for (auto &event : eventNames) { @@ -513,7 +520,7 @@ - (UIView *)createViewRecursiveFromRenderObjectWithNOLock:(NativeRenderObjectVie index++; } view.nativeRenderObjectView = renderObject; - view.renderImpl = self; + view.renderManager = [self renderManager]; [view clearSortedSubviews]; [view didUpdateNativeRenderSubviews]; NSMutableSet *applierBlocks = [NSMutableSet setWithCapacity:1]; @@ -587,7 +594,7 @@ - (UIView *)createViewByComponentData:(NativeRenderComponentData *)componentData if (view) { view.viewName = viewName; view.rootTag = rootTag; - view.renderImpl = self; + view.renderManager = [self renderManager]; [componentData setProps:props forView:view]; // Must be done before bgColor to prevent wrong default if ([view respondsToSelector:@selector(nativeRenderComponentDidFinishTransaction)]) { @@ -635,14 +642,10 @@ - (__kindof NativeRenderViewManager *)renderViewManagerForViewName:(NSString *)v NSArray *classes = NativeRenderGetViewManagerClasses(); NSMutableDictionary *defaultViewManagerClasses = [NSMutableDictionary dictionaryWithCapacity:[classes count]]; for (Class cls in classes) { - if ([_extraComponents containsObject:cls]) { + NSString *viewName = GetViewNameFromViewManagerClass(cls); + if ([_viewManagers objectForKey:viewName]) { continue; } - NSString *viewName = GetViewNameFromViewManagerClass(cls); - HPAssert(![defaultViewManagerClasses objectForKey:viewName], - @"duplicated component %@ for class %@ and %@", viewName, - NSStringFromClass(cls), - NSStringFromClass([defaultViewManagerClasses objectForKey:viewName])); [defaultViewManagerClasses setObject:cls forKey:viewName]; } [_viewManagers addEntriesFromDictionary:defaultViewManagerClasses]; @@ -764,7 +767,7 @@ - (void)createRenderNodes:(std::vector> &&)nodes [self addUIBlock:^(NativeRenderImpl *renderContext, __unused NSDictionary *viewRegistry) { UIView *view = [renderContext createViewFromRenderObject:renderObject]; view.nativeRenderObjectView = renderObject; - view.renderImpl = renderContext; + view.renderManager = [renderContext renderManager]; }]; } } diff --git a/renderer/native/ios/renderer/NativeRenderManager.h b/renderer/native/ios/renderer/NativeRenderManager.h index 3e9532bd227..7e7e97384fb 100644 --- a/renderer/native/ios/renderer/NativeRenderManager.h +++ b/renderer/native/ios/renderer/NativeRenderManager.h @@ -29,6 +29,7 @@ #include "dom/render_manager.h" @class UIView, NativeRenderImpl; + class VFSUriLoader; namespace hippy { inline namespace dom { @@ -41,13 +42,15 @@ class RootNode; /** * NativeRenderManager is used to manager view creation, update and delete for Native UI */ -class NativeRenderManager : public hippy::RenderManager { +class NativeRenderManager : public hippy::RenderManager ,public std::enable_shared_from_this { public: NativeRenderManager(); NativeRenderManager(NativeRenderImpl *uiManager): hippy::RenderManager("NativeRenderManager"), renderImpl_(uiManager){} ~NativeRenderManager(); + + void Initialize(); /** * create views from dom nodes @@ -204,6 +207,13 @@ class NativeRenderManager : public hippy::RenderManager { */ void SetRootViewSizeChangedEvent(std::function cb); + /** + * Get NativeRenderImpl variable + * + * @return A NativeRenderImpl instance + */ + NativeRenderImpl *GetNativeRenderImpl(); + private: NativeRenderImpl *renderImpl_; }; diff --git a/renderer/native/ios/renderer/NativeRenderManager.mm b/renderer/native/ios/renderer/NativeRenderManager.mm index 2521b9fb6b1..94f20faf0e6 100644 --- a/renderer/native/ios/renderer/NativeRenderManager.mm +++ b/renderer/native/ios/renderer/NativeRenderManager.mm @@ -24,6 +24,7 @@ #import "NativeRenderManager.h" #import "NativeRenderObjectText.h" #import "RenderVsyncManager.h" +#import "HPAsserts.h" #include "dom/dom_manager.h" #include "dom/layout_node.h" @@ -38,12 +39,16 @@ using RootNode = hippy::RootNode; NativeRenderManager::NativeRenderManager(): hippy::RenderManager("NativeRenderManager") { - renderImpl_ = [[NativeRenderImpl alloc] init]; +} + +void NativeRenderManager::Initialize() { + renderImpl_ = [[NativeRenderImpl alloc] initWithRenderManager:weak_from_this()]; } void NativeRenderManager::CreateRenderNode(std::weak_ptr root_node, std::vector> &&nodes) { @autoreleasepool { + HPAssert(renderImpl_, @"renderImpl_ is null, did you forget to call Initialize()?"); [renderImpl_ createRenderNodes:std::move(nodes) onRootNode:root_node]; } } @@ -51,6 +56,7 @@ void NativeRenderManager::UpdateRenderNode(std::weak_ptr root_node, std::vector>&& nodes) { @autoreleasepool { + HPAssert(renderImpl_, @"renderImpl_ is null, did you forget to call Initialize()?"); [renderImpl_ updateRenderNodes:std::move(nodes) onRootNode:root_node]; } } @@ -58,6 +64,7 @@ void NativeRenderManager::DeleteRenderNode(std::weak_ptr root_node, std::vector>&& nodes) { @autoreleasepool { + HPAssert(renderImpl_, @"renderImpl_ is null, did you forget to call Initialize()?"); [renderImpl_ deleteRenderNodesIds:std::move(nodes) onRootNode:root_node]; } } @@ -65,6 +72,7 @@ void NativeRenderManager::UpdateLayout(std::weak_ptr root_node, const std::vector>& nodes) { @autoreleasepool { + HPAssert(renderImpl_, @"renderImpl_ is null, did you forget to call Initialize()?"); using DomNodeUpdateInfoTuple = std::tuple; std::vector nodes_infos; nodes_infos.reserve(nodes.size()); @@ -84,6 +92,7 @@ int32_t to_pid, int32_t index) { @autoreleasepool { + HPAssert(renderImpl_, @"renderImpl_ is null, did you forget to call Initialize()?"); [renderImpl_ renderMoveViews:std::move(moved_ids) fromContainer:from_pid toContainer:to_pid @@ -95,12 +104,14 @@ void NativeRenderManager::MoveRenderNode(std::weak_ptr root_node, std::vector>&& nodes) { @autoreleasepool { + HPAssert(renderImpl_, @"renderImpl_ is null, did you forget to call Initialize()?"); [renderImpl_ renderMoveNodes:std::move(nodes) onRootNode:root_node]; } } void NativeRenderManager::EndBatch(std::weak_ptr root_node) { @autoreleasepool { + HPAssert(renderImpl_, @"renderImpl_ is null, did you forget to call Initialize()?"); [renderImpl_ batchOnRootNode:root_node]; } } @@ -113,6 +124,7 @@ std::weak_ptr dom_node, const std::string& name) { @autoreleasepool { + HPAssert(renderImpl_, @"renderImpl_ is null, did you forget to call Initialize()?"); auto node = dom_node.lock(); if (node) { int32_t tag = node->GetId(); @@ -125,6 +137,7 @@ std::weak_ptr dom_node, const std::string &name) { @autoreleasepool { + HPAssert(renderImpl_, @"renderImpl_ is null, did you forget to call Initialize()?"); auto node = dom_node.lock(); if (node) { int32_t node_id = node->GetId(); @@ -139,6 +152,7 @@ const DomArgument& param, uint32_t cb) { @autoreleasepool { + HPAssert(renderImpl_, @"renderImpl_ is null, did you forget to call Initialize()?"); std::shared_ptr node = dom_node.lock(); if (node) { HippyValue dom_value; @@ -153,58 +167,73 @@ void NativeRenderManager::RegisterExtraComponent(NSArray *extraComponents) { @autoreleasepool { + HPAssert(renderImpl_, @"renderImpl_ is null, did you forget to call Initialize()?"); [renderImpl_ registerExtraComponent:extraComponents]; } } void NativeRenderManager::RegisterRootView(UIView *view, std::weak_ptr root_node) { @autoreleasepool { + HPAssert(renderImpl_, @"renderImpl_ is null, did you forget to call Initialize()?"); [renderImpl_ registerRootView:view asRootNode:root_node]; } } void NativeRenderManager::UnregisterRootView(uint32_t id) { @autoreleasepool { + HPAssert(renderImpl_, @"renderImpl_ is null, did you forget to call Initialize()?"); [renderImpl_ unregisterRootViewFromTag:@(id)]; } } NSArray *NativeRenderManager::rootViews() { @autoreleasepool { + HPAssert(renderImpl_, @"renderImpl_ is null, did you forget to call Initialize()?"); return [renderImpl_ rootViews]; } } void NativeRenderManager::SetDomManager(std::weak_ptr dom_manager) { @autoreleasepool { + HPAssert(renderImpl_, @"renderImpl_ is null, did you forget to call Initialize()?"); [renderImpl_ setDomManager:dom_manager]; } } void NativeRenderManager::SetUICreationLazilyEnabled(bool enabled) { + HPAssert(renderImpl_, @"renderImpl_ is null, did you forget to call Initialize()?"); renderImpl_.uiCreationLazilyEnabled = enabled; } void NativeRenderManager::AddImageProviderClass(Class cls) { @autoreleasepool { + HPAssert(renderImpl_, @"renderImpl_ is null, did you forget to call Initialize()?"); [renderImpl_ addImageProviderClass:cls]; } } NSArray> *NativeRenderManager::GetImageProviderClasses() { @autoreleasepool { + HPAssert(renderImpl_, @"renderImpl_ is null, did you forget to call Initialize()?"); return [renderImpl_ imageProviderClasses]; } } void NativeRenderManager::SetVFSUriLoader(std::shared_ptr loader) { - renderImpl_.VFSUriLoader = loader; + @autoreleasepool { + HPAssert(renderImpl_, @"renderImpl_ is null, did you forget to call Initialize()?"); + renderImpl_.VFSUriLoader = loader; + } } void NativeRenderManager::SetRootViewSizeChangedEvent(std::function cb) { [renderImpl_ setRootViewSizeChangedEvent:cb]; } +NativeRenderImpl *NativeRenderManager::GetNativeRenderImpl() { + return renderImpl_; +} + NativeRenderManager::~NativeRenderManager() { [renderImpl_ invalidate]; renderImpl_ = nil; diff --git a/renderer/native/ios/renderer/NativeRenderRootView.h b/renderer/native/ios/renderer/NativeRenderRootView.h index a33c537d099..4d99fc259b3 100644 --- a/renderer/native/ios/renderer/NativeRenderRootView.h +++ b/renderer/native/ios/renderer/NativeRenderRootView.h @@ -23,13 +23,6 @@ #import #import "HPInvalidating.h" -/** - * This notification is sent when the first subviews are added to the root view - * after the application has loaded. This is used to hide the `loadingView`, and - * is a good indicator that the application is ready to use. - */ -extern NSString *const NativeRenderContentDidAppearNotification; - /** * Native view used to host Hippy-managed views within the app. Can be used just * like any ordinary UIView. You can have multiple HippyRootViews on screen at @@ -42,7 +35,7 @@ extern NSString *const NativeRenderContentDidAppearNotification; */ @property (nonatomic, weak) UIViewController *nativeRenderViewController; -- (void)contentDidAppear:(NSUInteger)cost; +- (void)contentDidAppear; /** * Timings for hiding the loading view after the content has loaded. Both of diff --git a/renderer/native/ios/renderer/NativeRenderRootView.m b/renderer/native/ios/renderer/NativeRenderRootView.m index 5e2e8d5a025..871ad22a64a 100644 --- a/renderer/native/ios/renderer/NativeRenderRootView.m +++ b/renderer/native/ios/renderer/NativeRenderRootView.m @@ -24,11 +24,10 @@ #import "HPAsserts.h" #import "NativeRenderView.h" #import "UIView+NativeRender.h" +#import "NativeRenderDefines.h" #include -NSString *const NativeRenderContentDidAppearNotification = @"NativeRenderContentDidAppearNotification"; - NSNumber *AllocRootViewTag(void) { static NSString * const token = @"allocateRootTag"; @synchronized (token) { @@ -38,7 +37,6 @@ } @interface NativeRenderRootView () { - CFTimeInterval _cost; BOOL _contentHasAppeared; } @@ -48,26 +46,17 @@ @interface NativeRenderRootView () { @implementation NativeRenderRootView -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - _cost = CACurrentMediaTime() * 1000.f; - } - return self; -} - - (UIViewController *)nativeRenderViewController { return _nativeRenderViewController?:[super nativeRenderViewController]; } - (void)insertNativeRenderSubview:(UIView *)subview atIndex:(NSInteger)atIndex { [super insertNativeRenderSubview:subview atIndex:atIndex]; - CFTimeInterval cost = CACurrentMediaTime() * 1000.f; - CFTimeInterval diff = cost - _cost; if (!_contentHasAppeared) { _contentHasAppeared = YES; - [[NSNotificationCenter defaultCenter] postNotificationName:NativeRenderContentDidAppearNotification object:self userInfo:@{ - @"cost": @(diff) + [self contentDidAppear]; + [[NSNotificationCenter defaultCenter] postNotificationName:kRootViewDidAddContent object:nil userInfo:@{ + kRootViewKey: self }]; } } @@ -80,7 +69,7 @@ - (NSNumber *)componentTag { return super.componentTag; } -- (void)contentDidAppear:(__unused NSUInteger)cost { +- (void)contentDidAppear { } - (void)dealloc diff --git a/renderer/native/ios/renderer/UIView+Render.h b/renderer/native/ios/renderer/UIView+Render.h index 67ead91ace4..a32859744dc 100644 --- a/renderer/native/ios/renderer/UIView+Render.h +++ b/renderer/native/ios/renderer/UIView+Render.h @@ -28,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN @interface UIView (Render) -@property(nonatomic, weak)NativeRenderImpl *renderImpl; +- (NativeRenderImpl *)renderImpl; @end diff --git a/renderer/native/ios/renderer/UIView+Render.mm b/renderer/native/ios/renderer/UIView+Render.mm index dc0acf85382..e9549d58729 100644 --- a/renderer/native/ios/renderer/UIView+Render.mm +++ b/renderer/native/ios/renderer/UIView+Render.mm @@ -22,25 +22,20 @@ #import "UIView+Render.h" #import "NativeRenderImpl.h" +#import "NSObject+Render.h" +#import "NativeRenderManager.h" #include @implementation UIView (Render) -- (void)setRenderImpl:(NativeRenderImpl *)renderContext { - if (renderContext) { - NSHashTable *weakContainer = [NSHashTable weakObjectsHashTable]; - [weakContainer addObject:renderContext]; - objc_setAssociatedObject(self, @selector(renderImpl), weakContainer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - } - else { - objc_setAssociatedObject(self, @selector(renderImpl), nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - } -} - - (NativeRenderImpl *)renderImpl { - NSHashTable *hashTable = objc_getAssociatedObject(self, _cmd); - return [hashTable anyObject]; + auto renderManager = [self renderManager].lock(); + if (renderManager) { + auto nativeRenderManager = std::static_pointer_cast(renderManager); + return nativeRenderManager->GetNativeRenderImpl(); + } + return nil; } @end diff --git a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderObjectWaterfall.mm b/renderer/native/ios/renderer/component/waterfalllist/NativeRenderObjectWaterfall.mm index 98ac7e90487..504f6ba7d1f 100644 --- a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderObjectWaterfall.mm +++ b/renderer/native/ios/renderer/component/waterfalllist/NativeRenderObjectWaterfall.mm @@ -26,6 +26,8 @@ @implementation NativeRenderObjectWaterfall +static NSTimeInterval kWaterfallViewDelayTime = .1f; + - (void)amendLayoutBeforeMount:(NSMutableSet *)blocks { if ([self isPropagationDirty]) { __weak NativeRenderObjectWaterfall *weakSelf = self; @@ -37,7 +39,8 @@ - (void)amendLayoutBeforeMount:(NSMutableSet *)blocks NativeRenderWaterfallView *view = (NativeRenderWaterfallView *)[viewRegistry objectForKey:[strongSelf componentTag]]; HPAssert([view isKindOfClass:[NativeRenderWaterfallView class]], @"view must be kind of NativeRenderWaterfallView"); if ([view isKindOfClass:[NativeRenderWaterfallView class]]) { - [view reloadData]; + [NSObject cancelPreviousPerformRequestsWithTarget:view selector:@selector(reloadData) object:nil]; + [view performSelector:@selector(reloadData) withObject:nil afterDelay:kWaterfallViewDelayTime]; } }; [blocks addObject:block];