diff --git a/.claude/commands/Q.md b/.claude/commands/Q.md new file mode 100644 index 0000000000..d656761a9a --- /dev/null +++ b/.claude/commands/Q.md @@ -0,0 +1,66 @@ +--- +description: MCP-first cross-validated code intelligence across dependency graphs +argument-hint: [query] [optional:scope] [optional:mode] [optional:server=] [optional:prompt=] +allowed-tools: Bash(rg:*), Bash(git grep:*), Bash(git ls-files:*), Bash(git rev-parse:*) +--- + +# MCP-first code search and analysis (cross-validated) + +Use this command to answer code/navigation/impact questions by prioritizing MCP server tools over default editor tooling, then cross-validating with text/LSP search to increase precision and recall. + +Inputs +- Query: `$ARGUMENTS` +- Optional scope hint (directory/module/package) +- Optional mode: `search | navigate | impact | arch` +- Optional server: `server=` (normalized MCP server name from `/mcp`) +- Optional prompt: `prompt=` (specific MCP tool/prompt to prefer) + +Execution protocol +1) Discover and prioritize MCP tools + - If `server=` is provided, restrict MCP calls to that server; otherwise, iterate through all connected servers exposing dependency graph tools and prefer those indexing libraries/packages referenced by this workspace. + - Prefer these tools (by intent): + - Search/navigation: `search_graph`, `get_node_by_name`, `find_references`, `find_definition`, `get_dependencies`, `get_dependents`, `get_call_chain` + - Impact/quality: `analyze_impact`, `analyze_code_smells`, `analyze_circular_dependencies`, `analyze_architectural_layers`, `get_module_metrics`, `analyze_external_dependencies` + - Similarity/patterns: `find_similar_nodes`, `find_similar_implementations`, `search_by_pattern` + - Use narrow scope if provided; otherwise start broad, then refine by directories/languages as indicated by early results. + - If `prompt=` is provided, prefer calling that MCP tool/prompt first when applicable. + +2) Cross-validate with default/editor tools + - Use IDE symbol search/LSP capabilities to corroborate MCP findings (definitions, references, symbols). + - Run a conservative ripgrep for the query and any top MCP-derived identifiers to catch non-indexed text: + - !`rg -n --hidden --no-ignore -S "$ARGUMENTS" | head -200` + - If the query looks like a symbol/path, also search likely variants (case, suffix/prefix, interface/impl patterns) inferred from MCP results. + +3) Merge, rank, and reconcile + - Deduplicate by canonical node/file identity; prefer MCP graph semantics over plain-text hits. + - Rank by: + - Direct MCP matches (exact node/file) > MCP structural proximity (deps/dependents/call chains) > editor/LSP > raw text hits. + - Flag disagreements (present in text-only, absent in MCP) as potential extraction gaps or dead code. + +4) Produce decision-ready output + - Summary: 1–3 bullets with the most relevant findings and next action. + - Evidence (MCP): key nodes, relationships, call chains, impact sets, and metrics. + - Evidence (default): LSP/grep confirmations and any unique findings not in MCP. + - Cross-validation: overlaps, conflicts, and confidence score. + - If mode=impact, include affected files/modules and suggested test focus; if mode=arch, include layer/cycle findings. + +Heuristics +- Prefer MCP graph relationships over text proximity when signals conflict. +- When MCP tools return no results, expand scope and switch to text-first, then attempt to map text hits back to MCP nodes. +- For library/package queries not in the local workspace, query MCP servers that index those artifacts first; only then fall back to local search. + +Response format (concise) +- Summary +- Findings (ranked list with brief rationale) +- Cross-validation (MCP vs default) +- Next steps + +Notes +- This command assumes MCP prompts/tools are available via connected servers and will be invoked before editor defaults. +- Keep output focused; surface only the top-most actionable results, link deeper evidence on demand. + +Examples +- `/Q "retry policy backoff" scope=shared/utils mode=search server=depgraph-mcp` +- `/Q "transitive deps of FooService" mode=navigate server=depgraph-mcp prompt=get_dependencies` +- To discover server names and prompts exposed by MCP servers, use `/mcp` and look for commands like `/mcp____`. + diff --git a/.claude/commands/reflect.md b/.claude/commands/reflect.md new file mode 100644 index 0000000000..a2309682bb --- /dev/null +++ b/.claude/commands/reflect.md @@ -0,0 +1,49 @@ +You are an expert in prompt engineering, specializing in optimizing AI code assistant instructions. Your task is to analyze and improve the instructions for Claude Code found in u/CLAUDE.md. Follow these steps carefully: + +1. Analysis Phase: +Review the chat history in your context window. + +Then, examine the current Claude instructions: + +u/CLAUDE.md + + +Analyze the chat history and instructions to identify areas that could be improved. Look for: +- Inconsistencies in Claude's responses +- Misunderstandings of user requests +- Areas where Claude could provide more detailed or accurate information +- Opportunities to enhance Claude's ability to handle specific types of queries or tasks + +2. Interaction Phase: +Present your findings and improvement ideas to the human. For each suggestion: +a) Explain the current issue you've identified +b) Propose a specific change or addition to the instructions +c) Describe how this change would improve Claude's performance + +Wait for feedback from the human on each suggestion before proceeding. If the human approves a change, move it to the implementation phase. If not, refine your suggestion or move on to the next idea. + +3. Implementation Phase: +For each approved change: +a) Clearly state the section of the instructions you're modifying +b) Present the new or modified text for that section +c) Explain how this change addresses the issue identified in the analysis phase + +4. Output Format: +Present your final output in the following structure: + + +[List the issues identified and potential improvements] + + + +[For each approved improvement: +1. Section being modified +2. New or modified instruction text +3. Explanation of how this addresses the identified issue] + + + +[Present the complete, updated set of instructions for Claude, incorporating all approved changes] + + +Remember, your goal is to enhance Claude's performance and consistency while maintaining the core functionality and purpose of the AI assistant. Be thorough in your analysis, clear in your explanations, and precise in your implementations. diff --git a/.claude/skills b/.claude/skills new file mode 120000 index 0000000000..42c5394a18 --- /dev/null +++ b/.claude/skills @@ -0,0 +1 @@ +../skills \ No newline at end of file diff --git a/.codex/skills b/.codex/skills new file mode 120000 index 0000000000..42c5394a18 --- /dev/null +++ b/.codex/skills @@ -0,0 +1 @@ +../skills \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..24668a752a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,44 @@ +# Set default line ending behavior +* text=auto +# Force LF line endings for text files +*.js text eol=lf +*.ts text eol=lf linguist-detectable=false +*.jsx text eol=lf +*.tsx text eol=lf +*.json text eol=lf +*.md text eol=lf +*.yml text eol=lf +*.yaml text eol=lf +*.txt text eol=lf +*.sh text eol=lf +*.cc text eol=lf +*.cpp text eol=lf +*.c text eol=lf +*.h text eol=lf +*.hpp text eol=lf +*.dart text eol=lf +*.gradle text eol=lf +*.xml text eol=lf +*.html text eol=lf +*.css text eol=lf +# Keep Windows-specific files with CRLF +*.bat text eol=crlf +*.cmd text eol=crlf +# Binary files should not be modified +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.pdf binary +*.zip binary +*.tar.gz binary +*.so binary +*.dll binary +*.dylib binary +*.exe binary +# Debug symbols for Git LFS +debug-symbols/**/*.pdb filter=lfs diff=lfs merge=lfs -text +debug-symbols/**/*.debug filter=lfs diff=lfs merge=lfs -text +debug-symbols/**/*.dSYM filter=lfs diff=lfs merge=lfs -text +debug-symbols/**/*.7z filter=lfs diff=lfs merge=lfs -text diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 2a6e12cdbd..82121975b4 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,66 +1,134 @@ # OpenWebf Contributing Guide -0. Prerequisites - * [Node.js](https://nodejs.org/) v12.0 or later - * [Flutter](https://flutter.dev/docs/get-started/install) version in the `webf/pubspec.yaml` - * [CMake](https://cmake.org/) v3.10.0 or later - * [Xcode](https://developer.apple.com/xcode/) (10.12) or later (Running on macOS or iOS) - * [Android NDK](https://developer.android.com/studio/projects/install-ndk) version `22.1.7171670` (Running on Android) +## Prerequisites -1. Install - ```shell - $ npm install - ``` +**Install gperf** -2. Building bridge +**macOS** +```bash +brew install gperf +``` - Building bridge for all supported platform (macOS, linux, iOS, Android) - - > Debug is the default build type, if you want to have a release build, please add `:release` after your command. - > - > Exp: Execute `npm run build:bridge:macos:release` to build a release bridge for the macOS platform. +**Linux** +```bash +apt-get install gperf +``` - ```shell - $ npm run build:bridge:all - ``` +* [Node.js](https://nodejs.org/) v12.0 or later +* [Flutter](https://flutter.dev/docs/get-started/install) version in the `webf/pubspec.yaml` +* [CMake](https://cmake.org/) v3.12.0 or later +* [Xcode](https://developer.apple.com/xcode/) (10.12) or later (Running on macOS or iOS) +* [Android NDK](https://developer.android.com/studio/projects/install-ndk) version `22.1.7171670` (Running on Android) +* [Visual Studio 2019 or later](https://visualstudio.microsoft.com/) (Running on Windows) +* [Rust](https://www.rust-lang.org/) (For building Rust example apps.) +* [MSYS2](https://www.msys2.org/) (For building apps for Windows) - Building bridge for one platform - **macOS** +## Config environment for Windows - ```shell - $ npm run build:bridge:macos - ``` +1. Install MSYS2 +2. Run the following commands to install depdendencies - **linux** +``` +pacman -S mingw-w64-ucrt-x86_64-clang +pacman -S mingw-w64-ucrt-x86_64-cmake +pacman -S mingw-w64-ucrt-x86_64-ninja +pacman -S mingw-w64-clang-x86_64-icu - ```shell - $ npm run build:bridge:linux - ``` +``` +3. Run the npm run scripts to compile the bridge in ucrt environment. +4. Run flutter in powershell(not in msys2 ucrt environment). - **iOS** +## Get the code: - ```shell - $ npm run build:bridge:ios - ``` +**Additional configuration for Windows users** - **Android** +``` +git config --global core.symlinks true +git config --global core.autocrlf false +``` - > For Windows users, make sure that running this command under MINGW64 environment(eg. Git Bash). +----- - ```shell - $ npm run build:bridge:android - ``` +Running the following commands to get the code: -3. Start example - ```shell - $ cd webf/example - $ flutter run - ``` +``` +git clone git@github.com:openwebf/webf.git +git submodule update --init --recursive +``` -4. Test (Unit Test and Integration Test) - ```shell - $ npm test - ``` +## Install +```shell +$ npm install +``` + +## Prepare + +**Windows, Linux, Android** + +The current C/C++ code build process has been integrated into Flutter's compilation and build pipeline for Windows, Linux, and Android. + +Run the following script to generate C/C++ binding code using the code generator: + +```shell +npm run generate_binding_code +``` + +--- + +**iOS and macOS** + +> The default build type is Debug. To create a release build, add `:release` to your command. +> +> Example: Execute `npm run build:bridge:macos:release` to build a release bridge for macOS. + +```shell +$ npm run build:bridge:ios:release # iOS +$ npm run build:bridge:macos:release # macOS +``` + +--- + + +### Run Example + +```shell +$ cd webf/example +$ flutter run -d +``` + +## Run integration Test + +```shell +cd integration_tests +npm run integration +``` + +### Run specific group of test specs in integration test + +To run specify groups of test specs: + +```shell +SPEC_SCOPE=DOM npm run integration // match pattern is located in `spec_group.json` +``` + +### Run integration test without build test apps + +> Quicker start up if you changed the test specs only. + +```shell +npm run integration -- --skip-build +``` + +### Run one test spec only + +Change the `it` into `fit` to running this test spec only. + +```typescript +fit('document.all', () => { + expect(document.all).not.toBeUndefined(); + expect(document.all.length).toBeGreaterThan(0); +}); +``` diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..d95a2b40c3 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +# These are supported funding model platforms +github: [openwebf] diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 70abd2fda2..5a3208cbdb 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -10,6 +10,13 @@ body: label: Affected version description: Please specify which version of webf you are using. placeholder: 0.12.0 or main + - type: input + validations: + required: true + attributes: + label: Flutter versions + description: Please specify which version of flutter you are using. + placeholder: 3.13.5 - type: checkboxes validations: diff --git a/.github/workflows/build_checks.yml b/.github/workflows/build_checks.yml new file mode 100644 index 0000000000..94edb5c885 --- /dev/null +++ b/.github/workflows/build_checks.yml @@ -0,0 +1,133 @@ +name: Build Checks + +on: + pull_request: + push: + branches: + - main + - 'release/**' + +env: + nodeVersion: "22" + cmakeVersion: "4.0.3" + flutter: "3.29.0" + +jobs: + build-android-bridge: + name: Build Android Bridge + runs-on: [self-hosted, macos] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + + - name: Setup CMake + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: ${{ env.cmakeVersion }} + + - name: Setup Android SDK and NDK + uses: nttld/setup-ndk@v1 + id: setup-ndk + with: + ndk-version: r27d + env: + ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} + + - name: Setup build dependencies + run: | + # Check if build tools are already installed on self-hosted macOS runner + which ninja || brew install ninja + which pkg-config || brew install pkg-config + + - name: Verify toolchain + run: | + which clang + clang --version + cmake --version + ninja --version + echo "Android SDK: $ANDROID_HOME" + echo "Android NDK: ${{ steps.setup-ndk.outputs.ndk-path }}" + + - name: Install Node.js dependencies + run: npm ci + + - name: Generate binding code + run: npm run bindgen + + - name: Build Android bridge + env: + ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} + ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }} + WEBF_JS_ENGINE: quickjs + run: | + echo "🔨 Building Android bridge..." + echo "Using NDK: $ANDROID_NDK_HOME" + npm run build:bridge:android + + - name: Verify build outputs + run: | + echo "📋 Checking Android build outputs..." + if [ -d "bridge/build/android" ]; then + echo "✅ Android build directory exists" + find bridge/build/android -name "*.so" -o -name "*.a" | head -20 + else + echo "❌ Android build directory not found" + exit 1 + fi + + build-ios-example: + name: Build iOS Example + runs-on: [self-hosted, macos] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: ${{ env.flutter }} + channel: 'stable' + + - name: Verify toolchain + run: | + xcodebuild -version + flutter --version + pod --version + + - name: Install Node.js dependencies + run: npm ci + + - name: Generate binding code + run: npm run bindgen + + - name: Get Flutter dependencies + run: | + cd webf/example + flutter pub get + + - name: Build iOS example (no codesign) + run: | + cd webf/example + echo "🔨 Building iOS example..." + flutter build ios --no-codesign --debug + env: + FLUTTER_BUILD_MODE: debug + + - name: Verify build outputs + run: | + echo "📋 Checking iOS example build outputs..." + if [ -d "webf/example/build/ios" ]; then + echo "✅ iOS build directory exists" + ls -la webf/example/build/ios/ + else + echo "❌ iOS build directory not found" + exit 1 + fi diff --git a/.github/workflows/build_windows_msys2.yml b/.github/workflows/build_windows_msys2.yml new file mode 100644 index 0000000000..8b2b80fa0e --- /dev/null +++ b/.github/workflows/build_windows_msys2.yml @@ -0,0 +1,141 @@ +name: Build and Testing Windows + +on: + push: + branches: [ main, develop, 'fix/*', 'feature/*', "release/*" ] + pull_request: + branches: [ main, develop, "release/*" ] + workflow_dispatch: + +env: + nodeVersion: "22" + cmakeVersion: "4.0.2" + +jobs: + build_windows_bridge: + runs-on: windows-latest + strategy: + matrix: + build_mode: [Debug] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup MSYS2 + uses: msys2/setup-msys2@v2 + with: + msystem: UCRT64 + update: true + install: >- + mingw-w64-ucrt-x86_64-toolchain + mingw-w64-ucrt-x86_64-clang + mingw-w64-ucrt-x86_64-clang-tools-extra + mingw-w64-ucrt-x86_64-libc++ + mingw-w64-ucrt-x86_64-cmake + mingw-w64-ucrt-x86_64-ninja + mingw-w64-ucrt-x86_64-pkgconf + mingw-w64-ucrt-x86_64-gperf + mingw-w64-ucrt-x86_64-icu + mingw-w64-ucrt-x86_64-libiconv + mingw-w64-ucrt-x86_64-winpthreads + git + make + curl + unzip + + - name: Add MSYS2 to PATH + shell: bash + run: | + echo "C:/msys64/ucrt64/bin" >> $GITHUB_PATH + echo "C:/msys64/usr/bin" >> $GITHUB_PATH + + - name: Install NVM and Node.js + shell: msys2 {0} + run: | + # Unset npm_config_prefix to avoid NVM conflicts + unset npm_config_prefix + + # Install NVM + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash + + # Source nvm + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + + # Install and use Node.js + nvm install ${{ env.nodeVersion }} + nvm use ${{ env.nodeVersion }} + nvm alias default ${{ env.nodeVersion }} + + # Verify installation + node --version + npm --version + + - name: Verify toolchain + shell: bash + run: | + which clang + which clang++ + clang --version + cmake --version + ninja --version + + - name: Install Node.js dependencies + shell: msys2 {0} + run: | + unset npm_config_prefix + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + npm install + + - name: Generate binding code + shell: msys2 {0} + run: | + unset npm_config_prefix + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + npm run bindgen + + - name: Build WebF Bridge + shell: msys2 {0} + run: | + unset npm_config_prefix + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + export CC=clang + export CXX=clang++ + export WEBF_BUILD=${{ matrix.build_mode }} + export WEBF_JS_ENGINE=quickjs + # Set MSYS2 paths for CMake - use Windows-style paths for CMake + export MSYSTEM_PREFIX="D:/a/_temp/msys64/ucrt64" + export MINGW_64="D:/a/_temp/msys64/ucrt64" + + echo "Using MSYSTEM_PREFIX: $MSYSTEM_PREFIX" + echo "Using MINGW_64: $MINGW_64" + + # Verify MSYS2 installation path + ls -la /d/a/_temp/msys64/ucrt64/bin/ || echo "MSYS2 path not found, checking alternatives..." + + npm run build:bridge:windows:release + env: + WEBF_BUILD: ${{ matrix.build_mode }} + WEBF_JS_ENGINE: quickjs + + - name: Run Bridge Unit Tests + shell: powershell + run: | + node scripts/run_bridge_unit_test.js + + - name: Upload DLL files and executables + uses: actions/upload-artifact@v4 + with: + name: windows-binaries-${{ matrix.build_mode }} + path: | + bridge/build/windows/lib/bin/*.dll + bridge/build/windows/lib/bin/webf_unit_test.exe + webf/windows/*.dll + retention-days: 30 + if-no-files-found: warn \ No newline at end of file diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml new file mode 100644 index 0000000000..88a457c3f9 --- /dev/null +++ b/.github/workflows/claude.yml @@ -0,0 +1,26 @@ +name: Claude Assistant +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + +jobs: + claude-response: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - uses: anthropics/claude-code-action@beta + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + github_token: ${{ secrets.GITHUB_TOKEN }} + # Optional: add custom trigger phrase (default: @claude) + # trigger_phrase: "/claude" + # Optional: add assignee trigger for issues + # assignee_trigger: "claude" diff --git a/.github/workflows/code_linter.yml b/.github/workflows/code_linter.yml index e3808bdd31..8a9858cd5d 100644 --- a/.github/workflows/code_linter.yml +++ b/.github/workflows/code_linter.yml @@ -8,10 +8,12 @@ on: jobs: cppcheck: - runs-on: macos-12 + runs-on: [self-hosted, macOS, ARM64] name: Cppcheck steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + with: + submodules: recursive - name: Run cppcheck run: | brew install cppcheck @@ -28,27 +30,29 @@ jobs: name: Find merge conflicts steps: # Checkout the source code so we have some files to look at. - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + with: + submodules: recursive # Run the actual merge conflict finder - name: Merge Conflict finder uses: olivernybroe/action-conflict-finder@v3.0 - reformat-bridge: - runs-on: ubuntu-latest + # reformat-bridge: + # runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: DoozyX/clang-format-lint-action@v0.13 - with: - source: './bridge' - exclude: './bridge/third_party/*' - extensions: 'h,cc,c' - clangFormatVersion: 12 - inplace: True - - uses: EndBug/add-and-commit@v4 - with: - author_name: openwebf-bot - author_email: openwebf@openwebf.com - message: 'Committing clang-format changes' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # steps: + # - uses: actions/checkout@v3 + # with: + # submodules: recursive + # - uses: DoozyX/clang-format-lint-action@v0.13 + # with: + # source: './bridge' + # exclude: './bridge/third_party/*' + # extensions: 'h,cc,c' + # clangFormatVersion: 12 + # inplace: True + # - uses: EndBug/add-and-commit@v9 + # with: + # default_author: github_actions + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml index c560266a11..222782a215 100644 --- a/.github/workflows/contributors.yml +++ b/.github/workflows/contributors.yml @@ -1,4 +1,6 @@ name: Create contributors +permissions: + contents: read on: [workflow_dispatch] @@ -6,7 +8,9 @@ jobs: create-contributors-svg: runs-on: ubuntu-18.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + with: + submodules: recursive - uses: actions/setup-node@v2 with: node-version: 14 diff --git a/.github/workflows/example_build.yml b/.github/workflows/example_build.yml deleted file mode 100644 index 950fb0ffec..0000000000 --- a/.github/workflows/example_build.yml +++ /dev/null @@ -1,120 +0,0 @@ -name: example build - -on: - push: - branches: [main] - pull_request: - branches: [main] - workflow_dispatch: - -env: - nodeVersion: "16" - cmakeVersion: "3.22.x" - flutterVersion: "3.3.10" - -jobs: - build_android-app_in_macos: - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - # Set up the bridge compilation environment - - uses: actions/setup-node@v2 - with: - node-version: ${{ env.nodeVersion }} - - uses: jwlawson/actions-setup-cmake@v1.11 - with: - cmake-version: ${{ env.cmakeVersion }} - - run: npm i - - run: npm run build:bridge:android:release - - # Install Flutter SDK - - uses: subosito/flutter-action@v2 - with: - flutter-version: ${{ env.flutterVersion }} - - run: flutter doctor -v - - - name: android app build - run: | - cd webf/ - flutter pub get - cd example/ - flutter build apk --release - - build_ios-app_in_macos: - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: ${{ env.nodeVersion }} - - uses: jwlawson/actions-setup-cmake@v1.11 - with: - cmake-version: ${{ env.cmakeVersion }} - - run: npm i - - run: npm run build:bridge:ios:release - - - uses: subosito/flutter-action@v2 - with: - flutter-version: ${{ env.flutterVersion }} - - run: flutter doctor -v - - - name: ios app build - run: | - cd webf/ - flutter pub get - cd example/ - flutter build ios --release --no-codesign - - build_macos-app_in_macos: - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: ${{ env.nodeVersion }} - - uses: jwlawson/actions-setup-cmake@v1.11 - with: - cmake-version: ${{ env.cmakeVersion }} - - run: npm i - - run: npm run build:bridge:macos:release - - - uses: subosito/flutter-action@v2 - with: - flutter-version: ${{ env.flutterVersion }} - - run: flutter config --enable-macos-desktop - - run: flutter doctor -v - - - name: macos app build - run: | - cd webf/ - flutter pub get - cd example/ - flutter build macos --release - - build_linux-app_in_ubuntu: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: ${{ env.nodeVersion }} - - uses: jwlawson/actions-setup-cmake@v1.11 - with: - cmake-version: ${{ env.cmakeVersion }} - - run: | - sudo apt-get update - sudo apt-get install chrpath ninja-build pkg-config libgtk-3-dev -y - - run: npm i - - run: npm run build:bridge:linux:release - - - uses: subosito/flutter-action@v2 - with: - flutter-version: ${{ env.flutterVersion }} - - run: flutter config --enable-linux-desktop - - run: flutter doctor -v - - name: linux app build - run: | - cd webf/ - flutter pub get - cd example/ - flutter build linux --release diff --git a/.github/workflows/integration_test_flutter.yml b/.github/workflows/integration_test_flutter.yml index c4dfbaa4f2..53eb27a7e8 100644 --- a/.github/workflows/integration_test_flutter.yml +++ b/.github/workflows/integration_test_flutter.yml @@ -4,141 +4,107 @@ on: [workflow_dispatch, pull_request] env: nodeVersion: "16" - cmakeVersion: "3.22.x" - flutter33: "3.3.10" - flutter37: "3.7.3" + cmakeVersion: "4.0.3" + flutter: "3.38.0" # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: setup: - runs-on: macos-12 + runs-on: [self-hosted, macOS, ARM64, Integration] outputs: matrix: ${{ steps.matrix.outputs.value }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + with: + submodules: recursive + - run: npm i - id: matrix run: | - JSON=$(node -e "console.log(JSON.stringify(require('./integration_tests/spec_group.json').map(j=>j.name)))") + JSON=$(node -e "console.log(JSON.stringify(require('json5').parse(fs.readFileSync('./integration_tests/spec_group.json5')).map(j=>j.name)))") echo "::set-output name=value::$(echo $JSON)" build_bridge: - runs-on: macos-12 + runs-on: [self-hosted, macOS, ARM64, Integration] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + with: + submodules: recursive - uses: actions/setup-node@v2 with: node-version: ${{ env.nodeVersion }} - - uses: jwlawson/actions-setup-cmake@v1.11 + - uses: jwlawson/actions-setup-cmake@v2 with: cmake-version: ${{ env.cmakeVersion }} - run: npm i + - run: brew install gperf - run: npm run build:bridge:macos - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 with: name: macos_bridge_binary path: bridge/build/macos/ bridge_unit_test: - runs-on: ubuntu-latest + runs-on: [self-hosted, macOS, ARM64, Integration] + needs: [ setup, build_bridge ] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + with: + submodules: recursive - uses: actions/setup-node@v2 with: node-version: ${{ env.nodeVersion }} - - uses: jwlawson/actions-setup-cmake@v1.11 + - uses: actions/download-artifact@v4 with: - cmake-version: ${{ env.cmakeVersion }} - - run: | - sudo apt-get update - sudo apt-get install chrpath ninja-build pkg-config -y + name: macos_bridge_binary + path: bridge/build/macos/ - run: npm i - - run: ENABLE_ASAN=true npm run build:bridge:linux + - run: chmod +x ./bridge/build/macos/lib/universal/webf_unit_test - run: node scripts/run_bridge_unit_test.js - webf_unit_test_f33: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: subosito/flutter-action@v2 - with: - flutter-version: ${{ env.flutter33 }} - - run: flutter config --enable-macos-desktop - - run: flutter doctor -v - - run: cd webf && flutter test - - webf_unit_test_f37: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: subosito/flutter-action@v2 - with: - flutter-version: ${{ env.flutter37 }} - - run: flutter config --enable-macos-desktop - - run: flutter doctor -v - - run: cd webf && flutter test - - integration_test_f33: - runs-on: macos-12 + webf_unit_test: + runs-on: [self-hosted, macOS, ARM64, Integration] needs: [ setup, build_bridge ] - strategy: - fail-fast: false - matrix: - value: ${{fromJson(needs.setup.outputs.matrix)}} steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 with: - node-version: ${{ env.nodeVersion }} + submodules: recursive - uses: subosito/flutter-action@v2 with: - flutter-version: ${{ env.flutter33 }} + flutter-version: ${{ env.flutter }} - run: flutter config --enable-macos-desktop - - run: flutter doctor -v - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4 with: name: macos_bridge_binary path: bridge/build/macos/ - - run: cd integration_tests && npm i - - name: Run Test - run: cd integration_tests && SPEC_SCOPE="${{ matrix.value }}" npm run integration - id: test - continue-on-error: true - - uses: actions/upload-artifact@v2 - if: steps.test.outcome != 'success' - with: - name: integration_${{ matrix.value }}_snapshots - path: integration_tests/snapshots - - name: Check on failures - if: steps.test.outcome != 'success' - run: exit 1 + - run: cd webf && flutter test - integration_test_f37: - runs-on: self-hosted + integration_test: + runs-on: [self-hosted, macOS, ARM64, Integration] needs: [ setup, build_bridge ] strategy: fail-fast: false matrix: value: ${{fromJson(needs.setup.outputs.matrix)}} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + with: + submodules: recursive - uses: actions/setup-node@v2 with: node-version: ${{ env.nodeVersion }} - # self-hosted had bundled flutter 3.7.3 - # - uses: subosito/flutter-action@v2 - # with: - # flutter-version: ${{ env.flutter37 }} + - uses: subosito/flutter-action@v2 + with: + flutter-version: ${{ env.flutter }} - run: flutter config --enable-macos-desktop - - run: flutter doctor -v - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4 with: name: macos_bridge_binary path: bridge/build/macos/ - run: cd integration_tests && npm i - name: Run Test - run: cd integration_tests && SPEC_SCOPE="${{ matrix.value }}" npm run integration + run: cd integration_tests && CI=true SPEC_SCOPE="${{ matrix.value }}" npm run integration id: test continue-on-error: true - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: steps.test.outcome != 'success' with: name: integration_${{ matrix.value }}_snapshots @@ -146,55 +112,3 @@ jobs: - name: Check on failures if: steps.test.outcome != 'success' run: exit 1 - plugin_test: - runs-on: macos-12 - needs: [ build_bridge ] - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: ${{ env.nodeVersion }} - - uses: actions/download-artifact@v2 - with: - name: macos_bridge_binary - path: bridge/build/macos/ - - uses: subosito/flutter-action@v2 - with: - flutter-version: ${{ env.flutter33 }} - - run: flutter config --enable-macos-desktop - - run: flutter doctor -v - - name: Run Plugin Test - run: cd integration_tests && npm run plugin_test - id: test - continue-on-error: true - - uses: actions/upload-artifact@v2 - with: - name: plugin_snapshots - path: integration_tests/snapshots/plugins - - name: Check on failures - if: steps.test.outcome != 'success' - run: exit 1 - multiple_page_test: - runs-on: macos-12 - needs: [ build_bridge ] - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: ${{ env.nodeVersion }} - - uses: actions/download-artifact@v2 - with: - name: macos_bridge_binary - path: bridge/build/macos/ - - uses: subosito/flutter-action@v2 - with: - flutter-version: ${{ env.flutter33 }} - - run: flutter config --enable-macos-desktop - - run: flutter doctor -v - - name: Run MultiplePageTest Test - run: cd integration_tests && npm run multiple_page_test - id: test - continue-on-error: true - - name: Check on failures - if: steps.test.outcome != 'success' - run: exit 1 diff --git a/.github/workflows/publish_npm_packages.yml b/.github/workflows/publish_npm_packages.yml new file mode 100644 index 0000000000..071cb2846d --- /dev/null +++ b/.github/workflows/publish_npm_packages.yml @@ -0,0 +1,162 @@ +name: Publish NPM Packages + +on: + workflow_dispatch: + inputs: + packages: + description: "Comma-separated package paths (default: auto-discover packages/*)" + required: false + default: "" + type: string + access: + description: "npm access level (public or restricted)" + required: true + default: public + type: choice + options: + - public + - restricted + +concurrency: + group: publish-npm-packages + cancel-in-progress: false + +permissions: + contents: read + id-token: write + +jobs: + resolve-packages: + name: Resolve Packages + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.resolve.outputs.matrix }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Resolve package list + id: resolve + shell: bash + run: | + python3 - <<'PY' > packages.json + import glob + import json + import os + + raw = os.environ.get("INPUT_PACKAGES", "").strip() + packages = [] + + if raw: + for item in raw.split(","): + p = item.strip().rstrip("/") + if p: + packages.append(p) + else: + for pkg_json in sorted(glob.glob("packages/*/package.json")): + directory = os.path.dirname(pkg_json) + if "/node_modules/" in directory: + continue + packages.append(directory) + + seen = set() + packages = [p for p in packages if not (p in seen or seen.add(p))] + + if not packages: + raise SystemExit("No publishable packages found (set workflow input `packages` to override).") + + print(json.dumps({"include": [{"relativePath": p} for p in packages]})) + PY + + echo "Resolved packages:" + cat packages.json + echo "" + + MATRIX="$(cat packages.json)" + echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT" + env: + INPUT_PACKAGES: ${{ inputs.packages }} + + publish: + name: Publish + runs-on: ubuntu-latest + needs: [resolve-packages] + permissions: + contents: read + id-token: write + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.resolve-packages.outputs.matrix) }} + max-parallel: 1 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "lts/*" + check-latest: true + + - name: Upgrade npm (for OIDC Trusted Publishing) + run: npm i -g npm@latest + + - name: Validate package path + shell: bash + run: | + test -f "${{ matrix.relativePath }}/package.json" + echo "Publishing package at: ${{ matrix.relativePath }}" + + - name: Read package metadata + id: meta + working-directory: ${{ matrix.relativePath }} + shell: bash + run: | + python3 - <<'PY' >> "$GITHUB_OUTPUT" + import json + with open("package.json", "r", encoding="utf-8") as f: + pkg = json.load(f) + print(f"name={pkg.get('name', '')}") + print(f"version={pkg.get('version', '')}") + print(f"private={str(pkg.get('private', False)).lower()}") + PY + + - name: Validate package metadata + if: ${{ steps.meta.outputs.private != 'true' }} + shell: bash + run: | + test -n "${{ steps.meta.outputs.name }}" + test -n "${{ steps.meta.outputs.version }}" + + - name: Skip private package + if: ${{ steps.meta.outputs.private == 'true' }} + run: 'echo "Skipping private package: ${{ steps.meta.outputs.name }}"' + + - name: Check if version already published + if: ${{ steps.meta.outputs.private != 'true' }} + id: exists + shell: bash + run: | + set +e + npm view "${{ steps.meta.outputs.name }}@${{ steps.meta.outputs.version }}" version >/dev/null 2>&1 + if [ $? -eq 0 ]; then + echo "exists=true" >> "$GITHUB_OUTPUT" + echo "Already published: ${{ steps.meta.outputs.name }}@${{ steps.meta.outputs.version }}" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Install dependencies + if: ${{ steps.meta.outputs.private != 'true' && steps.exists.outputs.exists != 'true' }} + working-directory: ${{ matrix.relativePath }} + run: npm install + + - name: Build (if present) + if: ${{ steps.meta.outputs.private != 'true' && steps.exists.outputs.exists != 'true' }} + working-directory: ${{ matrix.relativePath }} + run: npm run build --if-present + + - name: Publish to npm (Trusted Publishing / OIDC) + if: ${{ steps.meta.outputs.private != 'true' && steps.exists.outputs.exists != 'true' }} + working-directory: ${{ matrix.relativePath }} + run: npm publish --provenance --access "${{ inputs.access }}" diff --git a/.github/workflows/publish_to_dart_dev.yml b/.github/workflows/publish_to_dart_dev.yml deleted file mode 100644 index b0eaa08048..0000000000 --- a/.github/workflows/publish_to_dart_dev.yml +++ /dev/null @@ -1,130 +0,0 @@ -name: Publish WebF To pub.dev - -on: - release: - types: [created] - workflow_dispatch: - -env: - nodeVersion: "16" - cmakeVersion: "3.22.x" - flutterVersion: "2.2.0" - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - build_linux_binary: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: "16" - - uses: jwlawson/actions-setup-cmake@v1.11 - with: - cmake-version: "3.22.x" - - run: | - sudo apt-get update - sudo apt-get install chrpath ninja-build pkg-config -y - - run: npm i - - run: npm run build:bridge:linux:release - - uses: actions/upload-artifact@v2 - with: - name: linux_binary - path: bridge/build/linux/ - build_macos_binary: - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: "16" - - uses: jwlawson/actions-setup-cmake@v1.11 - with: - cmake-version: ${{ env.cmakeVersion }} - - name: NPM INSTALL - run: npm install - - name: Build bridge binary - run: npm run build:bridge:macos:release - - uses: actions/upload-artifact@v2 - with: - name: macos_binary - path: bridge/build/macos/ - build_ios_binary: - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: "16" - - uses: jwlawson/actions-setup-cmake@v1.11 - with: - cmake-version: ${{ env.cmakeVersion }} - - name: NPM INSTALL - run: npm install - - name: Build bridge binary - run: npm run build:bridge:ios:release - - uses: actions/upload-artifact@v2 - with: - name: ios_binary - path: bridge/build/ios/ - build_android_binary: - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: "16" - - uses: nttld/setup-ndk@v1 - id: setup-ndk - with: - ndk-version: r22b - - uses: jwlawson/actions-setup-cmake@v1.11 - with: - cmake-version: ${{ env.cmakeVersion }} - - name: NPM INSTALL - run: npm install - - name: Build bridge binary - run: npm run build:bridge:android:release - env: - ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} - - uses: actions/upload-artifact@v2 - with: - name: android_binary - path: bridge/build/android/ - publish: - needs: [build_linux_binary, build_android_binary, build_ios_binary, build_macos_binary] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: subosito/flutter-action@v2 - with: - flutter-version: ${{ env.flutterVersion }} - - name: NPM INSTALL - run: npm install - - uses: actions/download-artifact@v2 - with: - name: linux_binary - path: bridge/build/linux/ - - uses: actions/download-artifact@v2 - with: - name: ios_binary - path: bridge/build/ios/ - - uses: actions/download-artifact@v2 - with: - name: macos_binary - path: bridge/build/macos/ - - uses: actions/download-artifact@v2 - with: - name: android_binary - path: bridge/build/android/ - - name: Prepare distribute binaries - run: node scripts/pre_publish_webf.js - - name: Publish - uses: k-paxian/dart-package-publisher@master - with: - credentialJson: ${{ secrets.CREDENTIAL_JSON }} - force: true - flutter: true - skipTests: true - relativePath: ./webf - diff --git a/.github/workflows/publish_to_dart_dev_nightly.yml b/.github/workflows/publish_to_dart_dev_nightly.yml deleted file mode 100644 index c086d1929a..0000000000 --- a/.github/workflows/publish_to_dart_dev_nightly.yml +++ /dev/null @@ -1,131 +0,0 @@ -name: Publish WebF To pub.dev Nightly - -on: - schedule: - # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule - # Run workflow every day at 21:00 - - cron: '0 0 * * 0' - workflow_dispatch: - -env: - nodeVersion: "16" - cmakeVersion: "3.22.x" - flutterVersion: "2.2.0" - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - build_linux_binary: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: "16" - - uses: jwlawson/actions-setup-cmake@v1.11 - with: - cmake-version: "3.22.x" - - - run: npm i - - run: npm run build:bridge:linux:release - - uses: actions/upload-artifact@v2 - with: - name: linux_binary - path: bridge/build/linux/ - build_macos_binary: - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: "16" - - uses: jwlawson/actions-setup-cmake@v1.11 - with: - cmake-version: ${{ env.cmakeVersion }} - - name: NPM INSTALL - run: npm install - - name: Build bridge binary - run: npm run build:bridge:macos:release - - uses: actions/upload-artifact@v2 - with: - name: macos_binary - path: bridge/build/macos/ - build_ios_binary: - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: "16" - - uses: jwlawson/actions-setup-cmake@v1.11 - with: - cmake-version: ${{ env.cmakeVersion }} - - name: NPM INSTALL - run: npm install - - name: Build bridge binary - run: npm run build:bridge:ios:release - - uses: actions/upload-artifact@v2 - with: - name: ios_binary - path: bridge/build/ios/ - build_android_binary: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: "16" - - uses: nttld/setup-ndk@v1 - id: setup-ndk - with: - ndk-version: r22b - env: - ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} - - uses: jwlawson/actions-setup-cmake@v1.11 - with: - cmake-version: ${{ env.cmakeVersion }} - - name: NPM INSTALL - run: npm install - - name: Build bridge binary - run: npm run build:bridge:android:release - - uses: actions/upload-artifact@v2 - with: - name: android_binary - path: bridge/build/android/ - publish: - needs: [build_linux_binary, build_android_binary, build_ios_binary, build_macos_binary] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: subosito/flutter-action@v2 - with: - flutter-version: ${{ env.flutterVersion }} - - name: NPM INSTALL - run: npm install - - name: Set up nightly version - run: node scripts/set_up_nightly_release.js - - uses: actions/download-artifact@v2 - with: - name: linux_binary - path: bridge/build/linux/ - - uses: actions/download-artifact@v2 - with: - name: ios_binary - path: bridge/build/ios/ - - uses: actions/download-artifact@v2 - with: - name: macos_binary - path: bridge/build/macos/ - - uses: actions/download-artifact@v2 - with: - name: android_binary - path: bridge/build/android/ - - name: Prepare distribute binaries - run: node scripts/pre_publish_kraken.js - - name: Publish - uses: k-paxian/dart-package-publisher@master - with: - credentialJson: ${{ secrets.CREDENTIAL_JSON }} - force: true - flutter: true - skipTests: true - relativePath: ./kraken diff --git a/.github/workflows/publish_webf_native_plugins.yml b/.github/workflows/publish_webf_native_plugins.yml new file mode 100644 index 0000000000..93b7a25244 --- /dev/null +++ b/.github/workflows/publish_webf_native_plugins.yml @@ -0,0 +1,130 @@ +name: Publish WebF Native Plugins + +on: + workflow_dispatch: + inputs: + packages: + description: "Comma-separated package paths (default: auto-discover native_uis/* + native_plugins/*)" + required: false + default: "" + type: string + dry_run: + description: "Run `flutter pub publish --dry-run` only" + required: true + default: true + type: boolean + force: + description: "Pass --force when publishing" + required: true + default: true + type: boolean + skip_tests: + description: "Skip tests in publisher action" + required: true + default: true + type: boolean + +concurrency: + group: publish-webf-native-plugins + cancel-in-progress: false + +permissions: + contents: read + +jobs: + resolve-packages: + name: Resolve Packages + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.resolve.outputs.matrix }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Resolve package list + id: resolve + shell: bash + run: | + python3 - <<'PY' > packages.json + import glob + import json + import os + + raw = os.environ.get("INPUT_PACKAGES", "").strip() + packages = [] + + if raw: + for item in raw.split(","): + p = item.strip().rstrip("/") + if p: + packages.append(p) + else: + # Auto-discover packages in native_uis/* + for pubspec in sorted(glob.glob("native_uis/*/pubspec.yaml")): + directory = os.path.dirname(pubspec) + if "/.dart_tool/" in directory: + continue + packages.append(directory) + + # Auto-discover packages in native_plugins/* + for pubspec in sorted(glob.glob("native_plugins/*/pubspec.yaml")): + directory = os.path.dirname(pubspec) + if "/.dart_tool/" in directory: + continue + packages.append(directory) + + # De-duplicate while preserving order + seen = set() + packages = [p for p in packages if not (p in seen or seen.add(p))] + + if not packages: + raise SystemExit("No publishable packages found (set workflow input `packages` to override).") + + print(json.dumps({"include": [{"relativePath": p} for p in packages]})) + PY + + echo "Resolved packages:" + cat packages.json + echo "" + + MATRIX="$(cat packages.json)" + echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT" + env: + INPUT_PACKAGES: ${{ inputs.packages }} + + publish: + name: Publish + runs-on: ubuntu-latest + needs: [resolve-packages] + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.resolve-packages.outputs.matrix) }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Validate package path + shell: bash + run: | + test -f "${{ matrix.relativePath }}/pubspec.yaml" + echo "Publishing package at: ${{ matrix.relativePath }}" + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + + - name: Pub publish (dry run) + if: ${{ inputs.dry_run }} + working-directory: ${{ matrix.relativePath }} + run: flutter pub publish --dry-run + + - name: Publish to pub.dev + if: ${{ !inputs.dry_run }} + uses: k-paxian/dart-package-publisher@master + with: + credentialJson: ${{ secrets.PUB_DEV_TOKEN }} + flutter: true + force: ${{ inputs.force }} + skipTests: ${{ inputs.skip_tests }} + relativePath: ${{ matrix.relativePath }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..85a678bde3 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,993 @@ +name: Release WebF Package + +# Trigger only when a new release is published +on: + workflow_dispatch: + +jobs: + # Build bridge binaries for each platform + build-windows: + name: Build Windows Bridge + runs-on: windows-latest + + env: + nodeVersion: "22" + cmakeVersion: "4.0.3" + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + + - name: Setup CMake + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: ${{ env.cmakeVersion }} + + - name: Setup MSYS2 + uses: msys2/setup-msys2@v2 + with: + msystem: UCRT64 + update: true + install: >- + mingw-w64-ucrt-x86_64-toolchain + mingw-w64-ucrt-x86_64-clang + mingw-w64-ucrt-x86_64-clang-tools-extra + mingw-w64-ucrt-x86_64-libc++ + mingw-w64-ucrt-x86_64-cmake + mingw-w64-ucrt-x86_64-ninja + mingw-w64-ucrt-x86_64-pkgconf + mingw-w64-ucrt-x86_64-gperf + mingw-w64-ucrt-x86_64-icu + mingw-w64-ucrt-x86_64-libiconv + mingw-w64-ucrt-x86_64-winpthreads + git + make + curl + unzip + + - name: Add MSYS2 to PATH + shell: bash + run: | + echo "C:/msys64/ucrt64/bin" >> $GITHUB_PATH + echo "C:/msys64/usr/bin" >> $GITHUB_PATH + + - name: Install NVM and Node.js + shell: msys2 {0} + run: | + # Unset npm_config_prefix to avoid NVM conflicts + unset npm_config_prefix + + # Install NVM + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash + + # Source nvm + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + + # Install and use Node.js + nvm install ${{ env.nodeVersion }} + nvm use ${{ env.nodeVersion }} + nvm alias default ${{ env.nodeVersion }} + + # Verify installation + node --version + npm --version + + - name: Verify toolchain + shell: bash + run: | + which clang + which clang++ + clang --version + cmake --version + ninja --version + + - name: Install Node.js dependencies + shell: msys2 {0} + run: | + unset npm_config_prefix + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + npm install + + - name: Generate binding code + shell: msys2 {0} + run: | + unset npm_config_prefix + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + npm run bindgen + + - name: Build Windows bridge + shell: msys2 {0} + run: | + echo "🔨 Building Windows bridge..." + unset npm_config_prefix + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + export CC=clang + export CXX=clang++ + export WEBF_BUILD=Release + export WEBF_JS_ENGINE=quickjs + # Set MSYS2 paths for CMake - use Windows-style paths for CMake + export MSYSTEM_PREFIX="D:/a/_temp/msys64/ucrt64" + export MINGW_64="D:/a/_temp/msys64/ucrt64" + + echo "Using MSYSTEM_PREFIX: $MSYSTEM_PREFIX" + echo "Using MINGW_64: $MINGW_64" + + # Verify MSYS2 installation path + ls -la /d/a/_temp/msys64/ucrt64/bin/ || echo "MSYS2 path not found, checking alternatives..." + + npm run build:bridge:windows:release + env: + WEBF_BUILD: Release + WEBF_JS_ENGINE: quickjs + + - name: Run Bridge Unit Tests + shell: powershell + run: | + node scripts/run_bridge_unit_test.js + + - name: Upload Windows debug symbols artifact + if: success() + uses: actions/upload-artifact@v4 + with: + name: debug-symbols-windows + path: | + bridge/build/windows/**/*.debug + bridge/build/windows/**/*.pdb + retention-days: 1 + if-no-files-found: warn + + - name: Upload Windows binaries + uses: actions/upload-artifact@v4 + with: + name: windows-binaries + path: | + bridge/build/windows/**/*.dll + bridge/build/windows/**/webf_unit_test.exe + retention-days: 1 + if-no-files-found: warn + + build-linux: + name: Build Linux Bridge + runs-on: ubuntu-latest + + env: + nodeVersion: "22" + cmakeVersion: "4.0.3" + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + + - name: Setup CMake + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: ${{ env.cmakeVersion }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.nodeVersion }} + cache: 'npm' + + - name: Install build dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential \ + ninja-build \ + clang \ + libc++-dev \ + libc++abi-dev \ + chrpath \ + gperf \ + patchelf \ + pkg-config \ + openjdk-17-jdk \ + libiconv-hook-dev \ + libicu-dev + + - name: Verify toolchain + run: | + which clang + which clang++ + clang --version + clang++ --version + cmake --version + ninja --version + chrpath --version + gperf --version + + - name: Install Node.js dependencies + run: npm ci + + - name: Generate binding code + run: npm run bindgen + + - name: Build Linux bridge + run: | + echo "🔨 Building Linux bridge..." + export CC=clang + export CXX=clang++ + export WEBF_BUILD=Release + export WEBF_JS_ENGINE=quickjs + npm run build:bridge:linux:release + env: + CC: clang + CXX: clang++ + WEBF_BUILD: Release + WEBF_JS_ENGINE: quickjs + + - name: Run Bridge Unit Tests + run: | + node scripts/run_bridge_unit_test.js + + - name: Upload Linux binaries + uses: actions/upload-artifact@v4 + with: + name: linux-binaries + path: | + bridge/build/linux/**/*.so + bridge/build/linux/**/*.so.* + bridge/build/linux/**/webf_unit_test + retention-days: 1 + if-no-files-found: warn + + - name: Upload Linux debug symbols + uses: actions/upload-artifact@v4 + with: + name: debug-symbols-linux + path: bridge/build/linux/**/*.debug + retention-days: 1 + if-no-files-found: warn + + build-macos: + name: Build macOS Bridge + runs-on: macos-latest + + env: + nodeVersion: "22" + cmakeVersion: "4.0.3" + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + + - name: Setup CMake + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: ${{ env.cmakeVersion }} + - name: Install gperf + run: brew install gperf + + # - name: Setup Node.js + # uses: actions/setup-node@v4 + # with: + # node-version: ${{ env.nodeVersion }} + # cache: 'npm' + + - name: Install build dependencies + run: | + # Check if ninja is already installed on self-hosted runner + which ninja || brew install ninja + + - name: Verify toolchain + run: | + which clang + which clang++ + clang --version + cmake --version + ninja --version + + - name: Install Node.js dependencies + run: npm ci + + - name: Generate binding code + run: npm run bindgen + + - name: Build macOS bridge + run: | + echo "🔨 Building macOS bridge..." + export CC=clang + export CXX=clang++ + export WEBF_BUILD=Release + export WEBF_JS_ENGINE=quickjs + npm run build:bridge:macos:release + env: + CC: clang + CXX: clang++ + WEBF_BUILD: Release + WEBF_JS_ENGINE: quickjs + + - name: Run Bridge Unit Tests + run: | + node scripts/run_bridge_unit_test.js + + - name: Upload macOS debug symbols artifact + if: success() + uses: actions/upload-artifact@v4 + with: + name: debug-symbols-macos + path: bridge/build/macos/**/*.dSYM/ + retention-days: 1 + if-no-files-found: warn + + - name: Upload macOS binaries + uses: actions/upload-artifact@v4 + with: + name: macos-binaries + path: | + bridge/build/macos/**/*.dylib + bridge/build/macos/**/webf_unit_test + retention-days: 1 + if-no-files-found: warn + + build-ios: + name: Build iOS Bridge + runs-on: macos-latest + + env: + nodeVersion: "22" + cmakeVersion: "4.0.3" + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + + - name: Setup CMake + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: ${{ env.cmakeVersion }} + + # - name: Setup Node.js + # uses: actions/setup-node@v4 + # with: + # node-version: ${{ env.nodeVersion }} + # cache: 'npm' + + - name: Verify toolchain + run: | + xcodebuild -version + which clang + clang --version + + - name: Install Node.js dependencies + run: npm install + + - name: Install gperf + run: brew install gperf + + - name: Generate binding code + run: npm run bindgen + + - name: Build iOS bridge + run: | + echo "🔨 Building iOS bridge..." + export WEBF_BUILD=Release + export WEBF_JS_ENGINE=quickjs + # Ensure dSYMs are created but not embedded in Release builds + export INCLUDE_DSYMS_IN_XCFRAMEWORK=false + npm run build:bridge:ios:release + env: + WEBF_BUILD: Release + WEBF_JS_ENGINE: quickjs + INCLUDE_DSYMS_IN_XCFRAMEWORK: false + + - name: Upload iOS binaries + uses: actions/upload-artifact@v4 + with: + name: ios-binaries + path: | + bridge/build/ios/**/*.xcframework + retention-days: 1 + if-no-files-found: warn + + - name: Upload iOS debug symbols + uses: actions/upload-artifact@v4 + with: + name: debug-symbols-ios + path: | + bridge/build/ios/**/*.dSYMs/ + retention-days: 1 + if-no-files-found: warn + + build-android: + name: Build Android Bridge + runs-on: macos-latest + + env: + nodeVersion: "22" + cmakeVersion: "4.0.3" + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + + - name: Setup CMake + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: ${{ env.cmakeVersion }} + + # - name: Setup Node.js + # uses: actions/setup-node@v4 + # with: + # node-version: ${{ env.nodeVersion }} + # cache: 'npm' + + - name: Setup Android SDK and NDK + uses: nttld/setup-ndk@v1 + id: setup-ndk + with: + ndk-version: r27d + env: + ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} + + - name: Setup build dependencies + run: | + # Check if build tools are already installed on self-hosted macOS runner + which ninja || brew install ninja + which pkg-config || brew install pkg-config + + - name: Verify toolchain + run: | + which clang + clang --version + cmake --version + ninja --version + echo "Android SDK: $ANDROID_HOME" + echo "Android NDK: ${{ steps.setup-ndk.outputs.ndk-path }}" + ls -la "${{ steps.setup-ndk.outputs.ndk-path }}" || echo "NDK directory not found" + + - name: Install Node.js dependencies + run: npm ci + + - name: Install gperf + run: brew install gperf + + - name: Generate binding code + run: npm run bindgen + + - name: Build Android bridge + env: + ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} + ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }} + WEBF_BUILD: Release + WEBF_JS_ENGINE: quickjs + run: | + echo "🔨 Building Android bridge..." + echo "Using NDK: $ANDROID_NDK_HOME" + echo "NDK version: ${{ steps.setup-ndk.outputs.ndk-full-version }}" + export WEBF_BUILD=Release + export WEBF_JS_ENGINE=quickjs + npm run build:bridge:android:release + + - name: Upload Android debug symbols artifact + if: success() + uses: actions/upload-artifact@v4 + with: + name: debug-symbols-android + path: bridge/build/android/**/*.debug + retention-days: 1 + if-no-files-found: warn + + - name: Upload Android binaries + uses: actions/upload-artifact@v4 + with: + name: android-binaries + path: | + bridge/build/android/**/*.so + bridge/build/android/**/*.a + retention-days: 1 + if-no-files-found: warn + + collect-debug-symbols: + name: Collect and Upload Debug Symbols to LFS + runs-on: ubuntu-latest + # Wait for all bridge builds to complete + needs: [build-windows, build-linux, build-macos, build-ios, build-android] + + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Git LFS + run: | + git lfs install + git config --local user.email "actions@github.com" + git config --local user.name "GitHub Actions" + + - name: Download all debug symbol artifacts + uses: actions/download-artifact@v4 + with: + pattern: debug-symbols-* + path: ./debug-artifacts + + - name: Install 7-Zip + run: | + sudo apt-get update + sudo apt-get install -y p7zip-full + + - name: Organize and compress debug symbols + run: | + echo "🔍 Organizing and compressing debug symbols..." + + # Get branch name from GitHub context + BRANCH_NAME="${{ github.ref_name }}" + echo "📋 Current branch: $BRANCH_NAME" + + # Extract version from branch name or use input version + if [[ "$BRANCH_NAME" == release/* ]]; then + VERSION="${BRANCH_NAME#release/}" + elif [ -n "${{ inputs.version }}" ]; then + VERSION="${{ inputs.version }}" + else + # Fallback to branch name itself + VERSION="$BRANCH_NAME" + fi + + echo "📋 Using version: $VERSION" + + # Pick available 7-Zip binary + SEVENZIP_BIN="$(command -v 7z || command -v 7zz || command -v 7za || true)" + if [[ -z "$SEVENZIP_BIN" ]]; then + echo "❌ 7-Zip binary not found (7z/7zz/7za)" + exit 1 + fi + + # Create version-based directory structure + mkdir -p "debug-symbols/$VERSION" + + # Process and compress each platform's debug symbols + for platform in windows linux macos ios android; do + artifact_dir="debug-artifacts/debug-symbols-${platform}" + + if [[ -d "$artifact_dir" ]] && find "$artifact_dir" -type f -print -quit | grep -q .; then + echo "📦 Compressing $platform debug symbols..." + + # Count files + file_count=$(find "$artifact_dir" -type f | wc -l | tr -d '[:space:]') + + if [[ "$file_count" -gt 0 ]]; then + # Get size before compression + size_before=$(du -sh "$artifact_dir" | cut -f1) + + dest="debug-symbols/$VERSION/${platform}-debug-symbols.7z" + tmp="${dest}.tmp" + + # Remove any stale/corrupt outputs first + rm -f "$tmp" "$dest" + + # Compress the directory (safer than using '*'), force overwrite with -y + "$SEVENZIP_BIN" a -t7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on -y \ + "$tmp" "$artifact_dir" >/dev/null + + # Validate and finalize atomically + if "$SEVENZIP_BIN" t "$tmp" >/dev/null 2>&1; then + mv -f "$tmp" "$dest" + size_after=$(du -sh "$dest" | cut -f1) + echo " ✅ $platform: $file_count files, $size_before → $size_after" + else + echo " ❌ $platform: archive validation failed" + rm -f "$tmp" + fi + fi + else + echo " ⚠️ No $platform debug symbols found" + fi + done + + # Count total compressed archives + total_archives=$(find "debug-symbols/$VERSION" -name "*.7z" -type f | wc -l) + echo "📊 Total compressed debug symbol archives: $total_archives" + + if [ "$total_archives" -gt 0 ]; then + # List all compressed debug symbols + echo "📋 Compressed debug symbols summary for version $VERSION:" + ls -lh "debug-symbols/$VERSION"/*.7z + + # Calculate total size + total_size=$(du -sh "debug-symbols/$VERSION" | cut -f1) + echo "💾 Total compressed size: $total_size" + + # Setup Git LFS tracking (idempotent) + git lfs track "debug-symbols/**/*.7z" >/dev/null 2>&1 || true + + # Add all debug symbols + git add .gitattributes + git add debug-symbols/ + + # Branch that triggered this action + TRIGGER_BRANCH="${{ github.ref_name }}" + echo "📋 Triggered from branch: $TRIGGER_BRANCH" + + # Extract version for commit message + if [[ "$TRIGGER_BRANCH" == release/* ]]; then + VERSION="${TRIGGER_BRANCH#release/}" + elif [ -n "${{ inputs.version }}" ]; then + VERSION="${{ inputs.version }}" + else + VERSION="$TRIGGER_BRANCH" + fi + + # Commit and push only if there are changes + if git diff --staged --quiet; then + echo "ℹ️ No changes to commit" + else + git commit -m "Add compressed debug symbols for release $VERSION" + git push origin HEAD:refs/heads/$TRIGGER_BRANCH + echo "✅ Successfully uploaded compressed debug symbols to Git LFS on branch: $TRIGGER_BRANCH" + fi + else + echo "⚠️ No debug symbols found to upload" + fi + + prepare-and-publish: + name: Prepare and Publish WebF Package + runs-on: ubuntu-latest + # Wait for all bridge builds and debug symbol collection + needs: [build-windows, build-linux, build-macos, build-ios, build-android, collect-debug-symbols] + + permissions: + contents: write + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + # Fetch full history for git operations + fetch-depth: 0 + # Use a personal access token to allow pushing to webf directory + token: ${{ secrets.GITHUB_TOKEN }} + submodules: recursive + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.29.0' + channel: 'stable' + + - name: Setup Git configuration + run: | + git config --global user.name "GitHub Actions" + git config --global user.email "actions@github.com" + + - name: Download all build artifacts + uses: actions/download-artifact@v4 + with: + path: ./artifacts + continue-on-error: false + + - name: Restore bridge binaries + run: | + echo "📦 Restoring bridge binaries from artifacts..." + + # Create bridge/build directory structure + mkdir -p bridge/build + + # Track restoration status + RESTORE_FAILED=false + + # Restore Windows binaries (maintaining path structure from bridge/build/windows/) + if [ -d "artifacts/windows-binaries" ]; then + mkdir -p bridge/build/windows + cp -r artifacts/windows-binaries/* bridge/build/windows/ + echo "✅ Windows binaries restored" + else + echo "⚠️ Windows binaries not found" + RESTORE_FAILED=true + fi + + # Restore Linux binaries (maintaining path structure from bridge/build/linux/) + if [ -d "artifacts/linux-binaries" ]; then + mkdir -p bridge/build/linux + cp -r artifacts/linux-binaries/* bridge/build/linux/ + echo "✅ Linux binaries restored" + else + echo "⚠️ Linux binaries not found" + RESTORE_FAILED=true + fi + + # Restore macOS binaries (maintaining path structure from bridge/build/macos/) + if [ -d "artifacts/macos-binaries" ]; then + mkdir -p bridge/build/macos + cp -r artifacts/macos-binaries/* bridge/build/macos/ + echo "✅ macOS binaries restored" + else + echo "⚠️ macOS binaries not found" + RESTORE_FAILED=true + fi + + # Restore iOS binaries (maintaining path structure from bridge/build/ios/) + if [ -d "artifacts/ios-binaries" ]; then + mkdir -p bridge/build/ios + cp -r artifacts/ios-binaries/* bridge/build/ios/ + echo "✅ iOS binaries restored" + else + echo "⚠️ iOS binaries not found" + RESTORE_FAILED=true + fi + + # Restore Android binaries (maintaining path structure from bridge/build/android/) + if [ -d "artifacts/android-binaries" ]; then + mkdir -p bridge/build/android + cp -r artifacts/android-binaries/* bridge/build/android/ + echo "✅ Android binaries restored" + else + echo "⚠️ Android binaries not found" + RESTORE_FAILED=true + fi + + echo "📋 Bridge build directory structure:" + find bridge/build -type f -name "*.so" -o -name "*.dll" -o -name "*.dylib" -o -name "*.a" -o -name "*.framework" | head -20 + + # Exit with error if any platform binaries are missing + if [ "$RESTORE_FAILED" = true ]; then + echo "❌ Error: Some platform binaries are missing. Please check the build jobs." + exit 1 + fi + + - name: Prepare package for release + run: | + echo "🧹 Preparing package for release..." + npm run prepare-release + + - name: Copy prebuilt binaries to WebF package + run: | + echo "🚀 Copying prebuilt binaries to WebF package..." + + # Windows binaries + echo "📦 Windows binaries..." + npm run use-prebuilt:windows || echo "⚠️ Windows binaries copy failed (may be expected on Linux)" + + # Linux binaries + echo "📦 Linux binaries..." + npm run use-prebuilt:linux + + # macOS binaries (skip on Linux CI, would fail) + echo "📦 macOS binaries..." + npm run use-prebuilt:macos + + # iOS binaries + echo "📦 iOS binaries..." + npm run use-prebuilt:ios + + # Android binaries + echo "📦 Android binaries..." + npm run use-prebuilt:android + + echo "✅ All prebuilt binaries copied" + + - name: Verify prepared package + run: | + echo "🔍 Verifying prepared package structure..." + + # Check Linux libraries + if ls webf/linux/*.so 1> /dev/null 2>&1; then + echo "✅ Linux libraries found:" + ls -lh webf/linux/*.so + else + echo "⚠️ Linux libraries not found" + fi + + # Check Windows libraries + if ls webf/windows/*.dll 1> /dev/null 2>&1; then + echo "✅ Windows libraries found:" + ls -lh webf/windows/*.dll + else + echo "⚠️ Windows libraries not found" + fi + + # Check macOS libraries + if ls webf/macos/*.dylib 1> /dev/null 2>&1; then + echo "✅ macOS libraries found:" + ls -lh webf/macos/*.dylib + else + echo "⚠️ macOS libraries not found" + fi + + # Check iOS frameworks + if [ -d "webf/ios/Frameworks" ]; then + echo "✅ iOS frameworks found:" + ls -la webf/ios/Frameworks/ + else + echo "⚠️ iOS frameworks not found" + fi + + # Check Android libraries + if [ -d "webf/android/jniLibs" ]; then + echo "✅ Android JNI libraries found:" + find webf/android/jniLibs -name "*.so" | head -10 + else + echo "⚠️ Android JNI libraries not found" + fi + + - name: Publish webf to pub.dev + uses: k-paxian/dart-package-publisher@master + with: + credentialJson: ${{ secrets.PUB_DEV_TOKEN }} + flutter: true + force: true + skipTests: true + relativePath: webf + + - name: Publish webf_cupertino_ui to pub.dev + uses: k-paxian/dart-package-publisher@master + with: + credentialJson: ${{ secrets.PUB_DEV_TOKEN }} + flutter: true + force: true + skipTests: true + relativePath: native_uis/webf_cupertino_ui + + - name: Publish webf_share to pub.dev + uses: k-paxian/dart-package-publisher@master + with: + credentialJson: ${{ secrets.PUB_DEV_TOKEN }} + flutter: true + force: true + skipTests: true + relativePath: native_plugins/share + + - name: Summary + run: | + # Get version for summary + if [ -n "${{ inputs.version }}" ]; then + VERSION="${{ inputs.version }}" + elif [[ "${{ github.ref_name }}" == release/* ]]; then + VERSION="${{ github.ref_name }}" + VERSION="${VERSION#release/}" + else + VERSION="unknown" + fi + + echo "🎉 WebF packages published successfully!" + echo "📋 Summary:" + echo " - Version: $VERSION" + echo " - Branch: ${{ github.ref_name }}" + echo " - Commit: ${{ github.sha }}" + echo " - Published packages:" + echo " • webf (https://pub.dev/packages/webf)" + echo " • webf_cupertino_ui (https://pub.dev/packages/webf_cupertino_ui)" + echo " • webf_share (https://pub.dev/packages/webf_share)" + echo " - Debug symbols stored in Git LFS" + + create-pr-to-main: + name: Create PR to Main Branch + runs-on: ubuntu-latest + needs: [prepare-and-publish] + if: success() + + permissions: + contents: write + pull-requests: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Git configuration + run: | + git config --global user.name "GitHub Actions" + git config --global user.email "actions@github.com" + + - name: Extract version and branch info + id: version-info + run: | + # Determine version from inputs or branch name + if [ -n "${{ inputs.version }}" ]; then + VERSION="${{ inputs.version }}" + elif [[ "${{ github.ref_name }}" == release/* ]]; then + VERSION="${{ github.ref_name }}" + VERSION="${VERSION#release/}" + else + echo "❌ Error: Version not specified and not on a release branch" + exit 1 + fi + + # Remove 'v' prefix if present + VERSION="${VERSION#v}" + echo "version=$VERSION" >> $GITHUB_OUTPUT + + # Determine the release branch name + RELEASE_BRANCH="release/$VERSION" + echo "release_branch=$RELEASE_BRANCH" >> $GITHUB_OUTPUT + + echo "📋 Version: $VERSION" + echo "📋 Release branch: $RELEASE_BRANCH" + + - name: Create Pull Request to main + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Fetch latest changes + git fetch origin + + # Check if PR already exists + EXISTING_PR=$(gh pr list --base main --head "${{ steps.version-info.outputs.release_branch }}" --state open --json number --jq '.[0].number' || echo "") + + if [ -n "$EXISTING_PR" ]; then + echo "✅ Pull request #$EXISTING_PR already exists" + echo "📋 PR URL: https://github.com/${{ github.repository }}/pull/$EXISTING_PR" + else + echo "📝 Creating pull request from ${{ steps.version-info.outputs.release_branch }} to main..." + + # Create the pull request + PR_URL=$(gh pr create \ + --base main \ + --head "${{ steps.version-info.outputs.release_branch }}" \ + --title "Release ${{ steps.version-info.outputs.version }}" \ + --body "## 🚀 Release ${{ steps.version-info.outputs.version }} + + This pull request merges the release branch back to the main branch after successful package publication. + + ### 📋 Release Information + - **Version**: ${{ steps.version-info.outputs.version }} + - **Branch**: ${{ steps.version-info.outputs.release_branch }} + - **Published to**: [pub.dev](https://pub.dev/packages/webf) + + ### ✅ Automated Checks Completed + - All platform bridges built successfully + - Debug symbols collected and stored in Git LFS + - Package published to pub.dev + + ### 📝 Merge Checklist + - [ ] Review the changes in this release + - [ ] Verify package is available on pub.dev + - [ ] Confirm all CI checks pass + + --- + *This PR was automatically created by the release workflow.*") + + echo "✅ Pull request created successfully!" + echo "📋 PR URL: $PR_URL" + fi + + - name: Add summary to workflow + run: | + echo "## 📋 Release Workflow Complete" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### ✅ Release Published" >> $GITHUB_STEP_SUMMARY + echo "- **Version**: ${{ steps.version-info.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "- **Package**: Published to [pub.dev](https://pub.dev/packages/webf)" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### 🔄 Pull Request" >> $GITHUB_STEP_SUMMARY + echo "A pull request has been created to merge the release changes back to main branch." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Please review and merge the PR to complete the release process." >> $GITHUB_STEP_SUMMARY diff --git a/.gitignore b/.gitignore index 062b86230f..f64b1d5952 100644 --- a/.gitignore +++ b/.gitignore @@ -59,7 +59,6 @@ cmake-build-release cmake-build-macos # Node.js -package-lock.json Podfile.lock .cxx @@ -68,3 +67,10 @@ temp coverage pubspec.lock .fvm + +**/.claude/settings.local.json +**/.preview-bundles +**/.tmp-chrome-profile/ + +**/.dartServer/ +**/.dart-tool/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..0f2ede0d18 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "bridge/third_party/quickjs"] + path = bridge/third_party/quickjs + url = git@github.com:openwebf/quickjs.git + branch = feat/official_version diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000000..700113020f --- /dev/null +++ b/.mcp.json @@ -0,0 +1,3 @@ +{ + "mcpServers": {} +} \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..1ad43a136f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,41 @@ +# Repository Guidelines + +## Project Structure & Module Organization +- `bridge/`: C++17 WebF bridge (JS runtime, DOM/CSS, codegen); tests in `bridge/test/`. +- `webf/`: Dart/Flutter engine (DOM/layout/painting); tests in `webf/test/` and `webf/integration_test/`. +- `integration_tests/`: E2E and snapshot tests. +- `cli/`: WebF CLI (TypeScript) for code generation; tests in `cli/test/`. +- `scripts/`: Build, typings, and utility scripts. +- `webf_apps/`: Example apps (Vue + Cupertino UI). Third‑party in `bridge/third_party/`. + +## Build, Test, and Development Commands +- Build bridge (macOS): `npm run build:bridge:macos` +- Build all platforms: `npm run build:bridge:all` +- Clean: `npm run build:clean` +- Generate bindings/types: `node scripts/generate_binding_code.js` +- All tests: `npm test` +- Bridge unit tests: `node scripts/run_bridge_unit_test.js` +- Flutter tests: `cd webf && flutter test` +- Integration tests: `cd integration_tests && npm run integration` +- Lint/format (Dart): `npm run lint` / `npm run format` + +## Coding Style & Naming Conventions +- C++ (bridge): C++17, 2‑space indent, 120 cols, Chromium style (`.clang-format`). Files: `.cc/.h`. +- Dart (webf): See `webf/analysis_options.yaml`. Files: `snake_case.dart`; Classes: PascalCase; members: camelCase. +- TypeScript (cli): Strict TS; tests in `*.test.ts`. Keep generators pure and deterministic. + +## Testing Guidelines +- Bridge: Google Test via `run_bridge_unit_test.js`. Add tests under `bridge/test/`. +- Dart/Flutter: Widget/unit in `webf/test/`; integration in `webf/integration_test/`. Use `WebFWidgetTestUtils` and `pumpAndSettle()` where needed. +- CLI: Jest (`cli/test/`), target ≥70% coverage for core modules. +- Naming: Mirror source paths; prefer small, behavior‑focused tests. + +## Commit & Pull Request Guidelines +- Commits: Conventional style `type(scope): subject` (e.g., `fix(bridge): remove register kw from codegen`). +- PRs: Clear description, linked issues, steps to test; screenshots for UI changes in `webf_apps/`. +- CI hygiene: Run `node scripts/generate_binding_code.js`, build bridge, and ensure all tests + `npm run lint` pass. + +## Security & Configuration Tips +- Initialize submodules: `git submodule update --init --recursive`. +- Prereqs: Flutter SDK, CMake/Clang (macOS), Xcode/NDK as needed. Use `WEBF_BUILD=Release` or `ENABLE_PROFILE=true` when required. + diff --git a/AUTHORS.txt b/AUTHORS.txt deleted file mode 100644 index 821e7dbdfa..0000000000 --- a/AUTHORS.txt +++ /dev/null @@ -1,21 +0,0 @@ -Airing -airingdeng -anjihang -boiawang -haonan.whn -jiangzhou -lzl -qinxuliang -ranmo.cy -suyulin -tylorvan.fzm -xiaolun.zxl -ZeroLing -zhanwen.zw -不求圣剑 -任跃兵 -元彦 -史胜龙 -清扬陌客 -苏昱霖 -董天成 \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..23b57a79c0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,209 @@ +# WebF Development Guide + +This is the main guide for WebF development. Detailed content is organized into folder-specific guides. + +## 📚 Folder-Specific Guides + +| Guide | Description | +|-------|-------------| +| **[C++ Development](bridge/CLAUDE.md)** | C++ bridge development, build commands, FFI patterns, iOS troubleshooting | +| **[Dart/Flutter Development](webf/CLAUDE.md)** | Dart code, widget testing, Flutter patterns, render object system | +| **[Integration Testing](integration_tests/CLAUDE.md)** | Writing and running integration tests, snapshot testing | +| **[CLI Development](cli/CLAUDE.md)** | WebF CLI code generator for React/Vue bindings | +| **[Scripts](scripts/CLAUDE.md)** | Build scripts and utility tools | +| **[Architecture](docs/ARCHITECTURE.md)** | WebF architecture pipeline and design patterns | +| **[Memory & Performance](docs/MEMORY_PERFORMANCE.md)** | Performance optimization, caching, memory management | + +## 🚀 Quick Start + +### Repository Structure +- `bridge/`: C++ code providing JavaScript runtime and DOM API implementations +- `webf/`: Dart code implementing DOM/CSS and layout/painting on Flutter +- `integration_tests/`: Integration tests and visual regression tests +- `cli/`: WebF CLI for generating React/Vue bindings +- `scripts/`: Build and utility scripts + +### Common Commands + +| Task | Command | +|------|---------| +| Build C++ (macOS) | `npm run build:bridge:macos` | +| Build C++ (Android, static STL) | `npm run build:bridge:android` | +| Build C++ (Android, dynamic STL) | `npm run build:bridge:android:dynamic-stl` | +| Build C++ (Android, separate libs) | `npm run build:bridge:android:separate` | +| Build C++ (all platforms) | `npm run build:bridge:all` | +| Run all tests | `npm test` | +| Run integration tests | `cd integration_tests && npm run integration` | +| Run Dart tests | `cd webf && flutter test` | +| Lint code | `npm run lint` | +| Format code | `npm run format` | +| Clean build | `npm run build:clean` | + +## 🔍 Code Navigation + +### Search Strategies +- Use `Grep` for specific function/class names +- Use `Glob` for file patterns +- Batch related searches in parallel +- For cross-language features, search both C++ (.cc/.h) and Dart (.dart) files + +### Common Patterns +- Function usage: `FunctionName\(` +- Class definition: `class ClassName` +- FFI exports: `WEBF_EXPORT_C` + +## 📦 C++ Library Bundling + +### Android Library Bundling + +WebF supports two modes for bundling C++ libraries in Android builds: + +#### Bundled Libraries (Default, Recommended) +```bash +npm run build:bridge:android # Bundled QuickJS + static STL (default) +npm run build:bridge:android:release # Bundled QuickJS + static STL (release) +npm run build:bridge:android:dynamic-stl # Bundled QuickJS + dynamic STL +``` + +**Outputs:** +- `libwebf.so` - Contains WebF + QuickJS + STL code bundled together (default) +- `libc++_shared.so` - Android STL runtime (only with --dynamic-stl flag) + +**Advantages:** +- **Maximum simplicity** - Single `libwebf.so` file only (no STL dependencies) +- **Smallest deployment** - No separate `libc++_shared.so` required +- **Better optimization** - Cross-library inlining including STL +- **Reduced app size** - No duplicate symbols, smaller total size +- **Easier debugging** - Everything in one library + +#### Separate Libraries (Advanced) +```bash +npm run build:bridge:android:separate # Separate QuickJS library +WEBF_SEPARATE_QUICKJS=true npm run build:bridge:android +``` + +**Outputs:** +- `libwebf.so` - WebF core library +- `libquickjs.so` - JavaScript engine (separate) +- `libc++_shared.so` - Android STL runtime + +**Use Cases:** +- When you need to share QuickJS with other libraries +- For debugging library boundaries +- Advanced library management scenarios + +### Build Options + +| Flag/Environment | Description | Default | +|------------------|-------------|---------| +| `--static-quickjs` | Bundle QuickJS into webf library | Android: true, Others: false | +| `--dynamic-stl` | Use dynamic C++ standard library (Android) | false | +| `WEBF_SEPARATE_QUICKJS=true` | Build QuickJS as separate library | false | +| `ANDROID_STL=c++_shared` | Override default STL type | `c++_static` | +| `--enable-log` | Enable debug logging | false | + +### Android STL Options + +| STL Type | Description | Libraries Required | +|----------|-------------|-------------------| +| `c++_static` (**default**) | Static C++ standard library | `libwebf.so` only | +| `c++_shared` | Dynamic C++ standard library | `libwebf.so` + `libc++_shared.so` | +| `system` | System C++ library (deprecated) | `libwebf.so` only | + +## 🌉 Cross-Platform Development + +### FFI Best Practices +- Always free allocated memory in Dart FFI +- Use `malloc.free()` for `toNativeUtf8()` allocations +- Track pointer ownership in callbacks +- Document memory ownership clearly +- Use RAII patterns in C++ where possible + +### Thread Communication +- `PostToJs`: Execute on JS thread +- `PostToDart`: Return results to Dart isolate +- `PostToJsSync`: Synchronous execution (avoid when possible) + +## 🧪 Testing + +### Test Types +- **Unit Tests**: See folder-specific guides +- **Integration Tests**: See [Integration Development Guide](integration_tests/CLAUDE.md) +- **Flutter Widget Tests**: See [Dart Development Guide](webf/CLAUDE.md) + +### Running Tests +```bash +# All tests +npm test + +# Specific integration test +cd integration_tests && npm run integration -- specs/css/css-display/display.ts + +# Flutter tests +cd webf && flutter test + +# Bridge unit tests +node scripts/run_bridge_unit_test.js +``` + +## 📦 WebF CLI + +The CLI generates type-safe bindings between Flutter/Dart and JavaScript frameworks. + +```bash +# Basic usage +webf codegen my-typings --flutter-package-src=../webf_package + +# With auto-publish +webf codegen --flutter-package-src=../webf_package --publish-to-npm + +# Custom registry +webf codegen --flutter-package-src=../webf_package --publish-to-npm --npm-registry=https://custom.registry.com/ +``` + +## 🏢 Enterprise Features + +WebF Enterprise is a closed-source product requiring subscription: + +```yaml +dependencies: + webf: ^0.23.10 # Enterprise version available on pub.dev +``` + +## 📊 WebF MCP Server + +The MCP server provides dependency graph analysis: + +### Key Features +- 8,244+ nodes across multiple languages +- Dependency analysis and impact assessment +- Code quality metrics +- Cross-language FFI analysis +- Architecture validation + +### Example Usage +```bash +# Find a class +mcp__webf__get_node_by_name(name="WebFController") + +# Analyze dependencies +mcp__webf__get_dependencies(node_name="RenderStyle", max_depth=2) + +# Find code smells +mcp__webf__analyze_code_smells(god_class_threshold=20) +``` + +## 📝 Documentation Guidelines + +### Writing Good Docs +1. Clarify that WebF builds Flutter apps, not web apps +2. Provide complete, runnable examples +3. Include WebFControllerManager setup in examples +4. Structure information hierarchically +5. Test all code examples + +### Important Reminders +- Do what has been asked; nothing more, nothing less +- NEVER create files unless absolutely necessary +- ALWAYS prefer editing existing files +- Only create documentation when explicitly requested \ No newline at end of file diff --git a/GOVERNANCE.md b/GOVERNANCE.md deleted file mode 100644 index fd54abf8d7..0000000000 --- a/GOVERNANCE.md +++ /dev/null @@ -1,180 +0,0 @@ -# OpenWebF Project Governance - - - -* [Collaborators](#collaborators) - * [Collaborator activities](#collaborator-activities) - * [Collaborator nominations](#collaborator-nominations) -* [Technical steering committee](#technical-steering-committee) - * [Establishment TSC](#establishment-tsc) - * [TSC elections](#tsc-elections) - * [TSC meetings](#tsc-meetings) - * [TSC voting](#tsc-voting) - - - -## Collaborators - -OpenWebF Core Collaborators maintain the [openwebf/webf][] GitHub repository. -The GitHub team for OpenWebF Core Collaborators is @openwebf/collaborators. -Collaborators have: - -* Commit access to the [openwebf/webf][] repository -* Access to the OpenWebF continuous integration (CI) jobs - -Both Collaborators and non-Collaborators may propose changes to the OpenWebF -source code. The mechanism to propose such a change is a GitHub pull request. -Collaborators review and merge (_land_) pull requests. - -Two Collaborators must approve a pull request before the pull request can land. -(One Collaborator approval is enough if the pull request has been open for more -than 7 days.) Approving a pull request indicates that the Collaborator accepts -responsibility for the change. Approval must be from Collaborators who are not -authors of the change. - -If a Collaborator opposes a proposed change, then the change cannot land. The -exception is if the TSC votes to approve the change despite the opposition. -Usually, involving the TSC is unnecessary. Often, discussions or further changes -result in Collaborators removing their opposition. - -### Collaborator activities - -* Helping users and novice contributors -* Contributing code and documentation changes that improve the project -* Reviewing and commenting on issues and pull requests -* Participation in working groups -* Merging pull requests - -The TSC can remove inactive Collaborators or provide them with _Emeritus_ -status. Emeriti may request that the TSC restore them to active status. - -### Collaborator nominations - -Existing Collaborators can nominate someone to become a Collaborator. Nominees should have significant and valuable contributions across the OpenWebF organization. - -To nominate a new Collaborator, open an issue in the [openwebf/webf][] repository. Provide a summary of the nominee's contributions. For example: - -* Commits in the [openwebf/webf][] repository - * Use the link `https://github.com/openwebf/webf/commits?author=GITHUB_ID` -* Pull requests and issues opened in the [openwebf/webf][] repository - * Use the link `https://github.com/openwebf/webf/issues?q=author:GITHUB_ID` -* Comments on pull requests and issues in the [openwebf/webf][] repository - * Use the link `https://github.com/openwebf/webf/issues?q=commenter:GITHUB_ID` -* Reviews on pull requests in the [openwebf/webf][] repository - * Use the link `https://github.com/openwebf/webf/pulls?q=reviewed-by:GITHUB_ID` -* Pull requests and issues opened throughout the OpenWebF organization - * Use the link `https://github.com/search?q=author:GITHUB_ID+org:openwebf` -* Comments on pull requests and issues throughout the OpenWebF organization - * Use the link `https://github.com/search?q=commenter:GITHUB_ID+org:openwebf` -* Help provided to end-users and novice contributors -* Participation in other projects, teams, and working groups of the OpenWebF organization - -Mention @openwebf/collaborators in the issue to notify other Collaborators about the nomination. -The nomination passes if no Collaborators oppose it after one week. Otherwise, the nomination fails. - -## Technical Steering Committee - -A subset of the Collaborators forms the Technical Steering Committee (TSC). The TSC is responsible for all technical development within the OpenWebF project, including: - -* Setting release dates. -* Release quality standards. -* Technical direction. -* Project governance and process (including this policy). -* GitHub repository hosting. -* Conduct guidelines. -* Maintaining the list of additional Collaborators. -* Development process and any coding standards. -* Mediating technical conflicts between Collaborators projects. -* The TSC will define OpenWebF project’s release vehicles. - -### Establishment TSC - -The TSC members will elect from Collaborators. TSC memberships are not time-limited. There is no maximum size of the TSC. The size is expected to vary in order to ensure adequate coverage of important areas of expertise, balanced with the ability to make decisions efficiently. The TSC must have at least three members. - -There is no specific set of requirements or qualifications for TSC membership beyond these rules. The TSC may add additional members to the TSC by a standard TSC motion and vote. A TSC member may be removed from the TSC by voluntary resignation, by a standard TSC motion, or in accordance to the participation rules described below. - -Changes to TSC membership should be posted in the agenda, and may be suggested as any other agenda item. - -TSC members are expected to regularly participate in TSC activities. If a TSC member does not attend any meeting within three months, the TSC membership will automatically become invalid and removed. - -### TSC elections - -Leadership roles in the OpenWebF project will be peer elected representatives of the community. - -The TSC will elect from amongst voting TSC members a TSC Chairperson to work on building an agenda for TSC meetings. -The TSC shall hold annual elections to select a TSC Chairperson and -there are no limits on the number of terms a TSC Chairperson. - -The TSC will elect from amongst voting TSC members a TSC Chairperson to work -on building an agenda for TSC meetings. -The TSC shall hold annual elections to select a TSC Chairperson and -there are no limits on the number of terms a TSC Chairperson. - -For election of persons (such as the TSC Chairperson), a multiple-candidate -method should be used, such as: - -* [Condorcet][] or -* [Single Transferable Vote][] - -Multiple-candidate methods may be reduced to simple election by plurality -when there are only two candidates for one position to be filled. No -election is required if there is only one candidate and no objections to -the candidate's election. Elections shall be done within the projects by -the Collaborators active in the project. - -### TSC meetings - -The TSC shall meet regularly using tools that enable participation by the community (e.g. weekly on a DingTalk online conference -, or through any other appropriate means selected by the TSC). The meeting shall be directed by the TSC Chairperson. Responsibility for directing individual meetings may be delegated by the TSC Chairperson to any other TSC member. Minutes or an appropriate recording shall be taken and made available to the community through accessible public postings. - -The TSC agenda includes issues that are at an impasse. The intention of the -agenda is not to review or approve all patches. Collaborators review and approve -patches on GitHub. - -Any community member can create a GitHub issue asking that the TSC review -something. If consensus-seeking fails for an issue, a Collaborator may apply the -`tsc-agenda` label. That will add it to the TSC meeting agenda. - -Before each TSC meeting, the meeting chair will share the agenda with members of -the TSC. TSC members can also add items to the agenda at the beginning of each -meeting. The meeting chair and the TSC cannot veto or remove items. - -The TSC may invite people to take part in a non-voting capacity. - -During the meeting, the TSC chair ensures that someone takes minutes. After the -meeting, the TSC chair ensures that someone opens a pull request with the -minutes. - -The TSC seeks to resolve as many issues as possible outside meetings using -[the TSC issue tracker](https://github.com/openwebf/TSC/issues). The process in -the issue tracker is: - -* A TSC member opens an issue explaining the proposal/issue and @-mentions - @openwebf/tsc. -* The proposal passes if, after 72 hours, there are two or more TSC approvals - and no TSC opposition. -* If there is an extended impasse, a TSC member may make a motion for a vote. - -### TSC voting - -For internal project decisions, Collaborators shall operate under [Lazy Consensus][]. -The TSC shall establish appropriate guidelines for implementing Lazy Consensus -(e.g. expected notification and review time periods) within the development process. - -The TSC follows a [Consensus Seeking][] decision making model. When an agenda -item has appeared to reach a consensus the moderator will ask "Does anyone object?" -as a final call for dissent from the consensus. - -If an agenda item cannot reach a consensus a TSC member can call for either a -closing vote or a vote to table the issue to the next meeting. -The call for a vote must be seconded by a majority of the TSC or else the discussion will continue. - -For all votes, a simple majority of all TSC members for, or against, the issue wins. -A TSC member may choose to participate in any vote through abstention. - - -[openwebf/webf]: https://github.com/openwebf/webf -[Lazy Consensus]: https://community.apache.org/committers/lazyConsensus.html -[Consensus Seeking]: https://en.wikipedia.org/wiki/Consensus-seeking_decision-making -[Condorcet]: https://en.wikipedia.org/wiki/Condorcet_method -[Single Transferable Vote]: https://en.wikipedia.org/wiki/Single_transferable_vote diff --git a/GOVERNANCE.zh-CN.md b/GOVERNANCE.zh-CN.md deleted file mode 100644 index 78de70390c..0000000000 --- a/GOVERNANCE.zh-CN.md +++ /dev/null @@ -1,132 +0,0 @@ -# OpenWebF 项目协同 - - - -* [协作者](#协作者) - * [协作者职责](#协作者职责) - * [协作者提名](#协作者提名) -* [技术委员会](#技术委员会) - * [组建委员会](#组建委员会) - * [委员会选举](#委员会选举) - * [委员会会议](#委员会会议) - * [委员会投票](#委员会投票) - - - -## 协作者 - -OpenWebF 的协作者将共同维护 [openwebf/webf] 仓库,协作者在 GitHub 上的小组名称为 @openwebf/collaborators。 - -协作者拥有以下权限: - -* [openwebf/webf] 仓库的 Commit 权限 -* OpenWebF 持续集成(CI)任务的权限 - -无论协作者还是非协作者都可以提交 OpenWebF 的更改,提交更改的机制是通过 Pull Request (PR)。 - -PR 至少两个协作者 Review 通过后才能 Merge,但如果 PR 已经打开超过 7 天,则获得一个协作者的 Review 通过就可以 Merge。 - -协作者同意该 PR 表示该协作者接受和承担变更所导致的风险与责任,同时协作者不能同意通过自己提交的 PR。 - -任何协作者都可反对任何 PR 中的改动,一旦发生,则该 PR 暂时无法 Merge,可以通过讨论或进一步的修改让反对的协作者同意该 PR 中的改动。当僵持无法达成共识时,可以通过在技术委员会里使用 投票的机制来决定。 - -### 协作者职责 - -* 帮助新手贡献者 -* 贡献代码和更新文档 -* 审查和评论 Issue 并提交 Pull Request -* 参加工作组 -* 合并 Pull Request - -技术委员会可以移除不活跃的协作者或将他们转移到**荣誉组**,荣誉组成员可以让技术委员会将他们恢复为协作者。 - -### 协作者提名 - -现有的协作者可以提名某人成为协作者,被提名人应当是在整个 OpenWebF 生态上做出贡献的组织或个人。 - -要提名新的协作者,需要到 [openwebf/webf][] 仓库中提出一个 Issue,并提供被提名人相关贡献的概述。例如: - -* 在 [openwebf/webf][] 仓库中的 Commits - * 链接格式 `https://github.com/openwebf/webf/commits?author=GITHUB_ID` -* 在 [openwebf/webf][] 仓库中提交的 Pull requests 和 Issues - * 链接格式 `https://github.com/openwebf/webf/issues?q=author:GITHUB_ID` -* 在 [openwebf/webf][] 仓库中对 Pull requests 和 Issues 的评论 - * 链接格式 `https://github.com/openwebf/webf/issues?q=commenter:GITHUB_ID` -* 在 [openwebf/webf][] 仓库中的 Reviews - * 链接格式 `https://github.com/openwebf/webf/pulls?q=reviewed-by:GITHUB_ID` -* 在整个 OpenWebF 组织下提交的 Pull requests 和 Issues - * 链接格式 `https://github.com/search?q=author:GITHUB_ID + org:openwebf` -* 在整个 OpenWebF 组织下对 Pull requests 和 Issues 的评论 - * 链接格式 `https:////github.com/search?q=commenter:GITHUB_ID + org:openwebf` -* 向 OpenWebF 用户和新手贡献者提供的帮助 -* 参加 OpenWebF 的其他项目,团队、工作组或组织 - -在 Issue 中 `@openwebf/collaborators` 以通知其他协作者。如果一周后没有任何协作者反对,则提名通过,否则提名失败。 - -## 技术委员会 - -一部分协作者组成了技术委员会(TSC),以下简称委员会。委员会为 OpenWebF 项目内的所有技术开发行为负责,其职责包括: - -* 设置发布日期 -* 发布质量标准 -* 引导技术方向 -* 项目管理和流程(包括本政策) -* 管理代码仓库 -* 进行技术指导 -* 维护协作者列表 -* 维护开发过程和任何编码标准 -* 协调技术冲突 -* 委员会将提供 OpenWebF 项目的发布工具 - -### 组建委员会 - -委员会成员将从协作者中选出,委员会成员的任期、人数没有上限。为了保证投票公平,以及覆盖更多专业领域,委员会至少包含 3 名成员。 - -委员会成员可以通过发起议题或投票的方式进行扩充,也可以自愿退出委员会。 - -任何委员会成员的变动都应该被记录在委员会会议议程中,并且和其它议程一样,允许对其提出建议和意见。 - -委员会应当定期参与委员会活动,如果委员会成员三个月内未参加任何会议,则视为自愿退出委员会。 - -### 委员会选举 - -委员会将从委员会成员中选出委员会主席,以制定委员会会议议程。委员会将举行年度选举以选举委员会主席,并且委员会主席的任期没有限制。 - -对于选举人由多名候选人组成,投票机制可以选择的方法有: -* [孔多塞投票法](https://zh.wikipedia.org/wiki/%E5%AD%94%E5%A4%9A%E5%A1%9E%E6%8A%95%E7%A5%A8%E6%B3%95) -* [可转移单票制](https://zh.wikipedia.org/wiki/%E5%8F%AF%E8%BD%89%E7%A7%BB%E5%96%AE%E7%A5%A8%E5%88%B6) - -当只有两个候选人匹配一个职位时,可以将多个候选人的方法简化为简单的选举。如果只有一名候选人,并且对该候选人没有异议,则无需选举。选举应由活跃于项目中的协作者中进行。 - -### 委员会会议 - -委员会主席召开在线会议与主持会议。委员会议程通常会讨论陷入僵局的 Issue,议程的目的不是 Review 所有 PR,这些 PR 应该由协作者 Review 与 Approve。 - -任何社区成员都可以创建一个 Issue 请求委员会 Review 内容。如果对于一个 Issue 无法达成共识,则协作者可以增加 `tsc-agenda` 标签,这会将这个问题添加到委员会会议议程中。 - -在每次委员会会议之前,会议主席将与委员会成员共同制定议程。委员会成员还可以在每次会议开始时将需要讨论的议题添加到议程中。会议主席和其他委员会成员都无法否决或删除讨论的议题。 - -委员会可以邀请协作者与社区成员参加无投票权的活动。 - -在会议期间,委员会主席需要确保有人在记录会议纪要。在会议结束后,委员会主席需要保证会议纪要被上传。 - -委员会可以使用[委员会问题跟踪区](https://github.com/openwebf/TSC/issues) 在会议之外解决尽可能多的问题。 问题跟踪区的运行机制是: - -* 委员会成员打开一个问题,解释提案/问题和 @提及 - @openwebf/tsc -* 如果 72 小时后获得两个或更多委员会成员批准,且没有委员会成员的反对,则提案通过 -* 如果不能够达成共识,委员会成员可以提出发起投票 - -### 委员会投票 - -对于内部项目决策,协作者应在 [Lazy Consensus][] 下进行操作。委员会同时需建立在开发过程中实施 [Lazy Consensus][] 的指南,例如预期的通知和 Review 时间段。 - -委员会遵循寻 [Consensus Seeking][] 的决策模型。当议程接近达成共识时,主持人会问 “有人反对吗?” 作为对提出异议的最终询问。 - -如果某个议题暂时无法达成共识,则委员会成员可以要求结束投票表决或将问题搁置到下次会议进行,但如果多数委员会成员反对,则投票将继续进行。 - -对于所有投票,绝大多数委员会成员同意则该提议通过,委员会成员也可以选择放弃投票。 - -[openwebf/webf]: https://github.com/openwebf/webf -[Lazy Consensus]: https://community.apache.org/committers/lazyConsensus.html -[Consensus Seeking]: https://en.wikipedia.org/wiki/Consensus-seeking_decision-making diff --git a/LICENSE b/LICENSE index bf4dc33c51..b7762d3296 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,531 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2022 OpenWebF - - 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. +GNU GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright © 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing +it is not allowed. + +Preamble + +The GNU General Public License is a free, copyleft license for software and other kinds of works. + +The licenses for most software and other practical works are designed to take away your freedom to +share and change the works. By contrast, the GNU General Public License is intended to guarantee +your freedom to share and change all versions of a program--to make sure it remains free software +for all its users. We, the Free Software Foundation, use the GNU General Public License for most of +our software; it applies also to any other work released this way by its authors. You can apply it +to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses +are designed to make sure that you have the freedom to distribute copies of free software (and +charge for them if you wish), that you receive source code or can get it if you want it, that you +can change the software or use pieces of it in new free programs, and that you know you can do these +things. + +To protect your rights, we need to prevent others from denying you these rights or asking you to +surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the +software, or if you modify it: responsibilities to respect the freedom of others. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass +on to the recipients the same freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they know their rights. + +Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the +software, and (2) offer you this License giving you legal permission to copy, distribute and/or +modify it. + +For the developers' and authors' protection, the GPL clearly explains that there is no warranty for +this free software. For both users' and authors' sake, the GPL requires that modified versions be +marked as changed, so that their problems will not be attributed erroneously to authors of previous +versions. + +Some devices are designed to deny users access to install or run modified versions of the software +inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the +area of products for individuals to use, which is precisely where it is most unacceptable. +Therefore, we have designed this version of the GPL to prohibit the practice for those products. If +such problems arise substantially in other domains, we stand ready to extend this provision to those +domains in future versions of the GPL, as needed to protect the freedom of users. + +Finally, every program is threatened constantly by software patents. States should not allow patents +to restrict development and use of software on general-purpose computers, but in those that do, we +wish to avoid the special danger that patents applied to a free program could make it effectively +proprietary. To prevent this, the GPL assures that patents cannot be used to render the program +non-free. + +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS + +0. Definitions. + +“This License” refers to version 3 of the GNU General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor +masks. + +“The Program” refers to any copyrightable work licensed under this License. Each licensee is +addressed as “you”. “Licensees” and “recipients” may be individuals or organizations. + +To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring +copyright permission, other than the making of an exact copy. The resulting work is called a +“modified version” of the earlier work or a work “based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based on the Program. + +To “propagate” a work means to do anything with it that, without permission, would make you directly +or secondarily liable for infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, distribution (with or without +modification), making available to the public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other parties to make or receive +copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not +conveying. + +An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a +convenient and prominently visible feature that (1) displays an appropriate copyright notice, and ( +2) tells the user that there is no warranty for the work (except to the extent that warranties are +provided), that licensees may convey the work under this License, and how to view a copy of this +License. If the interface presents a list of user commands or options, such as a menu, a prominent +item in the list meets this criterion. + +1. Source Code. + The “source code” for a work means the preferred form of the work for making modifications to it. + “Object code” means any non-source form of a work. + +A “Standard Interface” means an interface that either is an official standard defined by a +recognized standards body, or, in the case of interfaces specified for a particular programming +language, one that is widely used among developers working in that language. + +The “System Libraries” of an executable work include anything, other than the work as a whole, +that (a) is included in the normal form of packaging a Major Component, but which is not part of +that Major Component, and (b) serves only to enable use of the work with that Major Component, or to +implement a Standard Interface for which an implementation is available to the public in source code +form. A “Major Component”, in this context, means a major essential component (kernel, window +system, and so on) of the specific operating system (if any) on which the executable work runs, or a +compiler used to produce the work, or an object code interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all the source code needed to +generate, install, and (for an executable work) run the object code and to modify the work, +including scripts to control those activities. However, it does not include the work's System +Libraries, or general-purpose tools or generally available free programs which are used unmodified +in performing those activities but which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for the work, and the source code +for shared libraries and dynamically linked subprograms that the work is specifically designed to +require, such as by intimate data communication or control flow between those subprograms and other +parts of the work. + +The Corresponding Source need not include anything that users can regenerate automatically from +other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +2. Basic Permissions. + All rights granted under this License are granted for the term of copyright on the Program, and + are irrevocable provided the stated conditions are met. This License explicitly affirms your + unlimited permission to run the unmodified Program. The output from running a covered work is + covered by this License only if the output, given its content, constitutes a covered work. This + License acknowledges your rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, without conditions so long as +your license otherwise remains in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you with facilities for running +those works, provided that you comply with the terms of this License in conveying all material for +which you do not control copyright. Those thus making or running the covered works for you must do +so exclusively on your behalf, under your direction and control, on terms that prohibit them from +making any copies of your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the conditions stated below. +Sublicensing is not allowed; section 10 makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. + No covered work shall be deemed part of an effective technological measure under any applicable + law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December + 1996, or similar laws prohibiting or restricting circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention of technological +measures to the extent such circumvention is effected by exercising rights under this License with +respect to the covered work, and you disclaim any intention to limit operation or modification of +the work as a means of enforcing, against the work's users, your or third parties' legal rights to +forbid circumvention of technological measures. + +4. Conveying Verbatim Copies. + You may convey verbatim copies of the Program's source code as you receive it, in any medium, + provided that you conspicuously and appropriately publish on each copy an appropriate copyright + notice; keep intact all notices stating that this License and any non-permissive terms added in + accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; + and give all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer support or +warranty protection for a fee. + +5. Conveying Modified Source Versions. + You may convey a work based on the Program, or the modifications to produce it from the Program, + in the form of source code under the terms of section 4, provided that you also meet all of these + conditions: + + a) The work must carry prominent notices stating that you modified it, and giving a relevant + date. + + b) The work must carry prominent notices stating that it is released under this License and any + conditions added under section 7. This requirement modifies the requirement in section 4 to “keep + intact all notices”. + + c) You must license the entire work, as a whole, under this License to anyone who comes into + possession of a copy. This License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, regardless of how they are + packaged. This License gives no permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; + however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, + your work need not make them do so. + +A compilation of a covered work with other separate and independent works, which are not by their +nature extensions of the covered work, and which are not combined with it such as to form a larger +program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the +compilation and its resulting copyright are not used to limit the access or legal rights of the +compilation's users beyond what the individual works permit. Inclusion of a covered work in an +aggregate does not cause this License to apply to the other parts of the aggregate. + +6. Conveying Non-Source Forms. + You may convey a covered work in object code form under the terms of sections 4 and 5, provided + that you also convey the machine-readable Corresponding Source under the terms of this License, + in one of these ways: + + a) Convey the object code in, or embodied in, a physical product (including a physical + distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product (including a physical + distribution medium), accompanied by a written offer, valid for at least three years and valid + for as long as you offer spare parts or customer support for that product model, to give anyone + who possesses the object code either (1) a copy of the Corresponding Source for all the software + in the product that is covered by this License, on a durable physical medium customarily used for + software interchange, for a price no more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the Corresponding Source from a network server at no + charge. + + c) Convey individual copies of the object code with a copy of the written offer to provide the + Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only + if you received the object code with such an offer, in accord with subsection 6b. + + d) Convey the object code by offering access from a designated place (gratis or for a charge), + and offer equivalent access to the Corresponding Source in the same way through the same place at + no further charge. You need not require recipients to copy the Corresponding Source along with + the object code. If the place to copy the object code is a network server, the Corresponding + Source may be on a different server (operated by you or a third party) that supports equivalent + copying facilities, provided you maintain clear directions next to the object code saying where + to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you + remain obligated to ensure that it is available for as long as needed to satisfy these + requirements. + + e) Convey the object code using peer-to-peer transmission, provided you inform other peers where + the object code and Corresponding Source of the work are being offered to the general public at + no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the Corresponding Source +as a System Library, need not be included in conveying the object code work. + +A “User Product” is either (1) a “consumer product”, which means any tangible personal property +which is normally used for personal, family, or household purposes, or (2) anything designed or sold +for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful +cases shall be resolved in favor of coverage. For a particular product received by a particular +user, “normally used” refers to a typical or common use of that class of product, regardless of the +status of the particular user or of the way in which the particular user actually uses, or expects +or is expected to use, the product. A product is a consumer product regardless of whether the +product has substantial commercial, industrial or non-consumer uses, unless such uses represent the +only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, procedures, authorization keys, or +other information required to install and execute modified versions of a covered work in that User +Product from a modified version of its Corresponding Source. The information must suffice to ensure +that the continued functioning of the modified object code is in no case prevented or interfered +with solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically for use in, a User +Product, and the conveying occurs as part of a transaction in which the right of possession and use +of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of +how the transaction is characterized), the Corresponding Source conveyed under this section must be +accompanied by the Installation Information. But this requirement does not apply if neither you nor +any third party retains the ability to install modified object code on the User Product (for +example, the work has been installed in ROM). + +The requirement to provide Installation Information does not include a requirement to continue to +provide support service, warranty, or updates for a work that has been modified or installed by the +recipient, or for the User Product in which it has been modified or installed. Access to a network +may be denied when the modification itself materially and adversely affects the operation of the +network or violates the rules and protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord with this section +must be in a format that is publicly documented (and with an implementation available to the public +in source code form), and must require no special password or key for unpacking, reading or copying. + +7. Additional Terms. + “Additional permissions” are terms that supplement the terms of this License by making exceptions + from one or more of its conditions. Additional permissions that are applicable to the entire + Program shall be treated as though they were included in this License, to the extent that they + are valid under applicable law. If additional permissions apply only to part of the Program, that + part may be used separately under those permissions, but the entire Program remains governed by + this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any additional permissions +from that copy, or from any part of it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place additional permissions on +material, added by you to a covered work, for which you have or can give appropriate copyright +permission. + +Notwithstanding any other provision of this License, for material you add to a covered work, you +may (if authorized by the copyright holders of that material) supplement the terms of this License +with terms: + + a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or authors of the material; or + + e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. + +All other non-permissive additional terms are considered “further restrictions” within the meaning +of section 10. If the Program as you received it, or any part of it, contains a notice stating that +it is governed by this License along with a term that is a further restriction, you may remove that +term. If a license document contains a further restriction but permits relicensing or conveying +under this License, you may add to a covered work material governed by the terms of that license +document, provided that the further restriction does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in the relevant +source files, a statement of the additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a separately written +license, or stated as exceptions; the above requirements apply either way. + +8. Termination. + You may not propagate or modify a covered work except as expressly provided under this License. + Any attempt otherwise to propagate or modify it is void, and will automatically terminate your + rights under this License (including any patent licenses granted under the third paragraph of + section 11). + +However, if you cease all violation of this License, then your license from a particular copyright +holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder fails to notify you of the +violation by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently if the copyright +holder notifies you of the violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that copyright holder, and you cure +the violation prior to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of parties who have +received copies or rights from you under this License. If your rights have been terminated and not +permanently reinstated, you do not qualify to receive new licenses for the same material under +section 10. + +9. Acceptance Not Required for Having Copies. + You are not required to accept this License in order to receive or run a copy of the Program. + Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer + transmission to receive a copy likewise does not require acceptance. However, nothing other than + this License grants you permission to propagate or modify any covered work. These actions + infringe copyright if you do not accept this License. Therefore, by modifying or propagating a + covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. + Each time you convey a covered work, the recipient automatically receives a license from the + original licensors, to run, modify and propagate that work, subject to this License. You are not + responsible for enforcing compliance by third parties with this License. + +An “entity transaction” is a transaction transferring control of an organization, or substantially +all assets of one, or subdividing an organization, or merging organizations. If propagation of a +covered work results from an entity transaction, each party to that transaction who receives a copy +of the work also receives whatever licenses to the work the party's predecessor in interest had or +could give under the previous paragraph, plus a right to possession of the Corresponding Source of +the work from the predecessor in interest, if the predecessor has it or can get it with reasonable +efforts. + +You may not impose any further restrictions on the exercise of the rights granted or affirmed under +this License. For example, you may not impose a license fee, royalty, or other charge for exercise +of rights granted under this License, and you may not initiate litigation (including a cross-claim +or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, +offering for sale, or importing the Program or any portion of it. + +11. Patents. + A “contributor” is a copyright holder who authorizes use under this License of the Program or a + work on which the Program is based. The work thus licensed is called the contributor's + “contributor version”. + +A contributor's “essential patent claims” are all patent claims owned or controlled by the +contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, +permitted by this License, of making, using, or selling its contributor version, but do not include +claims that would be infringed only as a consequence of further modification of the contributor +version. For purposes of this definition, “control” includes the right to grant patent sublicenses +in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the +contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, +modify and propagate the contents of its contributor version. + +In the following three paragraphs, a “patent license” is any express agreement or commitment, +however denominated, not to enforce a patent (such as an express permission to practice a patent or +covenant not to sue for patent infringement). To “grant” such a patent license to a party means to +make such an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of +the work is not available for anyone to copy, free of charge and under the terms of this License, +through a publicly available network server or other readily accessible means, then you must +either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of +the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent +with the requirements of this License, to extend the patent license to downstream recipients. +“Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying +the covered work in a country, or your recipient's use of the covered work in a country, would +infringe one or more identifiable patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate +by procuring conveyance of, a covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of +the covered work, then the patent license you grant is automatically extended to all recipients of +the covered work and works based on it. + +A patent license is “discriminatory” if it does not include within the scope of its coverage, +prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that +are specifically granted under this License. You may not convey a covered work if you are a party to +an arrangement with a third party that is in the business of distributing software, under which you +make payment to the third party based on the extent of your activity of conveying the work, and +under which the third party grants, to any of the parties who would receive the covered work from +you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by +you (or copies made from those copies), or (b) primarily for and in connection with specific +products or compilations that contain the covered work, unless you entered into that arrangement, or +that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied license or other +defenses to infringement that may otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. + If conditions are imposed on you (whether by court order, agreement or otherwise) that + contradict the conditions of this License, they do not excuse you from the conditions of this + License. If you cannot convey a covered work so as to satisfy simultaneously your obligations + under this License and any other pertinent obligations, then as a consequence you may not convey + it at all. For example, if you agree to terms that obligate you to collect a royalty for further + conveying from those to whom you convey the Program, the only way you could satisfy both those + terms and this License would be to refrain entirely from conveying the Program. + +13. Use with the GNU Affero General Public License. + Notwithstanding any other provision of this License, you have permission to link or combine any + covered work with a work licensed under version 3 of the GNU Affero General Public License into + a single combined work, and to convey the resulting work. The terms of this License will + continue to apply to the part which is the covered work, but the special requirements of the GNU + Affero General Public License, section 13, concerning interaction through a network will apply + to the combination as such. + +14. Revised Versions of this License. + The Free Software Foundation may publish revised and/or new versions of the GNU General Public + License from time to time. Such new versions will be similar in spirit to the present version, + but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies that a certain +numbered version of the GNU General Public License “or any later version” applies to it, you have +the option of following the terms and conditions either of that numbered version or of any later +version published by the Free Software Foundation. If the Program does not specify a version number +of the GNU General Public License, you may choose any version ever published by the Free Software +Foundation. + +If the Program specifies that a proxy can decide which future versions of the GNU General Public +License can be used, that proxy's public statement of acceptance of a version permanently authorizes +you to choose that version for the Program. + +Later license versions may give you additional or different permissions. However, no additional +obligations are imposed on any author or copyright holder as a result of your choosing to follow a +later version. + +15. Disclaimer of Warranty. + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN + OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS + IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK + AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE + DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, + OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU + FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF + THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING + RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO + OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. + If the disclaimer of warranty and limitation of liability provided above cannot be given local + legal effect according to their terms, reviewing courts shall apply local law that most closely + approximates an absolute waiver of all civil liability in connection with the Program, unless a + warranty or assumption of liability accompanies a copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to the public, the +best way to achieve this is to make it free software which everyone can redistribute and change +under these terms. + +To do so, attach the following notices to the program. It is safest to attach them to the start of +each source file to most effectively state the exclusion of warranty; and each file should have at +least the “copyright” line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program does terminal interaction, make it output a short notice like this when it starts in +an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate parts of the General +Public License. Of course, your program's commands might be different; for a GUI interface, you +would use an “about box”. + +You should also get your employer (if you work as a programmer) or school, if any, to sign a +“copyright disclaimer” for the program, if necessary. For more information on this, and how to apply +and follow the GNU GPL, see . + +The GNU General Public License does not permit incorporating your program into proprietary programs. +If your program is a subroutine library, you may consider it more useful to permit linking +proprietary applications with the library. If this is what you want to do, use the GNU Lesser +General Public License instead of this License. But first, please +read . + +THE OPENWEBF ENTERPRISE EXCEPTION + +As a special exception to the terms and conditions of the GNU General Public License version +3 ("GPL"), The OpenWebF Company and its affiliated entities grant you additional permission to use, +copy, modify, and distribute the software and documentation covered by this license, provided that: + +1. You are a current enterprise or professional subscriber who has signed the OpenWebF Software + Agreement, and +2. Your use complies with all terms specified in that Agreement. + +This exception does not invalidate any other reason why a work based on this license might be +covered by the GNU GPL version 3. + +This exception is an additional permission under section 7 of the GNU General Public License, +version 3 ("GPLv3"). + +For any parts of the software not explicitly covered by this exception, the terms of the GPL v3 +continue to apply. + +END OF THE OPENWEBF ENTERPRISE EXCEPTION diff --git a/README.md b/README.md index c91a481b3f..362f9a6552 100644 --- a/README.md +++ b/README.md @@ -1,122 +1,302 @@ -# [WebF](https://openwebf.com/) [![pub package](https://img.shields.io/pub/v/webf.svg)](https://pub.dev/packages/webf) +

+OpenWebF +

+

+ Bring JavaScript and Web Dev to Flutter
+

+

+ + Learn WebF + + | + + Developer Guide + + | + + Add WebF to Flutter + +

+

+ + + + + + + + + +

-WebF (Web on Flutter) is a W3C standards compliant Web rendering engine based on Flutter, it can run web applications on Flutter natively. +## What is WebF? -- **W3C Standards Compliant:** WebF use HTML/CSS and JavaScript to render contents on flutter. It can achieve 100% consistency with browser rendering. -- **Front-End Framework Supports:** WebF is W3C standards compliant, so it can be used by many Front-End frameworks, such as [React](https://reactjs.org/), [Vue](https://vuejs.org/). -- **Expand your Web app with Flutter:** WebF is fully customizable. You can define a customized HTML element with Flutter Widget and use it in your application. Or add a JavaScript API for any Dart library from registry. -- **Web Development Experience:** WebF supports inspecting DOM structure, CSS styles and debugging JavaScript with Chrome DevTools, just like the web development experience of your browser. -- **Write Once, Run Anywhere:** With the power of WebF, You can write your web application and run it on any device flutter supports, and you can still run your apps in Node.js and Web browsers with the same codebase. +**WebF is a W3C/WHATWG-compliant web runtime for Flutter** that implements HTML, CSS, and the DOM, running JavaScript in a browser-like environment. It's not a browser—it's an **application runtime optimized for building native apps** using web technologies. +Unlike traditional WebViews, WebF features: +- A **custom Flutter-based rendering engine** rather than relying on system browsers +- **Direct JavaScript-to-native communication** without traditional bridge limitations +- Ability to **embed Flutter widgets as HTML elements** within the app UI +- An **application-first design** with a persistent JavaScript context -## The Relationship between WebF and Kraken +## Why WebF? -The WebF project is a community support version of [Alibaba's Kraken Project](https://github.com/openkraken/kraken). On May 10, 2022, The Kraken Dev Team was dismissed and the project itself are discontinued. +**Build Fast. Ship Fast. Run Fast.** -The core developer and architector: [andycall](https://github.com/andycall), who is from the original Kraken Team. Leave the Alibaba Group and launch this project, to keep following the original ambition of the Kraken project. +WebF seamlessly glues Web, Flutter, and Native platforms together, enabling you to: -For more details(zh_CN): https://www.zhihu.com/question/534811524/answer/2595510449 +- **🚀 Build Fast:** Develop with React, Vue, Svelte, Solid + TailwindCSS, build with Vite or Webpack, and leverage the entire npm ecosystem - it all just works in WebF +- **📦 Ship Fast:** Deploy once across all Flutter-supported platforms (iOS, Android, Windows, macOS, Linux) from a single codebase +- **⚡ Run Fast:** Experience native-like performance with sub-100ms cold starts and 60fps animations that outpaces traditional WebView solutions -## Join the community (Beta) +## Key Features + +### Web Standards Compliance + +- **🔷 Modern JavaScript (ES6+)** - QuickJS runtime with async/await, Promises, modules, optional chaining, and template literals +- **🔷 Essential DOM APIs** - Element creation/manipulation, event listeners (capture/bubble), query selectors, classList, custom elements, MutationObserver +- **🔷 Comprehensive CSS Support** - Flexbox layouts, positioned layouts (absolute/relative/fixed/sticky), flow layouts, colors, gradients, transforms (2D/3D), transitions, animations, CSS variables, media queries, pseudo-classes +- **🔷 Web APIs** - `fetch`, `XMLHttpRequest`, `WebSockets`, `localStorage`, `sessionStorage`, `Canvas 2D`, `SVG`, URL parsing, timers -[![Discord Shield](https://discordapp.com/api/guilds/1008119434688344134/widget.png?style=banner1)](https://discord.gg/DvUBtXZ5rK) +### Framework & Tooling Compatibility -## Version requirement +- **⚛️ Frameworks:** React, Vue, Svelte, Preact, Solid, Qwik - your existing components and hooks work without modification +- **🛠️ Build Tools:** Vite (recommended), Webpack, esbuild, Rollup, Parcel - HMR, tree-shaking, code splitting all supported +- **🎨 Styling:** Tailwind CSS v3, Sass/SCSS, PostCSS, CSS Modules, Styled Components, Emotion +- **📦 npm Ecosystem:** Access to thousands of npm packages and the entire JavaScript ecosystem -| WebF | Flutter | -| -------------------- | ------- | -| `>= 0.12.0 < 0.14.0` | `3.0.5` | -| `>= 0.14.0` | `3.3.10` and `3.7.3` | +### Flutter Integration -## How to use +- **🔗 Hybrid UI** - Embed Flutter widgets as HTML custom elements with native performance and platform-appropriate appearance +- **🎯 Advanced Gestures** - Handle complex touch interactions with native precision via `FlutterGestureDetector` +- **📱 Native Plugins** - Access Flutter plugins (Share, Deep linking, and more) as npm packages +- **🏗️ Cupertino Components** - iOS-style native components without CSS emulation -> All front-end frameworks based on the WhatWG DOM standard are supported; this time, we are using Vue as an example. +### Developer Experience -### 1. Use vue-cli to generate your front-end project +- **🔍 Chrome DevTools** - Console, DOM inspection, and network monitoring +- **📊 In-App DevTools** - FPS, frame timing, and memory monitoring +- **🔥 Hot Module Replacement** - Full HMR support that preserves state across updates +- **⚡ Async Rendering** - Batched DOM updates that are 20x cheaper than browser implementations -> ES6 modules are not supported yet, so Vite is not supported. +### Deployment & Performance -```bash -vue create app -cd app -npm run serve -``` +- **🚀 Over-the-Air Updates** - Deploy instantly via CDN without app store reviews (compliant with Apple App Store and Google Play Store policies) +- **⚡ Fast Startup** - Production cold start < 100ms, development 200-300ms +- **🎮 Smooth Animations** - 60fps/120fps CSS transform animations with hardware acceleration +- **💾 Optimized Memory** - Typical 10-30MB JavaScript heap with shared rendering context +- **🔒 Security** - Application sandbox, keychain/keystore encrypted storage with biometric protection, HTTPS enforcement -And the Vue development server will be hosted at `http://:8080/`. +## How It Works -### 2. Add webf as a dependency for your flutter apps. +### Architecture Overview -**packages.yaml** +WebF combines two complementary layers to deliver a complete web runtime: -```yaml -dependencies: - webf: - webf_websocket: -``` +#### 1. Web Standards Layer +- **QuickJS JavaScript Runtime** - Lightweight engine supporting ES6+ with a single, persistent context per instance +- **W3C/WHATWG DOM Implementation** - Essential DOM APIs with event handling (capture/bubble phases) +- **CSSOM Implementation** - CSS parsing and rule calculation following web standards -**import** +#### 2. Custom Rendering Engine +- **Flutter-Based Layout Engine** - Supports Flexbox (recommended), positioned layouts, and flow layouts +- **Hardware-Accelerated Rendering** - Direct integration with Flutter's rendering pipeline +- **No System Dependencies** - Independent of system WebViews or browser engines -```dart -import 'package:webf/webf.dart'; -import 'package:webf_websocket/webf_websocket.dart'; -``` +### Rendering Pipeline + +1. **JavaScript Execution** → Modifies the DOM +2. **CSS Calculation** → Rules are calculated and applied +3. **Layout** → Element positions and sizes determined +4. **Paint** → Visual representation created +5. **Composite** → Flutter widgets composite the final output + +**Key Optimization:** WebF tracks "dirty" nodes to recalculate only affected subtrees (similar to React's reconciliation), and batches DOM updates to process them in the next frame, preventing layout thrashing. + +### Performance Benefits + +- **Native-Like Speed** - No WebView overhead, runs directly on Flutter's rendering pipeline +- **Fast Startup** - Sub-100ms cold starts with lightweight runtime +- **Optimized Memory** - Efficient resource usage with shared rendering context +- **Smooth Animations** - 60fps/120fps performance across all platforms +- **Dedicated Thread** - JavaScript runs in a dedicated thread without blocking UI + +## Getting Started + +### For Web Developers + +Get started quickly using **WebF Go** - a preview app that lets you test WebF applications on real devices without building a custom Flutter app. -**init** +#### Prerequisites +- Node.js (latest LTS recommended) + +#### Quick Start + +**1. Download WebF Go** + - **Desktop**: Download from [https://openwebf.com/en/go](https://openwebf.com/en/go) (macOS, Windows, or Linux) + - **Mobile**: Download from App Store (iOS) or Google Play (Android) + +**2. Create Your Project** + ```bash + npm create vite@latest + ``` + Select your preferred framework (React, Vue, Svelte, etc.) when prompted. + +**3. Start Development Server** + ```bash + cd + npm install + npm run dev + ``` + +**4. Load in WebF Go** + - Copy the Network URL from your terminal (typically `http://localhost:5173`) + - Paste it into the WebF Go app's input field + - Tap "Go" + +Your application will render in the native WebF environment with hot-reload support for instant code changes! + +### For Flutter Developers + +Add WebF to your existing Flutter app to enable web content rendering. + +#### Installation + +1. Add WebF dependency to your `pubspec.yaml`: + ```yaml + dependencies: + webf: ^0.23.10 # Check pub.dev for latest version + ``` + +2. Run `flutter pub get` + +#### Basic Setup ```dart +import 'package:webf/webf.dart'; + void main() { - WebFWebSocket.initialize(); - runApp(MyApp()); -} -``` + // Initialize WebF controller manager + WebFControllerManager.instance.initialize( + WebFControllerManagerConfig( + maxAliveInstances: 5, + maxAttachedInstances: 3, + ), + ); + // Add a controller with prerendering + WebFControllerManager.instance.addWithPrerendering( + name: 'home', + createController: () => WebFController(), + bundle: WebFBundle.fromUrl('https://example.com/'), + ); -### 3. Add the WebF widget to run your web applications. + runApp(MyApp()); +} -```dart -@override -Widget build(BuildContext context) { - final MediaQueryData queryData = MediaQuery.of(context); - final Size viewportSize = queryData.size; - - return Scaffold( - body: Center( - child: Column( - children: [ - WebF( - devToolsService: ChromeDevToolsService(), // Enable Chrome DevTools Services - viewportWidth: viewportSize.width - queryData.padding.horizontal, // Adjust the viewportWidth - viewportHeight: viewportSize.height - queryData.padding.vertical, // Adjust the viewportHeight - bundle: WebFBundle.fromUrl('http://:8080/'), // The page entry point +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: WebF.fromControllerName( + controllerName: 'home', + loadingWidget: CircularProgressIndicator(), ), - ], - ), - )); + ), + ); + } } ``` -### 4. Run +#### Loading Content -```bash -flutter run -``` +WebF supports multiple content sources: +- **Remote URLs:** `WebFBundle.fromUrl('https://example.com/')` +- **Local assets:** `WebFBundle.fromUrl('assets:///assets/web/index.html')` +- **Development servers:** `WebFBundle.fromUrl('http://localhost:3000/')` +- **Inline HTML:** `WebFBundle.fromContent('...')` + +## Documentation + +📚 **[Complete Documentation](https://openwebf.com/en/docs)** - Learn WebF architecture, developer guides, and Flutter integration + +- **[Learn WebF](https://openwebf.com/en/docs/learn-webf)** - Overview, architecture, and key features +- **[Developer Guide](https://openwebf.com/en/docs/developer-guide)** - Getting started, frameworks, CSS, debugging, deployment +- **[Add WebF To Flutter](https://openwebf.com/en/docs/add-webf-to-flutter)** - Integration guide for Flutter engineers + +## Use Cases + +WebF is ideal for: - +- **✅ Content-Heavy Applications** - Apps with dynamic, frequently-updated content +- **✅ Rapid Prototyping** - Leverage web development speed for fast iteration +- **✅ Cross-Platform Apps** - Single codebase for iOS, Android, and desktop +- **✅ Hybrid Native-Web UIs** - Mix Flutter widgets with web content seamlessly +- **✅ Over-the-Air Updates** - Deploy features and fixes without app store review delays -## How it works +## Sponsors -WebF provides a rendering engine which follows the W3C standards like web browsers do. It can render HTML/CSS and execute JavaScript. It's built on top of the flutter rendering pipelines and implements its own layout and painting algorithms. -With WebF, Web Apps and Flutter Apps share the same rendering context. It means that you can use Flutter Widgets to define your HTML elements and embed your Web App as a Flutter Widget in your flutter apps. +

+ + black-logo + +

+ +## License + +WebF is dual-licensed to provide flexibility for different use cases: + +### Open Source License (GPL-3.0) + +WebF is licensed under the **GNU General Public License version 3 (GPL-3.0)** with the OpenWebF Enterprise Exception. + +**What this means:** +- ✅ **Free for open-source projects** - Use WebF freely in open-source applications under GPL-3.0 terms +- ✅ **Source code available** - Full access to the source code on [GitHub](https://github.com/openwebf/webf) +- ✅ **Community contributions welcome** - Join the community and contribute to the project +- ✅ **Package developers exemption** - Published open-source packages (npm/Flutter packages) that depend on WebF can use any license +- ⚠️ **GPL requirements apply to applications** - Applications using WebF must comply with GPL-3.0 terms (open source your application code) + +### Enterprise License + +For commercial applications that cannot comply with GPL-3.0 requirements, we offer the **OpenWebF Enterprise License**: + +- ✅ **Commercial use** - Use WebF in closed-source commercial applications +- ✅ **No GPL restrictions** - Freedom from GPL copyleft requirements +- ✅ **Enterprise support** - Priority technical support and assistance +- ✅ **Additional features** - Access to enterprise-only features and early releases + +**Enterprise Installation:** +```yaml +dependencies: + webf: ^0.23.10 # Enterprise version available on pub.dev +``` + +### Choosing the Right License - +| Use Case | Recommended License | Notes | +|----------|---------------------|-------| +| Open-source applications | GPL-3.0 (Open Source) | Your app must be GPL-3.0 compatible | +| Published npm/Flutter packages | Apache-2.0 or MIT | Your package can use permissive licenses | +| Internal/non-distributed apps | GPL-3.0 (Open Source) | No distribution = no GPL obligations | +| Commercial closed-source apps | Enterprise License | Required for proprietary applications | +| Apps distributed via app stores | Enterprise License | Required unless app is open source | +| Educational/research projects | GPL-3.0 (Open Source) | Free for academic use | -## 👏 Contributing [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://github.com/openwebf/webf/pulls) +### Contact -By contributing to WebF, you agree that your contributions will be licensed under its Apache-2.0 License. +For licensing questions or to obtain an Enterprise License: +- **Email:** support@openwebf.com +- **Website:** [https://openwebf.com](https://openwebf.com) +- **Software Agreement:** [https://openwebf.com/en/software-agreement](https://openwebf.com/en/software-agreement) -Read our [contributing guide](https://github.com/openwebf/webf/blob/main/.github/CONTRIBUTING.md) and let's build a better WebF project together. +See the [LICENSE](LICENSE) file for the full GPL-3.0 license text and OpenWebF Enterprise Exception. -Thank you to all the people who already contributed to [OpenWebF](https://github.com/openwebf) and [OpenKraken](https://github.com/openkraken)! +## Community & Support -Copyright (c) 2022-present, The OpenWebF authors. +- **Discord:** [Join our community](https://discord.gg/DvUBtXZ5rK) +- **Twitter/X:** [@openwebf](https://x.com/openwebf) +- **GitHub:** [github.com/openwebf/webf](https://github.com/openwebf/webf) +- **Email:** support@openwebf.com diff --git a/THIRD_PARTY_NOTICES b/THIRD_PARTY_NOTICES new file mode 100644 index 0000000000..08b309adfe --- /dev/null +++ b/THIRD_PARTY_NOTICES @@ -0,0 +1,229 @@ +This project includes third-party software components. The following sections +summarize those components, their licenses, and where to find the full license +texts or headers in this repository. + +This file is informational only and does not modify the terms of any license. + +------------------------------------------------------------------------------- +Dart SDK headers and Dart CSS parser code +------------------------------------------------------------------------------- + +- Component: Dart embedding APIs (C/C++ headers) and Dart CSS parser code +- Origin: Dart project authors +- License: BSD-style, 3-clause +- License text: + - third_party/dart/LICENSE + - bridge/third_party/dart/LICENSE +- Representative source locations: + - bridge/third_party/dart/include/* + - webf/lib/src/css/parser/* + - opensource/legacy_impls/webf/lib/src/css/parser/* + +------------------------------------------------------------------------------- +Flutter (FML and framework-derived code) +------------------------------------------------------------------------------- + +- Component: Reference-counting utilities and other runtime helpers +- Origin: Flutter (FML and framework) +- License: BSD-style, 3-clause +- License text: + - bridge/third_party/flutter/LICENSE +- Representative source locations: + - bridge/foundation/ref_counter.h + - bridge/foundation/ref_ptr.h + - bridge/foundation/ref_counted_internal.h + - opensource/legacy_impls/bridge/foundation/* + - various gesture/rendering helpers in opensource/legacy_impls/webf/* + +------------------------------------------------------------------------------- +Chromium / Blink derived code +------------------------------------------------------------------------------- + +- Component: Base utilities, numerics, memory helpers, URL and CSS implementation +- Origin: The Chromium Authors +- License: BSD-style, 3-clause +- License text: + - bridge/third_party/chromium/LICENSE +- Representative source locations: + - bridge/core/base/* + - bridge/core/base/numerics/* + - bridge/core/base/memory/* + - bridge/core/base/strings/* + - bridge/core/css/* + - bridge/core/platform/url/* + - bridge/scripts/code_generator/templates/json_templates/* + +------------------------------------------------------------------------------- +WebKit / Apple Inc. derived code +------------------------------------------------------------------------------- + +- Component: DOM, CSS, URL, and math utilities +- Origin: Apple Inc. and related WebKit contributors +- License: BSD-style, 2- or 3-clause (text embedded in file headers) +- License text: + - Embedded in individual source files (see per-file headers) +- Representative source locations: + - bridge/core/platform/math_extras.h + - bridge/core/platform/url/kurl.cc + - bridge/core/css/* + - bridge/core/dom/* + - bridge/foundation/string/* + - bridge/scripts/code_generator/templates/json_templates/style_property_shorthand*.tpl + +------------------------------------------------------------------------------- +WebKit dtoa implementation +------------------------------------------------------------------------------- + +- Component: dtoa (double to string) utilities +- Origin: Apple Inc. / WebKit +- License: GNU Lesser General Public License, version 2.1 or later (LGPL-2.1+) +- License text: + - COPYING.LIB (pointer to official LGPL-2.1 text) +- Representative source locations: + - bridge/foundation/dtoa.cc + - bridge/foundation/dtoa.h + +------------------------------------------------------------------------------- +Mozilla URL parser (nsURLParsers) +------------------------------------------------------------------------------- + +- Component: URL parsing logic +- Origin: Mozilla (Netscape Communications Corporation and contributors) +- License: Tri-licensed MPL-1.1 / GPL-2.0-or-later / LGPL-2.1-or-later +- License texts (references): + - licenses/MPL-1.1.txt + - licenses/GPL-2.0.txt + - COPYING.LIB (for LGPL-2.1) +- Representative source locations: + - bridge/core/platform/url/url_parse.cc + +Note: This project uses the GPL path (GPL-2.0-or-later) for compatibility with +the overall copyleft licensing. + +------------------------------------------------------------------------------- +QuickJS JavaScript engine +------------------------------------------------------------------------------- + +- Component: QuickJS JavaScript engine +- Origin: Fabrice Bellard and Charlie Gordon +- License: MIT +- License text: + - third_party/quickjs/LICENSE + - bridge/third_party/quickjs/LICENSE +- Representative source locations: + - third_party/quickjs/* + - bridge/third_party/quickjs/* + - webf/ios/Classes/third_party/quickjs/* + +------------------------------------------------------------------------------- +double-conversion +------------------------------------------------------------------------------- + +- Component: double-conversion floating-point routines +- Origin: V8 project authors +- License: BSD-style, 3-clause +- License text: + - third_party/double_conversion/LICENSE + - bridge/third_party/double_conversion/LICENSE +- Representative source locations: + - third_party/double_conversion/double-conversion/* + - bridge/third_party/double_conversion/double-conversion/* + +------------------------------------------------------------------------------- +CityHash +------------------------------------------------------------------------------- + +- Component: CityHash hashing functions +- Origin: Google Inc. +- License: MIT (text embedded in header) +- License text: + - third_party/cityhash/city.h (header comment) + - bridge/third_party/cityhash/city.h (header comment) +- Representative source locations: + - third_party/cityhash/* + - bridge/third_party/cityhash/* + +------------------------------------------------------------------------------- +modp_b64 +------------------------------------------------------------------------------- + +- Component: High-performance Base64 encoder/decoder +- Origin: Nick Galbreath / modp.com +- License: BSD-style, 3-clause (text embedded in source) +- License text: + - third_party/modp_b64/modp_b64.cc (header comment) + - bridge/third_party/modp_b64/modp_b64.cc (header comment) +- Representative source locations: + - third_party/modp_b64/* + - bridge/third_party/modp_b64/* + +------------------------------------------------------------------------------- +Gumbo-Parser +------------------------------------------------------------------------------- + +- Component: HTML5 parsing library +- Origin: Google Inc. +- License: Apache License 2.0 +- License text: + - Header comments in bridge/third_party/gumbo-parser/src/* + - Header comments in third_party/gumbo-parser/src/* +- Representative source locations: + - third_party/gumbo-parser/src/* + - bridge/third_party/gumbo-parser/src/* + +------------------------------------------------------------------------------- +Google Benchmark +------------------------------------------------------------------------------- + +- Component: microbenchmark support library +- Origin: Google Inc. +- License: Apache License 2.0 +- License text: + - bridge/third_party/benchmark/LICENSE +- Representative source locations: + - bridge/third_party/benchmark/* + +------------------------------------------------------------------------------- +GoogleTest +------------------------------------------------------------------------------- + +- Component: C++ unit testing framework +- Origin: Google Inc. +- License: BSD-style, 3-clause +- License text: + - bridge/third_party/googletest/LICENSE +- Representative source locations: + - bridge/third_party/googletest/* + +------------------------------------------------------------------------------- +ios-cmake toolchain +------------------------------------------------------------------------------- + +- Component: CMake iOS toolchain file +- Origin: ios-cmake project (multiple authors) +- License: BSD-3-Clause (text embedded in file) +- License text: + - bridge/cmake/ios.toolchain.cmake (header comment) +- Representative source locations: + - bridge/cmake/ios.toolchain.cmake + +------------------------------------------------------------------------------- +Node.js / JavaScript ecosystem dependencies +------------------------------------------------------------------------------- + +The repository uses a number of JavaScript/TypeScript packages in +development tools, CLI utilities, integration tests, and example apps. +These dependencies are typically installed under: + +- bridge/polyfill/node_modules/* +- integration_tests/node_modules/* +- opensource/cli/node_modules/* +- opensource/use_cases/node_modules/* +- webf_apps/*/node_modules/* + +Most of these packages are licensed under permissive licenses such as MIT, +BSD, Apache-2.0, or ISC; some data or utility packages may use CC0-1.0 or +CC-BY-style licenses. The authoritative license for each package can be +found in its own LICENSE file within node_modules or in the relevant lock +files (package-lock.json, pnpm-lock.yaml). + diff --git a/bridge/.gitignore b/bridge/.gitignore index 2f874e24f5..650e020120 100644 --- a/bridge/.gitignore +++ b/bridge/.gitignore @@ -1,5 +1,15 @@ xcschememanagement.plist cmake-build-* -build +polyfill/build/ out +code_gen +.cache + +bin + +# ignore blink soft link +blink_source + +# ignore preview tools +.preview-bundles \ No newline at end of file diff --git a/bridge/.pubignore b/bridge/.pubignore new file mode 100644 index 0000000000..4b3566c6ab --- /dev/null +++ b/bridge/.pubignore @@ -0,0 +1,2 @@ +cmake-* +scripts/code_generator \ No newline at end of file diff --git a/bridge/AGENTS.md b/bridge/AGENTS.md new file mode 100644 index 0000000000..e8cf16e893 --- /dev/null +++ b/bridge/AGENTS.md @@ -0,0 +1,43 @@ +# Repository Guidelines + +## Project Structure & Module Organization +- `bridge/`: C++17 native bridge (JS runtime, DOM/CSS, bindings). Core code in `bridge/core/`; generated code in `bridge/code_gen/`; unit tests in `bridge/test/`; third-party deps in `bridge/third_party/`. +- `webf/`: Dart/Flutter engine (DOM/layout/painting). Tests in `webf/test/` and `webf/integration_test/`. +- `cli/`: TypeScript CLI/codegen helpers. Tests in `cli/test/`. +- `integration_tests/`: E2E + snapshot runner and tooling. +- `scripts/`: Repo-wide build/codegen utilities invoked by `npm run ...`. + +## Build, Test, and Development Commands +Run from repo root (`../`) unless noted: +- Install dependencies: `npm install` +- Build bridge (macOS): `npm run build:bridge:macos` (other targets: `build:bridge:{android,ios,linux,windows}`) +- Clean build artifacts: `npm run build:clean` +- Regenerate bindings/types: `npm run bindgen` (don’t hand-edit files under `bridge/code_gen/`) +- Bridge unit tests: `node scripts/run_bridge_unit_test.js` +- Flutter analyze/format: `npm run lint` / `npm run format` +- Flutter tests: `cd webf && flutter test` +- Integration tests: `cd integration_tests && npm run integration` +- CLI tests: `cd cli && npm test` + +## Coding Style & Naming Conventions +- C++ (bridge): 2-space indent, 120 cols, Chromium style via `.clang-format`; files use `.cc`/`.h`. +- Dart (webf): follow `webf/analysis_options.yaml`; files `snake_case.dart`, types `PascalCase`, members `camelCase`. +- TypeScript (cli): strict TS; keep generators deterministic; Jest tests use `*.test.ts`. + +## Testing Guidelines +- Bridge uses GoogleTest; keep tests small and behavior-focused, and mirror source paths under `bridge/test/`. +- Do not create or run tests unless explicitly requested. +- Do not modify existing tests unless explicitly instructed. + +## Commit & Pull Request Guidelines +- Use Conventional Commits: `type(scope): subject` (e.g., `fix(bridge): handle null QualifiedName`). +- PRs should include rationale, linked issues, and exact verification steps; add screenshots for UI changes in `webf_apps/`. +- Do not commit anything unless explicitly requested. + +## Agent-Specific Instructions +- Always explain why changes are needed before making them. +- Do not build anything unless explicitly requested. + +## Security & Configuration Tips +- Initialize submodules: `git submodule update --init --recursive`. +- Common build env: `WEBF_BUILD=Release`, `ENABLE_PROFILE=true`. Platform deps include Flutter SDK + CMake/Clang (macOS) and Xcode/NDK where applicable. diff --git a/bridge/CLAUDE.md b/bridge/CLAUDE.md new file mode 100644 index 0000000000..fe7e9aa75a --- /dev/null +++ b/bridge/CLAUDE.md @@ -0,0 +1,393 @@ +# C++ Development Guide (bridge/) + +This guide covers C++ development in the `bridge/` directory, which contains the JavaScript runtime, DOM API implementations, and HTML parsing logic. + +## Build Commands +- Build for macOS: `npm run build:bridge:macos` (debug) or `npm run build:bridge:macos:release` (release) +- Build for iOS: `npm run build:bridge:ios` (debug) or `npm run build:bridge:ios:release` (release) +- Build for Android: `npm run build:bridge:android` (debug) or `npm run build:bridge:android:release` (release) +- Clean build: `npm run build:clean` + +## Build Error Resolution +- When encountering build errors: + - Read the full error message to identify the specific issue (missing includes, type mismatches, undefined symbols) + - For C++ errors, check: + - Missing header includes + - Namespace qualifications + - Template instantiation issues + - FFI type compatibility (Handle vs Dart_Handle) + - Build incrementally after each fix + - Use `npm run build:bridge:macos` for quick iteration on macOS +- Common C++ build issues: + - `Handle` should be `Dart_Handle` in FFI contexts + - Lambda signatures must match expected function signatures + - Include necessary headers for all used types + +## C++ Code Style +- Based on Chromium style (.clang-format) +- Standard: C++17 +- Column limit: 120 characters +- Use 2-space indentation + +## C++ Testing +- Run bridge unit tests: `node scripts/run_bridge_unit_test.js` +- See Bridge Unit Tests section for detailed guide + +## Important C++ Files and Patterns +- `webf_bridge.cc`: Main bridge entry point +- `executing_context.cc`: JavaScript context management +- `binding_object.h`: Base class for JS bindings +- `WEBF_EXPORT_C`: Macro for exporting C functions to Dart FFI + +## Bridge Unit Tests +Guide for running and debugging bridge unit tests. + +### Running Bridge Unit Tests +```bash +# Run all bridge unit tests (automatically builds and runs debug version) +node scripts/run_bridge_unit_test.js +``` + +### Understanding Test Output +- Tests run via Google Test framework +- Green `[ PASSED ]` indicates success +- Red `[ FAILED ]` shows failures with details +- Summary shows total tests run and time taken + +### Common Issues +1. **Build Failures**: The script automatically builds before running tests. If build fails: + - Check CMake configuration + - Ensure all dependencies are installed + - Try `npm run build:bridge:macos` first to isolate build issues + +2. **Test Failures**: + - Check the assertion message for details + - Use `--verbose` flag to see more output + - Individual test files are in `bridge/test/` + +### Writing New Tests +- Add test files to `bridge/test/` +- Use Google Test macros: `TEST()`, `EXPECT_EQ()`, etc. +- Tests are automatically discovered by CMake + +## iOS Build Troubleshooting +Guide for fixing iOS undefined symbol errors and understanding the build structure. + +### Common iOS Build Issues + +#### Undefined Symbol Errors +When encountering undefined symbol errors like: +``` +Undefined symbols for architecture arm64: + "_InvokeBindingObject", referenced from: + webf::MemberMutationCallback::Fire(...) +``` + +**Root Cause**: Missing source files in iOS build configuration + +**Solution**: +1. Check `ios/webf_ios.podspec` for missing source files +2. Add missing files to the `source_files` pattern: + ```ruby + s.source_files = 'Classes/**/*', 'bridge/**/*.{h,cc,cpp,m,mm}' + ``` +3. Clean and rebuild: + ```bash + cd ios && pod install --repo-update + cd example/ios && pod install --repo-update + flutter clean && flutter build ios + ``` + +#### Build Configuration Structure +``` +webf/ +├── ios/ +│ ├── webf_ios.podspec # Main pod specification +│ ├── Classes/ # iOS-specific code +│ └── prepare.sh # Build preparation script +├── bridge/ # C++ source files +│ ├── bindings/ +│ │ └── qjs/ +│ │ └── member_installer.cc # Often missing file +│ └── CMakeLists.txt +``` + +#### Key Files to Check +1. **webf_ios.podspec**: Defines which source files are included +2. **CMakeLists.txt**: C++ build configuration +3. **prepare.sh**: Pre-build script that sets up the environment + +#### Debugging Steps +1. **Verify File Inclusion**: + ```bash + # Check if file is included in build + grep -r "member_installer" ios/ + ``` + +2. **Clean Build**: + ```bash + rm -rf ~/Library/Developer/Xcode/DerivedData + cd ios && rm -rf Pods Podfile.lock + flutter clean + ``` + +3. **Verbose Build**: + ```bash + flutter build ios --verbose + ``` + +## iOS WebF Source Compilation +iOS-specific source compilation details. + +### iOS Build System Overview +WebF uses CocoaPods to integrate C++ code into iOS Flutter apps. The build process involves: + +1. **CMake Build**: Compiles C++ bridge code +2. **Pod Integration**: Links compiled libraries with Flutter +3. **Asset Bundling**: Includes required resources + +### Source File Patterns +The `webf_ios.podspec` must include all necessary source files: + +```ruby +s.source_files = [ + 'Classes/**/*.{h,m,mm}', # iOS platform code + 'bridge/**/*.{h,cc,cpp}', # C++ bridge code + 'bridge/bindings/**/*.{h,cc}', # Binding implementations + 'bridge/third_party/**/*.{h,c,cc}' # Third-party dependencies +] +``` + +### Common Compilation Issues + +#### Missing Headers +``` +fatal error: 'member_installer.h' file not found +``` +**Fix**: Add to `preserve_paths` and `public_header_files` + +#### Symbol Visibility +``` +Undefined symbols for architecture x86_64 +``` +**Fix**: Ensure C++ symbols are exported: +```cpp +WEBF_EXPORT_C void SomeFunction() { } +``` + +#### Architecture Mismatches +**Fix**: Ensure all libraries are built for required architectures: +```ruby +s.pod_target_xcconfig = { + 'VALID_ARCHS' => 'arm64 x86_64', +} +``` + +### Build Optimization +1. **Precompiled Headers**: Use PCH for common includes +2. **Module Maps**: Define module boundaries +3. **Link-Time Optimization**: Enable LTO for release builds + +## UI Command Ring Buffer Design +Design documentation for the UI command ring buffer. + +### Overview +The UI Command Ring Buffer is a high-performance, lock-free data structure designed to handle UI commands between the JS thread and Flutter UI thread in WebF. + +### Design Goals +1. **Lock-free**: Minimize thread contention +2. **Bounded memory**: Fixed-size buffer with overwrite semantics +3. **Cache-friendly**: Optimize for CPU cache lines +4. **Type-safe**: Compile-time command validation + +### Architecture + +#### Ring Buffer Structure +```cpp +template +class RingBuffer { + static_assert((Size & (Size - 1)) == 0, "Size must be power of 2"); + +private: + alignas(64) std::atomic head_{0}; // Producer position + alignas(64) std::atomic tail_{0}; // Consumer position + alignas(64) T buffer_[Size]; // Command storage + +public: + bool try_push(const T& item); + bool try_pop(T& item); +}; +``` + +#### Command Structure +```cpp +struct UICommand { + enum Type : uint8_t { + CREATE_ELEMENT = 0, + UPDATE_STYLE = 1, + REMOVE_ELEMENT = 2, + // ... more types + }; + + Type type; + uint32_t target_id; + union { + CreateElementData create; + UpdateStyleData style; + RemoveElementData remove; + } data; +}; +``` + +### Performance Characteristics +- **Throughput**: ~10M ops/sec on modern CPUs +- **Latency**: <100ns per operation +- **Memory**: Fixed O(1) memory usage +- **Scalability**: Single producer, single consumer + +### Usage Patterns + +#### Producer (JS Thread) +```cpp +void dispatchUICommand(const UICommand& cmd) { + if (!command_buffer_.try_push(cmd)) { + // Handle overflow - log or implement backpressure + handleOverflow(); + } +} +``` + +#### Consumer (UI Thread) +```cpp +void processUICommands() { + UICommand cmd; + while (command_buffer_.try_pop(cmd)) { + executeCommand(cmd); + } +} +``` + +## UI Command Ring Buffer Usage Guide + +### Quick Start + +#### Including the Buffer +```cpp +#include "ui_command_buffer.h" + +// Create a buffer with 1024 command slots +UICommandBuffer<1024> command_buffer; +``` + +#### Sending Commands +```cpp +// From JS thread +void sendCreateElement(uint32_t id, const char* tag_name) { + UICommand cmd; + cmd.type = UICommand::CREATE_ELEMENT; + cmd.target_id = id; + strncpy(cmd.data.create.tag_name, tag_name, 63); + + if (!command_buffer.try_push(cmd)) { + LOG(ERROR) << "Command buffer full!"; + } +} +``` + +#### Processing Commands +```cpp +// On UI thread - typically called each frame +void onFrame() { + UICommand cmd; + int processed = 0; + + while (command_buffer.try_pop(cmd) && processed < MAX_COMMANDS_PER_FRAME) { + switch (cmd.type) { + case UICommand::CREATE_ELEMENT: + createElement(cmd.target_id, cmd.data.create.tag_name); + break; + // ... handle other command types + } + processed++; + } +} +``` + +### Best Practices + +1. **Buffer Sizing**: Choose power-of-2 sizes (512, 1024, 2048) +2. **Overflow Handling**: Implement metrics and alerts +3. **Batching**: Process multiple commands per frame +4. **Priority**: Consider separate buffers for high-priority commands + +### Performance Tuning + +#### Optimal Buffer Size +```cpp +// Measure and adjust based on workload +constexpr size_t BUFFER_SIZE = + DEBUG_BUILD ? 512 : // Smaller for testing + MOBILE ? 1024 : // Mobile constraints + 2048; // Desktop performance +``` + +#### CPU Affinity +```cpp +// Pin threads to cores for optimal cache usage +std::thread ui_thread([]() { + setCPUAffinity(UI_THREAD_CORE); + runUILoop(); +}); +``` + +## FFI Integration Patterns + +### C++ to Dart FFI +- Function naming conventions: + - C++ exports: `GetObjectPropertiesFromDart` + - Dart typedefs: `NativeGetObjectPropertiesFunc` + - Dart functions: `GetObjectPropertiesFunc` +- Async callback patterns: + - Use `Dart_Handle` for object handles + - Pass callbacks as function pointers + - Return results via callback, not return value +- String handling: + - Copy strings that might be freed: `std::string(const_char_ptr)` + - Use `toNativeUtf8()` and remember to free + +### Memory Management in FFI +- **Dart Handle Persistence**: + - For async operations, use `Dart_NewPersistentHandle_DL` to keep Dart objects alive + - Convert back with `Dart_HandleFromPersistent_DL` before use + - Always call `Dart_DeletePersistentHandle_DL` after the async operation completes + - Example pattern: + ```cpp + Dart_PersistentHandle persistent = Dart_NewPersistentHandle_DL(dart_handle); + // ... async operation ... + Dart_Handle handle = Dart_HandleFromPersistent_DL(persistent); + callback(handle, result); + Dart_DeletePersistentHandle_DL(persistent); + ``` + +### Thread Communication +- PostToJs: Execute on JS thread from other threads +- PostToDart: Return results to Dart isolate +- PostToJsSync: Synchronous execution (use sparingly, can cause deadlocks) + +### Error Handling in Async FFI +- For async callbacks with potential errors: + - Always provide error path in callbacks (e.g., `callback(object, nullptr)`) + - Handle cancellation cases in async operations + - Propagate errors through callback parameters, not exceptions +- Thread-safe error reporting: + - Copy error messages before crossing thread boundaries + - Use `std::string` to ensure string lifetime + - Consider error callback separate from result callback + +### Common FFI Pitfalls to Avoid +- **Handle lifetime**: Regular Dart_Handle becomes invalid after crossing threads +- **String ownership**: `const char*` from Dart may be freed after call +- **Callback lifetime**: Ensure callbacks aren't invoked after context destruction +- **Type mismatches**: `Handle` vs `Dart_Handle` in different contexts +- **Lambda captures**: Be explicit about what's captured and its lifetime +- **Synchronous deadlocks**: Avoid PostToJsSync when threads may interdepend \ No newline at end of file diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 3a746a2c3d..4e917575a2 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -1,42 +1,144 @@ -cmake_minimum_required(VERSION 3.10.0) -set(CMAKE_OSX_DEPLOYMENT_TARGET 10.11) +cmake_minimum_required(VERSION 3.20) project(WebF) -set(CMAKE_OSX_DEPLOYMENT_TARGET 10.11) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_OSX_DEPLOYMENT_TARGET 10.14) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to 'Release' as none was specified.") + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") +endif() + +if(CMAKE_SYSTEM_NAME MATCHES "MSYS" OR MINGW) + message("Running in an MSYS2 environment (MSYS or MinGW)") + + # Check environment variables first, then fall back to defaults + if(DEFINED ENV{MSYSTEM_PREFIX}) + set(MSYS2_PREFIX "$ENV{MSYSTEM_PREFIX}") + message("Using MSYSTEM_PREFIX from environment: $ENV{MSYSTEM_PREFIX}") + else() + set(ENV{MSYSTEM_PREFIX} "C:/msys64/clang64") + set(MSYS2_PREFIX "C:/msys64/clang64") + message("Using default MSYSTEM_PREFIX: C:/msys64/clang64") + endif() + + # Set MINGW_64 based on MSYS2_PREFIX or environment variable + if(DEFINED ENV{MINGW_64}) + set(MINGW_64 "$ENV{MINGW_64}") + message("Using MINGW_64 from environment: $ENV{MINGW_64}") + else() + # Try to derive from MSYS2_PREFIX, otherwise use default + string(REPLACE "/clang64" "/ucrt64" MINGW_64 "${MSYS2_PREFIX}") + if(MINGW_64 STREQUAL MSYS2_PREFIX) + set(MINGW_64 "C:/msys64/ucrt64") + endif() + message("Using derived/default MINGW_64: ${MINGW_64}") + endif() + +# set(CMAKE_C_COMPILER clang) +# set(CMAKE_CXX_COMPILER clang++) +# set(CMAKE_ASM_COMPILER clang) +# set(CMAKE_LINKER lld) +# set(CMAKE_AR llvm-ar) +# set(CMAKE_RANLIB llvm-ranlib) + + # Set C++20 standard for MSYS2 with libc++ + set(CMAKE_CXX_STANDARD 20) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) + add_compile_options(-stdlib=libc++) + add_link_options(-stdlib=libc++) + + # Define math constants that are not in standard C++ + add_compile_definitions(_USE_MATH_DEFINES) + # Exclude GDI from Windows.h + add_compile_definitions(NOGDI) + + if (DEFINED ENV{LIBRARY_OUTPUT_DIR}) + set(CMAKE_INSTALL_PREFIX $ENV{LIBRARY_OUTPUT_DIR}) + endif() + + # MSYS2 environments set the MSYSTEM_PREFIX environment variable + # which points to the root of the specific environment (e.g., /mingw64, /ucrt64, /mingw32) + set(LIBPTHREAD_DLL_PATH "${MINGW_64}/bin/libwinpthread-1.dll") + set(LIBGCC_DLL_PATH "${MINGW_64}/bin/libgcc_s_seh-1.dll") + set(LIBSTDCXX_DLL_PATH "${MINGW_64}/bin/libc++.dll") + + # Add ICU library support for MinGW + find_package(PkgConfig QUIET) + if(PKG_CONFIG_FOUND) + pkg_check_modules(ICU QUIET icu-uc icu-i18n icu-io) + if(ICU_FOUND) + message(STATUS "Found ICU via pkg-config") + list(APPEND BRIDGE_INCLUDE ${ICU_INCLUDE_DIRS}) + endif() + endif() + + + # Install the DLL to the runtime binary directory + # 'bin' is the standard destination for runtime executables and required DLLs + install(FILES "${LIBPTHREAD_DLL_PATH}" DESTINATION .) + install(FILES "${LIBGCC_DLL_PATH}" DESTINATION .) + install(FILES "${LIBSTDCXX_DLL_PATH}" DESTINATION .) +endif() + + if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64") + # Allow CMAKE_OSX_ARCHITECTURES to be set from command line + # If not specified, default to native architecture + if(NOT CMAKE_OSX_ARCHITECTURES) + # Detect native architecture + execute_process( + COMMAND uname -m + OUTPUT_VARIABLE NATIVE_ARCH + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(NATIVE_ARCH STREQUAL "arm64") + set(CMAKE_OSX_ARCHITECTURES "arm64") + else() + set(CMAKE_OSX_ARCHITECTURES "x86_64") + endif() + endif() +endif () + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + # Use libc++ statically linked on Linux + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++ -static-libgcc") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -stdlib=libc++ -static-libgcc") + + # Ensure we're using clang/clang++ for libc++ support + if (NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") + message(WARNING "Using libc++ requires Clang compiler. Current compiler: ${CMAKE_CXX_COMPILER_ID}") + endif() + + message(STATUS "Using static libc++ for Linux build") + + # STATIC_QUICKJS is set via build script (-DSTATIC_QUICKJS=true) +endif () + +set(WEBF_JS_ENGINE $ENV{WEBF_JS_ENGINE}) +if(NOT WEBF_JS_ENGINE) + set(WEBF_JS_ENGINE "quickjs") endif() +find_package(Threads REQUIRED) + if (${ENABLE_PROFILE}) add_definitions(-DENABLE_PROFILE=1) else () add_definitions(-DENABLE_PROFILE=0) endif () -execute_process( - COMMAND bash "-c" "npm install" - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/scripts/code_generator -) # install code_generator deps - -execute_process( - COMMAND bash "-c" "npm run build" - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/scripts/code_generator -) # g - -execute_process( - COMMAND bash "-c" "node bin/code_generator -s ../../core -d ../../out" - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/scripts/code_generator -) # generate elements code - -execute_process( - COMMAND bash "-c" "read dart_sdk < <(type -p dart) && echo $\{dart_sdk%/*\}/cache/dart-sdk/include | xargs" - OUTPUT_VARIABLE DART_SDK -) -string(REGEX REPLACE "\n$" "" DART_SDK "${DART_SDK}") +if (${ENABLE_LOG}) + add_definitions(-DENABLE_LOG=1) +else() + add_definitions(-DENABLE_LOG=0) +endif() list(APPEND WEBF_PUBLIC_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/include/webf_bridge.h @@ -63,56 +165,71 @@ if (ENABLE_ASAN) add_link_options(-fsanitize=address -fno-omit-frame-pointer) endif () -if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") - # Avoid quickjs stackoverflow. - add_compile_options(-O1) -endif() - if (DEFINED PLATFORM) if (${PLATFORM} STREQUAL "OS") add_compile_options(-fno-aligned-allocation) endif() endif() -list(APPEND BRIDGE_SOURCE - webf_bridge.cc - foundation/logging.cc - foundation/native_string.cc - foundation/ui_task_queue.cc - foundation/inspector_task_queue.cc - foundation/task_queue.cc - foundation/string_view.cc - foundation/native_value.cc - foundation/ui_command_buffer.cc - polyfill/dist/polyfill.cc +# Ensure code_gen is in the include path for generated headers +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/code_gen) + +## Load BRIDGE_SOURCE from JSON5 via Node.js if present +set(BRIDGE_SOURCE_LOADED FALSE) +set(_BRIDGE_SOURCES_JSON5 "${CMAKE_CURRENT_SOURCE_DIR}/bridge_sources.json5") +if(EXISTS "${_BRIDGE_SOURCES_JSON5}") + # Ensure changes to bridge_sources.json5 trigger a CMake reconfigure so newly + # added source files are picked up (otherwise builds can fail at link time + # with unresolved symbols). + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_BRIDGE_SOURCES_JSON5}") + execute_process( + COMMAND node ${CMAKE_CURRENT_SOURCE_DIR}/scripts/read_bridge_sources.js ${_BRIDGE_SOURCES_JSON5} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE _bridge_sources_status + OUTPUT_VARIABLE _bridge_sources_output + ERROR_VARIABLE _bridge_sources_error ) - -list(APPEND GUMBO_PARSER - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/attribute.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/char_ref.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/error.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/parser.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/string_buffer.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/string_piece.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/tag.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/string_piece.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/tokenizer.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/utf8.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/util.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/vector.c + execute_process( + COMMAND node ${CMAKE_CURRENT_SOURCE_DIR}/scripts/read_quickjs_sources.js ${_BRIDGE_SOURCES_JSON5} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE _quickjs_sources_status + OUTPUT_VARIABLE _quickjs_sources_output + ERROR_VARIABLE _quickjs_sources_error ) + if(_bridge_sources_status EQUAL 0) + # Normalize newlines and convert to a CMake list + string(REPLACE "\r\n" "\n" _bridge_sources_output "${_bridge_sources_output}") + string(REPLACE "\r\n" "\n" _quickjs_sources_output "${_quickjs_sources_output}") + string(REPLACE "\n" ";" _bridge_sources_list "${_bridge_sources_output}") + string(REPLACE "\n" ";" _quickjs_sources_list "${_quickjs_sources_output}") + list(REMOVE_ITEM _bridge_sources_list "") + list(REMOVE_ITEM _quickjs_sources_list "") + set(BRIDGE_SOURCE ${_bridge_sources_list}) + set(QUICK_JS_SOURCE ${_quickjs_sources_list}) + set(BRIDGE_SOURCE_LOADED TRUE) + list(LENGTH _bridge_sources_list _bridge_sources_count) + message(STATUS "Loaded SOURCE from JSON5 (${_bridge_sources_count} files)") + else() + message(WARNING "Failed to load SOURCE from JSON5: ${_bridge_sources_error}") + endif() +else() + message(STATUS "BRIDGE sources JSON5 not found: ${_BRIDGE_SOURCES_JSON5}") +endif() list(APPEND BRIDGE_INCLUDE ${CMAKE_CURRENT_LIST_DIR}/foundation - ${CMAKE_CURRENT_LIST_DIR}/out + ${CMAKE_CURRENT_LIST_DIR}/code_gen ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/include ${CMAKE_CURRENT_LIST_DIR}/polyfill/dist - ${DART_SDK} + ${CMAKE_CURRENT_LIST_DIR}/core_rs/include + ${CMAKE_CURRENT_LIST_DIR}/third_party/dart + ${CMAKE_CURRENT_LIST_DIR}/third_party/double_conversion + ${CMAKE_CURRENT_BINARY_DIR} ${ADDITIONAL_INCLUDE_DIRS} - ) +) -if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") +if(${WEBF_JS_ENGINE} MATCHES "quickjs") add_compile_options(-DWEBF_QUICK_JS_ENGINE=1) execute_process( @@ -120,370 +237,116 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") OUTPUT_VARIABLE QUICKJS_VERSION ) - list(APPEND QUICK_JS_SOURCE - third_party/quickjs/src/libbf.c - third_party/quickjs/src/cutils.c - third_party/quickjs/src/libregexp.c - third_party/quickjs/src/libunicode.c - third_party/quickjs/src/core/string.c - third_party/quickjs/src/core/function.c - third_party/quickjs/src/core/memory.c - third_party/quickjs/src/core/bytecode.c - third_party/quickjs/src/core/object.c - third_party/quickjs/src/core/exception.c - third_party/quickjs/src/core/gc.c - third_party/quickjs/src/core/malloc.c - third_party/quickjs/src/core/shape.c - third_party/quickjs/src/core/parser.c - third_party/quickjs/src/core/convertion.c - third_party/quickjs/src/core/runtime.c - third_party/quickjs/src/core/module.c - third_party/quickjs/src/core/ic.c - third_party/quickjs/src/core/builtins/js-array.c - third_party/quickjs/src/core/builtins/js-async-function.c - third_party/quickjs/src/core/builtins/js-async-generator.c - third_party/quickjs/src/core/builtins/js-atomics.c - third_party/quickjs/src/core/builtins/js-big-num.c - third_party/quickjs/src/core/builtins/js-boolean.c - third_party/quickjs/src/core/builtins/js-date.c - third_party/quickjs/src/core/builtins/js-function.c - third_party/quickjs/src/core/builtins/js-generator.c - third_party/quickjs/src/core/builtins/js-json.c - third_party/quickjs/src/core/builtins/js-map.c - third_party/quickjs/src/core/builtins/js-math.c - third_party/quickjs/src/core/builtins/js-number.c - third_party/quickjs/src/core/builtins/js-object.c - third_party/quickjs/src/core/builtins/js-closures.c - third_party/quickjs/src/core/builtins/js-operator.c - third_party/quickjs/src/core/builtins/js-promise.c - third_party/quickjs/src/core/builtins/js-proxy.c - third_party/quickjs/src/core/builtins/js-reflect.c - third_party/quickjs/src/core/builtins/js-regexp.c - third_party/quickjs/src/core/builtins/js-string.c - third_party/quickjs/src/core/builtins/js-symbol.c - third_party/quickjs/src/core/builtins/js-typed-array.c - ) + if (${CMAKE_BUILD_TYPE} MATCHES "Debug") + add_definitions(-DDDEBUG) + add_compile_options(-g3 -O0) + endif () + if(${STATIC_QUICKJS}) add_library(quickjs STATIC ${QUICK_JS_SOURCE}) else() add_library(quickjs SHARED ${QUICK_JS_SOURCE}) endif() - target_include_directories(quickjs PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/third_party/quickjs/include) + add_executable(qjsc ${QUICK_JS_SOURCE} + ./third_party/quickjs/quickjs-libc.c + ./third_party/quickjs/qjsc.c + ) - list(APPEND BRIDGE_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/third_party) - list(APPEND BRIDGE_LINK_LIBS quickjs) + target_link_libraries(quickjs PRIVATE Threads::Threads) + target_link_libraries(qjsc PRIVATE Threads::Threads m) - list(APPEND BRIDGE_SOURCE - # Binding files - bindings/qjs/dictionary_base.cc - bindings/qjs/js_based_event_listener.cc - bindings/qjs/js_event_handler.cc - bindings/qjs/js_event_listener.cc - bindings/qjs/binding_initializer.cc - bindings/qjs/member_installer.cc - bindings/qjs/source_location.cc - bindings/qjs/cppgc/gc_visitor.cc - bindings/qjs/cppgc/mutation_scope.cc - bindings/qjs/script_wrappable.cc - bindings/qjs/native_string_utils.cc - bindings/qjs/qjs_engine_patch.cc - bindings/qjs/qjs_function.cc - bindings/qjs/script_value.cc - bindings/qjs/script_promise.cc - bindings/qjs/script_promise_resolver.cc - bindings/qjs/atomic_string.cc - bindings/qjs/exception_state.cc - bindings/qjs/exception_message.cc - bindings/qjs/rejected_promises.cc - bindings/qjs/union_base.cc - # Core sources - core/executing_context.cc - core/script_state.cc - core/page.cc - core/dart_methods.cc - core/dart_context.cc - core/dart_context_data.cc - core/executing_context_data.cc - core/fileapi/blob.cc - core/fileapi/blob_part.cc - core/fileapi/blob_property_bag.cc - core/frame/console.cc - core/frame/dom_timer.cc - core/frame/dom_timer_coordinator.cc - core/frame/window_or_worker_global_scope.cc - core/frame/module_listener.cc - core/frame/module_listener_container.cc - core/frame/module_manager.cc - core/frame/module_callback.cc - core/frame/module_context_coordinator.cc - core/frame/window.cc - core/frame/screen.cc - core/frame/legacy/location.cc - core/timing/performance.cc - core/timing/performance_mark.cc - core/timing/performance_entry.cc - core/timing/performance_measure.cc - core/css/css_style_declaration.cc - core/css/inline_css_style_declaration.cc - core/css/computed_css_style_declaration.cc - core/dom/frame_request_callback_collection.cc - core/dom/events/registered_eventListener.cc - core/dom/events/event_listener_map.cc - core/dom/events/event.cc - core/dom/events/custom_event.cc - core/dom/events/event_target.cc - core/dom/events/event_listener_map.cc - core/dom/events/event_target_impl.cc - core/binding_object.cc - core/dom/node.cc - core/dom/node_traversal.cc - core/dom/live_node_list_base.cc - core/dom/character_data.cc - core/dom/comment.cc - core/dom/text.cc - core/dom/tree_scope.cc - core/dom/element.cc - core/dom/parent_node.cc - core/dom/element_data.cc - core/dom/document.cc - core/dom/dom_token_list.cc - core/dom/space_split_string.cc - core/dom/scripted_animation_controller.cc - core/dom/node_data.cc - core/dom/document_fragment.cc - core/dom/child_node_list.cc - core/dom/empty_node_list.cc - core/dom/container_node.cc - core/html/custom/widget_element.cc - core/events/error_event.cc - core/events/message_event.cc - core/events/animation_event.cc - core/events/close_event.cc - core/events/ui_event.cc - core/events/focus_event.cc - core/events/gesture_event.cc - core/events/input_event.cc - core/events/touch_event.cc - core/events/mouse_event.cc - core/events/pop_state_event.cc - core/events/pointer_event.cc - core/events/transition_event.cc - core/events/intersection_change_event.cc - core/events/keyboard_event.cc - core/events/promise_rejection_event.cc - core/html/parser/html_parser.cc - core/html/legacy/html_collection.cc - core/html/html_element.cc - core/html/html_div_element.cc - core/html/html_head_element.cc - core/html/html_body_element.cc - core/html/html_html_element.cc - core/html/html_template_element.cc - core/html/html_all_collection.cc - core/html/html_anchor_element.cc - core/html/html_image_element.cc - core/html/html_script_element.cc - core/html/html_link_element.cc - core/html/html_unknown_element.cc - core/html/image.cc - core/html/canvas/html_canvas_element.cc - core/html/canvas/canvas_rendering_context.cc - core/html/canvas/canvas_rendering_context_2d.cc - core/html/canvas/canvas_gradient.cc - core/html/canvas/canvas_pattern.cc - core/geometry/dom_matrix.cc - core/geometry/dom_matrix_readonly.cc - core/html/forms/html_button_element.cc - core/html/forms/html_input_element.cc - core/html/forms/html_form_element.cc - core/html/forms/html_textarea_element.cc - # Legacy implements, should remove them in the future. - core/dom/legacy/element_attributes.cc - core/dom/legacy/bounding_client_rect.cc - core/input/touch.cc - core/input/touch_list.cc - ) + set(MI_OVERRIDE OFF) +# if (NOT MSVC AND NOT DEFINED USE_SYSTEM_MALLOC) +# add_compile_definitions(ENABLE_MI_MALLOC=1) +# add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/third_party/quickjs/vendor/mimalloc) +# include_directories(${CMAKE_CURRENT_SOURCE_DIR}/third_party/quickjs/vendor/mimalloc/include) +# target_link_libraries(quickjs mimalloc-static) +# target_link_libraries(qjsc mimalloc-static) +# endif() - # Gen sources. - list(APPEND BRIDGE_SOURCE - out/names_installer.cc - out/qjs_console.cc - out/qjs_module_manager.cc - out/qjs_window_or_worker_global_scope.cc - out/qjs_window.cc - out/qjs_location.cc - out/qjs_blob.cc - out/qjs_event.cc - out/qjs_add_event_listener_options.cc - out/qjs_event_listener_options.cc - out/qjs_error_event.cc - out/qjs_message_event.cc - out/qjs_message_event_init.cc - out/qjs_close_event.cc - out/qjs_close_event_init.cc - out/qjs_focus_event.cc - out/qjs_focus_event_init.cc - out/qjs_input_event.cc - out/qjs_input_event_init.cc - out/qjs_pop_state_event.cc - out/qjs_pop_state_event_init.cc - out/qjs_ui_event.cc - out/qjs_ui_event_init.cc - out/qjs_gesture_event.cc - out/qjs_gesture_event_init.cc - out/qjs_intersection_change_event.cc - out/qjs_intersection_change_event_init.cc - out/qjs_touch.cc - out/qjs_touch_init.cc - out/qjs_touch_list.cc - out/qjs_touch_event.cc - out/qjs_touch_event_init.cc - out/qjs_pointer_event.cc - out/qjs_pointer_event_init.cc - out/qjs_mouse_event.cc - out/qjs_mouse_event_init.cc - out/qjs_transition_event.cc - out/qjs_transition_event_init.cc - out/event_factory.cc - out/qjs_custom_event.cc - out/qjs_custom_event_init.cc - out/qjs_keyboard_event.cc - out/qjs_keyboard_event_init.cc - out/qjs_animation_event.cc - out/qjs_animation_event_init.cc - out/qjs_error_event_init.cc - out/qjs_event_init.cc - out/qjs_event_target.cc - out/qjs_node.cc - out/qjs_document.cc - out/qjs_element.cc - out/qjs_dom_token_list.cc - out/qjs_element_attributes.cc - out/qjs_character_data.cc - out/qjs_comment.cc - out/qjs_document_fragment.cc - out/qjs_bounding_client_rect.cc - out/qjs_css_style_declaration.cc - out/qjs_inline_css_style_declaration.cc - out/qjs_computed_css_style_declaration.cc - out/qjs_text.cc - out/qjs_screen.cc - out/qjs_node_list.cc - out/event_type_names.cc - out/built_in_string.cc - out/binding_call_methods.cc - out/qjs_scroll_options.cc - out/qjs_scroll_to_options.cc - out/qjs_html_element.cc - out/qjs_html_all_collection.cc - out/qjs_html_anchor_element.cc - out/qjs_html_div_element.cc - out/qjs_html_head_element.cc - out/qjs_html_body_element.cc - out/qjs_html_html_element.cc - out/qjs_html_image_element.cc - out/qjs_html_canvas_element.cc - out/qjs_html_link_element.cc - out/qjs_image.cc - out/qjs_widget_element.cc - out/qjs_canvas_rendering_context_2d.cc - out/qjs_canvas_rendering_context.cc - out/qjs_canvas_gradient.cc - out/qjs_canvas_pattern.cc - out/qjs_dom_matrix.cc - out/qjs_dom_matrix_readonly.cc - out/qjs_union_dom_string_sequencedouble.cc - out/qjs_unionhtml_image_elementhtml_canvas_element.cc - out/qjs_union_dom_stringcanvas_gradient.cc - out/canvas_types.cc - out/qjs_html_button_element.cc - out/qjs_html_input_element.cc - out/qjs_html_form_element.cc - out/qjs_html_textarea_element.cc - out/qjs_html_script_element.cc - out/qjs_promise_rejection_event.cc - out/qjs_promise_rejection_event_init.cc - out/qjs_html_template_element.cc - out/qjs_html_unknown_element.cc - out/qjs_performance.cc - out/qjs_performance_entry.cc - out/qjs_performance_mark.cc - out/qjs_performance_measure.cc - out/performance_entry_names.cc - out/qjs_performance_measure_options.cc - out/qjs_performance_mark_options.cc - out/performance_mark_constants.cc - out/html_element_factory.cc - out/html_names.cc - out/script_type_names.cc - out/defined_properties.cc - out/defined_properties_initializer.cc - out/element_attribute_names.cc - out/element_namespace_uris.cc - ) + target_include_directories(quickjs PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/third_party/quickjs/include) + target_include_directories(qjsc PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/third_party/quickjs/include) - # Quickjs use __builtin_frame_address() to get stack pointer, we should add follow options to get it work with -O2 - # https://stackoverflow.com/questions/14735010/how-do-you-get-gccs-builtin-frame-address-to-work-with-o2 - add_compile_options(-fno-optimize-sibling-calls -fno-omit-frame-pointer) - target_compile_options(quickjs PUBLIC -DCONFIG_VERSION=${\"QUICKJS_VERSION\"}) + list(APPEND BRIDGE_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/third_party) + list(APPEND BRIDGE_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/third_party/modp_b64/include) + list(APPEND BRIDGE_LINK_LIBS quickjs) + if (NOT MSVC) + # Quickjs use __builtin_frame_address() to get stack pointer, we should add follow options to get it work with -O2 + # https://stackoverflow.com/questions/14735010/how-do-you-get-gccs-builtin-frame-address-to-work-with-o2 + add_compile_options(-fno-optimize-sibling-calls -fno-omit-frame-pointer) + endif() + target_compile_options(quickjs PUBLIC -DCONFIG_VERSION=${\"QUICKJS_VERSION\"} -DCONFIG_BIGNUM=1) + target_compile_options(qjsc PUBLIC -DCONFIG_VERSION=${\"QUICKJS_VERSION\"} -DCONFIG_BIGNUM=1) endif () list(APPEND PUBLIC_HEADER include/webf_bridge.h ) -add_library(webf SHARED ${BRIDGE_SOURCE}) -add_library(webf_static STATIC ${BRIDGE_SOURCE}) - -target_compile_definitions(webf PUBLIC -DFLUTTER_BACKEND=1) - -add_library(gumbo_parse_static STATIC ${GUMBO_PARSER}) -list(APPEND BRIDGE_LINK_LIBS gumbo_parse_static) - if (${IS_ANDROID}) find_library(log-lib log) + # add_link_options("-Wl,-z,max-page-size=16384") + if (${ANDROID_ABI} MATCHES "armeabi-v7a" OR ${ANDROID_ABI} MATCHES "x86") add_definitions(-DANDROID_32_BIT=1) endif() add_definitions(-DIS_ANDROID=1) + add_definitions(-DANDROID=1) list(APPEND BRIDGE_LINK_LIBS ${log-lib}) elseif(${IS_IOS}) add_definitions(-DIS_IOS=1) endif() -### webf +# Always create a static library with core sources to avoid duplicate compilation +# Remove webf_bridge.cc from the core static library sources +# webf_bridge.cc contains the export symbols and needs to be compiled directly into the shared library +set(WEBF_CORE_SOURCE ${BRIDGE_SOURCE}) +list(REMOVE_ITEM WEBF_CORE_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/webf_bridge.cc) + +add_library(webf_core STATIC ${WEBF_CORE_SOURCE}) +# Set position-independent code for static library that will be linked into shared library +set_target_properties(webf_core PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_include_directories(webf_core PUBLIC + ${BRIDGE_INCLUDE} + ${CMAKE_CURRENT_SOURCE_DIR} + ./include) +target_link_libraries(webf_core PUBLIC ${BRIDGE_LINK_LIBS}) + +# webf shared library compiles webf_bridge.cc directly to ensure symbols are exported +add_library(webf SHARED ${CMAKE_CURRENT_SOURCE_DIR}/webf_bridge.cc) +target_link_libraries(webf PUBLIC webf_core ${BRIDGE_LINK_LIBS}) target_include_directories(webf PRIVATE ${BRIDGE_INCLUDE} - ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC ./include) -target_link_libraries(webf PRIVATE ${BRIDGE_LINK_LIBS}) + ${CMAKE_CURRENT_SOURCE_DIR} + ./include) + + + + +## webf +# Include directories and link libraries are already set above when creating webf -if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") - if (${CMAKE_BUILD_TYPE} STREQUAL "Release" OR ${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo") - target_compile_options(webf PRIVATE -fno-exceptions -fvisibility=hidden -fno-rtti) +if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs" AND NOT MSVC) + if (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + target_compile_options(webf PRIVATE -fno-exceptions -fvisibility=hidden -fno-rtti -fdata-sections -ffunction-sections) + target_compile_options(webf_core PRIVATE -fno-exceptions -fvisibility=hidden -fno-rtti -fdata-sections -ffunction-sections) + target_compile_options(quickjs PRIVATE -fno-exceptions -fno-rtti -fdata-sections -ffunction-sections) else () ### remove dynamic_cast and exceptions target_compile_options(webf PRIVATE -fno-exceptions -fno-rtti) + target_compile_options(webf_core PRIVATE -fno-exceptions -fno-rtti) endif () endif () -### webfStatic -target_include_directories(webf_static PRIVATE - ${BRIDGE_INCLUDE} - ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC ./include) -target_link_libraries(webf_static ${BRIDGE_LINK_LIBS}) - execute_process( - COMMAND grep version: ./pubspec.yaml - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/../webf + COMMAND node get_app_ver.js + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/scripts OUTPUT_VARIABLE APP_VER ) -string(SUBSTRING ${APP_VER} 9 30 APP_VER) -string(REGEX REPLACE "\n$" "" APP_VER "${APP_VER}") - string(REPLACE \n "" APP_VER ${APP_VER}) # Remove last \n add_definitions(-DAPP_VERSION="${APP_VER}") # Read from dartfm version execute_process( @@ -494,24 +357,56 @@ execute_process( string(REPLACE \n "" GIT_HEAD ${GIT_HEAD}) # Remove last \n add_definitions(-DAPP_REV="${GIT_HEAD}") # Read from git head sha1 -if (${ENABLE_TEST}) +# Only include test.cmake on desktop platforms (macOS, Windows, Linux) +if (APPLE OR WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux") add_compile_definitions(IS_TEST=true) + include(./test/css_unittests.cmake) include(./test/test.cmake) endif () -if (DEFINED ENV{LIBRARY_OUTPUT_DIR}) - set_target_properties(webf - PROPERTIES - LIBRARY_OUTPUT_DIRECTORY "$ENV{LIBRARY_OUTPUT_DIR}" - ) - set_target_properties(webf_static PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "$ENV{LIBRARY_OUTPUT_DIR}") +# Android integration tests also need the test bridge exports, but they do not +# build the desktop-only unit test targets from test.cmake. +if (IS_ANDROID AND ENABLE_TEST) + add_compile_definitions(IS_TEST=true) + target_sources(webf PRIVATE + include/webf_bridge_test.h + webf_bridge_test.cc + ./test/test_framework_polyfill.c + test/webf_test_context.cc + test/webf_test_context.h + ) + target_include_directories(webf PRIVATE ./test) +endif () - if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") - set_target_properties(quickjs PROPERTIES LIBRARY_OUTPUT_DIRECTORY "$ENV{LIBRARY_OUTPUT_DIR}") +#if(CMAKE_SYSTEM_NAME MATCHES "MSYS" OR MINGW) +# install(TARGETS webf +# LIBRARY DESTINATION lib # For Unix-like OSes +# ARCHIVE DESTINATION lib # For static libs, if built +# RUNTIME DESTINATION bin # For Windows DLLs +# INCLUDES DESTINATION include) +#else() + if (DEFINED ENV{LIBRARY_OUTPUT_DIR}) + set_target_properties(webf + PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "$ENV{LIBRARY_OUTPUT_DIR}" + RUNTIME_OUTPUT_DIRECTORY "$ENV{LIBRARY_OUTPUT_DIR}" + ) + if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") + set_target_properties(quickjs + PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "$ENV{LIBRARY_OUTPUT_DIR}" + RUNTIME_OUTPUT_DIRECTORY "$ENV{LIBRARY_OUTPUT_DIR}" + ) + set_target_properties(qjsc + PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "$ENV{LIBRARY_OUTPUT_DIR}" + RUNTIME_OUTPUT_DIRECTORY "$ENV{LIBRARY_OUTPUT_DIR}" + ) + endif () + elseif (IS_ANDROID) + # android do nothing endif () -elseif (IS_ANDROID) - # android do nothing -endif () +#endif() if (${CMAKE_SYSTEM_NAME} MATCHES "iOS") set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO") @@ -525,7 +420,7 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "iOS") MACOSX_FRAMEWORK_BUNDLE_VERSION 1.0 MACOSX_FRAMEWORK_SHORT_VERSION_STRING 1.0 PUBLIC_HEADER ${WEBF_PUBLIC_HEADERS} - ) + ) # If quickjs is static, there will be no quickjs.framework any more. if(NOT DEFINED STATIC_QUICKJS) @@ -537,6 +432,6 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "iOS") MACOSX_FRAMEWORK_BUNDLE_VERSION 1.0 MACOSX_FRAMEWORK_SHORT_VERSION_STRING 1.0 PUBLIC_HEADER ${QUICKJS_PUBLIC_HEADERS} - ) + ) endif() endif () diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc deleted file mode 100644 index e253159093..0000000000 --- a/bridge/bindings/qjs/atomic_string.cc +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "atomic_string.h" -#include "built_in_string.h" -#include "qjs_engine_patch.h" - -namespace webf { - -AtomicString AtomicString::Empty() { - return built_in_string::kempty_string; -} - -AtomicString AtomicString::Null() { - return built_in_string::kNULL; -} - -AtomicString AtomicString::From(JSContext* ctx, NativeString* native_string) { - JSValue str = JS_NewUnicodeString(ctx, native_string->string(), native_string->length()); - auto result = AtomicString(ctx, str); - JS_FreeValue(ctx, str); - return result; -} - -namespace { - -AtomicString::StringKind GetStringKind(const std::string& string) { - AtomicString::StringKind predictKind = - std::islower(string[0]) ? AtomicString::StringKind::kIsLowerCase : AtomicString::StringKind::kIsUpperCase; - for (char i : string) { - if (predictKind == AtomicString::StringKind::kIsUpperCase && !std::isupper(i)) { - return AtomicString::StringKind::kIsMixed; - } else if (predictKind == AtomicString::StringKind::kIsLowerCase && !std::islower(i)) { - return AtomicString::StringKind::kIsMixed; - } - } - return predictKind; -} - -AtomicString::StringKind GetStringKind(JSValue stringValue) { - JSString* p = JS_VALUE_GET_STRING(stringValue); - - if (p->is_wide_char) { - return AtomicString::StringKind::kIsMixed; - } - - return GetStringKind(reinterpret_cast(p->u.str8)); -} - -AtomicString::StringKind GetStringKind(const NativeString* native_string) { - if (!native_string->length()) { - return AtomicString::StringKind::kIsMixed; - } - - AtomicString::StringKind predictKind = std::islower(native_string->string()[0]) - ? AtomicString::StringKind::kIsLowerCase - : AtomicString::StringKind::kIsUpperCase; - for (int i = 0; i < native_string->length(); i++) { - uint16_t c = native_string->string()[i]; - if (predictKind == AtomicString::StringKind::kIsUpperCase && !std::isupper(c)) { - return AtomicString::StringKind::kIsMixed; - } else if (predictKind == AtomicString::StringKind::kIsLowerCase && !std::islower(c)) { - return AtomicString::StringKind::kIsMixed; - } - } - - return predictKind; -} - -} // namespace - -AtomicString::AtomicString(JSContext* ctx, const std::string& string) - : runtime_(JS_GetRuntime(ctx)), - atom_(JS_NewAtomLen(ctx, string.c_str(), string.size())), - kind_(GetStringKind(string)), - length_(string.size()) {} - -AtomicString::AtomicString(JSContext* ctx, const char* str, size_t length) - : runtime_(JS_GetRuntime(ctx)), - atom_(JS_NewAtomLen(ctx, str, length)), - kind_(GetStringKind(str)), - length_(length) {} - -AtomicString::AtomicString(JSContext* ctx, const uint16_t* str, size_t length) : runtime_(JS_GetRuntime(ctx)) { - JSValue string = JS_NewUnicodeString(ctx, str, length); - atom_ = JS_ValueToAtom(ctx, string); - kind_ = GetStringKind(string); - length_ = length; - JS_FreeValue(ctx, string); -} - -AtomicString::AtomicString(JSContext* ctx, const NativeString* native_string) - : runtime_(JS_GetRuntime(ctx)), - atom_(JS_NewUnicodeAtom(ctx, native_string->string(), native_string->length())), - kind_(GetStringKind(native_string)), - length_(native_string->length()) {} - -AtomicString::AtomicString(JSContext* ctx, JSValue value) - : runtime_(JS_GetRuntime(ctx)), atom_(JS_ValueToAtom(ctx, value)) { - if (JS_IsString(value)) { - kind_ = GetStringKind(value); - length_ = JS_VALUE_GET_STRING(value)->len; - } else { - initFromAtom(ctx); - } -} - -AtomicString::AtomicString(JSContext* ctx, JSAtom atom) : runtime_(JS_GetRuntime(ctx)), atom_(JS_DupAtom(ctx, atom)) { - initFromAtom(ctx); -} - -void AtomicString::initFromAtom(JSContext* ctx) { - if (atom_ != JS_ATOM_NULL) { - auto atom_str = JS_AtomToValue(ctx, atom_); - kind_ = GetStringKind(atom_str); - length_ = JS_VALUE_GET_STRING(atom_str)->len; - JS_FreeValue(ctx, atom_str); - } else { - kind_ = StringKind::kIsMixed; - length_ = 0; - atom_upper_ = JS_ATOM_NULL; - atom_lower_ = JS_ATOM_NULL; - } -} - -bool AtomicString::IsEmpty() const { - return *this == built_in_string::kempty_string || IsNull(); -} - -bool AtomicString::IsNull() const { - return atom_ == JS_ATOM_NULL; -} - -bool AtomicString::Is8Bit() const { - return JS_AtomIs8Bit(runtime_, atom_); -} - -const uint8_t* AtomicString::Character8() const { - return JS_AtomRawCharacter8(runtime_, atom_); -} - -const uint16_t* AtomicString::Character16() const { - return JS_AtomRawCharacter16(runtime_, atom_); -} - -int AtomicString::Find(bool (*CharacterMatchFunction)(char)) const { - return JS_FindCharacterInAtom(runtime_, atom_, CharacterMatchFunction); -} - -int AtomicString::Find(bool (*CharacterMatchFunction)(uint16_t)) const { - return JS_FindWCharacterInAtom(runtime_, atom_, CharacterMatchFunction); -} - -std::string AtomicString::ToStdString(JSContext* ctx) const { - if (IsEmpty()) - return ""; - - JSValue str = JS_AtomToString(ctx, atom_); - size_t len; - const char* buf = JS_ToCStringLen(ctx, &len, str); - JS_FreeValue(ctx, str); - - // to support \0 in JS string. So must be passing len. - std::string result = std::string(buf, len); - JS_FreeCString(ctx, buf); - return result; -} - -std::unique_ptr AtomicString::ToNativeString(JSContext* ctx) const { - if (IsNull()) { - // Null string is same like empty string - return built_in_string::kempty_string.ToNativeString(ctx); - } - JSValue stringValue = JS_AtomToValue(ctx, atom_); - uint32_t length; - uint16_t* bytes = JS_ToUnicode(ctx, stringValue, &length); - JS_FreeValue(ctx, stringValue); - return std::make_unique(bytes, length); -} - -StringView AtomicString::ToStringView() const { - if (IsNull()) { - return built_in_string::kempty_string.ToStringView(); - } - return JSAtomToStringView(runtime_, atom_); -} - -AtomicString::AtomicString(const AtomicString& value) { - if (value.IsNull()) { - atom_ = value.atom_; - atom_upper_ = value.atom_upper_; - atom_lower_ = value.atom_lower_; - } else if (&value != this) { - atom_ = JS_DupAtomRT(value.runtime_, value.atom_); - } - runtime_ = value.runtime_; - length_ = value.length_; - kind_ = value.kind_; -} - -AtomicString& AtomicString::operator=(const AtomicString& other) { - if (&other != this && !other.IsNull()) { - JS_FreeAtomRT(other.runtime_, atom_); - atom_ = JS_DupAtomRT(other.runtime_, other.atom_); - } - runtime_ = other.runtime_; - length_ = other.length_; - kind_ = other.kind_; - return *this; -} - -AtomicString::AtomicString(AtomicString&& value) noexcept { - if (&value != this && !value.IsNull()) { - atom_ = JS_DupAtomRT(value.runtime_, value.atom_); - } - runtime_ = value.runtime_; - length_ = value.length_; - kind_ = value.kind_; -} - -AtomicString& AtomicString::operator=(AtomicString&& value) noexcept { - if (&value != this && !value.IsNull()) { - JS_FreeAtomRT(value.runtime_, atom_); - atom_ = JS_DupAtomRT(value.runtime_, value.atom_); - } - runtime_ = value.runtime_; - length_ = value.length_; - kind_ = value.kind_; - return *this; -} - -AtomicString AtomicString::ToUpperIfNecessary(JSContext* ctx) const { - if (kind_ == StringKind::kIsUpperCase) { - return *this; - } - if (atom_upper_ != JS_ATOM_NULL || IsNull()) - return *this; - AtomicString upperString = ToUpperSlow(ctx); - atom_upper_ = upperString.atom_; - return upperString; -} - -AtomicString AtomicString::ToUpperSlow(JSContext* ctx) const { - const char* cptr = JS_AtomToCString(ctx, atom_); - std::string str = std::string(cptr); - std::transform(str.begin(), str.end(), str.begin(), toupper); - JS_FreeCString(ctx, cptr); - return AtomicString(ctx, str); -} - -AtomicString AtomicString::ToLowerIfNecessary(JSContext* ctx) const { - if (kind_ == StringKind::kIsLowerCase) { - return *this; - } - if (atom_upper_ != JS_ATOM_NULL || IsNull()) - return *this; - AtomicString lowerString = ToLowerSlow(ctx); - atom_lower_ = lowerString.atom_; - return lowerString; -} - -AtomicString AtomicString::ToLowerSlow(JSContext* ctx) const { - const char* cptr = JS_AtomToCString(ctx, atom_); - std::string str = std::string(cptr); - std::transform(str.begin(), str.end(), str.begin(), tolower); - JS_FreeCString(ctx, cptr); - return AtomicString(ctx, str); -} - -} // namespace webf diff --git a/bridge/bindings/qjs/atomic_string.h b/bridge/bindings/qjs/atomic_string.h deleted file mode 100644 index 3b9e58d09c..0000000000 --- a/bridge/bindings/qjs/atomic_string.h +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_BINDINGS_QJS_ATOMIC_STRING_H_ -#define BRIDGE_BINDINGS_QJS_ATOMIC_STRING_H_ - -#include -#include -#include -#include -#include "foundation/macros.h" -#include "foundation/native_string.h" -#include "foundation/string_view.h" -#include "native_string_utils.h" -#include "qjs_engine_patch.h" - -namespace webf { - -// An AtomicString instance represents a string, and multiple AtomicString -// instances can share their string storage if the strings are -// identical. Comparing two AtomicString instances is much faster than comparing -// two String instances because we just check string storage identity. -class AtomicString { - WEBF_DISALLOW_NEW(); - - public: - enum class StringKind { kIsLowerCase, kIsUpperCase, kIsMixed }; - - struct KeyHasher { - std::size_t operator()(const AtomicString& k) const { return k.atom_; } - }; - - static AtomicString Empty(); - static AtomicString Null(); - static AtomicString From(JSContext* ctx, NativeString* native_string); - - AtomicString() = default; - AtomicString(JSContext* ctx, const std::string& string); - AtomicString(JSContext* ctx, const char* str, size_t length); - AtomicString(JSContext* ctx, const NativeString* native_string); - AtomicString(JSContext* ctx, const uint16_t* str, size_t length); - AtomicString(JSContext* ctx, JSValue value); - AtomicString(JSContext* ctx, JSAtom atom); - ~AtomicString() { JS_FreeAtomRT(runtime_, atom_); }; - - // Return the undefined string value from atom key. - JSValue ToQuickJS(JSContext* ctx) const { - if (ctx == nullptr || IsNull()) { - return JS_NULL; - } - - assert(ctx != nullptr); - return JS_AtomToValue(ctx, atom_); - }; - - bool IsEmpty() const; - bool IsNull() const; - - JSAtom Impl() const { return atom_; } - - int64_t length() const { return length_; } - - bool Is8Bit() const; - const uint8_t* Character8() const; - const uint16_t* Character16() const; - - int Find(bool (*CharacterMatchFunction)(char)) const; - int Find(bool (*CharacterMatchFunction)(uint16_t)) const; - - [[nodiscard]] std::string ToStdString(JSContext* ctx) const; - [[nodiscard]] std::unique_ptr ToNativeString(JSContext* ctx) const; - - StringView ToStringView() const; - - AtomicString ToUpperIfNecessary(JSContext* ctx) const; - AtomicString ToUpperSlow(JSContext* ctx) const; - - AtomicString ToLowerIfNecessary(JSContext* ctx) const; - AtomicString ToLowerSlow(JSContext* ctx) const; - - // Copy assignment - AtomicString(AtomicString const& value); - AtomicString& operator=(const AtomicString& other); - - // Move assignment - AtomicString(AtomicString&& value) noexcept; - AtomicString& operator=(AtomicString&& value) noexcept; - - bool operator==(const AtomicString& other) const { return other.atom_ == this->atom_; } - bool operator!=(const AtomicString& other) const { return other.atom_ != this->atom_; }; - bool operator>(const AtomicString& other) const { return other.atom_ > this->atom_; }; - bool operator<(const AtomicString& other) const { return other.atom_ < this->atom_; }; - - protected: - JSRuntime* runtime_{nullptr}; - int64_t length_{0}; - JSAtom atom_{JS_ATOM_empty_string}; - mutable JSAtom atom_upper_{JS_ATOM_NULL}; - mutable JSAtom atom_lower_{JS_ATOM_NULL}; - StringKind kind_; - - private: - void initFromAtom(JSContext* ctx); -}; - -} // namespace webf - -#endif // BRIDGE_BINDINGS_QJS_ATOMIC_STRING_H_ diff --git a/bridge/bindings/qjs/atomic_string_test.cc b/bridge/bindings/qjs/atomic_string_test.cc index d36c9a88f4..e2745b4e25 100644 --- a/bridge/bindings/qjs/atomic_string_test.cc +++ b/bridge/bindings/qjs/atomic_string_test.cc @@ -1,118 +1,374 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ -#include "atomic_string.h" #include #include -#include "built_in_string.h" +#include #include "event_type_names.h" #include "gtest/gtest.h" #include "native_string_utils.h" -#include "qjs_engine_patch.h" +#include "webf_test_env.h" using namespace webf; using TestCallback = void (*)(JSContext* ctx); -void TestAtomicString(TestCallback callback) { - JSRuntime* runtime = JS_NewRuntime(); - JSContext* ctx = JS_NewContext(runtime); - - built_in_string::Init(ctx); - - callback(ctx); - - JS_FreeContext(ctx); - - built_in_string::Dispose(); - JS_FreeRuntime(runtime); +TEST(AtomicString, Empty) { + TEST_init(); + AtomicString atomic_string = AtomicString::Empty(); + EXPECT_EQ(*atomic_string.Impl(), ""); } -TEST(AtomicString, Empty) { - TestAtomicString([](JSContext* ctx) { - AtomicString atomic_string = AtomicString::Empty(); - EXPECT_STREQ(atomic_string.ToStdString(ctx).c_str(), ""); - }); +TEST(AtomicString, HashShouldEqual8bitAnd16bit) { + TEST_init(); + AtomicString atomic_string = AtomicString::CreateFromUTF8("helloworld"); + AtomicString atomic_string2 = AtomicString(u"helloworld"); + EXPECT_EQ(atomic_string.Hash(), atomic_string2.Hash()); } TEST(AtomicString, FromNativeString) { - TestAtomicString([](JSContext* ctx) { - auto nativeString = stringToNativeString("helloworld"); - AtomicString value = AtomicString::From(ctx, nativeString.get()); - - EXPECT_STREQ(value.ToStdString(ctx).c_str(), "helloworld"); - }); + TEST_init(); + auto nativeString = stringToNativeString("helloworld"); + std::unique_ptr str = + std::unique_ptr(static_cast(nativeString.release())); + AtomicString value = AtomicString(str); + EXPECT_EQ(value, AtomicString::CreateFromUTF8("helloworld")); } TEST(AtomicString, CreateFromStdString) { - TestAtomicString([](JSContext* ctx) { - AtomicString&& value = AtomicString(ctx, "helloworld"); - EXPECT_STREQ(value.ToStdString(ctx).c_str(), "helloworld"); - }); + TEST_init(); + AtomicString value = AtomicString::CreateFromUTF8("helloworld"); + EXPECT_EQ(*value.Impl(), "helloworld"); } TEST(AtomicString, CreateFromJSValue) { - TestAtomicString([](JSContext* ctx) { - JSValue string = JS_NewString(ctx, "helloworld"); - AtomicString&& value = AtomicString(ctx, string); - EXPECT_STREQ(value.ToStdString(ctx).c_str(), "helloworld"); - JS_FreeValue(ctx, string); - }); + auto env = TEST_init(); + JSContext* ctx = env->page()->executingContext()->ctx(); + JSValue string = JS_NewString(ctx, "helloworld"); + std::shared_ptr value = + env->page()->dartIsolateContext()->stringCache()->GetStringFromJSAtom(ctx, JS_ValueToAtom(ctx, string)); + EXPECT_EQ(*value, "helloworld"); + JS_FreeValue(ctx, string); } TEST(AtomicString, ToQuickJS) { - TestAtomicString([](JSContext* ctx) { - AtomicString&& value = AtomicString(ctx, "helloworld"); - JSValue qjs_value = value.ToQuickJS(ctx); - const char* buffer = JS_ToCString(ctx, qjs_value); - EXPECT_STREQ(buffer, "helloworld"); - JS_FreeValue(ctx, qjs_value); - JS_FreeCString(ctx, buffer); - }); + auto env = TEST_init(); + AtomicString value = AtomicString::CreateFromUTF8("helloworld"); + JSContext* ctx = env->page()->executingContext()->ctx(); + JSValue qjs_value = env->page()->dartIsolateContext()->stringCache()->GetJSValueFromString(ctx, value.Impl()); + const char* buffer = JS_ToCString(ctx, qjs_value); + EXPECT_STREQ(buffer, "helloworld"); + JS_FreeValue(ctx, qjs_value); + JS_FreeCString(ctx, buffer); } TEST(AtomicString, ToNativeString) { - TestAtomicString([](JSContext* ctx) { - AtomicString&& value = AtomicString(ctx, "helloworld"); - auto native_string = value.ToNativeString(ctx); - const uint16_t* p = native_string->string(); - EXPECT_EQ(native_string->length(), 10); + TEST_init(); + AtomicString&& value = AtomicString::CreateFromUTF8("helloworld"); + auto native_string = value.ToNativeString(); + const uint16_t* p = native_string->string(); + EXPECT_EQ(native_string->length(), 10); - uint16_t result[10] = {'h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd'}; - for (int i = 0; i < native_string->length(); i++) { - EXPECT_EQ(result[i], p[i]); - } - }); + uint16_t result[10] = {'h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd'}; + for (int i = 0; i < native_string->length(); i++) { + EXPECT_EQ(result[i], p[i]); + } } TEST(AtomicString, CopyAssignment) { - TestAtomicString([](JSContext* ctx) { - AtomicString str = AtomicString(ctx, "helloworld"); - struct P { - AtomicString str; - }; - P p{AtomicString::Empty()}; - p.str = str; - EXPECT_EQ(p.str == str, true); - }); + TEST_init(); + AtomicString str = AtomicString::CreateFromUTF8("helloworld"); + struct P { + AtomicString str; + }; + P p{AtomicString::Empty()}; + p.str = str; + EXPECT_EQ(p.str == str, true); } TEST(AtomicString, MoveAssignment) { - TestAtomicString([](JSContext* ctx) { - auto&& str = AtomicString(ctx, "helloworld"); - auto&& str2 = AtomicString(std::move(str)); - EXPECT_STREQ(str2.ToStdString(ctx).c_str(), "helloworld"); - }); + TEST_init(); + auto str = AtomicString::CreateFromUTF8("helloworld"); + auto str2 = AtomicString(std::move(str)); + EXPECT_EQ(str2.ToUTF8String(), "helloworld"); } TEST(AtomicString, CopyToRightReference) { - TestAtomicString([](JSContext* ctx) { - AtomicString str = AtomicString::Empty(); - if (1 + 1 == 2) { - str = AtomicString(ctx, "helloworld"); - } - EXPECT_STREQ(str.ToStdString(ctx).c_str(), "helloworld"); - }); + TEST_init(); + AtomicString str = AtomicString::Empty(); + if (1 + 1 == 2) { + str = AtomicString::CreateFromUTF8("helloworld"); + } + EXPECT_EQ(str.ToUTF8String(), "helloworld"); +} + +TEST(AtomicString, UTF16StringCreation) { + TEST_init(); + // Test creating AtomicString from UTF-16 literal + AtomicString utf16_str = AtomicString(u"Hello UTF-16 世界"); + EXPECT_FALSE(utf16_str.Is8Bit()); + EXPECT_EQ(utf16_str.length(), 15); + + // Verify Characters16() returns correct data + const char16_t* chars = utf16_str.Characters16(); + EXPECT_EQ(chars[0], u'H'); + EXPECT_EQ(chars[6], u'U'); + EXPECT_EQ(chars[13], u'世'); + EXPECT_EQ(chars[14], u'界'); } + +TEST(AtomicString, UTF16ToStdString) { + TEST_init(); + // Test UTF-16 to UTF-8 conversion + AtomicString utf16_str = AtomicString(u"Hello 世界 🌍"); + UTF8String utf8_str = utf16_str.ToUTF8String(); + + // The UTF-8 representation should match + EXPECT_EQ(utf8_str, "Hello 世界 🌍"); +} + +TEST(AtomicString, UTF16ToQuickJS) { + auto env = TEST_init(); + JSContext* ctx = env->page()->executingContext()->ctx(); + + // Create UTF-16 string with various Unicode characters + AtomicString utf16_str = AtomicString(u"JavaScript 世界 🚀"); + + // Convert to QuickJS value + JSValue qjs_value = utf16_str.ToQuickJS(ctx); + + // Verify the string is correctly converted + const char* result = JS_ToCString(ctx, qjs_value); + EXPECT_STREQ(result, "JavaScript 世界 🚀"); + + JS_FreeCString(ctx, result); + JS_FreeValue(ctx, qjs_value); +} + +TEST(AtomicString, UTF16StringCache) { + auto env = TEST_init(); + JSContext* ctx = env->page()->executingContext()->ctx(); + + // Create UTF-16 string + std::u16string test_str = u"Cached UTF-16 String 测试"; + AtomicString utf16_str = AtomicString(test_str.c_str(), test_str.length()); + + // Put it in the cache + JSValue qjs_value = env->page()->dartIsolateContext()->stringCache()->GetJSValueFromString(ctx, utf16_str.Impl()); + + // Retrieve it back from cache + JSAtom atom = JS_ValueToAtom(ctx, qjs_value); + std::shared_ptr cached_str = env->page()->dartIsolateContext()->stringCache()->GetStringFromJSAtom(ctx, atom); + + // Verify it's the same string + EXPECT_FALSE(cached_str->Is8Bit()); + EXPECT_EQ(cached_str->length(), utf16_str.length()); + + // Compare the actual content + for (size_t i = 0; i < cached_str->length(); i++) { + EXPECT_EQ((*cached_str)[i], utf16_str[i]); + } + + JS_FreeAtom(ctx, atom); + JS_FreeValue(ctx, qjs_value); +} + +TEST(AtomicString, MixedUTF8AndUTF16) { + TEST_init(); + + // Create both UTF-8 and UTF-16 strings + AtomicString utf8_str = AtomicString::CreateFromUTF8("Hello ASCII"); + AtomicString utf16_str = AtomicString(u"Hello 世界"); + + // Verify their types + EXPECT_TRUE(utf8_str.Is8Bit()); + EXPECT_FALSE(utf16_str.Is8Bit()); + + // Convert both to UTF8String + UTF8String str1 = utf8_str.ToUTF8String(); + UTF8String str2 = utf16_str.ToUTF8String(); + + EXPECT_EQ(str1, "Hello ASCII"); + EXPECT_EQ(str2, "Hello 世界"); +} + +TEST(AtomicString, ConstructFromStringView) { + TEST_init(); + + String ascii = String::FromUTF8("string-view"); + StringView ascii_view(ascii); + AtomicString from_ascii(ascii_view); + EXPECT_TRUE(from_ascii.Is8Bit()); + EXPECT_EQ(from_ascii.ToUTF8String(), "string-view"); + + const char16_t wide_chars[] = u"Bảo hiểm"; + String wide(wide_chars, std::size(wide_chars) - 1); + StringView wide_view(wide); + AtomicString from_wide(wide_view); + EXPECT_FALSE(from_wide.Is8Bit()); + EXPECT_EQ(from_wide.ToUTF8String(), "Bảo hiểm"); + + StringView null_view; + AtomicString from_null(null_view); + EXPECT_TRUE(from_null.IsNull()); +} + +TEST(AtomicString, UTF16WithSurrogatePairs) { + TEST_init(); + + // Test with emoji that requires surrogate pairs + const char16_t emoji_str[] = u"Hello 😀 World 🌍"; + AtomicString utf16_str = AtomicString(emoji_str); + + EXPECT_FALSE(utf16_str.Is8Bit()); + + // Convert to UTF-8 and verify + UTF8String utf8_result = utf16_str.ToUTF8String(); + EXPECT_EQ(utf8_result, "Hello 😀 World 🌍"); +} + +TEST(AtomicString, UTF16ToNativeString) { + TEST_init(); + + // Create UTF-16 string + AtomicString utf16_str = AtomicString(u"Native 字符串"); + + // Convert to native string + auto native_str = utf16_str.ToNativeString(); + + EXPECT_EQ(native_str->length(), 10); + + // Verify the content + const uint16_t* chars = native_str->string(); + EXPECT_EQ(chars[0], u'N'); + EXPECT_EQ(chars[7], u'字'); + EXPECT_EQ(chars[9], u'串'); +} + +TEST(AtomicString, EmptyUTF16String) { + TEST_init(); + + // Test empty UTF-16 string + AtomicString empty_utf16 = AtomicString(u""); + + EXPECT_TRUE(empty_utf16.empty()); + EXPECT_EQ(empty_utf16.length(), 0); + EXPECT_EQ(empty_utf16.ToUTF8String(), ""); +} + +TEST(StringImpl, CreateFromUTF8ASCII) { + TEST_init(); + + // Test pure ASCII UTF-8 string + const char* ascii_utf8 = "Hello World"; + auto str = StringImpl::CreateFromUTF8(ascii_utf8, strlen(ascii_utf8)); + + EXPECT_TRUE(str->Is8Bit()); + EXPECT_EQ(str->length(), 11); + EXPECT_EQ(*str, "Hello World"); +} + +TEST(StringImpl, CreateFromUTF8WithUnicode) { + TEST_init(); + + // Test UTF-8 with Unicode characters + const char* utf8_str = "Hello 世界"; // "Hello World" in English and Chinese + auto str = StringImpl::CreateFromUTF8(utf8_str, strlen(utf8_str)); + + EXPECT_FALSE(str->Is8Bit()); + EXPECT_EQ(str->length(), 8); // "Hello " (6) + "世界" (2) + + const char16_t* chars = str->Characters16(); + EXPECT_EQ(chars[0], u'H'); + EXPECT_EQ(chars[5], u' '); + EXPECT_EQ(chars[6], u'世'); + EXPECT_EQ(chars[7], u'界'); +} + +TEST(StringImpl, CreateFromUTF8WithEmoji) { + TEST_init(); + + // Test UTF-8 with emoji (4-byte UTF-8, surrogate pairs in UTF-16) + const char* emoji_utf8 = "Hello 😀"; + auto str = StringImpl::CreateFromUTF8(emoji_utf8, strlen(emoji_utf8)); + + EXPECT_FALSE(str->Is8Bit()); + EXPECT_EQ(str->length(), 8); // "Hello " (6) + surrogate pair (2) + + const char16_t* chars = str->Characters16(); + // Check surrogate pair for 😀 (U+1F600) + EXPECT_EQ(chars[6], 0xD83D); // High surrogate + EXPECT_EQ(chars[7], 0xDE00); // Low surrogate +} + +TEST(StringImpl, CreateFromUTF8WithLatin1) { + TEST_init(); + + // Test UTF-8 with Latin-1 extended characters + const UTF8Char* latin1_utf8 = "Café"; // é is U+00E9 + auto str = StringImpl::CreateFromUTF8(latin1_utf8); + + EXPECT_TRUE(str->Is8Bit()); // Latin-1 fits in 8-bit + EXPECT_EQ(str->length(), 4); + + const LChar* chars = str->Characters8(); + EXPECT_EQ(chars[0], 'C'); + EXPECT_EQ(chars[1], 'a'); + EXPECT_EQ(chars[2], 'f'); + EXPECT_EQ(chars[3], 0xE9); +} + +TEST(StringImpl, CreateFromUTF8InvalidSequence) { + TEST_init(); + + // Test invalid UTF-8 sequence (should be replaced with U+FFFD) + const char invalid_utf8[] = "Hello\xFF\xFEWorld"; + auto str = StringImpl::CreateFromUTF8(invalid_utf8, sizeof(invalid_utf8) - 1); + + EXPECT_FALSE(str->Is8Bit()); + EXPECT_EQ(str->length(), 12); // "Hello" (5) + replacement chars (2) + "World" (5) + + const char16_t* chars = str->Characters16(); + EXPECT_EQ(chars[5], 0xFFFD); // Replacement character + EXPECT_EQ(chars[6], 0xFFFD); // Replacement character +} + +TEST(StringImpl, CreateFromUTF8Empty) { + TEST_init(); + + // Test empty string + auto str1 = StringImpl::CreateFromUTF8("", 0); + auto str2 = StringImpl::CreateFromUTF8(nullptr, 0); + + EXPECT_TRUE(str1->length() == 0); + EXPECT_TRUE(str2->length() == 0); + EXPECT_EQ(str1, str2); // Should return the same empty string singleton +} + +// Adding for hash collision reference +// TEST(StringImpl, HashCollision1) { +// TEST_init(); +// +// // Test hash collision +// auto str1 = StringImpl::CreateFromUTF8(R"#( +// * { +// padding: 0; +// margin: 0; +// } +// .q { +// margin: 10px; +// padding: 10px; +// flex: 1 1 auto; +// background: gold; +// } +// )#"); +// auto str2 = StringImpl::CreateFromUTF8("screen_async"); +// +// EXPECT_EQ(str1->GetHash(), str2->GetHash()); +// } diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 13d0aaa424..57e18bc57d 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #include "binding_initializer.h" @@ -16,50 +20,118 @@ #include "qjs_character_data.h" #include "qjs_close_event.h" #include "qjs_comment.h" -#include "qjs_computed_css_style_declaration.h" #include "qjs_console.h" -#include "qjs_css_style_declaration.h" +#include "qjs_css_import_rule.h" +#include "qjs_css_keyframe_rule.h" +#include "qjs_css_keyframes_rule.h" +#include "qjs_css_layer_block_rule.h" +#include "qjs_css_layer_statement_rule.h" +#include "qjs_css_rule.h" +#include "qjs_css_rule_list.h" + +#include "qjs_css_style_sheet.h" #include "qjs_custom_event.h" #include "qjs_document.h" #include "qjs_document_fragment.h" #include "qjs_dom_matrix.h" -#include "qjs_dom_matrix_readonly.h" +#include "qjs_dom_matrix_read_only.h" +#include "qjs_dom_point.h" +#include "qjs_dom_point_read_only.h" +#include "qjs_dom_string_map.h" #include "qjs_dom_token_list.h" #include "qjs_element.h" #include "qjs_element_attributes.h" #include "qjs_error_event.h" #include "qjs_event.h" #include "qjs_event_target.h" +#include "qjs_dart_binding_object.h" +#include "qjs_file.h" #include "qjs_focus_event.h" +#include "qjs_form_data.h" #include "qjs_gesture_event.h" +#include "qjs_hashchange_event.h" #include "qjs_html_all_collection.h" #include "qjs_html_anchor_element.h" +#include "qjs_html_article_element.h" +#include "qjs_html_aside_element.h" #include "qjs_html_body_element.h" +#include "qjs_html_bold_element.h" +#include "qjs_html_br_element.h" #include "qjs_html_button_element.h" #include "qjs_html_canvas_element.h" +#include "qjs_html_code_element.h" +#include "qjs_html_collection.h" +#include "qjs_html_dd_element.h" #include "qjs_html_div_element.h" +#include "qjs_html_dl_element.h" +#include "qjs_html_dt_element.h" #include "qjs_html_element.h" +#include "qjs_html_em_element.h" +#include "qjs_html_footer_element.h" #include "qjs_html_form_element.h" #include "qjs_html_head_element.h" +#include "qjs_html_header_element.h" +#include "qjs_html_heading_element.h" +#include "qjs_html_hr_element.h" #include "qjs_html_html_element.h" +#include "qjs_html_iframe_element.h" #include "qjs_html_image_element.h" #include "qjs_html_input_element.h" +#include "qjs_html_optgroup_element.h" +#include "qjs_html_option_element.h" +#include "qjs_html_select_element.h" +#include "qjs_html_italic_element.h" +#include "qjs_html_label_element.h" +#include "qjs_html_li_element.h" #include "qjs_html_link_element.h" +#include "qjs_html_main_element.h" +#include "qjs_html_mark_element.h" +#include "qjs_html_meta_element.h" +#include "qjs_html_nav_element.h" +#include "qjs_html_noscript_element.h" +#include "qjs_html_olist_element.h" +#include "qjs_html_paragraph_element.h" +#include "qjs_html_pre_element.h" +#include "qjs_html_quote_element.h" #include "qjs_html_script_element.h" +#include "qjs_html_section_element.h" +#include "qjs_html_small_element.h" +#include "qjs_html_span_element.h" +#include "qjs_html_strong_element.h" +#include "qjs_html_style_element.h" #include "qjs_html_template_element.h" #include "qjs_html_textarea_element.h" +#include "qjs_html_time_element.h" +#include "qjs_html_title_element.h" +#include "qjs_html_ulist_element.h" #include "qjs_html_unknown_element.h" +#include "qjs_image_bitmap.h" +#include "qjs_hybrid_router_change_event.h" +#include "qjs_idle_deadline.h" #include "qjs_image.h" -#include "qjs_inline_css_style_declaration.h" #include "qjs_input_event.h" #include "qjs_intersection_change_event.h" +#include "qjs_intersection_observer.h" +#include "qjs_intersection_observer_entry.h" #include "qjs_keyboard_event.h" +#include "qjs_computed_css_style_declaration.h" +#include "qjs_css_style_declaration.h" +#include "qjs_inline_css_style_declaration.h" +#include "qjs_legacy_computed_css_style_declaration.h" +#include "qjs_legacy_css_style_declaration.h" +#include "qjs_legacy_inline_css_style_declaration.h" #include "qjs_location.h" +#include "qjs_media_list.h" #include "qjs_message_event.h" #include "qjs_module_manager.h" #include "qjs_mouse_event.h" +#include "qjs_mutation_observer.h" +#include "qjs_mutation_observer_registration.h" +#include "qjs_mutation_record.h" +#include "qjs_native_loader.h" #include "qjs_node.h" #include "qjs_node_list.h" +#include "qjs_path_2d.h" #include "qjs_performance.h" #include "qjs_performance_entry.h" #include "qjs_performance_mark.h" @@ -68,12 +140,31 @@ #include "qjs_pop_state_event.h" #include "qjs_promise_rejection_event.h" #include "qjs_screen.h" +#include "qjs_screen_event.h" +#include "qjs_style_sheet.h" +#include "qjs_svg_circle_element.h" +#include "qjs_svg_element.h" +#include "qjs_svg_ellipse_element.h" +#include "qjs_svg_g_element.h" +#include "qjs_svg_geometry_element.h" +#include "qjs_svg_graphics_element.h" +#include "qjs_svg_line_element.h" +#include "qjs_svg_path_element.h" +#include "qjs_svg_rect_element.h" +#include "qjs_svg_style_element.h" +#include "qjs_svg_svg_element.h" +#include "qjs_svg_text_content_element.h" +#include "qjs_svg_text_element.h" +#include "qjs_svg_text_positioning_element.h" #include "qjs_text.h" +#include "qjs_text_metrics.h" #include "qjs_touch.h" #include "qjs_touch_event.h" #include "qjs_touch_list.h" #include "qjs_transition_event.h" #include "qjs_ui_event.h" +#include "qjs_webf_router_link_element.h" +#include "qjs_webf_touch_area_element.h" #include "qjs_widget_element.h" #include "qjs_window.h" #include "qjs_window_or_worker_global_scope.h" @@ -86,6 +177,7 @@ void InstallBindings(ExecutingContext* context) { QJSWindowOrWorkerGlobalScope::Install(context); QJSLocation::Install(context); QJSModuleManager::Install(context); + QJSDartBindingObject::Install(context); QJSConsole::Install(context); QJSEventTarget::Install(context); QJSWindow::Install(context); @@ -96,8 +188,11 @@ void InstallBindings(ExecutingContext* context) { QJSMessageEvent::Install(context); QJSAnimationEvent::Install(context); QJSCloseEvent::Install(context); + QJSHybridRouterChangeEvent::Install(context); + QJSScreenEvent::Install(context); QJSFocusEvent::Install(context); QJSGestureEvent::Install(context); + QJSHashchangeEvent::Install(context); QJSInputEvent::Install(context); QJSCustomEvent::Install(context); QJSMouseEvent::Install(context); @@ -121,41 +216,128 @@ void InstallBindings(ExecutingContext* context) { QJSHTMLHeadElement::Install(context); QJSHTMLBodyElement::Install(context); QJSHTMLHtmlElement::Install(context); + QJSHTMLIFrameElement::Install(context); QJSHTMLAnchorElement::Install(context); + QJSHTMLBoldElement::Install(context); QJSHTMLImageElement::Install(context); QJSHTMLInputElement::Install(context); + QJSHTMLSelectElement::Install(context); + QJSHTMLOptgroupElement::Install(context); + QJSHTMLOptionElement::Install(context); + QJSHTMLParagraphElement::Install(context); + QJSHTMLStyleElement::Install(context); QJSHTMLTextareaElement::Install(context); + QJSHTMLBrElement::Install(context); QJSHTMLButtonElement::Install(context); QJSHTMLFormElement::Install(context); QJSImage::Install(context); + QJSImageBitmap::Install(context); QJSHTMLScriptElement::Install(context); QJSHTMLLinkElement::Install(context); + QJSHTMLUListElement::Install(context); + QJSHTMLOListElement::Install(context); + QJSHTMLLIElement::Install(context); + QJSHTMLSpanElement::Install(context); + QJSHTMLPreElement::Install(context); + QJSHTMLCodeElement::Install(context); + QJSHTMLStrongElement::Install(context); + QJSHTMLEmElement::Install(context); + QJSHTMLSectionElement::Install(context); + QJSHTMLArticleElement::Install(context); + QJSHTMLNavElement::Install(context); + QJSHTMLHeaderElement::Install(context); + QJSHTMLFooterElement::Install(context); + QJSHTMLMainElement::Install(context); + QJSHTMLAsideElement::Install(context); + QJSHTMLTimeElement::Install(context); + QJSHTMLMarkElement::Install(context); + QJSHTMLMetaElement::Install(context); + QJSHTMLTitleElement::Install(context); + QJSHTMLLabelElement::Install(context); + QJSHTMLHRElement::Install(context); + QJSHTMLQuoteElement::Install(context); + QJSHTMLDLElement::Install(context); + QJSHTMLDTElement::Install(context); + QJSHTMLDDElement::Install(context); + QJSHTMLHeadingElement::Install(context); + QJSHTMLSmallElement::Install(context); + QJSHTMLNoScriptElement::Install(context); + QJSHTMLItalicElement::Install(context); + QJSHTMLUnknownElement::Install(context); QJSHTMLTemplateElement::Install(context); QJSHTMLCanvasElement::Install(context); + QJSWebFTouchAreaElement::Install(context); + QJSWebFRouterLinkElement::Install(context); QJSCanvasRenderingContext::Install(context); QJSCanvasRenderingContext2D::Install(context); QJSCanvasPattern::Install(context); QJSCanvasGradient::Install(context); - QJSDOMMatrixReadonly::Install(context); + QJSPath2D::Install(context); + QJSTextMetrics::Install(context); + QJSDOMMatrixReadOnly::Install(context); QJSDOMMatrix::Install(context); + QJSDOMPointReadOnly::Install(context); + QJSDOMPoint::Install(context); QJSCSSStyleDeclaration::Install(context); QJSInlineCssStyleDeclaration::Install(context); QJSComputedCssStyleDeclaration::Install(context); QJSBoundingClientRect::Install(context); - QJSHTMLAllCollection::Install(context); QJSScreen::Install(context); + QJSStyleSheet::Install(context); + QJSCSSStyleSheet::Install(context); + QJSCSSRule::Install(context); + QJSCSSRuleList::Install(context); + QJSCSSLayerBlockRule::Install(context); + QJSCSSLayerStatementRule::Install(context); + QJSCSSImportRule::Install(context); + QJSCSSKeyframeRule::Install(context); + QJSCSSKeyframesRule::Install(context); + QJSMediaList::Install(context); QJSBlob::Install(context); + QJSFile::Install(context); + QJSFormData::Install(context); QJSTouch::Install(context); QJSTouchList::Install(context); + QJSDOMStringMap::Install(context); + QJSMutationObserver::Install(context); + QJSMutationRecord::Install(context); + QJSMutationObserverRegistration::Install(context); QJSDOMTokenList::Install(context); QJSPerformance::Install(context); QJSPerformanceEntry::Install(context); QJSPerformanceMark::Install(context); QJSPerformanceMeasure::Install(context); + QJSHTMLCollection::Install(context); + QJSHTMLAllCollection::Install(context); + QJSIdleDeadline::Install(context); + + // SVG + QJSSVGElement::Install(context); + QJSSVGGraphicsElement::Install(context); + QJSSVGGeometryElement::Install(context); + QJSSVGSVGElement::Install(context); + QJSSVGRectElement::Install(context); + QJSSVGTextContentElement::Install(context); + QJSSVGTextPositioningElement::Install(context); + QJSSVGPathElement::Install(context); + QJSSVGTextElement::Install(context); + QJSSVGGElement::Install(context); + QJSSVGCircleElement::Install(context); + QJSSVGEllipseElement::Install(context); + QJSSVGStyleElement::Install(context); + QJSSVGLineElement::Install(context); + QJSNativeLoader::Install(context); + + // IntersectionObserver + QJSIntersectionObserver::Install(context); + QJSIntersectionObserverEntry::Install(context); // Legacy bindings, not standard. QJSElementAttributes::Install(context); + QJSLegacyCssStyleDeclaration::Install(context); + QJSLegacyComputedCssStyleDeclaration::Install(context); + QJSLegacyInlineCssStyleDeclaration::Install(context); } } // namespace webf diff --git a/bridge/bindings/qjs/binding_initializer.h b/bridge/bindings/qjs/binding_initializer.h index 9510ce44a5..ec54458d14 100644 --- a/bridge/bindings/qjs/binding_initializer.h +++ b/bridge/bindings/qjs/binding_initializer.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_BINDING_INITIALIZER_H diff --git a/bridge/bindings/qjs/converter.h b/bridge/bindings/qjs/converter.h index e60d2b1cdb..a7849ab199 100644 --- a/bridge/bindings/qjs/converter.h +++ b/bridge/bindings/qjs/converter.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_CONVERTER_H diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index a77d4d20e7..94a1cd9912 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -1,16 +1,20 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ #define BRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ #include -#include "atomic_string.h" +#include "foundation/string/atomic_string.h" +#include "foundation/string/wtf_string.h" #include "bindings/qjs/union_base.h" #include "converter.h" -#include "core/dom/document.h" #include "core/dom/events/event.h" #include "core/dom/events/event_target.h" #include "core/dom/node_list.h" @@ -29,6 +33,9 @@ #include "native_string_utils.h" #include "script_promise.h" +#include "core/css/computed_css_style_declaration.h" +#include "core/css/legacy/legacy_computed_css_style_declaration.h" + namespace webf { template @@ -190,7 +197,7 @@ struct Converter : public ConverterBase { JS_ToInt64(ctx, &v, value); return v; } - static JSValue ToValue(JSContext* ctx, uint32_t v) { return JS_NewInt64(ctx, v); } + static JSValue ToValue(JSContext* ctx, int64_t v) { return JS_NewInt64(ctx, v); } }; template <> @@ -213,16 +220,22 @@ struct Converter : public ConverterBase { } static JSValue ToValue(JSContext* ctx, const AtomicString& value) { return value.ToQuickJS(ctx); } - static JSValue ToValue(JSContext* ctx, NativeString* str) { + static JSValue ToValue(JSContext* ctx, SharedNativeString* str) { return JS_NewUnicodeString(ctx, str->string(), str->length()); } - static JSValue ToValue(JSContext* ctx, std::unique_ptr str) { + static JSValue ToValue(JSContext* ctx, std::unique_ptr str) { return JS_NewUnicodeString(ctx, str->string(), str->length()); } static JSValue ToValue(JSContext* ctx, uint16_t* bytes, size_t length) { - return JS_NewUnicodeString(ctx, bytes, length); + return JS_NewUnicodeString(ctx, bytes, static_cast(length)); } static JSValue ToValue(JSContext* ctx, const std::string& str) { return JS_NewString(ctx, str.c_str()); } + static JSValue ToValue(JSContext* ctx, const String& str) { + if (str.IsNull()) { + return JS_NULL; + } + return JS_NewString(ctx, str.Utf8().c_str()); + } }; template <> @@ -238,6 +251,10 @@ struct Converter> : public ConverterBase } static JSValue ToValue(JSContext* ctx, const std::string& str) { return Converter::ToValue(ctx, str); } static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { + if (value == AtomicString::Null()) { + return JS_UNDEFINED; + } + return Converter::ToValue(ctx, std::move(value)); } }; @@ -250,8 +267,13 @@ struct Converter> : public ConverterBase return Converter::FromValue(ctx, value, exception_state); } - static JSValue ToValue(JSContext* ctx, const std::string& value) { return AtomicString(ctx, value).ToQuickJS(ctx); } - static JSValue ToValue(JSContext* ctx, const AtomicString& value) { return value.ToQuickJS(ctx); } + static JSValue ToValue(JSContext* ctx, const std::string& value) { return AtomicString(value).ToQuickJS(ctx); } + static JSValue ToValue(JSContext* ctx, const AtomicString& value) { + if (value == AtomicString::Null()) { + return JS_NULL; + } + return value.ToQuickJS(ctx); + } }; template <> @@ -263,7 +285,7 @@ struct Converter : public ConverterBase return AtomicString(ctx, value); } - static JSValue ToValue(JSContext* ctx, const std::string& value) { return AtomicString(ctx, value).ToQuickJS(ctx); } + static JSValue ToValue(JSContext* ctx, const std::string& value) { return AtomicString(value).ToQuickJS(ctx); } static JSValue ToValue(JSContext* ctx, const AtomicString& value) { return value.ToQuickJS(ctx); } }; @@ -280,7 +302,14 @@ struct Converter> : public ConverterBase> { } ImplType v; - uint32_t length = Converter::FromValue(ctx, JS_GetPropertyStr(ctx, value, "length"), exception_state); + JSValue length_value = JS_GetPropertyStr(ctx, value, "length"); + if (JS_IsException(length_value)) { + exception_state.ThrowException(ctx, length_value); + JS_FreeValue(ctx, length_value); + return {}; + } + uint32_t length = Converter::FromValue(ctx, length_value, exception_state); + JS_FreeValue(ctx, length_value); v.reserve(length); @@ -314,6 +343,20 @@ struct Converter> : public ConverterBase> { return v; }; + static ImplType ArgumentsValue(ExecutingContext* context, + JSValue value, + uint32_t argv_index, + ExceptionState& exception_state) { + assert(!JS_IsException(value)); + if (JS_IsArray(context->ctx(), value)) { + return FromValue(context->ctx(), value, exception_state); + } + ImplType v; + exception_state.ThrowException(context->ctx(), ErrorType::TypeError, + ExceptionMessage::ArgumentNotOfType(argv_index, "Array")); + return v; + } + static JSValue ToValue(JSContext* ctx, ImplType value) { JSValue array = JS_NewArray(ctx); JS_SetPropertyStr(ctx, array, "length", Converter::ToValue(ctx, value.size())); @@ -334,6 +377,8 @@ struct Converter>> : public ConverterBase>::FromValue(ctx, value, exception_state); } + + static JSValue ToValue(JSContext* ctx, ImplType value) { return Converter>::ToValue(ctx, value); } }; template @@ -368,7 +413,12 @@ struct Converter : public ConverterBase { return BlobPart::Create(ctx, value, exception_state); } - static JSValue ToValue(JSContext* ctx, BlobPart* data) { return data->ToQuickJS(ctx); } + static JSValue ToValue(JSContext* ctx, BlobPart* data) { + if (data == nullptr) + return JS_NULL; + + return data->ToQuickJS(ctx); + } }; template <> @@ -384,6 +434,24 @@ template <> struct Converter : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); + if (JS_IsObject(value) && !JS_IsFunction(ctx, value)) { + JSValue handleEventMethod = JS_GetPropertyStr(ctx, value, "handleEvent"); + + if (JS_IsException(handleEventMethod)) { + exception_state.ThrowException(ctx, handleEventMethod); + JS_FreeValue(ctx, handleEventMethod); + return JSEventListener::CreateOrNull(nullptr); + } + + if (JS_IsFunction(ctx, handleEventMethod)) { + auto result = JSEventListener::CreateOrNull(QJSFunction::Create(ctx, handleEventMethod, value)); + JS_FreeValue(ctx, handleEventMethod); + return result; + } + + JS_FreeValue(ctx, handleEventMethod); + return JSEventListener::CreateOrNull(nullptr); + } return JSEventListener::CreateOrNull(QJSFunction::Create(ctx, value)); } }; @@ -434,6 +502,10 @@ struct Converter> : public ConverterBase::FromValue(ctx, value, exception_state); } @@ -446,6 +518,13 @@ struct Converter assert(!JS_IsException(value)); return T::Create(ctx, value, exception_state); } + + static JSValue ToValue(JSContext* ctx, typename T::ImplType value) { + if (value == nullptr) + return JS_NULL; + + return value->toQuickJS(ctx); + } }; template @@ -459,13 +538,7 @@ struct Converter::val uint32_t argv_index, ExceptionState& exception_state) { assert(!JS_IsException(value)); - const WrapperTypeInfo* wrapper_type_info = Node::GetStaticWrapperTypeInfo(); - if (JS_IsInstanceOf(context->ctx(), value, context->contextData()->constructorForType(wrapper_type_info))) { - return FromValue(context->ctx(), value, exception_state); - } - exception_state.ThrowException(context->ctx(), ErrorType::TypeError, - ExceptionMessage::ArgumentNotOfType(argv_index, wrapper_type_info->className)); - return nullptr; + return FromValue(context->ctx(), value, exception_state); } static JSValue ToValue(JSContext* ctx, typename T::ImplType value) { if (value == nullptr) @@ -519,8 +592,16 @@ struct ConverterclassName)); return nullptr; } - static JSValue ToValue(JSContext* ctx, T* value) { return value->ToQuickJS(); } - static JSValue ToValue(JSContext* ctx, const T* value) { return value->ToQuickJS(); } + static JSValue ToValue(JSContext* ctx, T* value) { + if (value == nullptr) + return JS_NULL; + return value->ToQuickJS(); + } + static JSValue ToValue(JSContext* ctx, const T* value) { + if (value == nullptr) + return JS_NULL; + return value->ToQuickJS(); + } }; template @@ -556,6 +637,106 @@ struct Converter +struct Converter { + using ImplType = ElementStyle; + + static ElementStyle FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + auto ectx = ExecutingContext::From(ctx); + if (ectx->isBlinkEnabled()) { + if (JS_IsNull(value)) { + return static_cast(nullptr); + } + + return Converter::FromValue(ctx, value, exception_state); + } else { + if (JS_IsNull(value)) { + return static_cast(nullptr); + } + + return Converter::FromValue(ctx, value, exception_state); + } + } + + static ElementStyle ArgumentsValue(ExecutingContext* context, + JSValue value, + uint32_t argv_index, + ExceptionState& exception_state) { + if (context->isBlinkEnabled()) { + if (JS_IsNull(value)) { + return static_cast(nullptr); + } + + return Converter::ArgumentsValue(context, value, argv_index, exception_state); + } else { + if (JS_IsNull(value)) { + return static_cast(nullptr); + } + + return Converter::ArgumentsValue(context, value, argv_index, exception_state); + } + } + + static JSValue ToValue(JSContext* ctx, ElementStyle value) { + return std::visit(MakeVisitor([&ctx](auto* style) { + if (style == nullptr) + return JS_NULL; + return Converter>>::ToValue(ctx, style); + }), + value); + } +}; + +template <> +struct Converter { + using ImplType = WindowComputedStyle; + + static WindowComputedStyle FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + auto ectx = ExecutingContext::From(ctx); + if (ectx->isBlinkEnabled()) { + if (JS_IsNull(value)) { + return static_cast(nullptr); + } + + return Converter::FromValue(ctx, value, exception_state); + } else { + if (JS_IsNull(value)) { + return static_cast(nullptr); + } + + return Converter::FromValue(ctx, value, exception_state); + } + } + + static WindowComputedStyle ArgumentsValue(ExecutingContext* context, + JSValue value, + uint32_t argv_index, + ExceptionState& exception_state) { + if (context->isBlinkEnabled()) { + if (JS_IsNull(value)) { + return static_cast(nullptr); + } + + return Converter::ArgumentsValue(context, value, argv_index, exception_state); + } else { + if (JS_IsNull(value)) { + return static_cast(nullptr); + } + + return Converter::ArgumentsValue(context, value, argv_index, exception_state); + } + } + + static JSValue ToValue(JSContext* ctx, WindowComputedStyle value) { + return std::visit(MakeVisitor([&ctx](auto* style) { + if (style == nullptr) + return JS_NULL; + return Converter>>::ToValue(ctx, style); + }), + value); + } +}; + }; // namespace webf #endif // BRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ diff --git a/bridge/bindings/qjs/cppgc/garbage_collected.h b/bridge/bindings/qjs/cppgc/garbage_collected.h index 055b438488..c2afa546f6 100644 --- a/bridge/bindings/qjs/cppgc/garbage_collected.h +++ b/bridge/bindings/qjs/cppgc/garbage_collected.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_GARBAGE_COLLECTED_H #define BRIDGE_GARBAGE_COLLECTED_H @@ -8,7 +12,6 @@ #include #include -#include "bindings/qjs/qjs_engine_patch.h" #include "foundation/casting.h" #include "foundation/macros.h" #include "local_handle.h" @@ -67,6 +70,15 @@ class MakeGarbageCollectedTrait { friend GarbageCollected; }; +class GarbageCollectedMixin { + public: + /** + * This Trace method must be overriden by objects inheriting from + * GarbageCollectedMixin. + */ + virtual void Trace(GCVisitor*) const {} +}; + template T* MakeGarbageCollected(Args&&... args) { static_assert(std::is_base_of::value, diff --git a/bridge/bindings/qjs/cppgc/gc_visitor.cc b/bridge/bindings/qjs/cppgc/gc_visitor.cc index 47fd9e2c43..0114205c91 100644 --- a/bridge/bindings/qjs/cppgc/gc_visitor.cc +++ b/bridge/bindings/qjs/cppgc/gc_visitor.cc @@ -1,13 +1,17 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #include "gc_visitor.h" #include "bindings/qjs/script_wrappable.h" namespace webf { -void GCVisitor::Trace(JSValue value) { +void GCVisitor::TraceValue(JSValue value) { JS_MarkValue(runtime_, value, markFunc_); } diff --git a/bridge/bindings/qjs/cppgc/gc_visitor.h b/bridge/bindings/qjs/cppgc/gc_visitor.h index 0e7516eddb..b7e4e3b5cf 100644 --- a/bridge/bindings/qjs/cppgc/gc_visitor.h +++ b/bridge/bindings/qjs/cppgc/gc_visitor.h @@ -1,11 +1,14 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_GC_VISITOR_H #define BRIDGE_GC_VISITOR_H -#include #include #include "foundation/macros.h" @@ -24,13 +27,13 @@ class GCVisitor final { explicit GCVisitor(JSRuntime* rt, JS_MarkFunc* markFunc) : runtime_(rt), markFunc_(markFunc){}; template - void Trace(const Member& target) { + void TraceMember(const Member& target) { if (target.Get() != nullptr) { JS_MarkValue(runtime_, target.Get()->jsObject_, markFunc_); } }; - void Trace(JSValue value); + void TraceValue(JSValue value); private: JSRuntime* runtime_{nullptr}; diff --git a/bridge/bindings/qjs/cppgc/local_handle.h b/bridge/bindings/qjs/cppgc/local_handle.h index 7408d5b71b..e3bd6add48 100644 --- a/bridge/bindings/qjs/cppgc/local_handle.h +++ b/bridge/bindings/qjs/cppgc/local_handle.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_BINDINGS_QJS_CPPGC_LOCAL_HANDLE_H_ diff --git a/bridge/bindings/qjs/cppgc/member.h b/bridge/bindings/qjs/cppgc/member.h index a23c092eae..b2b4b1ad11 100644 --- a/bridge/bindings/qjs/cppgc/member.h +++ b/bridge/bindings/qjs/cppgc/member.h @@ -1,15 +1,19 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_BINDINGS_QJS_CPPGC_MEMBER_H_ #define BRIDGE_BINDINGS_QJS_CPPGC_MEMBER_H_ #include -#include "bindings/qjs/qjs_engine_patch.h" #include "bindings/qjs/script_value.h" #include "bindings/qjs/script_wrappable.h" +#include "core/executing_context.h" #include "foundation/casting.h" #include "mutation_scope.h" @@ -25,23 +29,22 @@ class ScriptWrappable; template > class Member { public: + struct KeyHasher { + std::size_t operator()(const Member& k) const { return reinterpret_cast(k.raw_); } + }; + Member() = default; Member(T* ptr) { SetRaw(ptr); } Member(const Member& other) { raw_ = other.raw_; runtime_ = other.runtime_; + js_object_ptr_ = other.js_object_ptr_; + ((JSRefCountHeader*)other.js_object_ptr_)->ref_count++; } ~Member() { if (raw_ != nullptr) { assert(runtime_ != nullptr); - // There are two ways to free the member values: - // One is by GC marking and sweep stage. - // Two is by free directly when running out of function body. - // We detect the GC phase to handle case two, and free our members by hand(call JS_FreeValueRT directly). - JSGCPhaseEnum phase = JS_GetEnginePhase(runtime_); - if (phase == JS_GC_PHASE_DECREF) { - JS_FreeValueRT(runtime_, raw_->ToQuickJSUnsafe()); - } + JS_FreeValueRT(runtime_, JS_MKPTR(JS_TAG_OBJECT, js_object_ptr_)); } }; @@ -53,19 +56,23 @@ class Member { if (phase == JS_GC_PHASE_REMOVE_CYCLES) { // Free the pointer immediately if parent object are removed by GC. - JS_FreeValueRT(runtime_, raw_->ToQuickJSUnsafe()); + JS_FreeValueRT(runtime_, JS_MKPTR(JS_TAG_OBJECT, js_object_ptr_)); } else { auto* wrappable = To(raw_); + assert(wrappable->GetExecutingContext()->HasMutationScope()); // Record the free operation to avoid JSObject had been freed immediately. wrappable->GetExecutingContext()->mutationScope()->RecordFree(wrappable); } raw_ = nullptr; + js_object_ptr_ = nullptr; } // Copy assignment. Member& operator=(const Member& other) { raw_ = other.raw_; runtime_ = other.runtime_; + js_object_ptr_ = other.js_object_ptr_; + ((JSRefCountHeader*)other.js_object_ptr_)->ref_count++; return *this; } // Move assignment. @@ -90,6 +97,12 @@ class Member { T* operator->() const { return Get(); } T& operator*() const { return *Get(); } + T* Release() { + T* result = Get(); + Clear(); + return result; + } + private: void SetRaw(T* p) { if (p != nullptr) { @@ -97,12 +110,14 @@ class Member { assert_m(wrappable->GetExecutingContext()->HasMutationScope(), "Member must be used after MemberMutationScope allcated."); runtime_ = wrappable->runtime(); + js_object_ptr_ = JS_VALUE_GET_PTR(wrappable->ToQuickJSUnsafe()); JS_DupValue(wrappable->ctx(), wrappable->ToQuickJSUnsafe()); } raw_ = p; } mutable T* raw_{nullptr}; + mutable void* js_object_ptr_{nullptr}; JSRuntime* runtime_{nullptr}; }; diff --git a/bridge/bindings/qjs/cppgc/mutation_scope.cc b/bridge/bindings/qjs/cppgc/mutation_scope.cc index 5be04f98ff..63cde6fd6a 100644 --- a/bridge/bindings/qjs/cppgc/mutation_scope.cc +++ b/bridge/bindings/qjs/cppgc/mutation_scope.cc @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #include "mutation_scope.h" @@ -9,7 +13,8 @@ namespace webf { -MemberMutationScope::MemberMutationScope(ExecutingContext* context) : context_(context) { +MemberMutationScope::MemberMutationScope(ExecutingContext* context) + : context_(context), runtime_(context->GetScriptState()->runtime()) { context->SetMutationScope(*this); } @@ -35,10 +40,9 @@ void MemberMutationScope::RecordFree(ScriptWrappable* wrappable) { } void MemberMutationScope::ApplyRecord() { - JSContext* ctx = context_->ctx(); for (auto& entry : mutation_records_) { for (int i = 0; i < -entry.second; i++) { - JS_FreeValue(ctx, entry.first->ToQuickJSUnsafe()); + JS_FreeValueRT(runtime_, entry.first->ToQuickJSUnsafe()); } } } diff --git a/bridge/bindings/qjs/cppgc/mutation_scope.h b/bridge/bindings/qjs/cppgc/mutation_scope.h index e48ec54688..70c106ecf1 100644 --- a/bridge/bindings/qjs/cppgc/mutation_scope.h +++ b/bridge/bindings/qjs/cppgc/mutation_scope.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_BINDINGS_QJS_CPPGC_MUTATION_SCOPE_H_ @@ -36,6 +40,7 @@ class MemberMutationScope { MemberMutationScope* parent_scope_{nullptr}; ExecutingContext* context_; + JSRuntime* runtime_{nullptr}; std::unordered_map mutation_records_; }; diff --git a/bridge/bindings/qjs/cppgc/trace_if_needed.h b/bridge/bindings/qjs/cppgc/trace_if_needed.h index 263573c0a7..489bff45d8 100644 --- a/bridge/bindings/qjs/cppgc/trace_if_needed.h +++ b/bridge/bindings/qjs/cppgc/trace_if_needed.h @@ -1,17 +1,21 @@ /* - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef WEBF_BINDINGS_QJS_CPPGC_TRACE_IF_NEEDED_H_ #define WEBF_BINDINGS_QJS_CPPGC_TRACE_IF_NEEDED_H_ // clang-format off -#include "bindings/qjs/atomic_string.h" #include "bindings/qjs/js_event_handler.h" #include "bindings/qjs/js_event_listener.h" #include "bindings/qjs/script_promise.h" #include "bindings/qjs/script_wrappable.h" #include "bindings/qjs/idl_type.h" +#include "bindings/qjs/dictionary_base.h" #include "gc_visitor.h" #include "member.h" // clang-format on @@ -48,6 +52,16 @@ struct TraceIfNeeded : TraceIfNeededBase { static void Trace(GCVisitor*, const ImplType&) {} }; +template <> +struct TraceIfNeeded : TraceIfNeededBase { + static void Trace(GCVisitor* visitor, const ImplType& value) {} +}; + +template +struct TraceIfNeeded::value>> : TraceIfNeededBase { + static void Trace(GCVisitor* visitor, const typename T::ImplType& value) {} +}; + template struct TraceIfNeeded> : TraceIfNeededBase> { using ImplType = typename IDLSequence::ImplType>::ImplType; @@ -57,7 +71,7 @@ struct TraceIfNeeded> : TraceIfNeededBase> { template struct TraceIfNeeded::value>> { - static void Trace(GCVisitor* visitor, const Member& value) { visitor->Trace(value); } + static void Trace(GCVisitor* visitor, const Member& value) { visitor->TraceMember(value); } }; template <> diff --git a/bridge/bindings/qjs/cppgc/type-traits.h b/bridge/bindings/qjs/cppgc/type-traits.h new file mode 100644 index 0000000000..5230485388 --- /dev/null +++ b/bridge/bindings/qjs/cppgc/type-traits.h @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2022-2024 The WebF authors. All rights reserved. + +#ifndef BRIDGE_QJS_CPPGC_TYPE_TRAITS_H_ +#define BRIDGE_QJS_CPPGC_TYPE_TRAITS_H_ + +// This file should stay with minimal dependencies to allow embedder to check +// against Oilpan types without including any other parts. +#include +#include + +namespace cppgc { + +class Visitor; + +namespace internal { +template +class BasicMember; +struct DijkstraWriteBarrierPolicy; +struct NoWriteBarrierPolicy; +class StrongMemberTag; +class UntracedMemberTag; +class WeakMemberTag; + +// Not supposed to be specialized by the user. +template +struct IsWeak : std::false_type {}; + +// IsTraceMethodConst is used to verify that all Trace methods are marked as +// const. It is equivalent to IsTraceable but for a non-const object. +template +struct IsTraceMethodConst : std::false_type {}; + +template +struct IsTraceMethodConst().Trace(std::declval()))>> + : std::true_type {}; + +template +struct IsTraceable : std::false_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct IsTraceable().Trace(std::declval()))>> : std::true_type { + // All Trace methods should be marked as const. If an object of type + // 'T' is traceable then any object of type 'const T' should also + // be traceable. + static_assert(IsTraceMethodConst(), "Trace methods should be marked as const."); +}; + +template +constexpr bool IsTraceableV = IsTraceable::value; + +template +struct HasGarbageCollectedMixinTypeMarker : std::false_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct HasGarbageCollectedMixinTypeMarker< + T, + std::void_t::IsGarbageCollectedMixinTypeMarker>> : std::true_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct HasGarbageCollectedTypeMarker : std::false_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct HasGarbageCollectedTypeMarker::IsGarbageCollectedTypeMarker>> + : std::true_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template ::value, + bool = HasGarbageCollectedMixinTypeMarker::value> +struct IsGarbageCollectedMixinType : std::false_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct IsGarbageCollectedMixinType : std::true_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template ::value> +struct IsGarbageCollectedType : std::false_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct IsGarbageCollectedType : std::true_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct IsGarbageCollectedOrMixinType + : std::integral_constant::value || IsGarbageCollectedMixinType::value> { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template ::value && HasGarbageCollectedMixinTypeMarker::value)> +struct IsGarbageCollectedWithMixinType : std::false_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct IsGarbageCollectedWithMixinType : std::true_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct IsSubclassOfBasicMemberTemplate { + private: + template + static std::true_type SubclassCheck( + const BasicMember*); + static std::false_type SubclassCheck(...); + + public: + static constexpr bool value = decltype(SubclassCheck(std::declval*>()))::value; +}; + +template ::value> +struct IsMemberType : std::false_type {}; + +template +struct IsMemberType : std::true_type {}; + +template ::value> +struct IsWeakMemberType : std::false_type {}; + +template +struct IsWeakMemberType : std::true_type {}; + +template ::value> +struct IsUntracedMemberType : std::false_type {}; + +template +struct IsUntracedMemberType : std::true_type {}; + +template +struct IsComplete { + private: + template + static std::true_type IsSizeOfKnown(U*); + static std::false_type IsSizeOfKnown(...); + + public: + static constexpr bool value = decltype(IsSizeOfKnown(std::declval()))::value; +}; + +template +constexpr bool IsDecayedSameV = std::is_same_v, std::decay_t>; + +template +constexpr bool IsStrictlyBaseOfV = std::is_base_of_v, std::decay_t> && !IsDecayedSameV; + +template +constexpr bool IsAnyMemberTypeV = false; + +template +constexpr bool + IsAnyMemberTypeV> = true; + +} // namespace internal + +/** + * Value is true for types that inherit from `GarbageCollectedMixin` but not + * `GarbageCollected` (i.e., they are free mixins), and false otherwise. + */ +template +constexpr bool IsGarbageCollectedMixinTypeV = internal::IsGarbageCollectedMixinType::value; + +/** + * Value is true for types that inherit from `GarbageCollected`, and false + * otherwise. + */ +template +constexpr bool IsGarbageCollectedTypeV = internal::IsGarbageCollectedType::value; + +/** + * Value is true for types that inherit from either `GarbageCollected` or + * `GarbageCollectedMixin`, and false otherwise. + */ +template +constexpr bool IsGarbageCollectedOrMixinTypeV = internal::IsGarbageCollectedOrMixinType::value; + +/** + * Value is true for types that inherit from `GarbageCollected` and + * `GarbageCollectedMixin`, and false otherwise. + */ +template +constexpr bool IsGarbageCollectedWithMixinTypeV = internal::IsGarbageCollectedWithMixinType::value; + +/** + * Value is true for types of type `Member`, and false otherwise. + */ +template +constexpr bool IsMemberTypeV = internal::IsMemberType::value; + +/** + * Value is true for types of type `UntracedMember`, and false otherwise. + */ +template +constexpr bool IsUntracedMemberTypeV = internal::IsUntracedMemberType::value; + +/** + * Value is true for types of type `WeakMember`, and false otherwise. + */ +template +constexpr bool IsWeakMemberTypeV = internal::IsWeakMemberType::value; + +/** + * Value is true for types that are considered weak references, and false + * otherwise. + */ +template +constexpr bool IsWeakV = internal::IsWeak::value; + +/** + * Value is true for types that are complete, and false otherwise. + */ +template +constexpr bool IsCompleteV = internal::IsComplete::value; + +/** + * Value is true for member types `Member` and `WeakMember`. + */ +template +constexpr bool IsMemberOrWeakMemberTypeV = IsMemberTypeV || IsWeakMemberTypeV; + +/** + * Value is true for any member type. + */ +template +constexpr bool IsAnyMemberTypeV = internal::IsAnyMemberTypeV>; + +} // namespace cppgc + +#endif // BRIDGE_QJS_CPPGC_TYPE_TRAITS_H_ diff --git a/bridge/bindings/qjs/dictionary_base.cc b/bridge/bindings/qjs/dictionary_base.cc index 28427ebe61..70502408db 100644 --- a/bridge/bindings/qjs/dictionary_base.cc +++ b/bridge/bindings/qjs/dictionary_base.cc @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #include "dictionary_base.h" diff --git a/bridge/bindings/qjs/dictionary_base.h b/bridge/bindings/qjs/dictionary_base.h index e51e50a0b6..18c3554b79 100644 --- a/bridge/bindings/qjs/dictionary_base.h +++ b/bridge/bindings/qjs/dictionary_base.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_BINDINGS_QJS_DICTIONARY_BASE_H_ diff --git a/bridge/bindings/qjs/exception_message.cc b/bridge/bindings/qjs/exception_message.cc index 6ba169d568..80df2a0162 100644 --- a/bridge/bindings/qjs/exception_message.cc +++ b/bridge/bindings/qjs/exception_message.cc @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #include "exception_message.h" diff --git a/bridge/bindings/qjs/exception_message.h b/bridge/bindings/qjs/exception_message.h index 757887368e..eb4b965b9a 100644 --- a/bridge/bindings/qjs/exception_message.h +++ b/bridge/bindings/qjs/exception_message.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_BINDINGS_QJS_EXCEPTION_MESSAGE_H_ diff --git a/bridge/bindings/qjs/exception_state.cc b/bridge/bindings/qjs/exception_state.cc index a6262cfc9f..fa0977707d 100644 --- a/bridge/bindings/qjs/exception_state.cc +++ b/bridge/bindings/qjs/exception_state.cc @@ -1,11 +1,20 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #include "exception_state.h" +#include "plugin_api/exception_state.h" namespace webf { +ExceptionStatePublicMethods* ExceptionState::publicMethodPointer() { + return new ExceptionStatePublicMethods(); +} + void ExceptionState::ThrowException(JSContext* ctx, ErrorType type, const std::string& message) { switch (type) { case ErrorType::TypeError: @@ -42,4 +51,8 @@ JSValue ExceptionState::ToQuickJS() { return exception_; } +JSValue ExceptionState::CurrentException(JSContext* ctx) { + return JS_GetException(ctx); +} + } // namespace webf diff --git a/bridge/bindings/qjs/exception_state.h b/bridge/bindings/qjs/exception_state.h index 7369b1a9d0..10cd173053 100644 --- a/bridge/bindings/qjs/exception_state.h +++ b/bridge/bindings/qjs/exception_state.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_EXCEPTION_STATE_H #define BRIDGE_EXCEPTION_STATE_H @@ -13,6 +17,8 @@ namespace webf { +class ExceptionStatePublicMethods; + enum ErrorType { TypeError, InternalError, RangeError, ReferenceError, SyntaxError }; // ExceptionState is a scope-like class and provides a way to store an exception. @@ -21,6 +27,8 @@ class ExceptionState { WEBF_DISALLOW_NEW(); public: + static ExceptionStatePublicMethods* publicMethodPointer(); + void ThrowException(JSContext* ctx, ErrorType type, const std::string& message); void ThrowException(JSContext* ctx, JSValue exception); bool HasException(); @@ -28,6 +36,7 @@ class ExceptionState { ExceptionState& ReturnThis(); JSValue ToQuickJS(); + static JSValue CurrentException(JSContext* ctx); private: JSValue exception_{JS_NULL}; diff --git a/bridge/bindings/qjs/generated_code_helper.h b/bridge/bindings/qjs/generated_code_helper.h index 50a4aa554e..a9cc91b42c 100644 --- a/bridge/bindings/qjs/generated_code_helper.h +++ b/bridge/bindings/qjs/generated_code_helper.h @@ -1,14 +1,18 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_GENERATED_CODE_HELPER_H #define BRIDGE_GENERATED_CODE_HELPER_H -#include "atomic_string.h" #include "bindings/qjs/dictionary_base.h" #include "bindings/qjs/qjs_interface_bridge.h" #include "script_value.h" +#include "qjs_union_double_sequencedouble.h" #endif diff --git a/bridge/bindings/qjs/heap_deque.h b/bridge/bindings/qjs/heap_deque.h new file mode 100644 index 0000000000..0bd538b3e6 --- /dev/null +++ b/bridge/bindings/qjs/heap_deque.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. + */ + +/* + * replace third_party/blink/renderer/platform/heap/collection_support/heap_deque.h + */ + +#ifndef BRIDGE_BINDINGS_QJS_HEAP_DEQUE_H_ +#define BRIDGE_BINDINGS_QJS_HEAP_DEQUE_H_ + +// Include heap_vector.h to also make general VectorTraits available. +#include +#include +#include "bindings/qjs/cppgc/gc_visitor.h" + +namespace webf { + +template +class HeapDeque { + public: + HeapDeque() = default; + + explicit HeapDeque(size_t size) : deque_(size) {} + + HeapDeque(size_t size, const T& val) : deque_(size, val) {} + + HeapDeque(const HeapDeque& other) : deque_(other.deque_) {} + + HeapDeque& operator=(const HeapDeque& other) { + deque_ = other.deque_; + return *this; + } + + HeapDeque(HeapDeque&& other) noexcept : deque_(std::move(other.deque_)) {} + + HeapDeque& operator=(HeapDeque&& other) noexcept { + deque_ = std::move(other.deque_); + return *this; + } + + void push_back(const T& value) { deque_.push_back(value); } + + void push_front(const T& value) { deque_.push_front(value); } + + void pop_back() { deque_.pop_back(); } + + void pop_front() { deque_.pop_front(); } + + T& front() { return deque_.front(); } + + T& back() { return deque_.back(); } + + bool empty() const { return deque_.empty(); } + + size_t size() const { return deque_.size(); } + + void TraceValue(GCVisitor* visitor) const { + for (auto& item : deque_) { + visitor->TraceValue(item); + } + } + + void TraceMember(GCVisitor* visitor) const { + for (auto& item : deque_) { + visitor->TraceMember(item); + } + } + + private: + std::deque deque_; +}; + +} // namespace webf + +#endif // BRIDGE_BINDINGS_QJS_HEAP_DEQUE_H_ diff --git a/bridge/bindings/qjs/heap_hashmap.h b/bridge/bindings/qjs/heap_hashmap.h index 0baef2d3b7..adfdddba69 100644 --- a/bridge/bindings/qjs/heap_hashmap.h +++ b/bridge/bindings/qjs/heap_hashmap.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_BINDINGS_QJS_HEAP_HASHMAP_H_ @@ -68,7 +72,7 @@ void HeapHashMap::Erase(K key) { template void HeapHashMap::Trace(GCVisitor* visitor) const { for (auto& entry : entries_) { - visitor->Trace(entry.second); + visitor->TraceMember(entry.second); } } diff --git a/bridge/bindings/qjs/heap_vector.h b/bridge/bindings/qjs/heap_vector.h index dda66171f7..19441fd6e5 100644 --- a/bridge/bindings/qjs/heap_vector.h +++ b/bridge/bindings/qjs/heap_vector.h @@ -1,10 +1,22 @@ /* - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_BINDINGS_QJS_HEAP_VECTOR_H_ #define BRIDGE_BINDINGS_QJS_HEAP_VECTOR_H_ +#include +#include +#include +#include +#include + +#include "bindings/qjs/cppgc/gc_visitor.h" + namespace webf { template @@ -12,16 +24,81 @@ class HeapVector final { public: HeapVector() = default; - void Trace(GCVisitor* visitor) const; + void push_back(const V& value) { entries_.push_back(value); } + + const V& at(size_t index) const { + assert(index < entries_.size() && "Index out of range"); + return entries_.at(index); + } + + V& at(size_t index) { + assert(index < entries_.size() && "Index out of range"); + return entries_.at(index); + } + + size_t size() const { return entries_.size(); } + const V* data() const { return entries_.data(); } + + bool empty() const { return entries_.empty(); } + + void clear() { entries_.clear(); } + void reserve(size_t size) { entries_.reserve(size); } + + bool contains(const V& value) const { return std::find(entries_.begin(), entries_.end(), value) != entries_.end(); } + + int find(const V& value) const { + auto it = std::find(entries_.begin(), entries_.end(), value); + if (it != entries_.end()) { + return std::distance(entries_.begin(), it); + } else { + return -1; // not find + } + } + + bool erase_at(size_t index) { + if (index >= entries_.size()) { + std::cerr << "Index out of range" << std::endl; + return false; + } + entries_.erase(entries_.begin() + index); + return true; + } + + void AppendVector(const HeapVector& other) { + entries_.insert(entries_.end(), other.entries_.begin(), other.entries_.end()); + } + + void TraceValue(GCVisitor* visitor) const; + void TraceMember(GCVisitor* visitor) const; + + using iterator = typename std::vector::iterator; + using const_iterator = typename std::vector::const_iterator; + + iterator begin() { return entries_.begin(); } + const_iterator begin() const { return entries_.begin(); } + iterator end() { return entries_.end(); } + const_iterator end() const { return entries_.end(); } + + const std::vector& ToStdVector() const { return entries_; } + + using ValueType = V; + using value_type = V; private: std::vector entries_; }; template -void HeapVector::Trace(GCVisitor* visitor) const { +void HeapVector::TraceValue(GCVisitor* visitor) const { + for (auto& item : entries_) { + visitor->TraceValue(item); + } +} + +template +void HeapVector::TraceMember(GCVisitor* visitor) const { for (auto& item : entries_) { - visitor->Trace(item); + visitor->TraceMember(item); } } diff --git a/bridge/bindings/qjs/idl_type.h b/bridge/bindings/qjs/idl_type.h index 426d035344..4ea7fec2b4 100644 --- a/bridge/bindings/qjs/idl_type.h +++ b/bridge/bindings/qjs/idl_type.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_BINDINGS_QJS_CONVERTER_TS_TYPE_H_ @@ -40,11 +44,11 @@ struct IDLBoolean final : public IDLTypeBaseHelper {}; // Primitive types struct IDLInt32 final : public IDLTypeBaseHelper {}; -struct IDLInt64 final : public IDLTypeBaseHelper {}; +struct IDLInt64 final : public IDLTypeBaseHelper {}; struct IDLUint32 final : public IDLTypeBaseHelper {}; struct IDLDouble final : public IDLTypeBaseHelper {}; -class NativeString; +struct SharedNativeString; // DOMString is UTF-16 strings. // https://stackoverflow.com/questions/35123890/what-is-a-domstring-really struct IDLDOMString final : public IDLTypeBaseHelper {}; diff --git a/bridge/bindings/qjs/js_based_event_listener.cc b/bridge/bindings/qjs/js_based_event_listener.cc index 0f9b82237e..a5e9f7af3d 100644 --- a/bridge/bindings/qjs/js_based_event_listener.cc +++ b/bridge/bindings/qjs/js_based_event_listener.cc @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #include "js_based_event_listener.h" diff --git a/bridge/bindings/qjs/js_based_event_listener.h b/bridge/bindings/qjs/js_based_event_listener.h index a5965e6a9e..30b5c3f2f0 100644 --- a/bridge/bindings/qjs/js_based_event_listener.h +++ b/bridge/bindings/qjs/js_based_event_listener.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_BINDINGS_QJS_JS_BASED_EVENT_LISTENER_H_ diff --git a/bridge/bindings/qjs/js_event_handler.cc b/bridge/bindings/qjs/js_event_handler.cc index 937e528619..55aa148e20 100644 --- a/bridge/bindings/qjs/js_event_handler.cc +++ b/bridge/bindings/qjs/js_event_handler.cc @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #include "js_event_handler.h" @@ -68,15 +72,27 @@ void JSEventHandler::InvokeInternal(EventTarget& event_target, Event& event, Exc if (error_attribute.IsEmpty()) { error_attribute = ScriptValue::Empty(event.ctx()); } - arguments = {ScriptValue(ctx, Converter::ToValue(ctx, error_event->message())), - ScriptValue(ctx, Converter::ToValue(ctx, error_event->filename())), - ScriptValue(ctx, Converter::ToValue(ctx, error_event->lineno())), - ScriptValue(ctx, Converter::ToValue(ctx, error_event->colno())), error_attribute}; + + JSValue message = Converter::ToValue(ctx, error_event->message()); + JSValue filename = Converter::ToValue(ctx, error_event->filename()); + JSValue lineno = Converter::ToValue(ctx, error_event->lineno()); + JSValue colno = Converter::ToValue(ctx, error_event->colno()); + + arguments = {ScriptValue(ctx, message), + ScriptValue(ctx, filename), + ScriptValue(ctx, lineno), + ScriptValue(ctx, colno), error_attribute}; + + JS_FreeValue(ctx, message); + JS_FreeValue(ctx, filename); + JS_FreeValue(ctx, lineno); + JS_FreeValue(ctx, colno); } else { arguments.emplace_back(event.ToValue()); } - ScriptValue result = event_handler_->Invoke(event.ctx(), event_target.ToValue(), arguments.size(), arguments.data()); + ScriptValue result = + event_handler_->Invoke(event.ctx(), event_target.ToValue(), (int)arguments.size(), arguments.data()); if (result.IsException()) { exception_state.ThrowException(event.ctx(), result.QJSValue()); return; diff --git a/bridge/bindings/qjs/js_event_handler.h b/bridge/bindings/qjs/js_event_handler.h index 981ca7ac2d..f3c51097b1 100644 --- a/bridge/bindings/qjs/js_event_handler.h +++ b/bridge/bindings/qjs/js_event_handler.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_BINDINGS_QJS_JS_EVENT_HANDLER_H_ diff --git a/bridge/bindings/qjs/js_event_listener.cc b/bridge/bindings/qjs/js_event_listener.cc index 4f2f9a46e1..e0a9c2df27 100644 --- a/bridge/bindings/qjs/js_event_listener.cc +++ b/bridge/bindings/qjs/js_event_listener.cc @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #include "js_event_listener.h" diff --git a/bridge/bindings/qjs/js_event_listener.h b/bridge/bindings/qjs/js_event_listener.h index 5346f90a54..e9221e28eb 100644 --- a/bridge/bindings/qjs/js_event_listener.h +++ b/bridge/bindings/qjs/js_event_listener.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_BINDINGS_QJS_JS_EVENT_LISTENER_H_ diff --git a/bridge/bindings/qjs/macros.h b/bridge/bindings/qjs/macros.h index 0e1b33045f..fe8d539be5 100644 --- a/bridge/bindings/qjs/macros.h +++ b/bridge/bindings/qjs/macros.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_BINDING_MACROS_H diff --git a/bridge/bindings/qjs/member_installer.cc b/bridge/bindings/qjs/member_installer.cc index ffece4d1b4..6bd6181901 100644 --- a/bridge/bindings/qjs/member_installer.cc +++ b/bridge/bindings/qjs/member_installer.cc @@ -1,12 +1,15 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #include "member_installer.h" #include #include "core/executing_context.h" -#include "qjs_engine_patch.h" namespace webf { @@ -73,7 +76,7 @@ void MemberInstaller::InstallFunctions(ExecutingContext* context, std::initializer_list config) { JSContext* ctx = context->ctx(); for (auto& c : config) { - JSValue function = JS_NewCFunction(ctx, c.function, c.name, c.length); + JSValue function = JS_NewCFunction(ctx, c.function, c.name, (int)c.length); JS_DefinePropertyValueStr(ctx, root, c.name, function, c.flag); } } diff --git a/bridge/bindings/qjs/member_installer.h b/bridge/bindings/qjs/member_installer.h index 37772bd9e3..19a94ecedb 100644 --- a/bridge/bindings/qjs/member_installer.h +++ b/bridge/bindings/qjs/member_installer.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_MEMBER_INSTALLER_H diff --git a/bridge/bindings/qjs/native_string_utils.cc b/bridge/bindings/qjs/native_string_utils.cc index f28dfb2df6..8ba0ed1ab5 100644 --- a/bridge/bindings/qjs/native_string_utils.cc +++ b/bridge/bindings/qjs/native_string_utils.cc @@ -1,14 +1,22 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #include "native_string_utils.h" -#include "bindings/qjs/qjs_engine_patch.h" + +#include + +#include "foundation/dart_readable.h" +#include "foundation/string/wtf_string.h" namespace webf { -std::unique_ptr jsValueToNativeString(JSContext* ctx, JSValue value) { +std::unique_ptr jsValueToNativeString(JSContext* ctx, JSValue value) { bool isValueString = true; if (JS_IsNull(value)) { value = JS_NewString(ctx, ""); @@ -18,9 +26,15 @@ std::unique_ptr jsValueToNativeString(JSContext* ctx, JSValue valu isValueString = false; } - uint32_t length; - uint16_t* buffer = JS_ToUnicode(ctx, value, &length); - std::unique_ptr ptr = std::make_unique(buffer, length); + std::unique_ptr ptr; + if (JS_ValueGetStringLen(value) == 0) { + uint16_t tmp[] = {0}; + ptr = SharedNativeString::FromTemporaryString(tmp, 0); + } else { + uint32_t length; + uint16_t* buffer = JS_ToUnicode(ctx, value, &length); + ptr = std::make_unique(buffer, length); + } if (!isValueString) { JS_FreeValue(ctx, value); @@ -28,22 +42,42 @@ std::unique_ptr jsValueToNativeString(JSContext* ctx, JSValue valu return ptr; } -std::unique_ptr stringToNativeString(const std::string& string) { +std::unique_ptr stringToNativeString(const std::string& string) { std::u16string utf16; fromUTF8(string, utf16); - NativeString tmp{reinterpret_cast(utf16.c_str()), static_cast(utf16.size())}; - return std::make_unique(tmp.string(), tmp.length()); + SharedNativeString tmp{reinterpret_cast(utf16.c_str()), static_cast(utf16.size())}; + return SharedNativeString::FromTemporaryString(tmp.string(), tmp.length()); +} + +std::unique_ptr stringToNativeString(const String& string) { + uint32_t length = string.length(); + if (length == 0) { + return std::make_unique(nullptr, 0); + } + + auto* buffer = static_cast(dart_malloc(sizeof(uint16_t) * length)); + if (string.Is8Bit()) { + const LChar* p = string.Characters8(); + for (uint32_t i = 0; i < length; ++i) { + buffer[i] = static_cast(p[i]); + } + } else { + const UChar* p = string.Characters16(); + std::memcpy(buffer, p, sizeof(uint16_t) * length); + } + + return std::make_unique(buffer, length); } -std::string nativeStringToStdString(const NativeString* native_string) { +std::string nativeStringToStdString(const SharedNativeString* native_string) { std::u16string u16EventType = std::u16string(reinterpret_cast(native_string->string()), native_string->length()); return toUTF8(u16EventType); } -std::unique_ptr atomToNativeString(JSContext* ctx, JSAtom atom) { +std::unique_ptr atomToNativeString(JSContext* ctx, JSAtom atom) { JSValue stringValue = JS_AtomToString(ctx, atom); - std::unique_ptr string = jsValueToNativeString(ctx, stringValue); + std::unique_ptr string = jsValueToNativeString(ctx, stringValue); JS_FreeValue(ctx, stringValue); return string; } diff --git a/bridge/bindings/qjs/native_string_utils.h b/bridge/bindings/qjs/native_string_utils.h index b69ad3742e..d919753710 100644 --- a/bridge/bindings/qjs/native_string_utils.h +++ b/bridge/bindings/qjs/native_string_utils.h @@ -1,43 +1,80 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_NATIVE_STRING_UTILS_H #define BRIDGE_NATIVE_STRING_UTILS_H #include -#include -#include #include #include #include "foundation/native_string.h" +#include "core/base/strings/utf_string_conversion_utils.h" namespace webf { +class String; + // Convert to string and return a full copy of NativeString from JSValue. -std::unique_ptr jsValueToNativeString(JSContext* ctx, JSValue value); +std::unique_ptr jsValueToNativeString(JSContext* ctx, JSValue value); // Encode utf-8 to utf-16, and return a full copy of NativeString. -std::unique_ptr stringToNativeString(const std::string& string); +std::unique_ptr stringToNativeString(const std::string& string); + +// Copies a WebF String to a NativeString (UTF-16) without interning. +// Prefer this over AtomicString(value).ToNativeString() for transient values +// (e.g. style values) to avoid growing the AtomicString table. +std::unique_ptr stringToNativeString(const String& string); -std::string nativeStringToStdString(const NativeString* native_string); +std::string nativeStringToStdString(const SharedNativeString* native_string); template std::string toUTF8(const std::basic_string, std::allocator>& source) { - std::string result; + static_assert(sizeof(T) == sizeof(char16_t), "toUTF8 only supports UTF-16 input"); + const char16_t* src = reinterpret_cast(source.data()); + const size_t src_len = source.size(); - std::wstring_convert, T> convertor; - result = convertor.to_bytes(source); + std::string result; + base::PrepareForUTF8Output(src, src_len, &result); + size_t index = 0; + while (index < src_len) { + int32_t code_point = 0; + if (!base::ReadUnicodeCharacter(src, src_len, &index, &code_point)) { + // Use replacement character on invalid sequence. + code_point = 0xFFFD; + } + base::WriteUnicodeCharacter(code_point, &result); + ++index; + } return result; } template void fromUTF8(const std::string& source, std::basic_string, std::allocator>& result) { - std::wstring_convert, T> convertor; - result = convertor.from_bytes(source); + static_assert(sizeof(T) == sizeof(char16_t), "fromUTF8 only supports UTF-16 output"); + const char* src = source.data(); + const size_t src_len = source.size(); + + result.clear(); + base::PrepareForUTF16Or32Output(src, src_len, &result); + + size_t index = 0; + while (index < src_len) { + int32_t code_point = 0; + if (!base::ReadUnicodeCharacter(src, src_len, &index, &code_point)) { + // Use replacement character on invalid sequence. + code_point = 0xFFFD; + } + base::WriteUnicodeCharacter(code_point, &result); + ++index; + } } } // namespace webf diff --git a/bridge/bindings/qjs/qjs_dart_binding_object.cc b/bridge/bindings/qjs/qjs_dart_binding_object.cc new file mode 100644 index 0000000000..2d16833b9c --- /dev/null +++ b/bridge/bindings/qjs/qjs_dart_binding_object.cc @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. + */ + +#include "qjs_dart_binding_object.h" + +#include + +#include "bindings/qjs/converter_impl.h" +#include "bindings/qjs/cppgc/mutation_scope.h" +#include "bindings/qjs/exception_state.h" +#include "bindings/qjs/member_installer.h" +#include "bindings/qjs/script_value.h" +#include "core/dart_binding_object.h" +#include "core/executing_context.h" +#include "foundation/native_value_converter.h" + +namespace webf { + +// ExecutionContextData lazily creates a per-wrapper-type "constructor object" (a JS object with a JSClassCall) +// the first time `prototypeForType()` is requested. For wrapper types that are not installed onto the JS global +// object, that constructor object would otherwise remain as an external ref at runtime teardown (DUMP_LEAKS), +// because ExecutionContextData only caches raw JSValue handles and does not explicitly JS_FreeValue() them. +// +// DartBindingObject is such a type (its JS-visible constructor is injected from Dart), so we anchor the internal +// constructor object onto the global object under a private name to transfer ownership into the JS object graph. +static void EnsureDartBindingObjectConstructorAnchored(ExecutingContext* context) { + static constexpr const char* kCtorKey = "__webf_internal_dart_binding_object_constructor__"; + JSContext* ctx = context->ctx(); + + JSValue existing = JS_GetPropertyStr(ctx, context->Global(), kCtorKey); + bool already_defined = !JS_IsUndefined(existing) && !JS_IsException(existing); + JS_FreeValue(ctx, existing); + if (already_defined) { + return; + } + + // Transfer the cached constructor object's original reference to the global object. + JSValue ctor = context->contextData()->constructorForType(DartBindingObject::GetStaticWrapperTypeInfo()); + JS_DefinePropertyValueStr(ctx, context->Global(), kCtorKey, ctor, JS_PROP_CONFIGURABLE); +} + +static JSValue __webf_create_binding_object__(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + if (argc < 1) { + return JS_ThrowTypeError(ctx, "Failed to execute '__webf_create_binding_object__' : 1 argument required, but %d present.", + argc); + } + + ExceptionState exception_state; + ExecutingContext* context = ExecutingContext::From(ctx); + if (!context || !context->IsContextValid()) + return JS_NULL; + MemberMutationScope scope{context}; + + auto&& class_name = Converter::FromValue(ctx, argv[0], exception_state); + if (UNLIKELY(exception_state.HasException())) { + return exception_state.ToQuickJS(); + } + + std::vector native_args; + native_args.reserve(argc); + native_args.emplace_back(NativeValueConverter::ToNativeValue(ctx, class_name)); + + for (int i = 1; i < argc; i++) { + native_args.emplace_back(ScriptValue(ctx, argv[i]).ToNative(ctx, exception_state)); + if (UNLIKELY(exception_state.HasException())) { + return exception_state.ToQuickJS(); + } + } + + auto* binding_object = MakeGarbageCollected(context); + context->dartMethodPtr()->createBindingObject(context->isDedicated(), context->contextId(), binding_object->bindingObject(), + CreateBindingObjectType::kCreateCustomBindingObject, native_args.data(), + static_cast(native_args.size())); + + return binding_object->ToQuickJS(); +} + +void QJSDartBindingObject::Install(ExecutingContext* context) { + InstallGlobalFunctions(context); + EnsureDartBindingObjectConstructorAnchored(context); +} + +void QJSDartBindingObject::InstallGlobalFunctions(ExecutingContext* context) { + std::initializer_list functionConfig{ + {"__webf_create_binding_object__", __webf_create_binding_object__, 1}, + }; + MemberInstaller::InstallFunctions(context, context->Global(), functionConfig); +} + +} // namespace webf diff --git a/bridge/bindings/qjs/qjs_dart_binding_object.h b/bridge/bindings/qjs/qjs_dart_binding_object.h new file mode 100644 index 0000000000..2e0773d091 --- /dev/null +++ b/bridge/bindings/qjs/qjs_dart_binding_object.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_QJS_DART_BINDING_OBJECT_H +#define BRIDGE_QJS_DART_BINDING_OBJECT_H + +namespace webf { + +class ExecutingContext; + +class QJSDartBindingObject final { + public: + static void Install(ExecutingContext* context); + + private: + static void InstallGlobalFunctions(ExecutingContext* context); +}; + +} // namespace webf + +#endif // BRIDGE_QJS_DART_BINDING_OBJECT_H + diff --git a/bridge/bindings/qjs/qjs_engine_patch.cc b/bridge/bindings/qjs/qjs_engine_patch.cc deleted file mode 100644 index e610bf1a7b..0000000000 --- a/bridge/bindings/qjs/qjs_engine_patch.cc +++ /dev/null @@ -1,401 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "qjs_engine_patch.h" -#include -#include -#include - -typedef struct JSProxyData { - JSValue target; - JSValue handler; - uint8_t is_func; - uint8_t is_revoked; -} JSProxyData; - -typedef enum { - JS_GC_OBJ_TYPE_JS_OBJECT, - JS_GC_OBJ_TYPE_FUNCTION_BYTECODE, - JS_GC_OBJ_TYPE_SHAPE, - JS_GC_OBJ_TYPE_VAR_REF, - JS_GC_OBJ_TYPE_ASYNC_FUNCTION, - JS_GC_OBJ_TYPE_JS_CONTEXT, -} JSGCObjectTypeEnum; - -struct JSGCObjectHeader { - int ref_count; /* must come first, 32-bit */ - JSGCObjectTypeEnum gc_obj_type : 4; - uint8_t mark : 4; /* used by the GC */ - uint8_t dummy1; /* not used by the GC */ - uint16_t dummy2; /* not used by the GC */ - struct list_head link; -}; - -typedef struct JSShapeProperty { - uint32_t hash_next : 26; /* 0 if last in list */ - uint32_t flags : 6; /* JS_PROP_XXX */ - JSAtom atom; /* JS_ATOM_NULL = free property entry */ -} JSShapeProperty; - -struct JSShape { - /* hash table of size hash_mask + 1 before the start of the - structure (see prop_hash_end()). */ - JSGCObjectHeader header; - /* true if the shape is inserted in the shape hash table. If not, - JSShape.hash is not valid */ - uint8_t is_hashed; - /* If true, the shape may have small array index properties 'n' with 0 - <= n <= 2^31-1. If false, the shape is guaranteed not to have - small array index properties */ - uint8_t has_small_array_index; - uint32_t hash; /* current hash value */ - uint32_t prop_hash_mask; - int prop_size; /* allocated properties */ - int prop_count; /* include deleted properties */ - int deleted_prop_count; - JSShape* shape_hash_next; /* in JSRuntime.shape_hash[h] list */ - JSObject* proto; - JSShapeProperty prop[0]; /* prop_size elements */ -}; - -struct JSClass { - uint32_t class_id; /* 0 means free entry */ - JSAtom class_name; - JSClassFinalizer* finalizer; - JSClassGCMark* gc_mark; - JSClassCall* call; - /* pointers for exotic behavior, can be NULL if none are present */ - const JSClassExoticMethods* exotic; -}; - -struct JSRuntime { - JSMallocFunctions mf; - JSMallocState malloc_state; - const char* rt_info; - - int atom_hash_size; /* power of two */ - int atom_count; - int atom_size; - int atom_count_resize; /* resize hash table at this count */ - uint32_t* atom_hash; - JSString** atom_array; - int atom_free_index; /* 0 = none */ - - int class_count; /* size of class_array */ - JSClass* class_array; - - struct list_head context_list; /* list of JSContext.link */ - /* list of JSGCObjectHeader.link. List of allocated GC objects (used - by the garbage collector) */ - struct list_head gc_obj_list; - /* list of JSGCObjectHeader.link. Used during JS_FreeValueRT() */ - struct list_head gc_zero_ref_count_list; - struct list_head tmp_obj_list; /* used during GC */ - JSGCPhaseEnum gc_phase : 8; - size_t malloc_gc_threshold; -#ifdef DUMP_LEAKS - struct list_head string_list; /* list of JSString.link */ -#endif - /* stack limitation */ - const uint8_t* stack_top; - size_t stack_size; /* in bytes */ - - JSValue current_exception; - /* true if inside an out of memory error, to avoid recursing */ - BOOL in_out_of_memory : 8; - - struct JSStackFrame* current_stack_frame; - - JSInterruptHandler* interrupt_handler; - void* interrupt_opaque; - - JSHostPromiseRejectionTracker* host_promise_rejection_tracker; - void* host_promise_rejection_tracker_opaque; - - struct list_head job_list; /* list of JSJobEntry.link */ - - JSModuleNormalizeFunc* module_normalize_func; - JSModuleLoaderFunc* module_loader_func; - void* module_loader_opaque; - - BOOL can_block : 8; /* TRUE if Atomics.wait can block */ - /* used to allocate, free and clone SharedArrayBuffers */ - JSSharedArrayBufferFunctions sab_funcs; - - /* Shape hash table */ - int shape_hash_bits; - int shape_hash_size; - int shape_hash_count; /* number of hashed shapes */ - JSShape** shape_hash; -#ifdef CONFIG_BIGNUM - bf_context_t bf_ctx; - JSNumericOperations bigint_ops; - JSNumericOperations bigfloat_ops; - JSNumericOperations bigdecimal_ops; - uint32_t operator_count; -#endif - void* user_opaque; -}; - -typedef struct JSRegExp { - JSString* pattern; - JSString* bytecode; /* also contains the flags */ -} JSRegExp; - -typedef struct JSString JSString; - -struct JSObject { - union { - JSGCObjectHeader header; - struct { - int __gc_ref_count; /* corresponds to header.ref_count */ - uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */ - - uint8_t extensible : 1; - uint8_t free_mark : 1; /* only used when freeing objects with cycles */ - uint8_t is_exotic : 1; /* TRUE if object has exotic property handlers */ - uint8_t fast_array : 1; /* TRUE if u.array is used for get/put (for JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS and typed - arrays) */ - uint8_t is_constructor : 1; /* TRUE if object is a constructor function */ - uint8_t is_uncatchable_error : 1; /* if TRUE, error is not catchable */ - uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */ - uint8_t is_HTMLDDA : 1; /* specific annex B IsHtmlDDA behavior */ - uint16_t class_id; /* see JS_CLASS_x */ - }; - }; - /* byte offsets: 16/24 */ - JSShape* shape; /* prototype and property names + flag */ - void* prop; /* array of properties */ - /* byte offsets: 24/40 */ - struct JSMapRecord* first_weak_ref; /* XXX: use a bit and an external hash table? */ - /* byte offsets: 28/48 */ - union { - void* opaque; - struct JSBoundFunction* bound_function; /* JS_CLASS_BOUND_FUNCTION */ - struct JSCFunctionDataRecord* c_function_data_record; /* JS_CLASS_C_FUNCTION_DATA */ - struct JSForInIterator* for_in_iterator; /* JS_CLASS_FOR_IN_ITERATOR */ - struct JSArrayBuffer* array_buffer; /* JS_CLASS_ARRAY_BUFFER, JS_CLASS_SHARED_ARRAY_BUFFER */ - struct JSTypedArray* typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_DATAVIEW */ -#ifdef CONFIG_BIGNUM - struct JSFloatEnv* float_env; /* JS_CLASS_FLOAT_ENV */ - struct JSOperatorSetData* operator_set; /* JS_CLASS_OPERATOR_SET */ -#endif - struct JSMapState* map_state; /* JS_CLASS_MAP..JS_CLASS_WEAKSET */ - struct JSMapIteratorData* map_iterator_data; /* JS_CLASS_MAP_ITERATOR, JS_CLASS_SET_ITERATOR */ - struct JSArrayIteratorData* array_iterator_data; /* JS_CLASS_ARRAY_ITERATOR, JS_CLASS_STRING_ITERATOR */ - struct JSRegExpStringIteratorData* regexp_string_iterator_data; /* JS_CLASS_REGEXP_STRING_ITERATOR */ - struct JSGeneratorData* generator_data; /* JS_CLASS_GENERATOR */ - struct JSProxyData* proxy_data; /* JS_CLASS_PROXY */ - struct JSPromiseData* promise_data; /* JS_CLASS_PROMISE */ - struct JSPromiseFunctionData* - promise_function_data; /* JS_CLASS_PROMISE_RESOLVE_FUNCTION, JS_CLASS_PROMISE_REJECT_FUNCTION */ - struct JSAsyncFunctionData* - async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, JS_CLASS_ASYNC_FUNCTION_REJECT */ - struct JSAsyncFromSyncIteratorData* async_from_sync_iterator_data; /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR */ - struct JSAsyncGeneratorData* async_generator_data; /* JS_CLASS_ASYNC_GENERATOR */ - struct { /* JS_CLASS_BYTECODE_FUNCTION: 12/24 bytes */ - /* also used by JS_CLASS_GENERATOR_FUNCTION, JS_CLASS_ASYNC_FUNCTION and JS_CLASS_ASYNC_GENERATOR_FUNCTION */ - struct JSFunctionBytecode* function_bytecode; - void** var_refs; - JSObject* home_object; /* for 'super' access */ - } func; - struct { /* JS_CLASS_C_FUNCTION: 12/20 bytes */ - JSContext* realm; - JSCFunctionType c_function; - uint8_t length; - uint8_t cproto; - int16_t magic; - } cfunc; - /* array part for fast arrays and typed arrays */ - struct { /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS, JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ - union { - uint32_t size; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */ - struct JSTypedArray* typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ - } u1; - union { - JSValue* values; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */ - void* ptr; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ - int8_t* int8_ptr; /* JS_CLASS_INT8_ARRAY */ - uint8_t* uint8_ptr; /* JS_CLASS_UINT8_ARRAY, JS_CLASS_UINT8C_ARRAY */ - int16_t* int16_ptr; /* JS_CLASS_INT16_ARRAY */ - uint16_t* uint16_ptr; /* JS_CLASS_UINT16_ARRAY */ - int32_t* int32_ptr; /* JS_CLASS_INT32_ARRAY */ - uint32_t* uint32_ptr; /* JS_CLASS_UINT32_ARRAY */ - int64_t* int64_ptr; /* JS_CLASS_INT64_ARRAY */ - uint64_t* uint64_ptr; /* JS_CLASS_UINT64_ARRAY */ - float* float_ptr; /* JS_CLASS_FLOAT32_ARRAY */ - double* double_ptr; /* JS_CLASS_FLOAT64_ARRAY */ - } u; - uint32_t count; /* <= 2^31-1. 0 for a detached typed array */ - } array; /* 12/20 bytes */ - JSRegExp regexp; /* JS_CLASS_REGEXP: 8/16 bytes */ - JSValue object_data; /* for JS_SetObjectData(): 8/16/16 bytes */ - } u; - /* byte sizes: 40/48/72 */ -}; - -uint16_t* JS_ToUnicode(JSContext* ctx, JSValueConst value, uint32_t* length) { - if (JS_VALUE_GET_TAG(value) != JS_TAG_STRING) { - value = JS_ToPropertyKey(ctx, value); - if (JS_IsException(value)) - return nullptr; - } else { - value = JS_DupValue(ctx, value); - } - - uint16_t* buffer; - JSString* string = JS_VALUE_GET_STRING(value); - - if (!string->is_wide_char) { - uint8_t* p = string->u.str8; - uint32_t len = *length = string->len; - buffer = (uint16_t*)malloc(sizeof(uint16_t) * len * 2); - for (size_t i = 0; i < len; i++) { - buffer[i] = p[i]; - buffer[i + 1] = 0x00; - } - } else { - *length = string->len; - buffer = (uint16_t*)malloc(sizeof(uint16_t) * string->len); - memcpy(buffer, string->u.str16, sizeof(uint16_t) * string->len); - } - - JS_FreeValue(ctx, value); - return buffer; -} - -static JSString* js_alloc_string_rt(JSRuntime* rt, int max_len, int is_wide_char) { - JSString* str; - str = static_cast(js_malloc_rt(rt, sizeof(JSString) + (max_len << is_wide_char) + 1 - is_wide_char)); - if (unlikely(!str)) - return NULL; - str->header.ref_count = 1; - str->is_wide_char = is_wide_char; - str->len = max_len; - str->atom_type = 0; - str->hash = 0; /* optional but costless */ - str->hash_next = 0; /* optional */ -#ifdef DUMP_LEAKS - list_add_tail(&str->link, &rt->string_list); -#endif - return str; -} - -static JSString* js_alloc_string(JSRuntime* runtime, JSContext* ctx, int max_len, int is_wide_char) { - JSString* p; - p = js_alloc_string_rt(runtime, max_len, is_wide_char); - if (unlikely(!p)) { - JS_ThrowOutOfMemory(ctx); - return NULL; - } - return p; -} - -JSValue JS_NewUnicodeString(JSContext* ctx, const uint16_t* code, uint32_t length) { - JSString* str; - str = js_alloc_string(JS_GetRuntime(ctx), ctx, length, 1); - if (!str) - return JS_EXCEPTION; - memcpy(str->u.str16, code, length * 2); - return JS_MKPTR(JS_TAG_STRING, str); -} - -JSAtom JS_NewUnicodeAtom(JSContext* ctx, const uint16_t* code, uint32_t length) { - JSValue value = JS_NewUnicodeString(ctx, code, length); - JSAtom atom = JS_ValueToAtom(ctx, value); - JS_FreeValue(ctx, value); - return atom; -} - -JSClassID JSValueGetClassId(JSValue obj) { - JSObject* p; - if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) - return -1; - p = JS_VALUE_GET_OBJ(obj); - return p->class_id; -} - -bool JS_IsProxy(JSValue value) { - if (!JS_IsObject(value)) - return false; - JSObject* p = JS_VALUE_GET_OBJ(value); - return p->class_id == JS_CLASS_PROXY; -} - -bool JS_IsPromise(JSValue value) { - if (!JS_IsObject(value)) - return false; - JSObject* p = JS_VALUE_GET_OBJ(value); - return p->class_id == JS_CLASS_PROMISE; -} - -bool JS_IsArrayBuffer(JSValue value) { - if (!JS_IsObject(value)) - return false; - JSObject* p = JS_VALUE_GET_OBJ(value); - return p->class_id == JS_CLASS_ARRAY_BUFFER; -} - -bool JS_IsArrayBufferView(JSValue value) { - if (!JS_IsObject(value)) - return false; - JSObject* p = JS_VALUE_GET_OBJ(value); - return p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_DATAVIEW; -} - -bool JS_HasClassId(JSRuntime* runtime, JSClassID classId) { - if (runtime->class_count <= classId) - return false; - return runtime->class_array[classId].class_id == classId; -} - -int JS_AtomIs8Bit(JSRuntime* runtime, JSAtom atom) { - JSString* string = runtime->atom_array[atom]; - return string->is_wide_char == 0; -} - -const uint8_t* JS_AtomRawCharacter8(JSRuntime* runtime, JSAtom atom) { - JSString* string = runtime->atom_array[atom]; - return string->u.str8; -} - -const uint16_t* JS_AtomRawCharacter16(JSRuntime* runtime, JSAtom atom) { - JSString* string = runtime->atom_array[atom]; - return string->u.str16; -} - -int JS_FindCharacterInAtom(JSRuntime* runtime, JSAtom atom, bool (*CharacterMatchFunction)(char)) { - JSString* string = runtime->atom_array[atom]; - for (int i = 0; i < string->len; i++) { - if (CharacterMatchFunction(static_cast(string->u.str8[i]))) { - return i; - } - } - return -1; -} - -int JS_FindWCharacterInAtom(JSRuntime* runtime, JSAtom atom, bool (*CharacterMatchFunction)(uint16_t)) { - JSString* string = runtime->atom_array[atom]; - for (int i = 0; i < string->len; i++) { - if (CharacterMatchFunction(string->u.str16[i])) { - return i; - } - } - return -1; -} - -JSValue JS_GetProxyTarget(JSValue value) { - JSObject* p = JS_VALUE_GET_OBJ(value); - return p->u.proxy_data->target; -} - -JSGCPhaseEnum JS_GetEnginePhase(JSRuntime* runtime) { - return runtime->gc_phase; -} - -webf::StringView JSAtomToStringView(JSRuntime* runtime, JSAtom atom) { - JSString* string = runtime->atom_array[atom]; - return webf::StringView(string->u.str8, string->len, string->is_wide_char); -} \ No newline at end of file diff --git a/bridge/bindings/qjs/qjs_engine_patch.h b/bridge/bindings/qjs/qjs_engine_patch.h deleted file mode 100644 index b13af27d29..0000000000 --- a/bridge/bindings/qjs/qjs_engine_patch.h +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_QJS_PATCH_H -#define BRIDGE_QJS_PATCH_H - -#include -#include -#include "foundation/string_view.h" - -struct JSString { - JSRefCountHeader header; /* must come first, 32-bit */ - uint32_t len : 31; - uint8_t is_wide_char : 1; /* 0 = 8 bits, 1 = 16 bits characters */ - /* for JS_ATOM_TYPE_SYMBOL: hash = 0, atom_type = 3, - for JS_ATOM_TYPE_PRIVATE: hash = 1, atom_type = 3 - XXX: could change encoding to have one more bit in hash */ - uint32_t hash : 30; - uint8_t atom_type : 2; /* != 0 if atom, JS_ATOM_TYPE_x */ - uint32_t hash_next; /* atom_index for JS_ATOM_TYPE_SYMBOL */ -#ifdef DUMP_LEAKS - struct list_head link; /* string list */ -#endif - union { - uint8_t str8[0]; /* 8 bit strings will get an extra null terminator */ - uint16_t str16[0]; - } u; -}; - -typedef enum { - JS_GC_PHASE_NONE, - JS_GC_PHASE_DECREF, - JS_GC_PHASE_REMOVE_CYCLES, -} JSGCPhaseEnum; - -enum { - /* classid tag */ /* union usage | properties */ - JS_CLASS_OBJECT = 1, /* must be first */ - JS_CLASS_ARRAY, /* u.array | length */ - JS_CLASS_ERROR, - JS_CLASS_NUMBER, /* u.object_data */ - JS_CLASS_STRING, /* u.object_data */ - JS_CLASS_BOOLEAN, /* u.object_data */ - JS_CLASS_SYMBOL, /* u.object_data */ - JS_CLASS_ARGUMENTS, /* u.array | length */ - JS_CLASS_MAPPED_ARGUMENTS, /* | length */ - JS_CLASS_DATE, /* u.object_data */ - JS_CLASS_MODULE_NS, - JS_CLASS_C_FUNCTION, /* u.cfunc */ - JS_CLASS_BYTECODE_FUNCTION, /* u.func */ - JS_CLASS_BOUND_FUNCTION, /* u.bound_function */ - JS_CLASS_C_FUNCTION_DATA, /* u.c_function_data_record */ - JS_CLASS_GENERATOR_FUNCTION, /* u.func */ - JS_CLASS_FOR_IN_ITERATOR, /* u.for_in_iterator */ - JS_CLASS_REGEXP, /* u.regexp */ - JS_CLASS_ARRAY_BUFFER, /* u.array_buffer */ - JS_CLASS_SHARED_ARRAY_BUFFER, /* u.array_buffer */ - JS_CLASS_UINT8C_ARRAY, /* u.array (typed_array) */ - JS_CLASS_INT8_ARRAY, /* u.array (typed_array) */ - JS_CLASS_UINT8_ARRAY, /* u.array (typed_array) */ - JS_CLASS_INT16_ARRAY, /* u.array (typed_array) */ - JS_CLASS_UINT16_ARRAY, /* u.array (typed_array) */ - JS_CLASS_INT32_ARRAY, /* u.array (typed_array) */ - JS_CLASS_UINT32_ARRAY, /* u.array (typed_array) */ -#ifdef CONFIG_BIGNUM - JS_CLASS_BIG_INT64_ARRAY, /* u.array (typed_array) */ - JS_CLASS_BIG_UINT64_ARRAY, /* u.array (typed_array) */ -#endif - JS_CLASS_FLOAT32_ARRAY, /* u.array (typed_array) */ - JS_CLASS_FLOAT64_ARRAY, /* u.array (typed_array) */ - JS_CLASS_DATAVIEW, /* u.typed_array */ -#ifdef CONFIG_BIGNUM - JS_CLASS_BIG_INT, /* u.object_data */ - JS_CLASS_BIG_FLOAT, /* u.object_data */ - JS_CLASS_FLOAT_ENV, /* u.float_env */ - JS_CLASS_BIG_DECIMAL, /* u.object_data */ - JS_CLASS_OPERATOR_SET, /* u.operator_set */ -#endif - JS_CLASS_MAP, /* u.map_state */ - JS_CLASS_SET, /* u.map_state */ - JS_CLASS_WEAKMAP, /* u.map_state */ - JS_CLASS_WEAKSET, /* u.map_state */ - JS_CLASS_MAP_ITERATOR, /* u.map_iterator_data */ - JS_CLASS_SET_ITERATOR, /* u.map_iterator_data */ - JS_CLASS_ARRAY_ITERATOR, /* u.array_iterator_data */ - JS_CLASS_STRING_ITERATOR, /* u.array_iterator_data */ - JS_CLASS_REGEXP_STRING_ITERATOR, /* u.regexp_string_iterator_data */ - JS_CLASS_GENERATOR, /* u.generator_data */ - JS_CLASS_PROXY, /* u.proxy_data */ - JS_CLASS_PROMISE, /* u.promise_data */ - JS_CLASS_PROMISE_RESOLVE_FUNCTION, /* u.promise_function_data */ - JS_CLASS_PROMISE_REJECT_FUNCTION, /* u.promise_function_data */ - JS_CLASS_ASYNC_FUNCTION, /* u.func */ - JS_CLASS_ASYNC_FUNCTION_RESOLVE, /* u.async_function_data */ - JS_CLASS_ASYNC_FUNCTION_REJECT, /* u.async_function_data */ - JS_CLASS_ASYNC_FROM_SYNC_ITERATOR, /* u.async_from_sync_iterator_data */ - JS_CLASS_ASYNC_GENERATOR_FUNCTION, /* u.func */ - JS_CLASS_ASYNC_GENERATOR, /* u.async_generator_data */ - - JS_CLASS_INIT_COUNT, /* last entry for predefined classes */ -}; - -#ifdef __cplusplus -extern "C" { -#endif - -static inline bool __JS_AtomIsConst(JSAtom v) { -#if defined(DUMP_LEAKS) && DUMP_LEAKS > 1 - return (int32_t)v <= 0; -#else - return (int32_t)v < JS_ATOM_END; -#endif -} - -uint16_t* JS_ToUnicode(JSContext* ctx, JSValueConst value, uint32_t* length); -JSValue JS_NewUnicodeString(JSContext* ctx, const uint16_t* code, uint32_t length); -JSAtom JS_NewUnicodeAtom(JSContext* ctx, const uint16_t* code, uint32_t length); -JSClassID JSValueGetClassId(JSValue); -bool JS_IsProxy(JSValue value); -bool JS_IsPromise(JSValue value); -bool JS_IsArrayBuffer(JSValue value); -bool JS_IsArrayBufferView(JSValue value); -bool JS_HasClassId(JSRuntime* runtime, JSClassID classId); -int JS_AtomIs8Bit(JSRuntime* runtime, JSAtom atom); -const uint8_t* JS_AtomRawCharacter8(JSRuntime* runtime, JSAtom atom); -const uint16_t* JS_AtomRawCharacter16(JSRuntime* runtime, JSAtom atom); -int JS_FindCharacterInAtom(JSRuntime* runtime, JSAtom atom, bool (*CharacterMatchFunction)(char)); -int JS_FindWCharacterInAtom(JSRuntime* runtime, JSAtom atom, bool (*CharacterMatchFunction)(uint16_t)); -JSValue JS_GetProxyTarget(JSValue value); -JSGCPhaseEnum JS_GetEnginePhase(JSRuntime* runtime); -webf::StringView JSAtomToStringView(JSRuntime* runtime, JSAtom atom); - -static inline bool JS_AtomIsTaggedInt(JSAtom v) { - return (v & JS_ATOM_TAG_INT) != 0; -} - -static inline JSAtom JS_AtomFromUInt32(uint32_t v) { - return v | JS_ATOM_TAG_INT; -} - -static inline uint32_t JS_AtomToUInt32(JSAtom atom) { - return atom & ~JS_ATOM_TAG_INT; -} - -#ifdef __cplusplus -} -#endif - -#endif // BRIDGE_QJS_PATCH_H \ No newline at end of file diff --git a/bridge/bindings/qjs/qjs_engine_patch_test.cc b/bridge/bindings/qjs/qjs_engine_patch_test.cc deleted file mode 100644 index 7ed9911cda..0000000000 --- a/bridge/bindings/qjs/qjs_engine_patch_test.cc +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "qjs_engine_patch.h" -#include -#include "gtest/gtest.h" - -TEST(JS_ToUnicode, asciiWords) { - JSRuntime* runtime = JS_NewRuntime(); - JSContext* ctx = JS_NewContext(runtime); - JSValue value = JS_NewString(ctx, "helloworld"); - uint32_t bufferLength; - uint16_t* buffer = JS_ToUnicode(ctx, value, &bufferLength); - std::u16string u16Value = u"helloworld"; - std::u16string bufferString = std::u16string(reinterpret_cast(buffer), bufferLength); - - EXPECT_EQ(bufferString == u16Value, true); - - JS_FreeValue(ctx, value); - JS_FreeContext(ctx); - JS_FreeRuntime(runtime); - delete buffer; -} - -TEST(JS_ToUnicode, chineseWords) { - JSRuntime* runtime = JS_NewRuntime(); - JSContext* ctx = JS_NewContext(runtime); - JSValue value = JS_NewString(ctx, "a你的名字12345"); - uint32_t bufferLength; - uint16_t* buffer = JS_ToUnicode(ctx, value, &bufferLength); - std::u16string u16Value = u"a你的名字12345"; - std::u16string bufferString = std::u16string(reinterpret_cast(buffer), bufferLength); - - EXPECT_EQ(bufferString == u16Value, true); - - JS_FreeValue(ctx, value); - JS_FreeContext(ctx); - JS_FreeRuntime(runtime); -} - -TEST(JS_ToUnicode, emoji) { - JSRuntime* runtime = JS_NewRuntime(); - JSContext* ctx = JS_NewContext(runtime); - JSValue value = JS_NewString(ctx, "1😀2"); - uint32_t bufferLength; - uint16_t* buffer = JS_ToUnicode(ctx, value, &bufferLength); - std::u16string u16Value = u"1😀2"; - std::u16string bufferString = std::u16string(reinterpret_cast(buffer), bufferLength); - - EXPECT_EQ(bufferString == u16Value, true); - - JS_FreeValue(ctx, value); - JS_FreeContext(ctx); - JS_FreeRuntime(runtime); -} - -TEST(JS_NewUnicodeString, fromAscii) { - JSRuntime* runtime = JS_NewRuntime(); - JSContext* ctx = JS_NewContext(runtime); - std::u16string source = u"helloworld"; - JSValue result = JS_NewUnicodeString(ctx, reinterpret_cast(source.c_str()), source.length()); - const char* str = JS_ToCString(ctx, result); - EXPECT_STREQ(str, "helloworld"); - - JS_FreeCString(ctx, str); - JS_FreeValue(ctx, result); - JS_FreeContext(ctx); - JS_FreeRuntime(runtime); -} - -TEST(JS_NewUnicodeString, fromChieseCode) { - JSRuntime* runtime = JS_NewRuntime(); - JSContext* ctx = JS_NewContext(runtime); - std::u16string source = u"a你的名字12345"; - JSValue result = JS_NewUnicodeString(ctx, reinterpret_cast(source.c_str()), source.length()); - uint32_t length; - uint16_t* buffer = JS_ToUnicode(ctx, result, &length); - std::u16string bufferString = std::u16string(reinterpret_cast(buffer), length); - - EXPECT_EQ(bufferString == source, true); - - JS_FreeValue(ctx, result); - JS_FreeContext(ctx); - JS_FreeRuntime(runtime); -} diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index 2c0a1ddba1..7dc3dbf6b2 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -1,29 +1,51 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #include "qjs_function.h" #include #include #include "core/binding_object.h" #include "core/dom/events/event_target.h" +#include "core/executing_context.h" #include "cppgc/gc_visitor.h" namespace webf { -struct QJSFunctionCallbackContext { - QJSFunctionCallback qjs_function_callback; - void* private_data; -}; +static JSValue CreateJSFunctionWithFinalizerCallback(JSRuntime* runtime, + JSContext* ctx, + JSClassCall callback_fn, + JSClassGCMark mark_fn, + JSClassFinalizer finalized_fn, + void* private_data) { + JSClassDef def{}; + def.class_name = "fn"; + def.call = callback_fn; + def.gc_mark = mark_fn; + def.finalizer = finalized_fn; + + JSClassID class_id = 0; + JS_NewClassID(&class_id); + + JS_NewClass(runtime, class_id, &def); + + JSValue function = JS_NewObjectClass(ctx, class_id); + JS_SetOpaque(function, private_data); + + return function; +} static JSValue HandleQJSFunctionCallback(JSContext* ctx, + JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, - int magic, - JSValue* func_data) { - JSValue opaque_object = func_data[0]; - auto* callback_context = static_cast(JS_GetOpaque(opaque_object, JS_CLASS_OBJECT)); + int flags) { + auto* callback_context = static_cast(JS_GetOpaque(func_obj, JS_GetClassID(func_obj))); std::vector arguments; arguments.reserve(argc); for (int i = 0; i < argc; i++) { @@ -34,33 +56,38 @@ static JSValue HandleQJSFunctionCallback(JSContext* ctx, return JS_DupValue(ctx, result.QJSValue()); } -QJSFunction::QJSFunction(JSContext* ctx, QJSFunctionCallback qjs_function_callback, int32_t length, void* private_data) +QJSFunction::QJSFunction(JSContext* ctx, + QJSFunctionCallback qjs_function_callback, + int32_t length, + void* private_data, + JSClassGCMark gc_mark, + JSClassFinalizer gc_finalizer) : ctx_(ctx), runtime_(JS_GetRuntime(ctx)) { - JSValue opaque_object = JS_NewObject(ctx); auto* context = new QJSFunctionCallbackContext{qjs_function_callback, private_data}; - JS_SetOpaque(opaque_object, context); - function_ = JS_NewCFunctionData(ctx, HandleQJSFunctionCallback, length, 0, 1, &opaque_object); - JS_FreeValue(ctx, opaque_object); + function_ = + CreateJSFunctionWithFinalizerCallback(runtime_, ctx, HandleQJSFunctionCallback, gc_mark, gc_finalizer, context); } bool QJSFunction::IsFunction(JSContext* ctx) { return JS_IsFunction(ctx, function_); } -ScriptValue QJSFunction::Invoke(JSContext* ctx, const ScriptValue& this_val, int32_t argc, ScriptValue* arguments) { +ScriptValue QJSFunction::Invoke(JSContext* ctx, const ScriptValue& this_val, int argc, ScriptValue* arguments) { // 'm_function' might be destroyed when calling itself (if it frees the handler), so must take extra care. JS_DupValue(ctx, function_); - JSValue argv[std::max(1, argc)]; + auto* argv = new JSValue[std::max(1, argc)]; for (int i = 0; i < argc; i++) { argv[0 + i] = arguments[i].QJSValue(); } - JSValue returnValue = JS_Call(ctx, function_, this_val.QJSValue(), argc, argv); - ExecutingContext* context = ExecutingContext::From(ctx); - context->DrainPendingPromiseJobs(); + context->SetIsIdle(false); + + JSValue returnValue = JS_Call(ctx, function_, JS_IsNull(this_val_) ? this_val.QJSValue() : this_val_, argc, argv); + + context->DrainMicrotasks(); // Free the previous duplicated function. JS_FreeValue(ctx, function_); @@ -71,7 +98,7 @@ ScriptValue QJSFunction::Invoke(JSContext* ctx, const ScriptValue& this_val, int } void QJSFunction::Trace(GCVisitor* visitor) const { - visitor->Trace(function_); + visitor->TraceValue(function_); + visitor->TraceValue(this_val_); } - } // namespace webf diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index 8514f272c5..8d3b80a29d 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -1,10 +1,16 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_QJS_FUNCTION_H #define BRIDGE_QJS_FUNCTION_H +#include "foundation/casting.h" +#include "foundation/function.h" #include "script_value.h" namespace webf { @@ -15,9 +21,14 @@ using QJSFunctionCallback = ScriptValue (*)(JSContext* ctx, const ScriptValue* argv, void* private_data); +struct QJSFunctionCallbackContext { + QJSFunctionCallback qjs_function_callback; + void* private_data; +}; + // https://webidl.spec.whatwg.org/#dfn-callback-interface // QJSFunction memory are auto managed by std::shared_ptr. -class QJSFunction { +class QJSFunction : public Function { public: static std::shared_ptr Create(JSContext* ctx, JSValue function) { return std::make_shared(ctx, function); @@ -25,16 +36,36 @@ class QJSFunction { static std::shared_ptr Create(JSContext* ctx, QJSFunctionCallback qjs_function_callback, int32_t length, - void* private_data) { - return std::make_shared(ctx, qjs_function_callback, length, private_data); + void* private_data, + JSClassGCMark gc_mark, + JSClassFinalizer gc_finalizer) { + return std::make_shared(ctx, qjs_function_callback, length, private_data, gc_mark, gc_finalizer); } + static std::shared_ptr Create(JSContext* ctx, JSValue function, JSValue this_val) { + return std::make_shared(ctx, function, this_val); + }; explicit QJSFunction(JSContext* ctx, JSValue function) : ctx_(ctx), runtime_(JS_GetRuntime(ctx)), function_(JS_DupValue(ctx, function)){}; - explicit QJSFunction(JSContext* ctx, QJSFunctionCallback qjs_function_callback, int32_t length, void* private_data); - ~QJSFunction() { JS_FreeValueRT(runtime_, function_); } + explicit QJSFunction(JSContext* ctx, + QJSFunctionCallback qjs_function_callback, + int32_t length, + void* private_data, + JSClassGCMark gc_mark, + JSClassFinalizer gc_finalizer); + explicit QJSFunction(JSContext* ctx, JSValue function, JSValue this_val) + : ctx_(ctx), + runtime_(JS_GetRuntime(ctx)), + function_(JS_DupValue(ctx, function)), + this_val_(JS_DupValue(ctx, this_val)) {} + ~QJSFunction() { + JS_FreeValueRT(runtime_, function_); + JS_FreeValueRT(runtime_, this_val_); + } bool IsFunction(JSContext* ctx); + bool IsQJSFunction() const override { return true; } + JSValue ToQuickJS() { return JS_DupValue(ctx_, function_); }; JSValue ToQuickJSUnsafe() { return function_; } @@ -42,8 +73,8 @@ class QJSFunction { // https://webidl.spec.whatwg.org/#invoke-a-callback-function ScriptValue Invoke(JSContext* ctx, const ScriptValue& this_val, int32_t argc, ScriptValue* arguments); - bool operator==(const QJSFunction& other) { - return JS_VALUE_GET_PTR(function_) == JS_VALUE_GET_PTR(other.function_); + friend bool operator==(const QJSFunction& lhs, const QJSFunction& rhs) { + return JS_VALUE_GET_PTR(lhs.function_) == JS_VALUE_GET_PTR(rhs.function_); }; void Trace(GCVisitor* visitor) const; @@ -52,8 +83,13 @@ class QJSFunction { JSContext* ctx_{nullptr}; JSRuntime* runtime_{nullptr}; JSValue function_{JS_NULL}; + JSValue this_val_{JS_NULL}; }; +template <> +struct DowncastTraits { + static bool AllowFrom(const Function& function) { return function.IsQJSFunction(); } +}; } // namespace webf #endif // BRIDGE_QJS_FUNCTION_H diff --git a/bridge/bindings/qjs/qjs_interface_bridge.h b/bridge/bindings/qjs/qjs_interface_bridge.h index db6fbac0e1..6c38ef491c 100644 --- a/bridge/bindings/qjs/qjs_interface_bridge.h +++ b/bridge/bindings/qjs/qjs_interface_bridge.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ diff --git a/bridge/bindings/qjs/rejected_promises.cc b/bridge/bindings/qjs/rejected_promises.cc index 0feca933e4..ec88b7ac40 100644 --- a/bridge/bindings/qjs/rejected_promises.cc +++ b/bridge/bindings/qjs/rejected_promises.cc @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #include "rejected_promises.h" @@ -10,7 +14,7 @@ namespace webf { RejectedPromises::Message::Message(ExecutingContext* context, JSValue promise, JSValue reason) - : m_runtime(context->dartContext()->runtime()), + : m_runtime(context->dartIsolateContext()->runtime()), m_promise(JS_DupValue(context->ctx(), promise)), m_reason(JS_DupValue(context->ctx(), reason)) {} diff --git a/bridge/bindings/qjs/rejected_promises.h b/bridge/bindings/qjs/rejected_promises.h index eaeb74510e..ef7b6e8c29 100644 --- a/bridge/bindings/qjs/rejected_promises.h +++ b/bridge/bindings/qjs/rejected_promises.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_BINDINGS_QJS_REJECTED_PROMISES_H_ diff --git a/bridge/bindings/qjs/script_promise.cc b/bridge/bindings/qjs/script_promise.cc index e1f0d3cfb4..0e047f9c47 100644 --- a/bridge/bindings/qjs/script_promise.cc +++ b/bridge/bindings/qjs/script_promise.cc @@ -1,9 +1,12 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #include "script_promise.h" -#include "qjs_engine_patch.h" namespace webf { @@ -31,6 +34,8 @@ ScriptPromise::ScriptPromise(JSContext* ctx, } JSValue ScriptPromise::ToQuickJS() { + if (ctx_ == nullptr) + return JS_NULL; return JS_DupValue(ctx_, promise_.QJSValue()); } diff --git a/bridge/bindings/qjs/script_promise.h b/bridge/bindings/qjs/script_promise.h index 9802c24ee5..425e235609 100644 --- a/bridge/bindings/qjs/script_promise.h +++ b/bridge/bindings/qjs/script_promise.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_H_ #define BRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_H_ @@ -30,7 +34,7 @@ class ScriptPromise final { void Trace(GCVisitor* visitor); private: - JSContext* ctx_; + JSContext* ctx_{nullptr}; ScriptValue promise_; }; diff --git a/bridge/bindings/qjs/script_promise_resolver.cc b/bridge/bindings/qjs/script_promise_resolver.cc index a8e70938fc..7c9380049e 100644 --- a/bridge/bindings/qjs/script_promise_resolver.cc +++ b/bridge/bindings/qjs/script_promise_resolver.cc @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #include "script_promise_resolver.h" #include "core/executing_context.h" @@ -12,7 +16,7 @@ std::shared_ptr ScriptPromiseResolver::Create(ExecutingCo } ScriptPromiseResolver::ScriptPromiseResolver(ExecutingContext* context) - : context_(context), state_(ResolutionState::kPending) { + : context_(context), state_(ResolutionState::kPending), context_id_(context->contextId()) { JSValue resolving_funcs[2]; promise_ = JS_NewPromiseCapability(context->ctx(), resolving_funcs); resolve_func_ = resolving_funcs[0]; @@ -20,22 +24,34 @@ ScriptPromiseResolver::ScriptPromiseResolver(ExecutingContext* context) } ScriptPromiseResolver::~ScriptPromiseResolver() { + if (isContextValid(context_id_)) { + Reset(); + } +} + +void ScriptPromiseResolver::Reset() { JS_FreeValue(context_->ctx(), promise_); JS_FreeValue(context_->ctx(), resolve_func_); JS_FreeValue(context_->ctx(), reject_func_); + context_ = nullptr; } void ScriptPromiseResolver::Trace(GCVisitor* visitor) const { - visitor->Trace(promise_); - visitor->Trace(resolve_func_); - visitor->Trace(reject_func_); + visitor->TraceValue(promise_); + visitor->TraceValue(resolve_func_); + visitor->TraceValue(reject_func_); } ScriptPromise ScriptPromiseResolver::Promise() { - return ScriptPromise(context_->ctx(), promise_); + if (context_ == nullptr) + return {}; + return {context_->ctx(), promise_}; } void ScriptPromiseResolver::ResolveOrRejectImmediately(JSValue value) { + if (context_ == nullptr) + return; + context_->SetIsIdle(true); { if (state_ == kResolving) { JSValue arguments[] = {value}; @@ -54,7 +70,7 @@ void ScriptPromiseResolver::ResolveOrRejectImmediately(JSValue value) { JS_FreeValue(context_->ctx(), return_value); } } - context_->DrainPendingPromiseJobs(); + context_->DrainMicrotasks(); } } // namespace webf diff --git a/bridge/bindings/qjs/script_promise_resolver.h b/bridge/bindings/qjs/script_promise_resolver.h index 95b01c2f41..054d8eec07 100644 --- a/bridge/bindings/qjs/script_promise_resolver.h +++ b/bridge/bindings/qjs/script_promise_resolver.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_RESOLVER_H_ #define BRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_RESOLVER_H_ @@ -19,6 +23,13 @@ class ScriptPromiseResolver { ScriptPromiseResolver(ExecutingContext* context); ~ScriptPromiseResolver(); + void Reset(); + + FORCE_INLINE bool isAlive() const { + return context_ != nullptr && context_->IsContextValid() && context_->IsCtxValid(); + } + FORCE_INLINE ExecutingContext* context() const { return context_; } + // Return a promise object and wait to be resolve or reject. // Note that an empty ScriptPromise will be returned after resolve or // reject is called. @@ -70,6 +81,7 @@ class ScriptPromiseResolver { void ResolveOrRejectImmediately(JSValue value); ResolutionState state_; + double context_id_; ExecutingContext* context_{nullptr}; JSValue promise_{JS_NULL}; JSValue resolve_func_{JS_NULL}; diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index aa148dda1e..9f4724ca75 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -1,30 +1,58 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #include "script_value.h" + +#include +#include +#include +#include +#include +// #include "core/css/computed_css_style_declaration.h" +#include "core/css/legacy/legacy_computed_css_style_declaration.h" +#include #include #include "bindings/qjs/converter_impl.h" #include "core/binding_object.h" +#include "core/dart_binding_object.h" #include "core/executing_context.h" +#include "core/js_function_ref.h" #include "cppgc/gc_visitor.h" +#include "foundation/native_byte_data.h" #include "foundation/native_value_converter.h" #include "native_string_utils.h" #include "qjs_bounding_client_rect.h" -#include "qjs_engine_patch.h" #include "qjs_event_target.h" +#if defined(_WIN32) +#include +#endif + namespace webf { -static JSValue FromNativeValue(ExecutingContext* context, const NativeValue& native_value) { +static JSValue FromNativeValue(ExecutingContext* context, + const NativeValue& native_value, + bool shared_js_value = false) { switch (native_value.tag) { case NativeTag::TAG_STRING: { - auto* string = static_cast(native_value.u.ptr); - if (string == nullptr) - return JS_NULL; - JSValue returnedValue = JS_NewUnicodeString(context->ctx(), string->string(), string->length()); - delete string; - return returnedValue; + if (shared_js_value) { + auto* string = static_cast(native_value.u.ptr); + if (string == nullptr) + return JS_NULL; + JSValue returnedValue = JS_NewUnicodeString(context->ctx(), string->string(), string->length()); + return returnedValue; + } else { + std::unique_ptr string{static_cast(native_value.u.ptr)}; + if (string == nullptr) + return JS_NULL; + JSValue returnedValue = JS_NewUnicodeString(context->ctx(), string->string(), string->length()); + return returnedValue; + } } case NativeTag::TAG_INT: { return JS_NewInt64(context->ctx(), native_value.u.int64); @@ -38,41 +66,99 @@ static JSValue FromNativeValue(ExecutingContext* context, const NativeValue& nat case NativeTag::TAG_NULL: { return JS_NULL; } + case NativeTag::TAG_UNDEFINED: { + return JS_UNDEFINED; + } + case NativeTag::TAG_UINT8_BYTES: { + auto free_func = [](JSRuntime* rt, void* opaque, void* ptr) { +#if defined(_WIN32) + return CoTaskMemFree(ptr); +#else + return free(ptr); +#endif + }; + + return JS_NewArrayBuffer(context->ctx(), (uint8_t*)native_value.u.ptr, native_value.uint32, free_func, nullptr, + 0); + } case NativeTag::TAG_LIST: { size_t length = native_value.uint32; auto* arr = static_cast(native_value.u.ptr); JSValue array = JS_NewArray(context->ctx()); JS_SetPropertyStr(context->ctx(), array, "length", Converter::ToValue(context->ctx(), length)); for (int i = 0; i < length; i++) { - JSValue value = FromNativeValue(context, arr[i]); + JSValue value = FromNativeValue(context, arr[i], shared_js_value); JS_SetPropertyInt64(context->ctx(), array, i, value); } + if (!shared_js_value && arr != nullptr) { + dart_free(arr); + } return array; } case NativeTag::TAG_JSON: { auto* str = static_cast(native_value.u.ptr); JSValue returnedValue = JS_ParseJSON(context->ctx(), str, strlen(str), ""); - delete str; + dart_free(const_cast(str)); return returnedValue; } case NativeTag::TAG_POINTER: { auto* ptr = static_cast(native_value.u.ptr); - auto* binding_object = BindingObject::From(ptr); + auto pointer_type = static_cast(native_value.uint32); - // Only eventTarget can be converted from nativeValue to JSValue. - auto* event_target = DynamicTo(binding_object); - if (event_target) { - return event_target->ToQuickJS(); + switch (pointer_type) { + case JSPointerType::NativeBindingObject: { + auto* binding_object = BindingObject::From(ptr); + if (binding_object != nullptr) { + return binding_object->ToQuickJS(); + } + // NativeBindingObject pointers can be allocated on the Dart side (e.g. + // returned from a Dart binding property/method). In that case there is + // no existing C++ BindingObject wrapper yet; create a DartBindingObject + // wrapper on-demand so JS can access Dart-defined properties/methods. + return MakeGarbageCollected(context, ptr)->ToQuickJS(); + } + case JSPointerType::DOMMatrix: { + return MakeGarbageCollected(context, ptr)->ToQuickJS(); + } + case JSPointerType::BoundingClientRect: { + return MakeGarbageCollected(context, ptr)->ToQuickJS(); + } + case JSPointerType::Screen: { + return MakeGarbageCollected(context, ptr)->ToQuickJS(); + } + case JSPointerType::TextMetrics: { + return MakeGarbageCollected(context, ptr)->ToQuickJS(); + } + case JSPointerType::ComputedCSSStyleDeclaration: { + if (context->isBlinkEnabled()) { + return MakeGarbageCollected(context, ptr)->ToQuickJS(); + } + return MakeGarbageCollected(context, ptr)->ToQuickJS(); + } + case JSPointerType::DOMPoint: { + return MakeGarbageCollected(context, ptr)->ToQuickJS(); + } + case JSPointerType::CanvasGradient: { + return MakeGarbageCollected(context, ptr)->ToQuickJS(); + } + case JSPointerType::CanvasPattern: { + return MakeGarbageCollected(context, ptr)->ToQuickJS(); + } + case JSPointerType::Others: { + return JS_DupValue(context->ctx(), JS_MKPTR(JS_TAG_OBJECT, ptr)); + } + case JSPointerType::NativeByteData: + break; } - return JS_NULL; } } return JS_NULL; } -ScriptValue::ScriptValue(JSContext* ctx, const NativeValue& native_value) - : ctx_(ctx), runtime_(JS_GetRuntime(ctx)), value_(FromNativeValue(ExecutingContext::From(ctx), native_value)) {} +ScriptValue::ScriptValue(JSContext* ctx, const NativeValue& native_value, bool shared_js_value) + : runtime_(JS_GetRuntime(ctx)), + value_(FromNativeValue(ExecutingContext::From(ctx), native_value, shared_js_value)) {} ScriptValue ScriptValue::CreateErrorObject(JSContext* ctx, const char* errmsg) { JS_ThrowInternalError(ctx, "%s", errmsg); @@ -93,33 +179,35 @@ ScriptValue ScriptValue::Empty(JSContext* ctx) { return ScriptValue(ctx); } +ScriptValue ScriptValue::Undefined(JSContext* ctx) { + return ScriptValue(ctx, JS_UNDEFINED); +} + ScriptValue::ScriptValue(const ScriptValue& value) { if (&value != this) { - value_ = JS_DupValue(ctx_, value.value_); + value_ = JS_DupValueRT(value.runtime_, value.value_); } - ctx_ = value.ctx_; + runtime_ = value.runtime_; } ScriptValue& ScriptValue::operator=(const ScriptValue& value) { if (&value != this) { - JS_FreeValue(ctx_, value_); - value_ = JS_DupValue(ctx_, value.value_); + JS_FreeValueRT(runtime_, value_); + runtime_ = value.runtime_; + value_ = JS_DupValueRT(runtime_, value.value_); } - ctx_ = value.ctx_; return *this; } -ScriptValue::ScriptValue(ScriptValue&& value) noexcept { - if (&value != this) { - value_ = JS_DupValue(ctx_, value.value_); - } - ctx_ = value.ctx_; +ScriptValue::ScriptValue(ScriptValue&& value) noexcept : value_(value.value_), runtime_(value.runtime_) { + value.value_ = JS_NULL; } ScriptValue& ScriptValue::operator=(ScriptValue&& value) noexcept { if (&value != this) { - JS_FreeValue(ctx_, value_); - value_ = JS_DupValue(ctx_, value.value_); + JS_FreeValueRT(runtime_, value_); + value_ = value.value_; + runtime_ = value.runtime_; + value.value_ = JS_NULL; } - ctx_ = value.ctx_; return *this; } @@ -127,27 +215,40 @@ JSValue ScriptValue::QJSValue() const { return value_; } -ScriptValue ScriptValue::ToJSONStringify(ExceptionState* exception) const { - JSValue stringifyed = JS_JSONStringify(ctx_, value_, JS_NULL, JS_NULL); - ScriptValue result = ScriptValue(ctx_, stringifyed); +ScriptValue ScriptValue::ToJSONStringify(JSContext* ctx, ExceptionState* exception) const { + JSValue stringifyed = JS_JSONStringify(ctx, value_, JS_NULL, JS_NULL); + ScriptValue result = ScriptValue(ctx, stringifyed); // JS_JSONStringify may return JS_EXCEPTION if object is not valid. Return JS_EXCEPTION and let quickjs to handle it. if (result.IsException()) { - exception->ThrowException(ctx_, result.value_); - result = ScriptValue::Empty(ctx_); + exception->ThrowException(ctx, result.value_); + result = ScriptValue::Empty(ctx); } - JS_FreeValue(ctx_, stringifyed); + JS_FreeValue(ctx, stringifyed); return result; } -AtomicString ScriptValue::ToString() const { - return {ctx_, value_}; +String ScriptValue::ToString(JSContext* ctx) const { + return {ctx, value_}; +} + +AtomicString ScriptValue::ToAtomicString(JSContext* ctx) const { + return {ctx, value_}; +} + +AtomicString ScriptValue::ToLegacyDOMString(JSContext* ctx) const { + if (JS_IsNull(value_)) { + return AtomicString::Empty(); + } + return {ctx, value_}; } -std::unique_ptr ScriptValue::ToNativeString() const { - return ToString().ToNativeString(ctx_); +std::unique_ptr ScriptValue::ToNativeString(JSContext* ctx) const { + return ToAtomicString(ctx).ToNativeString(); } -NativeValue ScriptValue::ToNative(ExceptionState& exception_state) const { +namespace {} // namespace + +NativeValue ScriptValue::ToNative(JSContext* ctx, ExceptionState& exception_state, bool shared_js_value) const { int8_t tag = JS_VALUE_GET_TAG(value_); switch (tag) { @@ -155,35 +256,74 @@ NativeValue ScriptValue::ToNative(ExceptionState& exception_state) const { case JS_TAG_UNDEFINED: return Native_NewNull(); case JS_TAG_BOOL: - return Native_NewBool(JS_ToBool(ctx_, value_)); + return Native_NewBool(JS_ToBool(ctx, value_)); case JS_TAG_FLOAT64: { double v; - JS_ToFloat64(ctx_, &v, value_); + JS_ToFloat64(ctx, &v, value_); return Native_NewFloat64(v); } case JS_TAG_INT: { int32_t v; - JS_ToInt32(ctx_, &v, value_); + JS_ToInt32(ctx, &v, value_); return Native_NewInt64(v); } case JS_TAG_STRING: // NativeString owned by NativeValue will be freed by users. - return NativeValueConverter::ToNativeValue(ctx_, ToString()); + return NativeValueConverter::ToNativeValue(ctx, ToAtomicString(ctx)); case JS_TAG_OBJECT: { - if (JS_IsArray(ctx_, value_)) { - std::vector values = - Converter>::FromValue(ctx_, value_, ASSERT_NO_EXCEPTION()); + if (!shared_js_value && JS_IsFunction(ctx, value_)) { + auto* context = ExecutingContext::From(ctx); + if (!context || !context->IsContextValid()) { + return Native_NewNull(); + } + + auto* function_ref = new NativeJSFunctionRef(); + function_ref->context_status = context->status(); + function_ref->dispatcher = context->dartIsolateContext()->dispatcher().get(); + function_ref->context_id = static_cast(context->contextId()); + function_ref->is_dedicated = context->isDedicated(); + function_ref->ctx = ctx; + function_ref->function = JS_DupValue(ctx, value_); + + context->RegisterJSFunctionRef(function_ref); + + NativeValue native_value{}; + native_value.tag = NativeTag::TAG_FUNCTION; + native_value.u.ptr = function_ref; + native_value.uint32 = 0; + return native_value; + } + + if (JS_IsArrayBuffer(value_)) { + size_t byte_len; + uint8_t* bytes = JS_GetArrayBuffer(ctx, &byte_len, value_); + + return NativeValueConverter>::ToNativeValue(ctx, value_, bytes, byte_len); + } else if (JS_IsArray(ctx, value_)) { + std::vector values = Converter>::FromValue(ctx, value_, ASSERT_NO_EXCEPTION()); auto* result = new NativeValue[values.size()]; for (int i = 0; i < values.size(); i++) { - result[i] = values[i].ToNative(exception_state); + result[i] = values[i].ToNative(ctx, exception_state, shared_js_value); } return Native_NewList(values.size(), result); } else if (JS_IsObject(value_)) { - if (QJSEventTarget::HasInstance(ExecutingContext::From(ctx_), value_)) { + if (QJSEventTarget::HasInstance(ExecutingContext::From(ctx), value_)) { auto* event_target = toScriptWrappable(value_); - return Native_NewPtr(JSPointerType::Others, event_target->bindingObject()); + return Native_NewPtr(JSPointerType::NativeBindingObject, event_target->bindingObject()); + } + + if (shared_js_value) { + return Native_NewPtr(JSPointerType::Others, JS_VALUE_GET_PTR(value_)); + } + + JSClassID class_id = JS_GetClassID(value_); + auto* raw_binding_object = toScriptWrappable(value_); + if (IsWebFDefinedClass(class_id) && raw_binding_object != nullptr && raw_binding_object->IsBindingObject()) { + auto* binding_object = static_cast(raw_binding_object); + return Native_NewPtr(JSPointerType::NativeBindingObject, binding_object->bindingObject()); } - return NativeValueConverter::ToNativeValue(*this, exception_state); + + return NativeValueConverter::ToNativeValue(ctx, *this, exception_state); } } default: @@ -191,6 +331,12 @@ NativeValue ScriptValue::ToNative(ExceptionState& exception_state) const { } } +double ScriptValue::ToDouble(JSContext* ctx) const { + double v; + JS_ToFloat64(ctx, &v, value_); + return v; +} + bool ScriptValue::IsException() const { return JS_IsException(value_); } @@ -219,8 +365,12 @@ bool ScriptValue::IsBool() const { return JS_IsBool(value_); } +bool ScriptValue::IsNumber() const { + return JS_IsNumber(value_); +} + void ScriptValue::Trace(GCVisitor* visitor) const { - visitor->Trace(value_); + visitor->TraceValue(value_); } } // namespace webf diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 3d2e2ee757..9c9ff7493a 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_SCRIPT_VALUE_H #define BRIDGE_SCRIPT_VALUE_H @@ -8,17 +12,17 @@ #include #include -#include "atomic_string.h" +#include "../../foundation/string/atomic_string.h" #include "exception_state.h" #include "foundation/macros.h" #include "foundation/native_string.h" -#include "qjs_engine_patch.h" +#include "foundation/native_value.h" namespace webf { class ExecutingContext; class WrapperTypeInfo; -class NativeValue; +struct NativeValue; class GCVisitor; // ScriptValue is a stack allocate only QuickJS JSValue wrapper ScriptValuewhich hold all information to hide out @@ -35,15 +39,17 @@ class ScriptValue final { // Create an empty ScriptValue; static ScriptValue Empty(JSContext* ctx); + // Create an undefined ScriptValue; + static ScriptValue Undefined(JSContext* ctx); // Wrap an Quickjs JSValue to ScriptValue. - explicit ScriptValue(JSContext* ctx, JSValue value) - : ctx_(ctx), value_(JS_DupValue(ctx, value)), runtime_(JS_GetRuntime(ctx)){}; - explicit ScriptValue(JSContext* ctx, const NativeString* string) - : ctx_(ctx), value_(JS_NewUnicodeString(ctx, string->string(), string->length())), runtime_(JS_GetRuntime(ctx)) {} - explicit ScriptValue(JSContext* ctx, double v) - : ctx_(ctx), value_(JS_NewFloat64(ctx, v)), runtime_(JS_GetRuntime(ctx)) {} - explicit ScriptValue(JSContext* ctx) : ctx_(ctx), runtime_(JS_GetRuntime(ctx)){}; - explicit ScriptValue(JSContext* ctx, const NativeValue& native_value); + explicit ScriptValue(JSContext* ctx, JSValueConst value) : value_(JS_DupValue(ctx, value)), runtime_(JS_GetRuntime(ctx)){}; + explicit ScriptValue(JSContext* ctx, const AtomicString& value) + : value_(value.IsNull() ? JS_NULL : value.ToQuickJS(ctx)) , runtime_(JS_GetRuntime(ctx)){}; + explicit ScriptValue(JSContext* ctx, const SharedNativeString* string) + : value_(JS_NewUnicodeString(ctx, string->string(), string->length())), runtime_(JS_GetRuntime(ctx)) {} + explicit ScriptValue(JSContext* ctx, double v) : value_(JS_NewFloat64(ctx, v)), runtime_(JS_GetRuntime(ctx)) {} + explicit ScriptValue(JSContext* ctx) : runtime_(JS_GetRuntime(ctx)){}; + explicit ScriptValue(JSContext* ctx, const NativeValue& native_value, bool shared_js_value = false); ScriptValue() = default; // Copy and assignment @@ -54,14 +60,18 @@ class ScriptValue final { ScriptValue(ScriptValue&& value) noexcept; ScriptValue& operator=(ScriptValue&& value) noexcept; - ~ScriptValue() { JS_FreeValue(ctx_, value_); }; + ~ScriptValue() { JS_FreeValueRT(runtime_, value_); }; JSValue QJSValue() const; // Create a new ScriptValue from call JSON.stringify to current value. - ScriptValue ToJSONStringify(ExceptionState* exception) const; - AtomicString ToString() const; - std::unique_ptr ToNativeString() const; - NativeValue ToNative(ExceptionState& exception_state) const; + ScriptValue ToJSONStringify(JSContext* ctx, ExceptionState* exception) const; + String ToString(JSContext* ctx) const; + AtomicString ToAtomicString(JSContext* ctx) const; + AtomicString ToLegacyDOMString(JSContext* ctx) const; + std::unique_ptr ToNativeString(JSContext* ctx) const; + NativeValue ToNative(JSContext* ctx, ExceptionState& exception_state, bool shared_js_value = false) const; + + double ToDouble(JSContext* ctx) const; bool IsException() const; bool IsEmpty() const; @@ -70,11 +80,11 @@ class ScriptValue final { bool IsNull() const; bool IsUndefined() const; bool IsBool() const; + bool IsNumber() const; void Trace(GCVisitor* visitor) const; private: - JSContext* ctx_{nullptr}; JSRuntime* runtime_{nullptr}; JSValue value_{JS_NULL}; }; diff --git a/bridge/bindings/qjs/script_value_test.cc b/bridge/bindings/qjs/script_value_test.cc index 928ca3dc76..ace70c35e7 100644 --- a/bridge/bindings/qjs/script_value_test.cc +++ b/bridge/bindings/qjs/script_value_test.cc @@ -1,26 +1,25 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #include "script_value.h" #include #include -#include "atomic_string.h" #include "gtest/gtest.h" +#include "webf_test_env.h" using namespace webf; using TestCallback = void (*)(JSContext* ctx); void TestScriptValue(TestCallback callback) { - JSRuntime* runtime = JS_NewRuntime(); - JSContext* ctx = JS_NewContext(runtime); - - callback(ctx); - - JS_FreeContext(ctx); - JS_FreeRuntime(runtime); + auto env = TEST_init(); + callback(env->page()->executingContext()->ctx()); } TEST(ScriptValue, createErrorObject) { @@ -49,8 +48,8 @@ TEST(ScriptValue, ToString) { TestScriptValue([](JSContext* ctx) { std::string code = "{\"name\": 1}"; ScriptValue json = ScriptValue::CreateJsonObject(ctx, code.c_str(), code.size()); - AtomicString string = json.ToString(); - EXPECT_STREQ(string.ToStdString(ctx).c_str(), "[object Object]"); + AtomicString string = json.ToAtomicString(ctx); + EXPECT_STREQ(string.ToUTF8String().c_str(), "[object Object]"); }); } @@ -63,7 +62,7 @@ TEST(ScriptValue, CopyAssignment) { }; P p; p.value = json; - EXPECT_STREQ(p.value.ToJSONStringify(nullptr).ToString().ToStdString(ctx).c_str(), code.c_str()); + EXPECT_STREQ(p.value.ToJSONStringify(ctx, nullptr).ToAtomicString(ctx).ToUTF8String().c_str(), code.c_str()); }); } @@ -75,6 +74,6 @@ TEST(ScriptValue, MoveAssignment) { other = ScriptValue::CreateJsonObject(ctx, code.c_str(), code.size()); } - EXPECT_STREQ(other.ToJSONStringify(nullptr).ToString().ToStdString(ctx).c_str(), "{\"name\":1}"); + EXPECT_STREQ(other.ToJSONStringify(ctx, nullptr).ToAtomicString(ctx).ToUTF8String().c_str(), "{\"name\":1}"); }); } diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index d310c39ecd..e08ac9f527 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -1,16 +1,43 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #include "script_wrappable.h" +#include #include "core/executing_context.h" #include "cppgc/gc_visitor.h" +#include "foundation/logging.h" namespace webf { ScriptWrappable::ScriptWrappable(JSContext* ctx) - : ctx_(ctx), runtime_(JS_GetRuntime(ctx)), context_(ExecutingContext::From(ctx)) {} + : ctx_(ctx), + runtime_(JS_GetRuntime(ctx)), + context_(ExecutingContext::From(ctx)), + context_id_(context_->contextId()) {} + +ScriptWrappable::~ScriptWrappable() { + if (status_block_ != nullptr) { + status_block_->disposed = true; + } +} + +bool ScriptWrappable::IsPrototypeProperty(const webf::AtomicString& key) const { + JSValue proto = JS_GetPrototype(ctx_, jsObject_); + JSAtom key_atom = context_->stringCache()->GetJSAtomFromString(ctx_, key.Impl()); + bool is_proto_property = JS_HasProperty(ctx_, proto, key_atom); + JS_FreeValue(ctx_, proto); + return is_proto_property; +} + +bool ScriptWrappable::IsBindingObject() const { + return false; +} JSValue ScriptWrappable::ToQuickJS() const { return JS_DupValue(ctx_, jsObject_); @@ -24,11 +51,15 @@ ScriptValue ScriptWrappable::ToValue() { return ScriptValue(ctx_, jsObject_); } +multi_threading::Dispatcher* ScriptWrappable::GetDispatcher() const { + return context_->dartIsolateContext()->dispatcher().get(); +} + /// This callback will be called when QuickJS GC is running at marking stage. -/// Users of this class should override `void Trace(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func)` to +/// Users of this class should override `void TraceMember(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func)` to /// tell GC which member of their class should be collected by GC. static void HandleJSObjectGCMark(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func) { - auto* object = static_cast(JS_GetOpaque(val, JSValueGetClassId(val))); + auto* object = static_cast(JS_GetOpaque(val, JS_GetClassID(val))); GCVisitor visitor{rt, mark_func}; object->Trace(&visitor); } @@ -37,8 +68,16 @@ static void HandleJSObjectGCMark(JSRuntime* rt, JSValueConst val, JS_MarkFunc* m /// The deconstruct method of this class will be called and all memory about this class will be freed when finalize /// completed. static void HandleJSObjectFinalized(JSRuntime* rt, JSValue val) { - auto* object = static_cast(JS_GetOpaque(val, JSValueGetClassId(val))); - delete object; + auto* object = static_cast(JS_GetOpaque(val, JS_GetClassID(val))); + // When a JSObject got finalized by QuickJS GC, we can not guarantee the ExecutingContext are still alive and + // accessible. + if (isContextValid(object->contextId())) { + ExecutingContext* context = object->GetExecutingContext(); + MemberMutationScope scope{object->GetExecutingContext()}; + delete object; + } else { + delete object; + } } /// This callback will be called when JS code access this object using [] or `.` operator. @@ -46,22 +85,22 @@ static void HandleJSObjectFinalized(JSRuntime* rt, JSValue val) { /// When exec `obj['hello']`, it will call string_property_getter_handler_ defined in WrapperTypeInfo. static JSValue HandleJSPropertyGetterCallback(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver) { ExecutingContext* context = ExecutingContext::From(ctx); - auto* object = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); + auto* object = static_cast(JS_GetOpaque(obj, JS_GetClassID(obj))); auto* wrapper_type_info = object->GetWrapperTypeInfo(); - JSValue prototypeObject = context->contextData()->prototypeForType(wrapper_type_info); - if (JS_HasProperty(ctx, prototypeObject, atom)) { - JSValue ret = JS_GetPropertyInternal(ctx, prototypeObject, atom, obj, NULL, 0); - return ret; - } - + JSValue getterValue = JS_UNDEFINED; if (wrapper_type_info->indexed_property_getter_handler_ != nullptr && JS_AtomIsTaggedInt(atom)) { - return wrapper_type_info->indexed_property_getter_handler_(ctx, obj, JS_AtomToUInt32(atom)); + getterValue = wrapper_type_info->indexed_property_getter_handler_(ctx, obj, JS_AtomToUInt32(atom)); } else if (wrapper_type_info->string_property_getter_handler_ != nullptr) { - return wrapper_type_info->string_property_getter_handler_(ctx, obj, atom); + getterValue = wrapper_type_info->string_property_getter_handler_(ctx, obj, atom); } - return JS_UNDEFINED; + if (!JS_IsUndefined(getterValue)) { + return getterValue; + } + + JSValue prototypeObject = context->contextData()->prototypeForType(wrapper_type_info); + return JS_GetPropertyWithThisObj(ctx, prototypeObject, atom, obj, false); } /// This callback will be called when JS code set property on this object using [] or `.` operator. @@ -72,15 +111,27 @@ static int HandleJSPropertySetterCallback(JSContext* ctx, JSValueConst value, JSValueConst receiver, int flags) { - auto* object = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); + auto* object = static_cast(JS_GetOpaque(obj, JS_GetClassID(obj))); auto* wrapper_type_info = object->GetWrapperTypeInfo(); + bool is_success = false; + + if (wrapper_type_info->indexed_property_setter_handler_ != nullptr && JS_AtomIsTaggedInt(atom)) { + is_success = wrapper_type_info->indexed_property_setter_handler_(ctx, obj, JS_AtomToUInt32(atom), value); + } else if (wrapper_type_info->string_property_setter_handler_ != nullptr) { + is_success = wrapper_type_info->string_property_setter_handler_(ctx, obj, atom, value); + } + + if (is_success) { + return is_success; + } + ExecutingContext* context = ExecutingContext::From(ctx); JSValue prototypeObject = context->contextData()->prototypeForType(wrapper_type_info); if (JS_HasProperty(ctx, prototypeObject, atom)) { JSValue target = JS_DupValue(ctx, prototypeObject); JSValue setterFunc = JS_UNDEFINED; - while (JS_IsUndefined(setterFunc)) { + while (JS_IsUndefined(setterFunc) && JS_IsObject(target)) { JSPropertyDescriptor descriptor; descriptor.setter = JS_UNDEFINED; descriptor.getter = JS_UNDEFINED; @@ -89,6 +140,7 @@ static int HandleJSPropertySetterCallback(JSContext* ctx, setterFunc = descriptor.setter; if (JS_IsFunction(ctx, setterFunc)) { JS_FreeValue(ctx, descriptor.getter); + JS_FreeValue(ctx, descriptor.value); break; } @@ -97,23 +149,22 @@ static int HandleJSPropertySetterCallback(JSContext* ctx, target = new_target; JS_FreeValue(ctx, descriptor.getter); JS_FreeValue(ctx, descriptor.setter); + JS_FreeValue(ctx, descriptor.value); + } + + if (!JS_IsFunction(ctx, setterFunc)) { + return false; } assert_m(JS_IsFunction(ctx, setterFunc), "Setter on prototype should be an function."); JSValue ret = JS_Call(ctx, setterFunc, obj, 1, &value); if (JS_IsException(ret)) - return -1; + return false; JS_FreeValue(ctx, ret); JS_FreeValue(ctx, setterFunc); JS_FreeValue(ctx, target); - return 0; - } - - if (wrapper_type_info->indexed_property_setter_handler_ != nullptr && JS_AtomIsTaggedInt(atom)) { - return wrapper_type_info->indexed_property_setter_handler_(ctx, obj, JS_AtomToUInt32(atom), value); - } else if (wrapper_type_info->string_property_setter_handler_ != nullptr) { - return wrapper_type_info->string_property_setter_handler_(ctx, obj, atom, value); + return true; } return false; @@ -122,7 +173,7 @@ static int HandleJSPropertySetterCallback(JSContext* ctx, /// This callback will be called when JS code check property exit on this object using `in` operator. /// Wehn exec `'prop' in obj`, it will call. static int HandleJSPropertyCheckerCallback(JSContext* ctx, JSValueConst obj, JSAtom atom) { - auto* object = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); + auto* object = static_cast(JS_GetOpaque(obj, JS_GetClassID(obj))); auto* wrapper_type_info = object->GetWrapperTypeInfo(); return wrapper_type_info->property_checker_handler_(ctx, obj, atom); @@ -131,12 +182,21 @@ static int HandleJSPropertyCheckerCallback(JSContext* ctx, JSValueConst obj, JSA /// This callback will be called when JS code enumerate all own properties on this object. /// Exp: Object.keys(obj); static int HandleJSPropertyEnumerateCallback(JSContext* ctx, JSPropertyEnum** ptab, uint32_t* plen, JSValueConst obj) { - auto* object = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); + auto* object = static_cast(JS_GetOpaque(obj, JS_GetClassID(obj))); auto* wrapper_type_info = object->GetWrapperTypeInfo(); return wrapper_type_info->property_enumerate_handler_(ctx, ptab, plen, obj); } +/// This callback will be called when JS code delete properties on this object. +/// Exp: delete obj['name'] +static int HandleJSPropertyDelete(JSContext* ctx, JSValueConst obj, JSAtom prop) { + auto* object = static_cast(JS_GetOpaque(obj, JS_GetClassID(obj))); + auto* wrapper_type_info = object->GetWrapperTypeInfo(); + + return wrapper_type_info->property_delete_handler_(ctx, obj, prop); +} + static int HandleJSGetOwnPropertyNames(JSContext* ctx, JSPropertyEnum** ptab, uint32_t* plen, JSValueConst obj) { // All props and methods are finded in prototype object of scriptwrappable. JSValue proto = JS_GetPrototype(ctx, obj); @@ -192,16 +252,20 @@ void ScriptWrappable::InitializeQuickJSObject() { exotic_methods->get_own_property_names = HandleJSPropertyEnumerateCallback; exotic_methods->get_own_property = [](JSContext* ctx, JSPropertyDescriptor* desc, JSValueConst obj, JSAtom prop) -> int { - auto* object = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); + auto* object = static_cast(JS_GetOpaque(obj, JS_GetClassID(obj))); auto* wrapper_type_info = object->GetWrapperTypeInfo(); if (wrapper_type_info->string_property_getter_handler_ != nullptr) { JSValue return_value = wrapper_type_info->string_property_getter_handler_(ctx, obj, prop); if (!JS_IsNull(return_value)) { - desc->flags = JS_PROP_ENUMERABLE; - desc->value = return_value; - desc->getter = JS_NULL; - desc->setter = JS_NULL; + if (desc != nullptr) { + desc->flags = JS_PROP_ENUMERABLE; + desc->value = return_value; + desc->getter = JS_NULL; + desc->setter = JS_NULL; + } else { + JS_FreeValue(ctx, return_value); + } return true; } } @@ -210,10 +274,14 @@ void ScriptWrappable::InitializeQuickJSObject() { uint32_t index = JS_AtomToUInt32(prop); JSValue return_value = wrapper_type_info->indexed_property_getter_handler_(ctx, obj, index); if (!JS_IsNull(return_value)) { - desc->flags = JS_PROP_ENUMERABLE; - desc->value = return_value; - desc->getter = JS_NULL; - desc->setter = JS_NULL; + if (desc != nullptr) { + desc->flags = JS_PROP_ENUMERABLE; + desc->value = return_value; + desc->getter = JS_NULL; + desc->setter = JS_NULL; + } else { + JS_FreeValue(ctx, return_value); + } return true; } } @@ -226,6 +294,10 @@ void ScriptWrappable::InitializeQuickJSObject() { exotic_methods->get_own_property = HandleJSGetOwnProperty; } + if (UNLIKELY(wrapper_type_info->property_delete_handler_ != nullptr)) { + exotic_methods->delete_property = HandleJSPropertyDelete; + } + def.exotic = exotic_methods; def.finalizer = HandleJSObjectFinalized; @@ -239,18 +311,33 @@ void ScriptWrappable::InitializeQuickJSObject() { jsObject_ = JS_NewObjectClass(ctx_, wrapper_type_info->classId); JS_SetOpaque(jsObject_, this); - if (KeepAlive()) { - JS_DupValue(ctx_, jsObject_); - context_->RegisterActiveScriptWrappers(this); - } - // Let our instance into inherit prototype methods. JSValue prototype = GetExecutingContext()->contextData()->prototypeForType(wrapper_type_info); JS_SetPrototype(ctx_, jsObject_, prototype); } -bool ScriptWrappable::KeepAlive() const { - return false; +WebFValueStatus* ScriptWrappable::KeepAlive() { + if (alive_count == 0) { + context_->RegisterActiveScriptWrappers(this); + JS_DupValue(ctx_, jsObject_); + status_block_ = new WebFValueStatus(); + } + alive_count++; + return status_block_; +} + +void ScriptWrappable::ReleaseAlive() { + alive_count--; + if (alive_count == 0) { + delete status_block_; + status_block_ = nullptr; + if (isContextValid(context_id_)) { + context_->RemoveActiveScriptWrappers(this); + } + if (context_->IsCtxValid()) { + JS_FreeValue(ctx_, jsObject_); + } + } } } // namespace webf diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index e3a4f9bc2d..5465d93d22 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -1,15 +1,22 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_SCRIPT_WRAPPABLE_H #define BRIDGE_SCRIPT_WRAPPABLE_H #include +#include "../../foundation/string/atomic_string.h" #include "bindings/qjs/cppgc/garbage_collected.h" -#include "core/executing_context.h" +#include "core/dom/element_rare_data_field.h" #include "foundation/macros.h" +#include "multiple_threading/dispatcher.h" +#include "plugin_api/webf_value.h" #include "wrapper_type_info.h" namespace webf { @@ -36,16 +43,18 @@ class GCVisitor; // JavaScript object (platform object). ToQuickJS() converts a ScriptWrappable to // a QuickJS object and toScriptWrappable() converts a QuickJS object back to // a ScriptWrappable. -class ScriptWrappable : public GarbageCollected { +class ScriptWrappable : public GarbageCollected, public ElementRareDataField { public: ScriptWrappable() = delete; explicit ScriptWrappable(JSContext* ctx); - virtual ~ScriptWrappable() = default; + virtual ~ScriptWrappable(); // Returns the WrapperTypeInfo of the instance. virtual const WrapperTypeInfo* GetWrapperTypeInfo() const = 0; + virtual bool IsBindingObject() const; + void Trace(GCVisitor* visitor) const override{}; virtual JSValue ToQuickJS() const; @@ -53,23 +62,30 @@ class ScriptWrappable : public GarbageCollected { ScriptValue ToValue(); FORCE_INLINE ExecutingContext* GetExecutingContext() const { return context_; }; + multi_threading::Dispatcher* GetDispatcher() const; FORCE_INLINE JSContext* ctx() const { return ctx_; } FORCE_INLINE JSRuntime* runtime() const { return runtime_; } + FORCE_INLINE double contextId() const { return context_id_; } void InitializeQuickJSObject() override; + bool IsPrototypeProperty(const AtomicString& key) const; + /** - * Classes kept alive as long as - * they have a pending activity. Destroying the corresponding ExecutionContext - * implicitly releases them to avoid leaks. + * Classes kept alive as long as they have a pending activity. + * Release them via `ReleaseAlive` method. */ - virtual bool KeepAlive() const; + WebFValueStatus* KeepAlive(); + void ReleaseAlive(); private: + uint32_t alive_count = 0; JSValue jsObject_{JS_NULL}; JSContext* ctx_{nullptr}; ExecutingContext* context_{nullptr}; + double context_id_; JSRuntime* runtime_{nullptr}; + WebFValueStatus* status_block_{nullptr}; friend class GCVisitor; }; @@ -80,11 +96,11 @@ inline ScriptWrappable* toScriptWrappable(JSValue object) { if (JS_IsProxy(object)) { object = JS_GetProxyTarget(object); } - return static_cast(JS_GetOpaque(object, JSValueGetClassId(object))); + return static_cast(JS_GetOpaque(object, JS_GetClassID(object))); } template -Local::~Local() { +Local::~Local() { if (raw_ == nullptr) return; auto* wrappable = To(raw_); diff --git a/bridge/bindings/qjs/source_location.cc b/bridge/bindings/qjs/source_location.cc index 26b66687a8..090d19d22e 100644 --- a/bridge/bindings/qjs/source_location.cc +++ b/bridge/bindings/qjs/source_location.cc @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #include "source_location.h" diff --git a/bridge/bindings/qjs/source_location.h b/bridge/bindings/qjs/source_location.h index ce9de59442..0abc5bbe80 100644 --- a/bridge/bindings/qjs/source_location.h +++ b/bridge/bindings/qjs/source_location.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_BINDINGS_QJS_SOURCE_LOCATION_H_ #define BRIDGE_BINDINGS_QJS_SOURCE_LOCATION_H_ diff --git a/bridge/bindings/qjs/to_quickjs.h b/bridge/bindings/qjs/to_quickjs.h index e6fe578921..b05b22d635 100644 --- a/bridge/bindings/qjs/to_quickjs.h +++ b/bridge/bindings/qjs/to_quickjs.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_BINDINGS_QJS_TO_QUICKJS_H_ #define BRIDGE_BINDINGS_QJS_TO_QUICKJS_H_ @@ -9,7 +13,6 @@ #include #include "core/fileapi/array_buffer_data.h" #include "native_string_utils.h" -#include "qjs_engine_patch.h" #include "script_wrappable.h" namespace webf { @@ -35,10 +38,10 @@ inline JSValue toQuickJS(JSContext* ctx, const std::string& str) { inline JSValue toQuickJS(JSContext* ctx, const char* str) { return JS_NewString(ctx, str); } -inline JSValue toQuickJS(JSContext* ctx, std::unique_ptr& str) { +inline JSValue toQuickJS(JSContext* ctx, std::unique_ptr& str) { return JS_NewUnicodeString(ctx, str->string(), str->length()); } -inline JSValue toQuickJS(JSContext* ctx, NativeString* str) { +inline JSValue toQuickJS(JSContext* ctx, SharedNativeString* str) { return JS_NewUnicodeString(ctx, str->string(), str->length()); } diff --git a/bridge/bindings/qjs/union_base.cc b/bridge/bindings/qjs/union_base.cc index 5c55fe38e8..969c510ed3 100644 --- a/bridge/bindings/qjs/union_base.cc +++ b/bridge/bindings/qjs/union_base.cc @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #include "union_base.h" diff --git a/bridge/bindings/qjs/union_base.h b/bridge/bindings/qjs/union_base.h index 7749adf1aa..f8bae8ad5f 100644 --- a/bridge/bindings/qjs/union_base.h +++ b/bridge/bindings/qjs/union_base.h @@ -1,6 +1,10 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef WEBF_BINDINGS_QJS_UNION_BASE_H_ diff --git a/bridge/bindings/qjs/value_cache.cc b/bridge/bindings/qjs/value_cache.cc new file mode 100644 index 0000000000..0183724eda --- /dev/null +++ b/bridge/bindings/qjs/value_cache.cc @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. + */ + +#include "value_cache.h" +#include +#include "../../foundation/string/atomic_string_table.h" + +namespace webf { + +JSValue StringCache::GetJSValueFromString(JSContext* ctx, std::shared_ptr string_impl) { + JSAtom atom = GetJSAtomFromString(ctx, std::move(string_impl)); + return JS_AtomToValue(ctx, atom); +} + +JSAtom StringCache::GetJSAtomFromString(JSContext* ctx, std::shared_ptr string_impl) { + DCHECK(string_impl); + if (!string_impl->length()) { + return JS_ATOM_NULL; + } + + auto cached_qjs_string = string_cache_.find(string_impl); + + if (cached_qjs_string != string_cache_.end()) { + return cached_qjs_string->second; + } + + return CreateStringAndInsertIntoCache(ctx, string_impl); +} + +JSAtom StringCache::CreateStringAndInsertIntoCache(JSContext* ctx, std::shared_ptr string_impl) { + DCHECK(string_impl); + DCHECK(!string_cache_.contains(string_impl)); + DCHECK(string_impl->length()); + + JSAtom new_string_atom; + + if (string_impl->Is8Bit()) { + // For 8-bit strings (Latin1), use the characters directly + auto str = JS_NewRawUTF8String(ctx, string_impl->Characters8(), string_impl->length()); + new_string_atom = JS_ValueToAtom(ctx, str); + JS_FreeValue(ctx, str); + } else { + // For 16-bit strings (UTF-16), use QuickJS's Unicode atom function + new_string_atom = JS_NewUnicodeAtom(ctx, reinterpret_cast(string_impl->Characters16()), string_impl->length()); + } + + string_cache_[string_impl] = JS_DupAtom(ctx, new_string_atom); + + JSAtom cache_key; + if (UNLIKELY(atom_to_string_cache.contains(new_string_atom))) { + cache_key = new_string_atom; + } else { + cache_key = JS_DupAtom(ctx, new_string_atom); + } + atom_to_string_cache[cache_key] = string_impl; + + JS_FreeAtom(ctx, new_string_atom); + + return new_string_atom; +} + +std::shared_ptr StringCache::GetStringFromJSAtom(JSContext* ctx, JSAtom atom) { + auto it = atom_to_string_cache.find(atom); + if (it != atom_to_string_cache.end()) { + return it->second; + } + + // Atom not in cache, create StringImpl and cache it + bool is_wide_char = !JS_AtomIsTaggedInt(atom) && JS_IsAtomWideChar(JS_GetRuntime(ctx), atom); + std::shared_ptr string_impl; + + if (LIKELY(!is_wide_char)) { + uint32_t slen; + auto* str = reinterpret_cast(JS_AtomRawCharacter8(JS_GetRuntime(ctx), atom, &slen)); + string_impl = AtomicStringTable::Instance().AddLatin1(str, slen); + } else { + uint32_t slen; + auto* wstrs = reinterpret_cast(JS_AtomRawCharacter16(JS_GetRuntime(ctx), atom, &slen)); + string_impl = AtomicStringTable::Instance().Add(wstrs, slen); + } + + // Cache the mapping - duplicate the atom since we're storing it + atom_to_string_cache[JS_DupAtom(ctx, atom)] = string_impl; + return string_impl; +} + +void StringCache::Dispose() { + for (auto&& item : string_cache_) { + JS_FreeAtomRT(runtime_, item.second); + } + + for (auto&& item : atom_to_string_cache) { + JS_FreeAtomRT(runtime_, item.first); + } + + // Clear the maps to release all StringImpl references + string_cache_.clear(); + atom_to_string_cache.clear(); +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/bindings/qjs/value_cache.h b/bridge/bindings/qjs/value_cache.h new file mode 100644 index 0000000000..d2809c323b --- /dev/null +++ b/bridge/bindings/qjs/value_cache.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. + */ + +#ifndef WEBF_BINDINGS_QJS_VALUE_CACHE_H_ +#define WEBF_BINDINGS_QJS_VALUE_CACHE_H_ + +#include +#include +#include "../../foundation/string/string_impl.h" +#include "foundation/macros.h" + +namespace webf { + +// String cache helps convert WebF strings (std::shared_ptr) into QJS strings by +// only creating a QuickJS string for a particular std::shared_ptr once and caching it +// for future use. +class StringCache { + USING_FAST_MALLOC(StringCache); + + public: + explicit StringCache(JSRuntime* runtime) : runtime_(runtime) {} + StringCache(const StringCache&) = delete; + StringCache() = delete; + StringCache& operator=(const StringCache&) = delete; + + JSValue GetJSValueFromString(JSContext* ctx, std::shared_ptr string_impl); + JSAtom GetJSAtomFromString(JSContext* ctx, std::shared_ptr string_impl); + std::shared_ptr GetStringFromJSAtom(JSContext* ctx, JSAtom atom); + + void Dispose(); + + private: + JSAtom CreateStringAndInsertIntoCache(JSContext* ctx, std::shared_ptr); + + JSRuntime* runtime_; + std::unordered_map, JSAtom> string_cache_; + std::unordered_map> atom_to_string_cache; +}; + +} // namespace webf + +#endif // WEBF_BINDINGS_QJS_VALUE_CACHE_H_ diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index ef352459a2..7a69e3fb92 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -1,13 +1,16 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-2024 The WebF authors. All rights reserved. */ #ifndef BRIDGE_WRAPPER_TYPE_INFO_H #define BRIDGE_WRAPPER_TYPE_INFO_H #include #include -#include "bindings/qjs/qjs_engine_patch.h" namespace webf { @@ -29,9 +32,12 @@ enum { JS_CLASS_CUSTOM_EVENT, JS_CLASS_TRANSITION_EVENT, JS_CLASS_INPUT_EVENT, + JS_CLASS_HYBRID_ROUTER_CHANGE_EVENT, + JS_CLASS_SCREEN_EVENT, JS_CLASS_ANIMATION_EVENT, JS_CLASS_FOCUS_EVENT, JS_CLASS_GESTURE_EVENT, + JS_CLASS_HASHCHANGE_EVENT, JS_CLASS_POP_STATE_EVENT, JS_CLASS_INTERSECTION_CHANGE_EVENT, JS_CLASS_KEYBOARD_EVENT, @@ -56,6 +62,7 @@ enum { JS_CLASS_BOUNDING_CLIENT_RECT, JS_CLASS_ELEMENT_ATTRIBUTES, JS_CLASS_HTML_ALL_COLLECTION, + JS_CLASS_HTML_COLLECTION, JS_CLASS_HTML_ELEMENT, JS_CLASS_WIDGET_ELEMENT, JS_CLASS_HTML_DIV_ELEMENT, @@ -64,27 +71,139 @@ enum { JS_CLASS_HTML_HTML_ELEMENT, JS_CLASS_HTML_IMAGE_ELEMENT, JS_CLASS_HTML_SCRIPT_ELEMENT, + JS_CLASS_HTML_STYLE_ELEMENT, + JS_CLASS_HTMLI_FRAME_ELEMENT, JS_CLASS_HTML_ANCHOR_ELEMENT, JS_CLASS_HTML_LINK_ELEMENT, + JS_CLASS_HTML_BR_ELEMENT, JS_CLASS_HTML_CANVAS_ELEMENT, + JS_CLASS_HTML_STYLE_SHEET, + JS_CLASS_STYLE_SHEET, JS_CLASS_IMAGE, + JS_CLASS_IMAGE_BITMAP, + JS_CLASS_MUTATION_OBSERVER, + JS_CLASS_MUTATION_RECORD, + JS_CLASS_MUTATION_OBSERVER_REGISTRATION, JS_CLASS_CANVAS_RENDERING_CONTEXT, JS_CLASS_CANVAS_RENDERING_CONTEXT_2_D, JS_CLASS_CANVAS_GRADIENT, JS_CLASS_CANVAS_PATTERN, + JS_CLASS_PATH_2_D, + JS_CLASS_TEXT_METRICS, JS_CLASS_DOM_MATRIX, - JS_CLASS_DOM_MATRIX_READONLY, + JS_CLASS_DOM_MATRIX_READ_ONLY, + JS_CLASS_DOM_POINT, + JS_CLASS_DOM_POINT_READ_ONLY, JS_CLASS_HTML_TEMPLATE_ELEMENT, JS_CLASS_HTML_UNKNOWN_ELEMENT, JS_CLASS_HTML_INPUT_ELEMENT, JS_CLASS_HTML_BUTTON_ELEMENT, JS_CLASS_HTML_FORM_ELEMENT, JS_CLASS_HTML_TEXTAREA_ELEMENT, + JS_CLASS_HTML_LABEL_ELEMENT, + JS_CLASS_WEB_F_TOUCH_AREA_ELEMENT, + JS_CLASS_WEB_F_ROUTER_LINK_ELEMENT, + JS_CLASS_HTML_PARAGRAPH_ELEMENT, + JS_CLASS_HTML_HEADING_ELEMENT, + JS_CLASS_HTML_U_LIST_ELEMENT, + JS_CLASS_HTML_O_LIST_ELEMENT, + JS_CLASS_HTML_LI_ELEMENT, + JS_CLASS_HTML_STRONG_ELEMENT, + JS_CLASS_HTML_EM_ELEMENT, + JS_CLASS_HTML_CODE_ELEMENT, + JS_CLASS_HTML_SPAN_ELEMENT, + JS_CLASS_HTML_PRE_ELEMENT, + JS_CLASS_HTML_QUOTE_ELEMENT, + JS_CLASS_HTML_ARTICLE_ELEMENT, + JS_CLASS_HTML_SECTION_ELEMENT, + JS_CLASS_HTML_HEADER_ELEMENT, + JS_CLASS_HTML_FOOTER_ELEMENT, + JS_CLASS_HTML_NAV_ELEMENT, + JS_CLASS_HTML_BOLD_ELEMENT, + JS_CLASS_HTML_ITALIC_ELEMENT, + JS_CLASS_HTML_UNDERLINE_ELEMENT, + JS_CLASS_HTML_STRIKETHROUGH_ELEMENT, + JS_CLASS_HTML_MARK_ELEMENT, + JS_CLASS_HTML_SMALL_ELEMENT, + JS_CLASS_HTML_HR_ELEMENT, + JS_CLASS_HTML_ABBR_ELEMENT, + JS_CLASS_HTML_CITE_ELEMENT, + JS_CLASS_HTML_KBD_ELEMENT, + JS_CLASS_HTML_VAR_ELEMENT, + JS_CLASS_HTML_DFN_ELEMENT, + JS_CLASS_HTML_SAMP_ELEMENT, + JS_CLASS_HTML_DATA_ELEMENT, + JS_CLASS_HTML_TIME_ELEMENT, + JS_CLASS_HTML_DL_ELEMENT, + JS_CLASS_HTML_DT_ELEMENT, + JS_CLASS_HTML_DD_ELEMENT, + JS_CLASS_HTML_FIGURE_ELEMENT, + JS_CLASS_HTML_FIG_CAPTION_ELEMENT, + JS_CLASS_HTML_ADDRESS_ELEMENT, + JS_CLASS_HTML_ASIDE_ELEMENT, + JS_CLASS_HTML_MAIN_ELEMENT, + JS_CLASS_HTML_DEL_ELEMENT, + JS_CLASS_HTML_INS_ELEMENT, + JS_CLASS_HTML_SELECT_ELEMENT, + JS_CLASS_HTML_OPTGROUP_ELEMENT, + JS_CLASS_HTML_OPTION_ELEMENT, + JS_CLASS_HTML_TITLE_ELEMENT, + JS_CLASS_HTML_META_ELEMENT, + JS_CLASS_HTML_NO_SCRIPT_ELEMENT, JS_CLASS_CSS_STYLE_DECLARATION, JS_CLASS_INLINE_CSS_STYLE_DECLARATION, JS_CLASS_COMPUTED_CSS_STYLE_DECLARATION, - + JS_CLASS_IDLE_DEADLINE, JS_CLASS_DOM_TOKEN_LIST, + JS_CLASS_DOM_STRING_MAP, + JS_CLASS_LEGACY_CSS_STYLE_DECLARATION, + JS_CLASS_LEGACY_INLINE_CSS_STYLE_DECLARATION, + JS_CLASS_LEGACY_COMPUTED_CSS_STYLE_DECLARATION, + + JS_CLASS_NATIVE_LOADER, + + // CSS + JS_CLASS_CSS_RULE, + JS_CLASS_CSS_STYLE_SHEET, + JS_CLASS_CSS_RULE_LIST, + JS_CLASS_MEDIA_LIST, + JS_CLASS_CSS_IMPORT_RULE, + JS_CLASS_CSS_KEYFRAME_RULE, + JS_CLASS_CSS_KEYFRAMES_RULE, + JS_CLASS_CSS_LAYER_BLOCK_RULE, + JS_CLASS_CSS_LAYER_STATEMENT_RULE, + + // SVG + JS_CLASS_SVG_ELEMENT, + JS_CLASS_SVG_GRAPHICS_ELEMENT, + JS_CLASS_SVG_GEOMETRY_ELEMENT, + JS_CLASS_SVG_TEXT_CONTENT_ELEMENT, + JS_CLASS_SVG_TEXT_POSITIONING_ELEMENT, + + JS_CLASS_SVG_RECT_ELEMENT, + JS_CLASS_SVG_SVG_ELEMENT, + JS_CLASS_SVG_PATH_ELEMENT, + JS_CLASS_SVG_TEXT_ELEMENT, + JS_CLASS_SVG_G_ELEMENT, + JS_CLASS_SVG_CIRCLE_ELEMENT, + JS_CLASS_SVG_ELLIPSE_ELEMENT, + JS_CLASS_SVG_STYLE_ELEMENT, + JS_CLASS_SVG_LINE_ELEMENT, + + // SVG unit + JS_CLASS_SVG_LENGTH, + JS_CLASS_SVG_ANIMATED_LENGTH, + + JS_CLASS_FORM_DATA, + JS_CLASS_FILE, + + // IntersectionObserver + JS_CLASS_INTERSECTION_OBSERVER, + JS_CLASS_INTERSECTION_OBSERVER_ENTRY, + + // Dart <-> JavaScript custom binding object + JS_CLASS_DART_BINDING_OBJECT, + JS_CLASS_CUSTOM_CLASS_INIT_COUNT /* last entry for predefined classes */ }; @@ -104,6 +223,10 @@ using IndexedPropertySetterHandler = bool (*)(JSContext* ctx, JSValueConst obj, // exp: obj['hello'] = value; using StringPropertySetterHandler = bool (*)(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value); +// Callback when delete property using string or symbol. +// exp: delete obj['hello'] +using StringPropertyDeleteHandler = bool (*)(JSContext* ctx, JSValueConst obj, JSAtom prop); + // Callback when check property exist on object. // exp: 'hello' in obj; using PropertyCheckerHandler = bool (*)(JSContext* ctx, JSValueConst obj, JSAtom atom); @@ -138,6 +261,7 @@ class WrapperTypeInfo final { StringPropertySetterHandler string_property_setter_handler_{nullptr}; PropertyCheckerHandler property_checker_handler_{nullptr}; PropertyEnumerateHandler property_enumerate_handler_{nullptr}; + StringPropertyDeleteHandler property_delete_handler_{nullptr}; }; } // namespace webf diff --git a/bridge/bridge_sources.json5 b/bridge/bridge_sources.json5 new file mode 100644 index 0000000000..29c81fb7f7 --- /dev/null +++ b/bridge/bridge_sources.json5 @@ -0,0 +1,892 @@ +{ + BRIDGE_SOURCE: [ + "foundation/metrics_registry.cc", + "foundation/logging.cc", + "foundation/ios_logger.mm", + "foundation/native_string.cc", + "foundation/shared_ui_command.cc", + "foundation/string/string_view.cc", + "foundation/native_value.cc", + "foundation/native_byte_data.cc", + "foundation/native_type.cc", + "foundation/stop_watch.cc", + "foundation/dart_readable.cc", + "foundation/rust_readable.cc", + "foundation/string/string_builder.cc", + "foundation/string/string_utils.cc", + "foundation/string/ascii_types.cc", + "foundation/ui_command_buffer.cc", + "foundation/ui_command_ring_buffer.cc", + "foundation/ui_command_strategy.cc", + "foundation/dtoa.cc", + "foundation/string/string_impl.cc", + "foundation/string/atomic_string.cc", + "foundation/string/string_statics.cc", + "foundation/string/atomic_string_table.cc", + "foundation/string/rapidhash.h", + "foundation/string/string_types.h", + "foundation/string/convert_to_8bit_hash_reader.h", + "foundation/string/utf8_codecs.h", + "foundation/string/wtf_string.cc", + "foundation/decimal.cc", + "foundation/utility/make_visitor.h", + "multiple_threading/dispatcher.cc", + "multiple_threading/looper.cc", + + // Bindings + "bindings/qjs/dictionary_base.cc", + "bindings/qjs/js_based_event_listener.cc", + "bindings/qjs/js_event_handler.cc", + "bindings/qjs/js_event_listener.cc", + "bindings/qjs/binding_initializer.cc", + "bindings/qjs/qjs_dart_binding_object.cc", + "bindings/qjs/member_installer.cc", + "bindings/qjs/source_location.cc", + "bindings/qjs/cppgc/gc_visitor.cc", + "bindings/qjs/cppgc/mutation_scope.cc", + "bindings/qjs/script_wrappable.cc", + "bindings/qjs/native_string_utils.cc", + "bindings/qjs/qjs_function.cc", + "bindings/qjs/script_value.cc", + "bindings/qjs/script_promise.cc", + "bindings/qjs/script_promise_resolver.cc", + "bindings/qjs/value_cache.cc", + "bindings/qjs/exception_state.cc", + "bindings/qjs/exception_message.cc", + "bindings/qjs/rejected_promises.cc", + "bindings/qjs/union_base.cc", + + // Core + "webf_bridge.cc", + "core/executing_context.cc", + "core/script_forbidden_scope.cc", + "core/script_state.cc", + "core/page.cc", + "core/dart_methods.cc", + "core/bridge_polyfill.c", + "core/devtools/remote_object.cc", + "core/devtools/devtools_bridge.cc", + "core/api/exception_state.cc", + "core/api/event_target.cc", + "core/api/node.cc", + "core/api/executing_context.cc", + "core/api/container_node.cc", + "core/api/document.cc", + "core/api/element.cc", + "core/api/html_element.cc", + "core/api/html_image_element.cc", + "core/api/html_canvas_element.cc", + "core/api/document_fragment.cc", + "core/api/window.cc", + "core/api/text.cc", + "core/api/comment.cc", + "core/api/character_data.cc", + "core/api/script_value_ref.cc", + "core/dart_isolate_context.cc", + "core/executing_context_data.cc", + "core/fileapi/blob.cc", + "core/fileapi/blob_part.cc", + "core/fileapi/file.cc", + "core/frame/console.cc", + "core/frame/dom_timer.cc", + "core/frame/dom_timer_coordinator.cc", + "core/frame/window_or_worker_global_scope.cc", + "core/frame/module_listener.cc", + "core/frame/module_listener_container.cc", + "core/frame/module_manager.cc", + "core/frame/module_callback.cc", + "core/frame/module_context_coordinator.cc", + "core/frame/window.cc", + "core/frame/window_idle_tasks.cc", + "core/frame/script_idle_task_controller.cc", + "core/frame/screen.cc", + "core/frame/idle_deadline.cc", + "core/frame/legacy/location.cc", + "core/timing/performance.cc", + "core/timing/performance_mark.cc", + "core/timing/performance_entry.cc", + "core/timing/performance_measure.cc", + + // Core base + "core/base/hash/hash.cc", + "core/base/strings/string_util.cc", + "core/base/strings/string_util_constants.cc", + "core/base/strings/string_number_conversions.cc", + "core/base/strings/utf_string_conversion_utils.cc", + "core/core_initializer.cc", + "core/base/hash/hash.cc", + "core/base/strings/string_util.cc", + "core/base/strings/string_util_constants.cc", + "core/base/strings/string_number_conversions.cc", + "core/base/strings/utf_string_conversion_utils.cc", + "core/core_initializer.cc", + + // Style + "core/css/style_element.cc", + "core/css/style_color.cc", + "core/css/active_style_sheets.cc", + "core/css/style_engine.cc", + "core/css/style_traversal_root.cc", + "core/css/style_invalidation_root.cc", + "core/css/style_recalc_root.cc", + "core/css/style_recalc_context.cc", + "core/css/css_global_rule_set.cc", + "core/css/rule_feature_set.cc", + "core/css/invalidation/invalidation_flags.cc", + "core/css/invalidation/invalidation_set.cc", + "core/css/invalidation/pending_invalidations.cc", + "core/css/invalidation/rule_invalidation_data.cc", + "core/css/invalidation/style_invalidator.cc", + "core/inspector/invalidation_set_to_selector_map.cc", + + // Animation + "core/animation/animation_time_delta.cc", + "core/animation/css/css_transition_data.cc", + "core/animation/css/css_timing_data.cc", + + + "core/css/if_condition.cc", + "core/css/style_sheet_contents.cc", + "core/css/css_style_sheet.cc", + "core/css/style_rule_import.cc", + "core/css/style_sheet.cc", + "core/css/resolver/style_resolver.cc", + "core/css/resolver/style_builder.cc", + "core/css/resolver/style_resolver_state.cc", + "core/css/resolver/font_builder.cc", + "core/css/resolver/style_cascade.cc", + "core/css/resolver/matched_properties_cache.cc", + "core/css/resolver/cascade_map.cc", + "core/css/resolver/cascade_resolver.cc", + "core/css/resolver/element_resolve_context.cc", + "core/css/resolver/style_adjuster.cc", + "core/css/resolver/style_builder_converter.cc", + "core/css/resolver/css_to_style_map_minimal.cc", + "core/css/element_rule_collector.cc", + "core/css/rule_set.cc", + "core/css/selector_filter.cc", + "core/style/computed_style.cc", + "core/css/parser/css_parser.cc", + "core/css/parser/css_parser_context.cc", + "core/css/parser/css_parser_idioms.cc", + "core/css/parser/css_parser_impl.cc", + "core/css/parser/css_lazy_parsing_state.cc", + "core/css/parser/at_rule_descriptor_parser.cc", + "core/css/parser/css_at_rule_id.cc", + "core/css/parser/css_if_parser.cc", + "core/css/parser/css_variable_parser.cc", + "core/css/parser/css_supports_parser.cc", + "core/css/parser/media_query_parser.cc", + "core/css/parser/container_query_parser.cc", + "core/css/parser/css_lazy_property_parser_impl.cc", + "core/css/parser/css_parser_token.cc", + "core/css/parser/css_parser_fast_path.cc", + "core/css/parser/font_variant_alternates_parser.cc", + "core/css/parser/css_parser_token_range.cc", + "core/css/parser/css_parser_token_stream.cc", + "core/css/parser/css_tokenizer.cc", + "core/css/parser/css_property_parser.cc", + "core/css/parser/css_tokenizer_input_stream.cc", + "core/css/parser/css_selector_parser.cc", + "core/css/parser/sizes_attribute_parser.cc", + "core/css/parser/sizes_math_function_parser.cc", + "core/css/properties/css_direction_aware_resolver.cc", + "core/css/properties/css_unresolved_property.cc", + "core/css/properties/css_property.cc", + "core/css/properties/css_parsing_utils.cc", + "core/css/properties/css_color_function_parser.cc", + "core/css/css_timing_function_value.cc", + "core/css/css_rule.cc", + "core/css/css_rule_list.cc", + "core/css/css_selector.cc", + "core/css/css_selector_list.cc", + "core/css/css_value.cc", + "core/css/media_list.cc", + "core/css/media_values_dynamic.cc", + "core/css/media_values.cc", + "core/css/style_scope.cc", + "core/css/cascade_layer.cc", + "core/css/cascade_layer_map.cc", + "core/css/container_selector.cc", + "core/css/container_query.cc", + "core/css/media_query_exp.cc", + "core/css/media_query.cc", + "core/css/media_query_evaluator.cc", + "core/css/css_content_distribution_value.cc", + "core/css/css_reflect_value.cc", + "core/css/css_border_image.cc", + "core/css/css_axis_value.cc", + "core/css/css_font_face_src_value.cc", + "core/css/properties/longhand.cc", + "core/css/properties/shorthands/shorthands_custom.cc", + "core/css/properties/longhands/longhands_custom.cc", + "core/css/abstract_property_set_css_style_declaration.cc", + "core/css/property_set_css_style_declaration.cc", + "core/css/style_rule.cc", + "core/css/style_rule_font_feature_values.cc", + "core/css/style_rule_counter_style.cc", + "core/css/css_grouping_rule.cc", + "core/css/css_layer_block_rule.cc", + "core/css/css_layer_statement_rule.cc", + "core/css/css_condition_rule.cc", + "core/css/css_container_rule.cc", + "core/css/css_scope_rule.cc", + "core/css/style_rule_nested_declarations.cc", + "core/css/css_nested_declarations_rule.cc", + "core/css/css_style_rule.cc", + "core/css/css_media_rule.cc", + "core/css/css_supports_rule.cc", + "core/css/css_counter_style_rule.cc", + "core/animation/timeline_offset.cc", + "core/css/css_property_value_set.cc", + "core/css/css_property_name.cc", + "core/css/css_property_value.cc", + "core/css/css_quad_value.cc", + "core/css/css_shadow_value.cc", + "core/css/css_font_style_range_value.cc", + "core/css/css_border_image_slice_value.cc", + "core/css/css_primitive_value.cc", + "core/css/css_image_value.cc", + "core/css/css_image_generator_value.cc", + "core/css/css_gradient_value.cc", + "core/css/css_image_set_type_value.cc", + "core/css/css_image_set_option_value.cc", + "core/css/css_image_set_value.cc", + "core/css/css_crossfade_value.cc", + "core/css/css_repeat_style_value.cc", + "core/css/css_font_family_value.cc", + "core/css/css_uri_value.cc", + "core/css/css_url_data.cc", + "core/css/css_pending_system_font_value.cc", + "core/css/css_font_variation_value.cc", + "core/css/css_font_feature_value.cc", + "core/css/css_alternate_value.cc", + "core/css/css_identifier_value.cc", + "core/css/css_numeric_literal_value.cc", + "core/css/css_math_expression_node.cc", + "core/css/css_ray_value.cc", + "core/css/css_to_length_conversion_data.cc", + "core/css/css_grid_auto_repeat_value.cc", + "core/css/css_grid_integer_repeat_value.cc", + "core/css/css_grid_template_areas_value.cc", + "core/css/css_scroll_value.cc", + "core/css/css_basic_shape_value.cc", + "core/css/css_math_function_value.cc", + "core/css/css_pending_substitution_value.cc", + "core/css/css_unparsed_declaration_value.cc", + "core/css/css_variable_data.cc", + "core/css/style_rule_keyframe.cc", + "core/css/css_math_operator.cc", + "core/css/css_ratio_value.cc", + "core/css/css_keyframes_rule.cc", + "core/css/css_keyframe_rule.cc", + "core/css/css_import_rule.cc", + "core/css/style_rule_css_style_declaration.cc", + "core/css/keyframe_style_rule_css_style_declaration.cc", + "core/css/style_attribute_mutation_scope.cc", + "core/css/css_unset_value.cc", + "core/css/css_value_pair.cc", + "core/css/css_light_dart_value_pair.cc", + "core/css/css_revert_layer_value.cc", + "core/css/css_revert_value.cc", + "core/css/css_initial_value.cc", + "core/css/css_inherit_value.cc", + "core/css/css_view_value.cc", + "core/css/css_initial_color_value.cc", + "core/css/css_invalid_variable_value.cc", + "core/css/css_keyframe_shorthand_value.cc", + "core/css/css_bracketed_value_list.cc", + "core/css/css_custom_ident_value.cc", + "core/css/css_value_list.cc", + "core/css/css_syntax_definition.cc", + "core/css/css_syntax_string_parser.cc", + "core/css/style_property_serializer.cc", + "core/css/style_property_shorthand_custom.cc", + "core/css/css_value_clamping_utils.cc", + "core/css/style_change_reason.cc", + "core/css/css_markup.cc", + "core/css/css_function_value.cc", + "core/css/css_appearance_auto_base_select_value_pair.cc", + "core/css/css_value_pool.cc", + "core/css/cssom_utils.cc", + "core/css/css_string_value.cc", + "core/css/css_raw_value.cc", + "core/css/css_initial_value.cc", + "core/css/css_numeric_literal_value", + "core/css/css_color.cc", + "core/css/anchor_query.h", + "core/css/css_length_resolver.cc", + "core/css/selector_query.cc", + "core/css/part_names.cc", + "core/css/selector_checker.cc", + "core/css/style_scope_data.cc", + "core/css/style_scope_frame.cc", + "core/style/scoped_css_name.cc", + "core/style/grid_area.h", + "core/platform/text/writing_mode.cc", + "core/platform/text/text_stream.cc", + "core/platform/text/text_direction.cc", + "core/platform/text/writing_direction_mode.cc", + "core/platform/text/string_to_number.cc", + "core/platform/graphics/color.cc", + "core/platform/graphics/color_conversions.cc", + "core/platform/graphics/graphic_types.cc", + "core/platform/graphics/gradient.cc", + "core/platform/geometry/length.cc", + "core/platform/geometry/dom_matrix.cc", + "core/platform/geometry/dom_point_read_only.cc", + "core/platform/geometry/dom_point.cc", + "core/platform/geometry/layout_unit.cc", + "core/platform/geometry/calculation_expression_node.cc", + "core/platform/geometry/dom_matrix_readonly.cc", + "core/platform/geometry/length_functions.cc", + "core/platform/geometry/calculation_value.cc", + "core/platform/url/kurl.cc", + "core/platform/url/scheme_host_port.cc", + "core/platform/url/scheme_registry.cc", + "core/platform/url/url_canon_etc.cc", + "core/platform/url/url_canon_filesystemurl.cc", + "core/platform/url/url_canon_fileurl.cc", + "core/platform/url/url_canon_host.cc", + "core/platform/url/url_canon_ip.cc", + "core/platform/url/url_canon_mailtourl.cc", + "core/platform/url/url_canon_non_special_url.cc", + "core/platform/url/url_canon_path.cc", + "core/platform/url/url_canon_pathurl.cc", + "core/platform/url/url_canon_query.cc", + "core/platform/url/url_canon_relative.cc", + "core/platform/url/url_canon_stdstring.cc", + "core/platform/url/url_parse.cc", + "core/platform/url/url_parse_file.cc", + "core/platform/url/url_util.cc", + "core/platform/url/url_canon_internal.cc", + "core/platform/url/url_canon.cc", + "core/platform/url/url_canon_stdurl.cc", + "core/platform/animation/timing_function.cc", + "core/platform/gfx/animation/keyframe/timing_function.cc", + "core/platform/gfx/geometry/cubic_bezier.cc", + "core/platform/gfx/geometry/point_conversions.cc", + "core/platform/gfx/geometry/point.cc", + "core/platform/gfx/geometry/point_f.cc", + "core/platform/gfx/geometry/size.cc", + "core/platform/gfx/geometry/size_f.cc", + "core/platform/gfx/geometry/vector2d.cc", + "core/platform/gfx/geometry/vector2d_f.cc", + "core/platform/gfx/geometry/insets.cc", + "core/platform/gfx/geometry/insets_conversions.cc", + "core/platform/gfx/geometry/insets_f.cc", + "core/platform/gfx/geometry/rect.cc", + "core/platform/gfx/geometry/rect_f.cc", + "core/platform/gfx/geometry/rect_conversions.cc", + "core/platform/gfx/geometry/size_conversions.cc", + "core/platform/fonts/font_family.cc", + "core/platform/fonts/font_selection_types.cc", + "core/platform/fonts/font.cc", + "core/platform/fonts/font_description.cc", + + // CSS Non Legacy + "core/css/css_style_declaration.h", + "core/css/css_style_declaration.cc", + "core/css/computed_css_style_declaration.h", + "core/css/computed_css_style_declaration.cc", + "core/css/inline_css_style_declaration.h", + "core/css/inline_css_style_declaration.cc", + "code_gen/qjs_css_style_declaration.cc", + "code_gen/qjs_inline_css_style_declaration.cc", + "code_gen/qjs_computed_css_style_declaration.cc", + + // CSS Legacy + "core/css/legacy/legacy_css_style_declaration.cc", + "core/css/legacy/legacy_inline_css_style_declaration.cc", + "core/css/legacy/legacy_computed_css_style_declaration.cc", + "code_gen/qjs_legacy_css_style_declaration.cc", + "code_gen/qjs_legacy_inline_css_style_declaration.cc", + "code_gen/qjs_legacy_computed_css_style_declaration.cc", + + // DOM + + "core/dom/node_lists_node_data.cc", + "core/dom/node_rare_data.cc", + "core/dom/frame_request_callback_collection.cc", + "core/dom/events/registered_eventListener.cc", + "core/dom/events/event_listener_map.cc", + "core/dom/events/event.cc", + "core/dom/events/custom_event.cc", + "core/dom/events/event_target.cc", + "core/dom/events/event_dispatch_forbidden_scope.cc", + "core/dom/events/event_listener_map.cc", + "core/dom/events/event_target_impl.cc", + "core/binding_object.cc", + "core/dart_binding_object.cc", + "core/dom/node.cc", + "core/dom/node_list.cc", + "core/dom/static_node_list.cc", + "core/dom/node_traversal.cc", + "core/dom/live_node_list_base.cc", + "core/dom/live_node_list.cc", + "core/dom/character_data.cc", + "core/dom/comment.cc", + "core/dom/text.cc", + "core/dom/tree_scope.cc", + "core/dom/element.cc", + "core/dom/parent_node.cc", + "core/dom/element_data.cc", + "core/dom/document.cc", + "core/dom/qualified_name.cc", + "core/dom/shadow_root.cc", + "core/dom/nth_index_cache.cc", + "core/dom/dom_token_list.cc", + "core/dom/dom_string_map.cc", + "core/dom/names_map.cc", + "core/dom/intersection_observer.cc", + "core/dom/intersection_observer_entry.cc", + "core/dom/element_rare_data_vector.cc", + "core/dom/space_split_string.cc", + "core/dom/scripted_animation_controller.cc", + "core/dom/document_fragment.cc", + "core/dom/child_node_list.cc", + "core/dom/empty_node_list.cc", + "core/dom/mutation_observer.cc", + "core/dom/mutation_observer_registration.cc", + "core/dom/mutation_observer_interest_group.cc", + "core/dom/mutation_record.cc", + "core/dom/child_list_mutation_scope.cc", + "core/dom/container_node.cc", + "core/html/custom/widget_element.cc", + "core/html/custom/webf_router_link_element.cc", + "core/html/custom/widget_element_shape.cc", + "core/input/touch.cc", + "core/input/touch_list.cc", + "core/events/error_event.cc", + "core/events/message_event.cc", + "core/events/animation_event.cc", + "core/events/close_event.cc", + "core/events/ui_event.cc", + "core/events/focus_event.cc", + "core/events/gesture_event.cc", + "core/events/hashchange_event.cc", + "core/events/input_event.cc", + "core/events/touch_event.cc", + "core/events/mouse_event.cc", + "core/events/hybrid_router_change_event.cc", + "core/events/pop_state_event.cc", + "core/events/pointer_event.cc", + "core/events/transition_event.cc", + "core/events/intersection_change_event.cc", + "core/events/keyboard_event.cc", + "core/events/promise_rejection_event.cc", + "core/events/screen_event.cc", + "core/html/parser/html_parser.cc", + "core/html/parser/html_parser_idioms.cc", + "core/html/html_element.cc", + "core/html/html_div_element.cc", + "core/html/html_head_element.cc", + "core/html/html_body_element.cc", + "core/html/html_style_element.cc", + "core/html/html_html_element.cc", + "core/html/html_template_element.cc", + "core/html/html_all_collection.cc", + "core/html/html_anchor_element.cc", + "core/html/html_image_element.cc", + "core/html/html_script_element.cc", + "core/html/html_iframe_element.cc", + "core/html/html_link_element.cc", + "core/html/html_unknown_element.cc", + "core/html/html_article_element.cc", + "core/html/html_br_element.cc", + "core/html/html_code_element.cc", + "core/html/html_em_element.cc", + "core/html/html_footer_element.cc", + "core/html/html_header_element.cc", + "core/html/html_heading_element.cc", + "core/html/html_li_element.cc", + "core/html/html_nav_element.cc", + "core/html/html_olist_element.cc", + "core/html/html_paragraph_element.cc", + "core/html/html_pre_element.cc", + "core/html/html_quote_element.cc", + "core/html/html_section_element.cc", + "core/html/html_span_element.cc", + "core/html/html_strong_element.cc", + "core/html/html_ulist_element.cc", + "core/html/html_bold_element.cc", + "core/html/html_italic_element.cc", + "core/html/html_underline_element.cc", + "core/html/html_strikethrough_element.cc", + "core/html/html_mark_element.cc", + "core/html/html_small_element.cc", + "core/html/html_hr_element.cc", + "core/html/html_dl_element.cc", + "core/html/html_dt_element.cc", + "core/html/html_dd_element.cc", + "core/html/html_figure_element.cc", + "core/html/html_figcaption_element.cc", + "core/html/html_address_element.cc", + "core/html/html_aside_element.cc", + "core/html/html_main_element.cc", + "core/html/html_del_element.cc", + "core/html/html_ins_element.cc", + "core/html/html_title_element.cc", + "core/html/html_meta_element.cc", + "core/html/html_noscript_element.cc", + "core/html/custom_html_element_factory.cc", + "core/html/image.cc", + "core/html/image_bitmap.cc", + "core/html/image_bitmap.cc", + "core/html/html_collection.cc", + "core/html/canvas/html_canvas_element.cc", + "core/html/canvas/canvas_rendering_context.cc", + "core/html/canvas/canvas_rendering_context_2d.cc", + "core/html/canvas/canvas_gradient.cc", + "core/html/canvas/canvas_pattern.cc", + "core/html/canvas/path_2d.cc", + "core/html/canvas/text_metrics.cc", + "core/html/forms/html_button_element.cc", + "core/html/forms/html_input_element.cc", + "core/html/forms/html_select_element.cc", + "core/html/forms/html_optgroup_element.cc", + "core/html/forms/html_option_element.cc", + "core/html/forms/html_form_element.cc", + "core/html/forms/html_textarea_element.cc", + "core/html/forms/html_label_element.cc", + "core/html/forms/form_data.cc", + "core/html/touches/webf_touch_area_element.cc", + + // SVG + + "core/svg/svg_element.cc", + "core/svg/svg_graphics_element.cc", + "core/svg/svg_geometry_element.cc", + "core/svg/svg_text_content_element.cc", + "core/svg/svg_text_positioning_element.cc", + "core/svg/svg_svg_element.cc", + "core/svg/svg_path_element.cc", + "core/svg/svg_rect_element.cc", + "core/svg/svg_text_element.cc", + "core/svg/svg_g_element.cc", + "core/svg/svg_circle_element.cc", + "core/svg/svg_ellipse_element.cc", + "core/svg/svg_style_element.cc", + "core/svg/svg_line_element.cc", + + // Native + "core/native/native_loader.cc", + "core/native/script_value_ref.cc", + + // Other Legacy Impls + "core/dom/legacy/element_attributes.cc", + "core/dom/legacy/bounding_client_rect.cc", + + + // third_party + "third_party/dart/include/dart_api_dl.c", + "third_party/double_conversion/double-conversion/bignum.cc", + "third_party/double_conversion/double-conversion/bignum-dtoa.cc", + "third_party/double_conversion/double-conversion/cached-powers.cc", + "third_party/double_conversion/double-conversion/double-to-string.cc", + "third_party/double_conversion/double-conversion/fast-dtoa.cc", + "third_party/double_conversion/double-conversion/fixed-dtoa.cc", + "third_party/double_conversion/double-conversion/string-to-double.cc", + "third_party/double_conversion/double-conversion/strtod.cc", + "third_party/gumbo-parser/src/attribute.c", + "third_party/gumbo-parser/src/char_ref.c", + "third_party/gumbo-parser/src/error.c", + "third_party/gumbo-parser/src/parser.c", + "third_party/gumbo-parser/src/string_buffer.c", + "third_party/gumbo-parser/src/string_piece.c", + "third_party/gumbo-parser/src/tag.c", + "third_party/gumbo-parser/src/string_piece.c", + "third_party/gumbo-parser/src/tokenizer.c", + "third_party/gumbo-parser/src/utf8.c", + "third_party/gumbo-parser/src/util.c", + "third_party/gumbo-parser/src/vector.c", + 'third_party/modp_b64/modp_b64.cc', + + // CodeGen + "code_gen/names_installer.cc", + "code_gen/qjs_console.cc", + "code_gen/qjs_module_manager.cc", + "code_gen/qjs_window_or_worker_global_scope.cc", + "code_gen/qjs_window.cc", + "code_gen/qjs_location.cc", + "code_gen/qjs_blob.cc", + "code_gen/qjs_blob_options.cc", + "code_gen/qjs_file.cc", + "code_gen/qjs_file_options.cc", + "code_gen/qjs_event.cc", + "code_gen/qjs_add_event_listener_options.cc", + "code_gen/qjs_event_listener_options.cc", + "code_gen/qjs_error_event.cc", + "code_gen/qjs_message_event.cc", + "code_gen/qjs_message_event_init.cc", + "code_gen/qjs_close_event.cc", + "code_gen/qjs_close_event_init.cc", + "code_gen/qjs_focus_event.cc", + "code_gen/qjs_focus_event_init.cc", + "code_gen/qjs_input_event.cc", + "code_gen/qjs_input_event_init.cc", + "code_gen/qjs_pop_state_event.cc", + "code_gen/qjs_pop_state_event_init.cc", + "code_gen/qjs_ui_event.cc", + "code_gen/qjs_ui_event_init.cc", + "code_gen/qjs_gesture_event.cc", + "code_gen/qjs_form_data.cc", + "code_gen/qjs_hashchange_event.cc", + "code_gen/qjs_hashchange_event_init.cc", + "code_gen/qjs_gesture_event_init.cc", + "code_gen/qjs_intersection_change_event.cc", + "code_gen/qjs_intersection_change_event_init.cc", + "code_gen/qjs_touch.cc", + "code_gen/qjs_touch_init.cc", + "code_gen/qjs_touch_list.cc", + "code_gen/qjs_mutation_record.cc", + "code_gen/qjs_mutation_observer.cc", + "code_gen/qjs_mutation_observer_init.cc", + "code_gen/qjs_mutation_observer_registration.cc", + "code_gen/qjs_touch_event.cc", + "code_gen/qjs_touch_event_init.cc", + "code_gen/qjs_hybrid_router_change_event.cc", + "code_gen/qjs_hybrid_router_change_event_init.cc", + "code_gen/qjs_pointer_event.cc", + "code_gen/qjs_pointer_event_init.cc", + "code_gen/qjs_mouse_event.cc", + "code_gen/qjs_mouse_event_init.cc", + "code_gen/qjs_transition_event.cc", + "code_gen/qjs_transition_event_init.cc", + "code_gen/event_factory.cc", + "code_gen/qjs_custom_event.cc", + "code_gen/qjs_custom_event_init.cc", + "code_gen/qjs_keyboard_event.cc", + "code_gen/qjs_keyboard_event_init.cc", + "code_gen/qjs_animation_event.cc", + "code_gen/qjs_screen_event.cc", + "code_gen/qjs_animation_event_init.cc", + "code_gen/qjs_error_event_init.cc", + "code_gen/qjs_event_init.cc", + "code_gen/qjs_event_target.cc", + "code_gen/qjs_node.cc", + "code_gen/qjs_union_dom_stringnode.cc", + "code_gen/qjs_union_dom_stringblob.cc", + "code_gen/qjs_unionhtml_image_elementimage_bitmap.cc", + "code_gen/qjs_document.cc", + "code_gen/qjs_element.cc", + "code_gen/qjs_dom_token_list.cc", + "code_gen/qjs_dom_string_map.cc", + "code_gen/qjs_element_attributes.cc", + "code_gen/qjs_character_data.cc", + "code_gen/qjs_comment.cc", + "code_gen/qjs_document_fragment.cc", + "code_gen/qjs_bounding_client_rect.cc", + "code_gen/qjs_native_loader.cc", + "code_gen/qjs_text.cc", + "code_gen/qjs_screen.cc", + "code_gen/qjs_node_list.cc", + "code_gen/qjs_idle_deadline.cc", + "code_gen/event_type_names.cc", + "code_gen/mutation_record_types.cc", + "code_gen/binding_call_methods.cc", + "code_gen/qjs_scroll_options.cc", + "code_gen/qjs_scroll_to_options.cc", + "code_gen/qjs_element_creation_options.cc", + "code_gen/qjs_unionelement_creation_options_dom_string.cc", + "code_gen/qjs_html_element.cc", + "code_gen/qjs_html_all_collection.cc", + "code_gen/qjs_html_collection.cc", + "code_gen/qjs_html_anchor_element.cc", + "code_gen/qjs_html_div_element.cc", + "code_gen/qjs_html_head_element.cc", + "code_gen/qjs_html_body_element.cc", + "code_gen/qjs_html_html_element.cc", + "code_gen/qjs_html_image_element.cc", + "code_gen/qjs_html_iframe_element.cc", + "code_gen/qjs_html_canvas_element.cc", + "code_gen/qjs_html_br_element.cc", + "code_gen/qjs_html_link_element.cc", + "code_gen/qjs_html_article_element.cc", + "code_gen/qjs_html_section_element.cc", + "code_gen/qjs_html_footer_element.cc", + "code_gen/qjs_html_header_element.cc", + "code_gen/qjs_html_nav_element.cc", + "code_gen/qjs_html_span_element.cc", + "code_gen/qjs_html_strong_element.cc", + "code_gen/qjs_html_em_element.cc", + "code_gen/qjs_html_pre_element.cc", + "code_gen/qjs_html_code_element.cc", + "code_gen/qjs_html_quote_element.cc", + "code_gen/qjs_html_li_element.cc", + "code_gen/qjs_html_ulist_element.cc", + "code_gen/qjs_html_olist_element.cc", + "code_gen/qjs_html_paragraph_element.cc", + "code_gen/qjs_html_heading_element.cc", + "code_gen/qjs_html_bold_element.cc", + "code_gen/qjs_html_italic_element.cc", + "code_gen/qjs_html_underline_element.cc", + "code_gen/qjs_html_strikethrough_element.cc", + "code_gen/qjs_html_mark_element.cc", + "code_gen/qjs_html_small_element.cc", + "code_gen/qjs_html_hr_element.cc", + "code_gen/qjs_html_abbr_element.cc", + "code_gen/qjs_html_cite_element.cc", + "code_gen/qjs_html_kbd_element.cc", + "code_gen/qjs_html_var_element.cc", + "code_gen/qjs_html_dfn_element.cc", + "code_gen/qjs_html_samp_element.cc", + "code_gen/qjs_html_data_element.cc", + "code_gen/qjs_html_time_element.cc", + "code_gen/qjs_html_dl_element.cc", + "code_gen/qjs_html_dt_element.cc", + "code_gen/qjs_html_dd_element.cc", + "code_gen/qjs_html_figure_element.cc", + "code_gen/qjs_html_figcaption_element.cc", + "code_gen/qjs_html_address_element.cc", + "code_gen/qjs_html_aside_element.cc", + "code_gen/qjs_html_main_element.cc", + "code_gen/qjs_html_label_element.cc", + "code_gen/qjs_html_del_element.cc", + "code_gen/qjs_html_ins_element.cc", + "code_gen/qjs_html_title_element.cc", + "code_gen/qjs_html_meta_element.cc", + "code_gen/qjs_html_noscript_element.cc", + "code_gen/qjs_image.cc", + "code_gen/qjs_image_bitmap.cc", + "code_gen/qjs_widget_element.cc", + "code_gen/qjs_canvas_rendering_context_2d.cc", + "code_gen/qjs_canvas_rendering_context.cc", + "code_gen/qjs_canvas_gradient.cc", + "code_gen/qjs_canvas_pattern.cc", + "code_gen/qjs_path_2d.cc", + "code_gen/qjs_text_metrics.cc", + "code_gen/qjs_dom_matrix_init.cc", + "code_gen/qjs_dom_matrix.cc", + "code_gen/qjs_dom_matrix_read_only.cc", + "code_gen/qjs_dom_point_init.cc", + "code_gen/qjs_dom_point.cc", + "code_gen/qjs_dom_point_read_only.cc", + "code_gen/qjs_intersection_observer.cc", + "code_gen/qjs_intersection_observer_entry.cc", + "code_gen/qjs_intersection_observer_init.cc", + "code_gen/qjs_union_double_sequencedouble.cc", + "code_gen/qjs_unionhtml_image_elementhtml_canvas_element.cc", + "code_gen/qjs_union_dom_stringcanvas_gradientcanvas_pattern.cc", + "code_gen/qjs_union_dom_string_double.cc", + "code_gen/qjs_unionpath_2_d_double.cc", + "code_gen/qjs_union_sequencedoubledom_matrix_init.cc", + "code_gen/qjs_union_doubledom_point_init.cc", + "code_gen/canvas_types.cc", + "code_gen/qjs_html_button_element.cc", + "code_gen/qjs_html_input_element.cc", + "code_gen/qjs_html_select_element.cc", + "code_gen/qjs_html_optgroup_element.cc", + "code_gen/qjs_html_option_element.cc", + "code_gen/qjs_html_form_element.cc", + "code_gen/qjs_html_textarea_element.cc", + "code_gen/qjs_html_script_element.cc", + "code_gen/qjs_html_style_element.cc", + "code_gen/qjs_promise_rejection_event.cc", + "code_gen/qjs_promise_rejection_event_init.cc", + "code_gen/qjs_unionevent_listener_options_boolean.cc", + "code_gen/qjs_unionadd_event_listener_options_boolean.cc", + "code_gen/qjs_unionpath_2_d_dom_string.cc", + "code_gen/qjs_html_template_element.cc", + "code_gen/qjs_html_unknown_element.cc", + "code_gen/qjs_webf_touch_area_element.cc", + "code_gen/qjs_webf_router_link_element.cc", + "code_gen/qjs_performance.cc", + "code_gen/qjs_performance_entry.cc", + "code_gen/qjs_performance_mark.cc", + "code_gen/qjs_performance_measure.cc", + "code_gen/performance_entry_names.cc", + "code_gen/qjs_performance_measure_options.cc", + "code_gen/qjs_performance_mark_options.cc", + "code_gen/performance_mark_constants.cc", + "code_gen/html_element_factory.cc", + "code_gen/html_names.cc", + "code_gen/webf_element_names.cc", + "code_gen/script_type_names.cc", + "code_gen/defined_properties.cc", + "code_gen/element_attribute_names.cc", + "code_gen/element_namespace_uris.cc", + "code_gen/qjs_window_idle_request_options.cc", + "code_gen/color_data.cc", + "code_gen/qjs_css_rule.cc", + "code_gen/font_family_names.cc", + "code_gen/media_feature_names.cc", + "code_gen/qjs_css_style_sheet.cc", + "code_gen/qjs_css_rule_list.cc", + "code_gen/qjs_css_layer_block_rule.cc", + "code_gen/qjs_css_layer_statement_rule.cc", + "code_gen/qjs_media_list.cc", + "code_gen/qjs_css_import_rule.cc", + "code_gen/qjs_css_keyframe_rule.cc", + "code_gen/qjs_css_keyframes_rule.cc", + "code_gen/event_type_names.cc", + "code_gen/at_rule_descriptors.cc", + "code_gen/media_type_names.cc", + "code_gen/qjs_style_sheet.cc", + "code_gen/qjs_union_dom_stringmedia_list.cc", + "code_gen/svg_names.cc", + "code_gen/svg_element_factory.cc", + "code_gen/css_value_keywords.cc", + "code_gen/css_property_names.cc", + "code_gen/property_bitset.cc", + "code_gen/longhands.cc", + "code_gen/shorthands.cc", + "code_gen/css_property_instance.cc", + "code_gen/style_property_shorthand.cc", + "code_gen/css_primitive_value_unit_trie.cc", + "code_gen/qjs_svg_element.cc", + "code_gen/qjs_svg_graphics_element.cc", + "code_gen/qjs_svg_geometry_element.cc", + "code_gen/qjs_svg_text_content_element.cc", + "code_gen/qjs_svg_text_positioning_element.cc", + "code_gen/qjs_svg_svg_element.cc", + "code_gen/qjs_svg_path_element.cc", + "code_gen/qjs_svg_rect_element.cc", + "code_gen/qjs_svg_text_element.cc", + "code_gen/qjs_svg_g_element.cc", + "code_gen/qjs_svg_circle_element.cc", + "code_gen/qjs_svg_ellipse_element.cc", + "code_gen/qjs_svg_style_element.cc", + "code_gen/qjs_svg_line_element.cc", + "code_gen/plugin_api_animation_event.cc", + "code_gen/plugin_api_bounding_client_rect.cc", + "code_gen/plugin_api_close_event.cc", + "code_gen/plugin_api_custom_event.cc", + "code_gen/plugin_api_dom_string_map.cc", + "code_gen/plugin_api_element_attributes.cc", + "code_gen/plugin_api_event.cc", + "code_gen/plugin_api_error_event.cc", + "code_gen/plugin_api_focus_event.cc", + "code_gen/plugin_api_gesture_event.cc", + "code_gen/plugin_api_hashchange_event.cc", + "code_gen/plugin_api_hybrid_router_change_event.cc", + "code_gen/plugin_api_input_event.cc", + "code_gen/plugin_api_intersection_change_event.cc", + "code_gen/plugin_api_computed_css_style_declaration.cc", + "code_gen/plugin_api_css_style_declaration.cc", + "code_gen/plugin_api_inline_css_style_declaration.cc", + "code_gen/plugin_api_legacy_computed_css_style_declaration.cc", + "code_gen/plugin_api_legacy_css_style_declaration.cc", + "code_gen/plugin_api_legacy_inline_css_style_declaration.cc", + "code_gen/plugin_api_pop_state_event.cc", + "code_gen/plugin_api_message_event.cc", + "code_gen/plugin_api_mouse_event.cc", + "code_gen/plugin_api_mutation_observer_registration.cc", + "code_gen/plugin_api_performance_entry.cc", + "code_gen/plugin_api_performance.cc", + "code_gen/plugin_api_performance_mark.cc", + "code_gen/plugin_api_performance_measure.cc", + "code_gen/plugin_api_pointer_event.cc", + "code_gen/plugin_api_promise_rejection_event.cc", + "code_gen/plugin_api_touch.cc", + "code_gen/plugin_api_transition_event.cc", + "code_gen/plugin_api_ui_event.cc", + ], + QUICKJS_SOURCE: [ + "third_party/quickjs/cutils.c", + "third_party/quickjs/dtoa.c", + "third_party/quickjs/libregexp.c", + "third_party/quickjs/libunicode.c", + "third_party/quickjs/quickjs.c" + ] +} diff --git a/bridge/core/animation/animation_time_delta.cc b/bridge/core/animation/animation_time_delta.cc new file mode 100644 index 0000000000..59741e7340 --- /dev/null +++ b/bridge/core/animation/animation_time_delta.cc @@ -0,0 +1,40 @@ +// Copyright 2018 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "core/animation/animation_time_delta.h" + +namespace webf { +// TODO(guopengfei):默认使用双精度 +//#if !BUILDFLAG(BLINK_ANIMATION_USE_TIME_DELTA) + +// Comparison operators on AnimationTimeDelta. +bool operator==(const AnimationTimeDelta& lhs, const AnimationTimeDelta& rhs) { + return lhs.InSecondsF() == rhs.InSecondsF(); +} +bool operator!=(const AnimationTimeDelta& lhs, const AnimationTimeDelta& rhs) { + return lhs.InSecondsF() != rhs.InSecondsF(); +} +bool operator>(const AnimationTimeDelta& lhs, const AnimationTimeDelta& rhs) { + return lhs.InSecondsF() > rhs.InSecondsF(); +} +bool operator<(const AnimationTimeDelta& lhs, const AnimationTimeDelta& rhs) { + return !(lhs >= rhs); +} +bool operator>=(const AnimationTimeDelta& lhs, const AnimationTimeDelta& rhs) { + return lhs.InSecondsF() >= rhs.InSecondsF(); +} +bool operator<=(const AnimationTimeDelta& lhs, const AnimationTimeDelta& rhs) { + return lhs.InSecondsF() <= rhs.InSecondsF(); +} + +std::ostream& operator<<(std::ostream& os, const AnimationTimeDelta& time) { + return os << time.InSecondsF() << " s"; +} +//#endif + +} // namespace webf diff --git a/bridge/core/animation/animation_time_delta.h b/bridge/core/animation/animation_time_delta.h new file mode 100644 index 0000000000..1fc57d9c67 --- /dev/null +++ b/bridge/core/animation/animation_time_delta.h @@ -0,0 +1,123 @@ +// Copyright 2018 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef WEBF_CORE_ANIMATION_ANIMATION_TIME_DELTA_H_ +#define WEBF_CORE_ANIMATION_ANIMATION_TIME_DELTA_H_ + +#include +#include +#include +#include +#include "foundation/macros.h" + +namespace webf { + +// AnimationTimeDelta exists to ease the transition of Blink animations from +// double-based time values to base::TimeDelta-based time values (see +// http://crbug.com/737867. +// +// It represents a delta between two times, analogous to base::TimeDelta. It is +// provided in two modes, based on a compiler flag. The first is the traditional +// (and default) double mode, where time deltas are represented using seconds in +// double-precision. The second mode uses base::TimeDelta to represent time +// instead. + +//#if !BUILDFLAG(BLINK_ANIMATION_USE_TIME_DELTA) + +// The double-based version of AnimationTimeDelta. Internally, time is stored as +// double-precision seconds. +// +// This class is modelled on the API from base::TimeDelta, with a lot of +// unnecessary methods stripped out. +class AnimationTimeDelta { + USING_FAST_MALLOC(AnimationTimeDelta); + + public: + constexpr AnimationTimeDelta() : delta_(0) {} + + // Do not use this directly -- use the macros below. + constexpr explicit AnimationTimeDelta(double delta) : delta_(delta) {} + +#define ANIMATION_TIME_DELTA_FROM_SECONDS(x) AnimationTimeDelta(x) +#define ANIMATION_TIME_DELTA_FROM_MILLISECONDS(x) AnimationTimeDelta(x / 1000.0) + + static constexpr AnimationTimeDelta Max() { return AnimationTimeDelta(std::numeric_limits::infinity()); } + + double InSecondsF() const { return delta_; } + double InMillisecondsF() const { return delta_ * 1000; } + double InMicrosecondsF() const { return delta_ * 1000000; } + + bool is_max() const { return delta_ == std::numeric_limits::infinity(); } + + bool is_inf() const { return std::isinf(delta_); } + + bool is_zero() const { return delta_ == 0; } + + AnimationTimeDelta operator+(AnimationTimeDelta other) const { return AnimationTimeDelta(delta_ + other.delta_); } + AnimationTimeDelta& operator+=(AnimationTimeDelta other) { return *this = (*this + other); } + AnimationTimeDelta operator-(AnimationTimeDelta other) const { return AnimationTimeDelta(delta_ - other.delta_); } + AnimationTimeDelta operator-() { return AnimationTimeDelta(-delta_); } + template + AnimationTimeDelta operator*(T a) const { + return AnimationTimeDelta(delta_ * a); + } + template + AnimationTimeDelta& operator*=(T a) { + return *this = (*this * a); + } + template + AnimationTimeDelta operator/(T a) const { + return AnimationTimeDelta(delta_ / a); + } + template + AnimationTimeDelta& operator/=(T a) { + return *this = (*this / a); + } + double operator/(AnimationTimeDelta a) const { + assert(!a.is_zero()); + assert(!is_inf() || !a.is_inf()); + return delta_ / a.delta_; + } + + protected: + // The time delta represented by this |AnimationTimeDelta|, in seconds. May be + // negative, in which case the end of the delta is before the start. + double delta_; +}; + +template +AnimationTimeDelta operator*(T a, AnimationTimeDelta td) { + return td * a; +} + +// Comparison operators on AnimationTimeDelta. +bool operator==(const AnimationTimeDelta& lhs, const AnimationTimeDelta& rhs); +bool operator!=(const AnimationTimeDelta& lhs, const AnimationTimeDelta& rhs); +bool operator>(const AnimationTimeDelta& lhs, const AnimationTimeDelta& rhs); +bool operator<(const AnimationTimeDelta& lhs, const AnimationTimeDelta& rhs); +bool operator>=(const AnimationTimeDelta& lhs, const AnimationTimeDelta& rhs); +bool operator<=(const AnimationTimeDelta& lhs, const AnimationTimeDelta& rhs); + +// Defined to allow DCHECK_EQ/etc to work with the class. +std::ostream& operator<<(std::ostream& os, const AnimationTimeDelta& time); +/* // TODO(guopengfei):默认使用双精度,by guopengfei +#else // !BUILDFLAG(BLINK_ANIMATION_USE_TIME_DELTA) + +// When compiling in TimeDelta-based mode, AnimationTimeDelta is equivalent to +// base::TimeDelta. +using AnimationTimeDelta = base::TimeDelta; + +#define ANIMATION_TIME_DELTA_FROM_SECONDS(x) base::Seconds(x) +#define ANIMATION_TIME_DELTA_FROM_MILLISECONDS(x) base::Milliseconds(x) + +#endif + */ + +} // namespace webf + +#endif // WEBF_CORE_ANIMATION_ANIMATION_TIME_DELTA_H_ diff --git a/bridge/core/animation/css/css_animation_data.cc b/bridge/core/animation/css/css_animation_data.cc new file mode 100644 index 0000000000..5a77208c74 --- /dev/null +++ b/bridge/core/animation/css/css_animation_data.cc @@ -0,0 +1,76 @@ +// Copyright 2014 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#include "core/animation/css/css_animation_data.h" + +#include "core/animation/timing.h" +#include "css_value_keywords.h" +// TODO(guopengfei): +//#include "third_party/blink/renderer/platform/runtime_enabled_features.h" + +namespace webf { + +CSSAnimationData::CSSAnimationData() : CSSTimingData(InitialDuration()) { + name_list_.push_back(InitialName()); + timeline_list_.push_back(InitialTimeline()); + iteration_count_list_.push_back(InitialIterationCount()); + direction_list_.push_back(InitialDirection()); + fill_mode_list_.push_back(InitialFillMode()); + play_state_list_.push_back(InitialPlayState()); + range_start_list_.push_back(InitialRangeStart()); + range_end_list_.push_back(InitialRangeEnd()); + composition_list_.push_back(InitialComposition()); +} + +CSSAnimationData::CSSAnimationData(const CSSAnimationData& other) = default; + +std::optional CSSAnimationData::InitialDuration() { + // TODO(guopengfei):not use std::nullopt + // if (RuntimeEnabledFeatures::ScrollTimelineEnabled()) { + // return std::nullopt; + //} + + return 0; +} + +const AtomicString& CSSAnimationData::InitialName() { + // TODO(guopengfei): + // DEFINE_STATIC_LOCAL(const AtomicString, name, ("none")); + // return name; + return AtomicString::Empty(); +} + +const StyleTimeline& CSSAnimationData::InitialTimeline() { + // DEFINE_STATIC_LOCAL(const StyleTimeline, timeline, (CSSValueID::kAuto)); + thread_local static const StyleTimeline timeline = new StyleTimeline(CSSValueID::kAuto) + + return timeline; +} + +bool CSSAnimationData::AnimationsMatchForStyleRecalc(const CSSAnimationData& other) const { + return name_list_ == other.name_list_ && timeline_list_ == other.timeline_list_ && + play_state_list_ == other.play_state_list_ && iteration_count_list_ == other.iteration_count_list_ && + direction_list_ == other.direction_list_ && fill_mode_list_ == other.fill_mode_list_ && + range_start_list_ == other.range_start_list_ && range_end_list_ == other.range_end_list_ && + TimingMatchForStyleRecalc(other); +} + +Timing CSSAnimationData::ConvertToTiming(size_t index) const { + assert(index < name_list_.size()); + Timing timing = CSSTimingData::ConvertToTiming(index); + timing.iteration_count = GetRepeated(iteration_count_list_, index); + timing.direction = GetRepeated(direction_list_, index); + timing.fill_mode = GetRepeated(fill_mode_list_, index); + timing.AssertValid(); + return timing; +} + +const StyleTimeline& CSSAnimationData::GetTimeline(size_t index) const { + assert(index < name_list_.size()); + return GetRepeated(timeline_list_, index); +} + +} // namespace webf diff --git a/bridge/core/animation/css/css_animation_data.h b/bridge/core/animation/css/css_animation_data.h new file mode 100644 index 0000000000..5e122108e3 --- /dev/null +++ b/bridge/core/animation/css/css_animation_data.h @@ -0,0 +1,103 @@ +// Copyright 2014 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#ifndef WEBF_CORE_ANIMATION_CSS_CSS_ANIMATION_DATA_H_ +#define WEBF_CORE_ANIMATION_CSS_CSS_ANIMATION_DATA_H_ + +#include + +#include "core/animation/css/css_timing_data.h" +#include "core/animation/effect_model.h" +#include "core/animation/timing.h" +#include "core/style/computed_style_constants.h" +#include "core/style/style_name_or_keyword.h" +#include "core/style/style_timeline.h" +#include "foundation/ptr_util.h" + +namespace webf { + +class CSSAnimationData final : public CSSTimingData { + public: + CSSAnimationData(); + explicit CSSAnimationData(const CSSAnimationData&); + + std::unique_ptr Clone() const { return base::WrapUnique(new CSSAnimationData(*this)); } + + bool AnimationsMatchForStyleRecalc(const CSSAnimationData& other) const; + bool operator==(const CSSAnimationData& other) const { return AnimationsMatchForStyleRecalc(other); } + + Timing ConvertToTiming(size_t index) const; + const StyleTimeline& GetTimeline(size_t index) const; + + const std::vector& NameList() const { return name_list_; } + const std::vector& TimelineList() const { return timeline_list_; } + + const std::vector& IterationCountList() const { return iteration_count_list_; } + const std::vector& DirectionList() const { return direction_list_; } + const std::vector& FillModeList() const { return fill_mode_list_; } + const std::vector& PlayStateList() const { return play_state_list_; } + const std::vector>& RangeStartList() const { return range_start_list_; } + const std::vector>& RangeEndList() const { return range_end_list_; } + + const std::vector& CompositionList() const { return composition_list_; } + + EffectModel::CompositeOperation GetComposition(size_t animation_index) const { + if (!composition_list_.size()) { + return EffectModel::kCompositeReplace; + } + uint32_t index = animation_index % composition_list_.size(); + return composition_list_[index]; + } + + std::vector& NameList() { return name_list_; } + std::vector& TimelineList() { return timeline_list_; } + std::vector& IterationCountList() { return iteration_count_list_; } + std::vector& DirectionList() { return direction_list_; } + std::vector& FillModeList() { return fill_mode_list_; } + std::vector& PlayStateList() { return play_state_list_; } + + std::vector>& RangeStartList() { return range_start_list_; } + std::vector>& RangeEndList() { return range_end_list_; } + std::vector& CompositionList() { return composition_list_; } + + bool HasSingleInitialTimeline() const { + return timeline_list_.size() == 1u && timeline_list_.front() == InitialTimeline(); + } + bool HasSingleInitialRangeStart() const { + return range_start_list_.size() == 1u && range_start_list_.front() == InitialRangeStart(); + } + bool HasSingleInitialRangeEnd() const { + return range_end_list_.size() == 1u && range_end_list_.front() == InitialRangeEnd(); + } + + static std::optional InitialDuration(); + static const AtomicString& InitialName(); + static const StyleTimeline& InitialTimeline(); + static Timing::PlaybackDirection InitialDirection() { return Timing::PlaybackDirection::NORMAL; } + static Timing::FillMode InitialFillMode() { return Timing::FillMode::FILL_MODE_NONE; } + static double InitialIterationCount() { return 1.0; } + static EAnimPlayState InitialPlayState() { return EAnimPlayState::kPlaying; } + static std::optional InitialRangeStart() { return std::nullopt; } + static std::optional InitialRangeEnd() { return std::nullopt; } + static EffectModel::CompositeOperation InitialComposition() { + return EffectModel::CompositeOperation::kCompositeReplace; + } + + private: + std::vector name_list_; + std::vector timeline_list_; + std::vector> range_start_list_; + std::vector> range_end_list_; + std::vector iteration_count_list_; + std::vector direction_list_; + std::vector fill_mode_list_; + std::vector play_state_list_; + std::vector composition_list_; +}; + +} // namespace webf + +#endif // WEBF_CORE_ANIMATION_CSS_CSS_ANIMATION_DATA_H_ diff --git a/bridge/core/animation/css/css_timing_data.cc b/bridge/core/animation/css/css_timing_data.cc new file mode 100644 index 0000000000..2ad9835dcf --- /dev/null +++ b/bridge/core/animation/css/css_timing_data.cc @@ -0,0 +1,51 @@ +// Copyright 2014 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#include "core/animation/css/css_timing_data.h" + +namespace webf { + +CSSTimingData::CSSTimingData(std::optional initial_duration) { + delay_start_list_.push_back(InitialDelayStart()); + delay_end_list_.push_back(InitialDelayEnd()); + duration_list_.push_back(initial_duration); + timing_function_list_.push_back(InitialTimingFunction()); +} + +CSSTimingData::CSSTimingData(const CSSTimingData& other) = default; + +Timing CSSTimingData::ConvertToTiming(size_t index) const { + Timing timing; + timing.start_delay = GetRepeated(delay_start_list_, index); + timing.end_delay = GetRepeated(delay_end_list_, index); + std::optional duration = GetRepeated(duration_list_, index); + assert(!duration.has_value() || !std::isnan(duration.value())); + timing.iteration_duration = + duration.has_value() ? std::make_optional(ANIMATION_TIME_DELTA_FROM_SECONDS(duration.value())) : std::nullopt; + timing.timing_function = GetRepeated(timing_function_list_, index); + timing.AssertValid(); + return timing; +} + +bool CSSTimingData::TimingMatchForStyleRecalc(const CSSTimingData& other) const { + if (delay_start_list_ != other.delay_start_list_) + return false; + if (delay_end_list_ != other.delay_end_list_) + return false; + if (duration_list_ != other.duration_list_) + return false; + if (timing_function_list_.size() != other.timing_function_list_.size()) + return false; + + for (uint32_t i = 0; i < timing_function_list_.size(); i++) { + if (!ValuesEquivalent(timing_function_list_.at(i), other.timing_function_list_.at(i))) { + return false; + } + } + return true; +} + +} // namespace webf diff --git a/bridge/core/animation/css/css_timing_data.h b/bridge/core/animation/css/css_timing_data.h new file mode 100644 index 0000000000..a6ad142c24 --- /dev/null +++ b/bridge/core/animation/css/css_timing_data.h @@ -0,0 +1,75 @@ +// Copyright 2014 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#ifndef WEBF_CORE_ANIMATION_CSS_CSS_TIMING_DATA_H_ +#define WEBF_CORE_ANIMATION_CSS_CSS_TIMING_DATA_H_ + +#include + +#include "core/animation/timeline_offset.h" +#include "core/animation/timing.h" +#include "core/platform/animation/timing_function.h" + +namespace webf { + +struct Timing; + +class CSSTimingData { + USING_FAST_MALLOC(CSSTimingData); + + public: + using DelayVector = std::vector; + using DurationVector = std::vector>; + using TimingFunctionVector = std::vector>; + + ~CSSTimingData() = default; + + const DelayVector& DelayStartList() const { return delay_start_list_; } + const DelayVector& DelayEndList() const { return delay_end_list_; } + const DurationVector& DurationList() const { return duration_list_; } + const TimingFunctionVector& TimingFunctionList() const { return timing_function_list_; } + + DelayVector& DelayStartList() { return delay_start_list_; } + DelayVector& DelayEndList() { return delay_end_list_; } + DurationVector& DurationList() { return duration_list_; } + TimingFunctionVector& TimingFunctionList() { return timing_function_list_; } + + bool HasSingleInitialDelayStart() const { + return delay_start_list_.size() == 1u && delay_start_list_.front() == InitialDelayStart(); + } + + bool HasSingleInitialDelayEnd() const { + return delay_end_list_.size() == 1u && delay_end_list_.front() == InitialDelayEnd(); + } + + static Timing::Delay InitialDelayStart() { return Timing::Delay(); } + static Timing::Delay InitialDelayEnd() { return Timing::Delay(); } + static std::shared_ptr InitialTimingFunction() { + return CubicBezierTimingFunction::Preset(CubicBezierTimingFunction::EaseType::EASE); + } + + template + static const T& GetRepeated(const std::vector& v, size_t index) { + return v[index % v.size()]; + } + + protected: + explicit CSSTimingData(std::optional initial_duration); + CSSTimingData(const CSSTimingData&); + + Timing ConvertToTiming(size_t index) const; + bool TimingMatchForStyleRecalc(const CSSTimingData&) const; + + private: + DelayVector delay_start_list_; + DelayVector delay_end_list_; + DurationVector duration_list_; + TimingFunctionVector timing_function_list_; +}; + +} // namespace webf + +#endif // WEBF_CORE_ANIMATION_CSS_CSS_TIMING_DATA_H_ diff --git a/bridge/core/animation/css/css_transition_data.cc b/bridge/core/animation/css/css_transition_data.cc new file mode 100644 index 0000000000..9bf5afb795 --- /dev/null +++ b/bridge/core/animation/css/css_transition_data.cc @@ -0,0 +1,33 @@ +// Copyright 2014 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#include "core/animation/css/css_transition_data.h" + +#include +#include "core/animation/timing.h" + +namespace webf { + +CSSTransitionData::CSSTransitionData() : CSSTimingData(InitialDuration()) { + property_list_.push_back(InitialProperty()); + behavior_list_.push_back(InitialBehavior()); +} + +CSSTransitionData::CSSTransitionData(const CSSTransitionData& other) = default; + +bool CSSTransitionData::TransitionsMatchForStyleRecalc(const CSSTransitionData& other) const { + return property_list_ == other.property_list_ && TimingMatchForStyleRecalc(other); +} + +Timing CSSTransitionData::ConvertToTiming(size_t index) const { + assert(index < property_list_.size()); + // Note that the backwards fill part is required for delay to work. + Timing timing = CSSTimingData::ConvertToTiming(index); + timing.fill_mode = Timing::FillMode::FILL_MODE_BACKWARDS; + return timing; +} + +} // namespace webf diff --git a/bridge/core/animation/css/css_transition_data.h b/bridge/core/animation/css/css_transition_data.h new file mode 100644 index 0000000000..b697d6368d --- /dev/null +++ b/bridge/core/animation/css/css_transition_data.h @@ -0,0 +1,85 @@ +// Copyright 2014 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBF_CORE_ANIMATION_CSS_CSS_TRANSITION_DATA_H_ +#define WEBF_CORE_ANIMATION_CSS_CSS_TRANSITION_DATA_H_ + +#include + +#include "core/animation/css/css_timing_data.h" +#include "code_gen/css_property_names.h" +#include "foundation/ptr_util.h" + +namespace webf { + +class CSSTransitionData final : public CSSTimingData { + public: + enum TransitionAnimationType { + kTransitionNone, + kTransitionKnownProperty, + kTransitionUnknownProperty, + }; + + enum TransitionBehavior { kNormal, kAllowDiscrete }; + + // FIXME: We shouldn't allow 'none' to be used alongside other properties. + struct TransitionProperty { + WEBF_DISALLOW_NEW(); + TransitionProperty(CSSPropertyID id) : property_type(kTransitionKnownProperty), unresolved_property(id) { + assert(id != CSSPropertyID::kInvalid); + } + + TransitionProperty(const AtomicString& string) + : property_type(kTransitionUnknownProperty), + unresolved_property(CSSPropertyID::kInvalid), + property_string(string) {} + + explicit TransitionProperty(TransitionAnimationType type) + : property_type(type), unresolved_property(CSSPropertyID::kInvalid) { + assert(type == kTransitionNone); + } + + bool operator==(const TransitionProperty& other) const { + return property_type == other.property_type && unresolved_property == other.unresolved_property && + property_string == other.property_string; + } + + TransitionAnimationType property_type; + CSSPropertyID unresolved_property; + AtomicString property_string; // std::string + }; + + using TransitionPropertyVector = std::vector; + using TransitionBehaviorVector = std::vector; + + std::unique_ptr Clone() { return base::WrapUnique(new CSSTransitionData(*this)); } + + CSSTransitionData(); + explicit CSSTransitionData(const CSSTransitionData&); + + bool TransitionsMatchForStyleRecalc(const CSSTransitionData& other) const; + bool operator==(const CSSTransitionData& other) const { return TransitionsMatchForStyleRecalc(other); } + + Timing ConvertToTiming(size_t index) const; + + const TransitionPropertyVector& PropertyList() const { return property_list_; } + TransitionPropertyVector& PropertyList() { return property_list_; } + + const TransitionBehaviorVector& BehaviorList() const { return behavior_list_; } + TransitionBehaviorVector& BehaviorList() { return behavior_list_; } + + static std::optional InitialDuration() { return 0; } + + static TransitionProperty InitialProperty() { return TransitionProperty(CSSPropertyID::kAll); } + + static TransitionBehavior InitialBehavior() { return TransitionBehavior::kNormal; } + + private: + TransitionPropertyVector property_list_; + TransitionBehaviorVector behavior_list_; +}; + +} // namespace webf + +#endif // WEBF_CORE_ANIMATION_CSS_CSS_TRANSITION_DATA_H_ diff --git a/bridge/core/animation/effect_model.cc b/bridge/core/animation/effect_model.cc new file mode 100644 index 0000000000..ff6fbf3bc4 --- /dev/null +++ b/bridge/core/animation/effect_model.cc @@ -0,0 +1,41 @@ +// Copyright 2017 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#include "core/animation/effect_model.h" + +//#include "third_party/blink/renderer/bindings/core/v8/v8_keyframe_effect_options.h" +#include "bindings/qjs/exception_state.h" + +namespace webf { +std::optional EffectModel::StringToCompositeOperation( + const std::string& composite_string) { + assert(composite_string == "replace" || composite_string == "add" || composite_string == "accumulate" || + composite_string == "auto"); + if (composite_string == "auto") + return std::nullopt; + if (composite_string == "add") + return kCompositeAdd; + if (composite_string == "accumulate") + return kCompositeAccumulate; + return kCompositeReplace; +} + +std::string EffectModel::CompositeOperationToString(std::optional composite) { + if (!composite) + return "auto"_s; + switch (composite.value()) { + case EffectModel::kCompositeAccumulate: + return "accumulate"_s; + case EffectModel::kCompositeAdd: + return "add"_s; + case EffectModel::kCompositeReplace: + return "replace"_s; + default: + assert_m(false, "EffectModel::CompositeOperationToString NOTREACHED_IN_MIGRATION"); + return String::EmptyString(); + } +} +} // namespace webf diff --git a/bridge/core/animation/effect_model.h b/bridge/core/animation/effect_model.h new file mode 100644 index 0000000000..9400ae97ee --- /dev/null +++ b/bridge/core/animation/effect_model.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2013 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#ifndef WEBF_CORE_ANIMATION_EFFECT_MODEL_H_ +#define WEBF_CORE_ANIMATION_EFFECT_MODEL_H_ + +#include + +#include "core/animation/animation_time_delta.h" +#include "core/animation/property_handle.h" +#include "code_gen/css_property_names.h" +#include "core/platform/animation/timing_function.h" + +namespace webf { + +class Interpolation; + +// Time independent representation of an Animation's content. +// Can be sampled for the active pairs of Keyframes (represented by +// Interpolations) at a given time fraction. +class EffectModel : public GarbageCollected { + public: + enum CompositeOperation { + kCompositeReplace, + kCompositeAdd, + kCompositeAccumulate, + }; + static std::optional StringToCompositeOperation(const std::string&); + static std::string CompositeOperationToString(std::optional); + + EffectModel() = default; + virtual ~EffectModel() = default; + virtual bool Sample(int iteration, + double fraction, + TimingFunction::LimitDirection, + AnimationTimeDelta iteration_duration, + std::vector>&) const = 0; + + virtual bool Affects(const PropertyHandle&) const { return false; } + virtual bool AffectedByUnderlyingAnimations() const = 0; + virtual bool IsTransformRelatedEffect() const { return false; } + virtual bool IsKeyframeEffectModel() const { return false; } + + virtual void Trace(GCVisitor* visitor) const {} +}; + +} // namespace webf + +#endif // WEBF_CORE_ANIMATION_EFFECT_MODEL_H_ diff --git a/bridge/core/animation/interpolable_value.cc b/bridge/core/animation/interpolable_value.cc new file mode 100644 index 0000000000..b3e1759b8b --- /dev/null +++ b/bridge/core/animation/interpolable_value.cc @@ -0,0 +1,220 @@ +// Copyright 2014 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#include "core/animation/interpolable_value.h" + +#include + +//#include "core/animation/css_color_interpolation_type.h" +//#include "core/animation/interpolable_style_color.h" +#include "core/css/css_math_expression_node.h" +#include "core/css/css_numeric_literal_value.h" + +namespace webf { + +namespace { + +using UnitType = CSSPrimitiveValue::UnitType; + +CSSMathExpressionNode* NumberNode(double number, UnitType unit_type = UnitType::kNumber) { + // TODO(guopengfei): + // return CSSMathExpressionNumericLiteral::Create( + // CSSNumericLiteralValue::Create(number, unit_type)); + return nullptr; +} + +} // namespace + +InterpolableNumber::InterpolableNumber(double value, UnitType unit_type) { + SetDouble(value, unit_type); +} + +InterpolableNumber::InterpolableNumber(const CSSMathExpressionNode& expression) { + SetExpression(expression); +} + +double InterpolableNumber::Value(const CSSLengthResolver& length_resolver) const { + if (IsDoubleValue()) { + return value_.Value(); + } + return expression_->ComputeNumber(length_resolver); +} + +void InterpolableNumber::SetExpression(const CSSMathExpressionNode& expression) { + type_ = Type::kExpression; + expression_ = std::shared_ptr(&expression); +} + +void InterpolableNumber::SetDouble(double value, UnitType unit_type) { + type_ = Type::kDouble; + value_.Set(value); + unit_type_ = unit_type; +} + +const CSSMathExpressionNode& InterpolableNumber::AsExpression() const { + if (IsExpression()) { + return *expression_; + } + return *NumberNode(value_.Value(), unit_type_); +} + +bool InterpolableNumber::Equals(const InterpolableValue& other) const { + const auto& other_number = To(other); + if (IsDoubleValue() && other_number.IsDoubleValue()) { + return value_.Value() == To(other).value_.Value(); + } + return AsExpression().CustomCSSText() == other_number.AsExpression().CustomCSSText(); +} + +bool InterpolableList::Equals(const InterpolableValue& other) const { + const auto& other_list = To(other); + if (length() != other_list.length()) + return false; + for (uint32_t i = 0; i < length(); i++) { + if (!values_[i]->Equals(*other_list.values_[i])) + return false; + } + return true; +} + +double InlinedInterpolableDouble::Interpolate(double to, const double progress) const { + if (progress == 0 || value_ == to) { + return value_; + } else if (progress == 1) { + return to; + } else { + return value_ * (1 - progress) + to * progress; + } +} + +void InterpolableNumber::AssertCanInterpolateWith(const InterpolableValue& other) const { + assert(other.IsNumber()); +} + +void InterpolableNumber::Interpolate(const InterpolableValue& to, + const double progress, + InterpolableValue& result) const { + const auto& to_number = To(to); + auto& result_number = To(result); + if (IsDoubleValue() && to_number.IsDoubleValue()) { + result_number.SetDouble(value_.Interpolate(to_number.Value(), progress), unit_type_); + return; + } + /* // TODO(guopengfei):CSSMathExpressionOperation未迁移 + CSSMathExpressionNode* blended_from = + CSSMathExpressionOperation::CreateArithmeticOperationAndSimplifyCalcSize( + &AsExpression(), NumberNode(1 - progress), + CSSMathOperator::kMultiply); + CSSMathExpressionNode* blended_to = + CSSMathExpressionOperation::CreateArithmeticOperationAndSimplifyCalcSize( + &to_number.AsExpression(), NumberNode(progress), + CSSMathOperator::kMultiply); + CSSMathExpressionNode* result_expression = + CSSMathExpressionOperation::CreateArithmeticOperationAndSimplifyCalcSize( + blended_from, blended_to, CSSMathOperator::kAdd); + result_number.SetExpression(*result_expression); + + */ +} + +void InterpolableList::AssertCanInterpolateWith(const InterpolableValue& other) const { + assert(other.IsList()); + assert(To(other).length() == length()); +} + +void InterpolableList::Interpolate(const InterpolableValue& to, + const double progress, + InterpolableValue& result) const { + const auto& to_list = To(to); + auto& result_list = To(result); + + for (uint32_t i = 0; i < length(); i++) { + assert(values_[i]); + assert(to_list.values_[i]); + if (values_[i]->IsStyleColor() || to_list.values_[i]->IsStyleColor() || result_list.values_[i]->IsStyleColor()) { + // TODO(guopengfei):未迁移 + // CSSColorInterpolationType::EnsureInterpolableStyleColor(result_list, i); + // InterpolableStyleColor::Interpolate(*values_[i], *(to_list.values_[i]), + // progress, *(result_list.values_[i])); + continue; + } + values_[i]->Interpolate(*(to_list.values_[i]), progress, *(result_list.values_[i])); + } +} + +std::shared_ptr InterpolableList::RawCloneAndZero() const { + std::shared_ptr result = std::make_shared(length()); + for (uint32_t i = 0; i < length(); i++) { + result->Set(i, values_[i]->CloneAndZero()); + } + return result; +} + +void InterpolableNumber::Scale(double scale) { + if (IsDoubleValue()) { + value_.Scale(scale); + return; + } + /* // TODO(guopengfei):未迁移 + SetExpression( + *CSSMathExpressionOperation::CreateArithmeticOperationAndSimplifyCalcSize( + expression_, NumberNode(scale), CSSMathOperator::kMultiply));*/ +} + +void InterpolableNumber::Scale(const InterpolableNumber& other) { + if (IsDoubleValue()) { + /* // TODO(guopengfei):未迁移 + SetExpression(*CSSMathExpressionOperation:: + CreateArithmeticOperationAndSimplifyCalcSize( + &other.AsExpression(), NumberNode(value_.Value()), + CSSMathOperator::kMultiply)); + + */ + return; + } + /* // TODO(guopengfei):未迁移 + SetExpression( + *CSSMathExpressionOperation::CreateArithmeticOperationAndSimplifyCalcSize( + expression_, &other.AsExpression(), CSSMathOperator::kMultiply)); + + */ +} + +void InterpolableList::Scale(double scale) { + for (uint32_t i = 0; i < length(); i++) + values_[i]->Scale(scale); +} + +void InterpolableNumber::Add(const InterpolableValue& other) { + const auto& other_number = To(other); + if (IsDoubleValue() && other_number.IsDoubleValue()) { + value_.Add(other_number.value_.Value()); + return; + } + /* // TODO(guopengfei):未迁移 + CSSMathExpressionNode* result = + CSSMathExpressionOperation::CreateArithmeticOperationAndSimplifyCalcSize( + &AsExpression(), &other_number.AsExpression(), CSSMathOperator::kAdd); + SetExpression(*result); + + */ +} + +void InterpolableList::Add(const InterpolableValue& other) { + const auto& other_list = To(other); + assert(other_list.length() == length()); + for (uint32_t i = 0; i < length(); i++) + values_[i]->Add(*other_list.values_[i]); +} + +void InterpolableList::ScaleAndAdd(double scale, const InterpolableValue& other) { + const auto& other_list = To(other); + assert(other_list.length() == length()); + for (uint32_t i = 0; i < length(); i++) + values_[i]->ScaleAndAdd(scale, *other_list.values_[i]); +} + +} // namespace webf diff --git a/bridge/core/animation/interpolable_value.h b/bridge/core/animation/interpolable_value.h new file mode 100644 index 0000000000..7a14e4421b --- /dev/null +++ b/bridge/core/animation/interpolable_value.h @@ -0,0 +1,224 @@ +// Copyright 2014 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#ifndef WEBF_CORE_ANIMATION_INTERPOLABLE_VALUE_H_ +#define WEBF_CORE_ANIMATION_INTERPOLABLE_VALUE_H_ + +#include +#include +#include + +#include "core/css/css_math_expression_node.h" +#include "foundation/casting.h" + +namespace webf { + +// Represents the components of a PropertySpecificKeyframe's value that change +// smoothly as it interpolates to an adjacent value. +class InterpolableValue { + public: + // Interpolates from |this| InterpolableValue towards |to| at the given + // |progress|, placing the output in |result|. That is: + // + // result = this * (1 - progress) + to * progress + // + // Callers must make sure that |this|, |to|, and |result| are all of the same + // concrete subclass. + virtual void Interpolate(const InterpolableValue& to, const double progress, InterpolableValue& result) const = 0; + + virtual bool IsDouble() const { return false; } + virtual bool IsNumber() const { return false; } + virtual bool IsBool() const { return false; } + virtual bool IsColor() const { return false; } + virtual bool IsStyleColor() const { return false; } + virtual bool IsScrollbarColor() const { return false; } + virtual bool IsList() const { return false; } + virtual bool IsLength() const { return false; } + virtual bool IsAspectRatio() const { return false; } + virtual bool IsShadow() const { return false; } + virtual bool IsFilter() const { return false; } + virtual bool IsTransformList() const { return false; } + virtual bool IsGridLength() const { return false; } + virtual bool IsGridTrackList() const { return false; } + virtual bool IsGridTrackRepeater() const { return false; } + virtual bool IsGridTrackSize() const { return false; } + virtual bool IsFontPalette() const { return false; } + virtual bool IsDynamicRangeLimit() const { return false; } + + // TODO(alancutter): Remove Equals(). + virtual bool Equals(const InterpolableValue&) const = 0; + virtual void Scale(double scale) = 0; + virtual void Add(const InterpolableValue& other) = 0; + // The default implementation should be sufficient for most types, but + // subclasses can override this to be more efficient if they chose. + virtual void ScaleAndAdd(double scale, const InterpolableValue& other) { + Scale(scale); + Add(other); + } + virtual void AssertCanInterpolateWith(const InterpolableValue& other) const = 0; + + // Clone this value, optionally zeroing out the components at the same time. + // These are not virtual to allow for covariant return types; see + // documentation on RawClone/RawCloneAndZero. + std::shared_ptr Clone() const { return RawClone(); } + std::shared_ptr CloneAndZero() const { return RawCloneAndZero(); } + + // TODO(guopengfei): + // virtual void Trace(Visitor*) const {} + + private: + // Helper methods to allow covariant Clone/CloneAndZero methods. Concrete + // subclasses should not expose these methods publically, but instead should + // declare their own version of Clone/CloneAndZero with a concrete return type + // if it is useful for their clients. + virtual std::shared_ptr RawClone() const = 0; + virtual std::shared_ptr RawCloneAndZero() const = 0; +}; + +class InlinedInterpolableDouble final { + WEBF_DISALLOW_NEW(); + + public: + InlinedInterpolableDouble() = default; + explicit InlinedInterpolableDouble(double d) : value_(d) {} + + double Value() const { return value_; } + void Set(double value) { value_ = value; } + + double Interpolate(double to, const double progress) const; + + void Scale(double scale) { value_ *= scale; } + void Add(double other) { value_ += other; } + void ScaleAndAdd(double scale, double other) { value_ = value_ * scale + other; } + // TODO(guopengfei): + // void Trace(Visitor*) const {} + + private: + double value_ = 0.; +}; + +class InterpolableNumber final : public InterpolableValue { + public: + InterpolableNumber() = default; + explicit InterpolableNumber(double value, + CSSPrimitiveValue::UnitType unit_type = CSSPrimitiveValue::UnitType::kNumber); + explicit InterpolableNumber(const CSSMathExpressionNode& expression); + + // TODO(crbug.com/1521261): Remove this, once the bug is fixed. + double Value() const { return value_.Value(); } + double Value(const CSSLengthResolver& length_resolver) const; + + // InterpolableValue + void Interpolate(const InterpolableValue& to, const double progress, InterpolableValue& result) const final; + bool IsNumber() const final { return true; } + bool Equals(const InterpolableValue& other) const final; + void Scale(double scale) final; + void Scale(const InterpolableNumber& other); + void Add(const InterpolableValue& other) final; + void AssertCanInterpolateWith(const InterpolableValue& other) const final; + + std::shared_ptr Clone() const { return std::static_pointer_cast(RawClone()); } + std::shared_ptr CloneAndZero() const { return std::static_pointer_cast(RawCloneAndZero()); } + // TODO(guopengfei): + // void Trace(Visitor* v) const override { + // InterpolableValue::Trace(v); + // v->Trace(value_); + // v->Trace(expression_); + //} + + private: + std::shared_ptr RawClone() const final { + if (IsDoubleValue()) { + return std::make_shared(value_.Value()); + } + return std::make_shared(*expression_); + } + std::shared_ptr RawCloneAndZero() const final { return std::make_shared(0); } + + bool IsDoubleValue() const { return type_ == Type::kDouble; } + bool IsExpression() const { return type_ == Type::kExpression; } + + void SetDouble(double value, CSSPrimitiveValue::UnitType unit_type); + void SetExpression(const CSSMathExpressionNode& expression); + const CSSMathExpressionNode& AsExpression() const; + + enum class Type { kDouble, kExpression }; + Type type_; + InlinedInterpolableDouble value_; + CSSPrimitiveValue::UnitType unit_type_; + std::shared_ptr expression_; +}; + +// static_assert(std::is_trivially_destructible_v, +// "Require trivial destruction for faster sweeping"); + +class InterpolableList final : public InterpolableValue { + public: + explicit InterpolableList(uint32_t size) : values_(size) { + // static_assert(std::is_trivially_destructible_v, + // "Require trivial destruction for faster sweeping"); + } + + explicit InterpolableList(std::vector>&& values) : values_(std::move(values)) {} + + InterpolableList(const InterpolableList&) = delete; + InterpolableList& operator=(const InterpolableList&) = delete; + InterpolableList(InterpolableList&&) = default; + InterpolableList& operator=(InterpolableList&&) = default; + + const std::shared_ptr Get(uint32_t position) const { return values_[position]; } + std::shared_ptr& GetMutable(uint32_t position) { return values_[position]; } + uint32_t length() const { return values_.size(); } + void Set(uint32_t position, std::shared_ptr value) { + if (position >= values_.size()) { + values_.resize(position + 1); + } + values_[position] = std::move(value); + } + + std::shared_ptr Clone() const { return std::static_pointer_cast(RawClone()); } + std::shared_ptr CloneAndZero() const { return std::static_pointer_cast(RawCloneAndZero()); } + + // InterpolableValue + void Interpolate(const InterpolableValue& to, const double progress, InterpolableValue& result) const final; + bool IsList() const final { return true; } + bool Equals(const InterpolableValue& other) const final; + void Scale(double scale) final; + void Add(const InterpolableValue& other) final; + // We override this to avoid two passes on the list from the base version. + void ScaleAndAdd(double scale, const InterpolableValue& other) final; + void AssertCanInterpolateWith(const InterpolableValue& other) const final; + // TODO(guopengfei): + // void Trace(Visitor* v) const override { + // InterpolableValue::Trace(v); + // v->Trace(values_); + //} + + private: + std::shared_ptr RawClone() const final { + std::shared_ptr result = std::make_shared(length()); + for (uint32_t i = 0; i < length(); i++) { + result->Set(i, values_[i]->Clone()); + } + return result; + } + std::shared_ptr RawCloneAndZero() const final; + + std::vector> values_; +}; + +template <> +struct DowncastTraits { + static bool AllowFrom(const InterpolableValue& value) { return value.IsNumber(); } +}; +template <> +struct DowncastTraits { + static bool AllowFrom(const InterpolableValue& value) { return value.IsList(); } +}; + +} // namespace webf + +#endif // WEBF_CORE_ANIMATION_INTERPOLABLE_VALUE_H_ diff --git a/bridge/core/animation/interpolation.h b/bridge/core/animation/interpolation.h new file mode 100644 index 0000000000..5d138bd65d --- /dev/null +++ b/bridge/core/animation/interpolation.h @@ -0,0 +1,87 @@ +// Copyright 2014 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#ifndef WEBF_CORE_ANIMATION_INTERPOLATION_H_ +#define WEBF_CORE_ANIMATION_INTERPOLATION_H_ + +#include "core/animation/interpolable_value.h" +#include "core/animation/property_handle.h" +//#include "third_party/blink/renderer/platform/wtf/forward.h" + +namespace webf { + +// The Interpolation class is an abstract class representing an animation effect +// between two keyframe values for the same property (CSS property, SVG +// attribute, etc), for example animating the CSS property 'left' from '100px' +// to '200px'. +// +// This is represented by a pair of keyframes, referred to as the start and end +// keyframes. Each keyframe contains a value for the given property, plus the +// value's 'composite mode', which indicates how the value applies on top of the +// element's existing value. For example, consider an element whose property +// 'left' computes to 50px before animations are applied, and which has an +// interpolation with start keyframe +// {'left': '100px', composite: 'add'} +// and end keyframe +// {'left': '200px', composite: 'replace'} +// This will produce an interpolated animation effect of the 'left' property of +// the element between the values 150px (i.e. 50px + 100px) and 200px. +// +// The subclasses of Interpolation store the start and end keyframes in +// different forms; see the description of each subclass for appropriate usage. +// +// At any given point in a keyframe animation effect, multiple interpolations +// may be in effect. These interpolations are referred to as the active +// interpolations, and are what are returned when sampling the effect model of +// an animation at a given local time (see EffectModel::Sample). Typically, +// there is only one active interpolation per property affected by the +// animation. +// +// Interpolations are used in two phases of animation computation: +// +// 1. Timing update phase: +// To determine the value of a property at the current point in time, the +// code calls the Interpolate function for each item in the list of active +// interpolations. The arguments to this function specify how far through the +// interpolation the animation currently is, and the function calculates the +// value of the property at this point, storing the result in the +// Interpolation object. +// +// 2. Effect application phase: +// The interpolation's effect at its current timing state is applied to the +// element. How this is done depends on the subclass of Interpolation. See +// the subclass documentation for more. +class Interpolation { + public: + Interpolation(const Interpolation&) = delete; + Interpolation& operator=(const Interpolation&) = delete; + virtual ~Interpolation() {} + + virtual void Interpolate(int iteration, double fraction) = 0; + + virtual bool IsInvalidatableInterpolation() const { return false; } + virtual bool IsTransitionInterpolation() const { return false; } + + virtual const PropertyHandle& GetProperty() const = 0; + + // Indicates whether the cached current value (as calculated by Interpolate) + // incorporates or replaces the property value that underlies the animation, + // as in the case of additive animations. This tells us whether we can + // optimise away computing underlying values. + virtual bool DependsOnUnderlyingValue() const { return false; } + + // virtual void Trace(Visitor*) const {} + + protected: + Interpolation() = default; +}; + +using ActiveInterpolations = std::vector>; +using ActiveInterpolationsMap = std::unordered_map>; + +} // namespace webf + +#endif // WEBF_CORE_ANIMATION_INTERPOLATION_H_ diff --git a/bridge/core/animation/interpolation_value.h b/bridge/core/animation/interpolation_value.h new file mode 100644 index 0000000000..b23395d2c0 --- /dev/null +++ b/bridge/core/animation/interpolation_value.h @@ -0,0 +1,72 @@ +// Copyright 2015 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#ifndef WEBF_CORE_ANIMATION_INTERPOLATION_VALUE_H_ +#define WEBF_CORE_ANIMATION_INTERPOLATION_VALUE_H_ + +#include +#include "core/animation/interpolable_value.h" +#include "core/animation/non_interpolable_value.h" + +namespace webf { + +// Represents a (non-strict) subset of a PropertySpecificKeyframe's value broken +// down into interpolable and non-interpolable parts. InterpolationValues can be +// composed together to represent a whole PropertySpecificKeyframe value. +struct InterpolationValue { + WEBF_DISALLOW_NEW(); + + explicit InterpolationValue(std::shared_ptr interpolable_value, + std::shared_ptr non_interpolable_value = nullptr) + : interpolable_value(interpolable_value), non_interpolable_value(std::move(non_interpolable_value)) {} + + InterpolationValue(std::nullptr_t) {} + + InterpolationValue(InterpolationValue&& other) + : interpolable_value(std::move(other.interpolable_value)), + non_interpolable_value(std::move(other.non_interpolable_value)) {} + + void operator=(InterpolationValue&& other) { + interpolable_value = std::move(other.interpolable_value); + non_interpolable_value = std::move(other.non_interpolable_value); + } + + operator bool() const { return interpolable_value.get(); } + + InterpolationValue Clone() const { + return InterpolationValue(interpolable_value ? interpolable_value->Clone() : nullptr, non_interpolable_value); + } + + void Clear() { + interpolable_value == nullptr; + non_interpolable_value = nullptr; + } + + // void Trace(GCVisitor* v) const { v->Trace(interpolable_value); } + + std::shared_ptr interpolable_value; + std::shared_ptr non_interpolable_value; +}; + +// Wrapper to be used with MakeGarbageCollected<>. +class InterpolationValueGCed { + public: + explicit InterpolationValueGCed(const InterpolationValue& underlying) : underlying_(underlying.Clone()) {} + + // void Trace(GCVisitor* v) const { v->Trace(underlying_); } + + InterpolationValue& underlying() { return underlying_; } + const InterpolationValue& underlying() const { return underlying_; } + + private: + InterpolationValue underlying_; +}; + +} // namespace webf + +// WTF_ALLOW_CLEAR_UNUSED_SLOTS_WITH_MEM_FUNCTIONS(blink::InterpolationValue) + +#endif // WEBF_CORE_ANIMATION_INTERPOLATION_VALUE_H_ diff --git a/bridge/core/animation/non_interpolable_value.h b/bridge/core/animation/non_interpolable_value.h new file mode 100644 index 0000000000..2b613617b8 --- /dev/null +++ b/bridge/core/animation/non_interpolable_value.h @@ -0,0 +1,33 @@ +// Copyright 2015 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#ifndef WEBF_CORE_ANIMATION_NON_INTERPOLABLE_VALUE_H_ +#define WEBF_CORE_ANIMATION_NON_INTERPOLABLE_VALUE_H_ + +namespace webf { + +// Represents components of a PropertySpecificKeyframe's value that either do +// not change or 50% flip when interpolating with an adjacent value. +class NonInterpolableValue { + public: + virtual ~NonInterpolableValue() = default; + + typedef const void* Type; + virtual Type GetType() const = 0; +}; + +// These macros provide safe downcasts of NonInterpolableValue subclasses with +// debug assertions. +// See CSSDefaultInterpolationType.cpp for example usage. +#define DECLARE_NON_INTERPOLABLE_VALUE_TYPE() \ + Type GetType() const final { return static_type_; } \ + static Type static_type_ + +#define DEFINE_NON_INTERPOLABLE_VALUE_TYPE(T) NonInterpolableValue::Type T::static_type_ = &T::static_type_ + +} // namespace webf + +#endif // WEBF_CORE_ANIMATION_NON_INTERPOLABLE_VALUE_H_ diff --git a/bridge/core/animation/property_handle.cc b/bridge/core/animation/property_handle.cc new file mode 100644 index 0000000000..f6a4945e53 --- /dev/null +++ b/bridge/core/animation/property_handle.cc @@ -0,0 +1,48 @@ +// Copyright 2015 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "core/animation/property_handle.h" + +#include "core/platform/text/atomic_string_hash.h" + +namespace webf { + +bool PropertyHandle::operator==(const PropertyHandle& other) const { + if (handle_type_ != other.handle_type_) + return false; + + switch (handle_type_) { + case kHandleCSSProperty: + case kHandlePresentationAttribute: + return css_property_ == other.css_property_; + case kHandleCSSCustomProperty: + return property_name_ == other.property_name_; + case kHandleSVGAttribute: + return svg_attribute_ == other.svg_attribute_; + default: + return true; + } +} + +unsigned PropertyHandle::GetHash() const { + switch (handle_type_) { + case kHandleCSSProperty: + return static_cast(css_property_->PropertyID()); + case kHandleCSSCustomProperty: + return webf::GetHash(property_name_.c_str()); + case kHandlePresentationAttribute: + return -static_cast(css_property_->PropertyID()); + case kHandleSVGAttribute: + return webf::GetHash(*svg_attribute_); + default: + assert_m(false, "PropertyHandle::GetHash() NOTREACHED_IN_MIGRATION"); + return 0; + } +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/animation/property_handle.h b/bridge/core/animation/property_handle.h new file mode 100644 index 0000000000..925c91f263 --- /dev/null +++ b/bridge/core/animation/property_handle.h @@ -0,0 +1,131 @@ +// Copyright 2015 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#ifndef WEBF_CORE_ANIMATION_PROPERTY_HANDLE_H_ +#define WEBF_CORE_ANIMATION_PROPERTY_HANDLE_H_ + +#include "core/css/css_property_name.h" +#include "core/css/properties/css_property.h" +#include "core/dom/qualified_name.h" +#include "core/platform/hash_traits.h" +#include "css_property_instance.h" +#include "foundation/string/atomic_string.h" + +namespace webf { + +// Represents the property of a PropertySpecificKeyframe. +class PropertyHandle { + WEBF_DISALLOW_NEW(); + + public: + explicit PropertyHandle(const CSSProperty& property, bool is_presentation_attribute = false) + : handle_type_(is_presentation_attribute ? kHandlePresentationAttribute : kHandleCSSProperty), + css_property_(&property) { + assert(CSSPropertyID::kVariable != property.PropertyID()); + } + + // TODO(crbug.com/980160): Eliminate call to GetCSSPropertyVariable(). + explicit PropertyHandle(const AtomicString& property_name) + : handle_type_(kHandleCSSCustomProperty), + css_property_(&GetCSSPropertyVariable()), + property_name_(property_name) {} + + // TODO(crbug.com/980160): Eliminate call to GetCSSPropertyVariable(). + explicit PropertyHandle(const CSSPropertyName& property_name) + : handle_type_(property_name.IsCustomProperty() ? kHandleCSSCustomProperty : kHandleCSSProperty), + css_property_(property_name.IsCustomProperty() ? &GetCSSPropertyVariable() + : &CSSProperty::Get(property_name.Id())), + property_name_(property_name.IsCustomProperty() ? property_name.ToAtomicString() : g_null_atom) {} + + explicit PropertyHandle(const QualifiedName& attribute_name) + : handle_type_(kHandleSVGAttribute), svg_attribute_(&attribute_name) {} + + bool operator==(const PropertyHandle&) const; + bool operator!=(const PropertyHandle& other) const { return !(*this == other); } + + unsigned GetHash() const; + + bool IsCSSProperty() const { return handle_type_ == kHandleCSSProperty || IsCSSCustomProperty(); } + const CSSProperty& GetCSSProperty() const { + assert(IsCSSProperty()); + return *css_property_; + } + + bool IsCSSCustomProperty() const { return handle_type_ == kHandleCSSCustomProperty; } + const AtomicString& CustomPropertyName() const { + assert(IsCSSCustomProperty()); + return property_name_; + } + + bool IsPresentationAttribute() const { return handle_type_ == kHandlePresentationAttribute; } + const CSSProperty& PresentationAttribute() const { + assert(IsPresentationAttribute()); + return *css_property_; + } + + bool IsSVGAttribute() const { return handle_type_ == kHandleSVGAttribute; } + const QualifiedName& SvgAttribute() const { + assert(IsSVGAttribute()); + return *svg_attribute_; + } + + CSSPropertyName GetCSSPropertyName() const { + if (handle_type_ == kHandleCSSCustomProperty) + return CSSPropertyName(property_name_); + assert(IsCSSProperty() || IsPresentationAttribute()); + return CSSPropertyName(css_property_->PropertyID()); + } + + // 显式默认析构函数 + //~PropertyHandle() = delete; + + private: + enum HandleType { + kHandleEmptyValueForHashTraits, + kHandleDeletedValueForHashTraits, + kHandleCSSProperty, + kHandleCSSCustomProperty, + kHandlePresentationAttribute, + kHandleSVGAttribute, + }; + + explicit PropertyHandle(HandleType handle_type) : handle_type_(handle_type), svg_attribute_(nullptr) {} + + static PropertyHandle EmptyValueForHashTraits() { return PropertyHandle(kHandleEmptyValueForHashTraits); } + + static PropertyHandle DeletedValueForHashTraits() { return PropertyHandle(kHandleDeletedValueForHashTraits); } + + bool IsDeletedValueForHashTraits() const { return handle_type_ == kHandleDeletedValueForHashTraits; } + + HandleType handle_type_; + union { + const CSSProperty* css_property_; + const QualifiedName* svg_attribute_; + }; + AtomicString property_name_; + + friend struct ::webf::HashTraits; +}; + +} // namespace webf + +namespace webf { + +template <> +struct HashTraits : SimpleClassHashTraits { + static unsigned GetHash(const webf::PropertyHandle& handle) { return handle.GetHash(); } + + static void ConstructDeletedValue(webf::PropertyHandle& slot) { + new (NotNullTag::kNotNull, &slot) webf::PropertyHandle(webf::PropertyHandle::DeletedValueForHashTraits()); + } + static bool IsDeletedValue(const webf::PropertyHandle& value) { return value.IsDeletedValueForHashTraits(); } + + static webf::PropertyHandle EmptyValue() { return webf::PropertyHandle::EmptyValueForHashTraits(); } +}; + +} // namespace webf + +#endif // WEBF_CORE_ANIMATION_PROPERTY_HANDLE_H_ diff --git a/bridge/core/animation/timeline_inset.h b/bridge/core/animation/timeline_inset.h new file mode 100644 index 0000000000..9c26623c3e --- /dev/null +++ b/bridge/core/animation/timeline_inset.h @@ -0,0 +1,37 @@ +// Copyright 2022 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#ifndef WEBF_CORE_ANIMATION_TIMELINE_INSET_H_ +#define WEBF_CORE_ANIMATION_TIMELINE_INSET_H_ + +#include "core/platform/geometry/length.h" + +namespace webf { + +// https://drafts.csswg.org/scroll-animations-1/#view-timeline-inset +class TimelineInset { + public: + TimelineInset() = default; + TimelineInset(const Length& start, const Length& end) : start_(start), end_(end) {} + + // Note these represent the logical start/end sides of the source scroller, + // not the start/end of the timeline. + // https://drafts.csswg.org/css-writing-modes-4/#css-start + const Length& GetStart() const { return start_; } + const Length& GetEnd() const { return end_; } + + bool operator==(const TimelineInset& o) const { return start_ == o.start_ && end_ == o.end_; } + + bool operator!=(const TimelineInset& o) const { return !(*this == o); } + + private: + Length start_; + Length end_; +}; + +} // namespace webf + +#endif // WEBF_CORE_ANIMATION_TIMELINE_INSET_H_ diff --git a/bridge/core/animation/timeline_offset.cc b/bridge/core/animation/timeline_offset.cc new file mode 100644 index 0000000000..3f53a2275f --- /dev/null +++ b/bridge/core/animation/timeline_offset.cc @@ -0,0 +1,40 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#include "timeline_offset.h" +#include + +#include "string/wtf_string.h" + +namespace webf { + +/* static */ +String TimelineOffset::TimelineRangeNameToString(TimelineOffset::NamedRange range_name) { + switch (range_name) { + case NamedRange::kNone: + return "none"_s; + + case NamedRange::kCover: + return "cover"_s; + + case NamedRange::kContain: + return "contain"_s; + + case NamedRange::kEntry: + return "entry"_s; + + case NamedRange::kEntryCrossing: + return "entry-crossing"_s; + + case NamedRange::kExit: + return "exit"_s; + + case NamedRange::kExitCrossing: + return "exit-crossing"_s; + } +} + +} // namespace webf diff --git a/bridge/core/animation/timeline_offset.h b/bridge/core/animation/timeline_offset.h new file mode 100644 index 0000000000..3f0ee4559e --- /dev/null +++ b/bridge/core/animation/timeline_offset.h @@ -0,0 +1,42 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#ifndef WEBF_TIMELINE_OFFSET_H +#define WEBF_TIMELINE_OFFSET_H + +#include +#include + +#include "string/wtf_string.h" + +namespace webf { + +class Document; +class Element; +class CSSValue; + +enum class Enum : size_t { kNone, kCover, kContain, kEntry, kEntryCrossing, kExit, kExitCrossing }; + +struct TimelineOffset { + using NamedRange = Enum; + + static String TimelineRangeNameToString(NamedRange range_name); + + // Add comparison operators for std::optional comparisons + bool operator==(const TimelineOffset& other) const { + // Since TimelineOffset is currently just a struct with static members, + // all instances are considered equal + return true; + } + + bool operator!=(const TimelineOffset& other) const { + return !(*this == other); + } +}; + +} // namespace webf + +#endif // WEBF_TIMELINE_OFFSET_H diff --git a/bridge/core/animation/timing.cc b/bridge/core/animation/timing.cc new file mode 100644 index 0000000000..522038ec8f --- /dev/null +++ b/bridge/core/animation/timing.cc @@ -0,0 +1,242 @@ +// Copyright 2014 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#include "core/animation/timing.h" + +#include "third_party/blink/renderer/bindings/core/v8/v8_computed_effect_timing.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_effect_timing.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_timeline_range_offset.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_union_cssnumericvalue_double.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_union_cssnumericvalue_string_unrestricteddouble.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_union_double_string.h" +//#include "core/animation/timing_calculations.h" +#include "core/css/cssom/css_unit_values.h" + +namespace webf { + +Timing::V8Delay* Timing::Delay::ToV8Delay() const { + // TODO(crbug.com/1216527) support delay as percentage. + return MakeGarbageCollected(AsTimeValue().InMillisecondsF()); +} + +std::string Timing::FillModeString(FillMode fill_mode) { + switch (fill_mode) { + case FillMode::NONE: + return "none"_s; + case FillMode::FORWARDS: + return "forwards"_s; + case FillMode::BACKWARDS: + return "backwards"_s; + case FillMode::BOTH: + return "both"_s; + case FillMode::AUTO: + return "auto"_s; + } + assert_m(false, "NOTREACHED_IN_MIGRATION"); + return "none"_s; +} + +Timing::FillMode Timing::StringToFillMode(const std::string& fill_mode) { + if (fill_mode == "none") + return Timing::FillMode::NONE; + if (fill_mode == "backwards") + return Timing::FillMode::BACKWARDS; + if (fill_mode == "both") + return Timing::FillMode::BOTH; + if (fill_mode == "forwards") + return Timing::FillMode::FORWARDS; + assert(fill_mode == "auto"); + return Timing::FillMode::AUTO; +} + +std::string Timing::PlaybackDirectionString(PlaybackDirection playback_direction) { + switch (playback_direction) { + case PlaybackDirection::NORMAL: + return "normal"_s; + case PlaybackDirection::REVERSE: + return "reverse"_s; + case PlaybackDirection::ALTERNATE_NORMAL: + return "alternate"_s; + case PlaybackDirection::ALTERNATE_REVERSE: + return "alternate-reverse"_s; + } + assert_m(false, "Timing::PlaybackDirectionString NOTREACHED_IN_MIGRATION"); + return "normal"_s; +} + +Timing::FillMode Timing::ResolvedFillMode(bool is_keyframe_effect) const { + if (fill_mode != Timing::FillMode::AUTO) + return fill_mode; + + // https://w3.org/TR/web-animations-1/#the-effecttiming-dictionaries + if (is_keyframe_effect) + return Timing::FillMode::NONE; + return Timing::FillMode::BOTH; +} + +EffectTiming* Timing::ConvertToEffectTiming() const { + EffectTiming* effect_timing = EffectTiming::Create(); + + // Specified values used here so that inputs match outputs for JS API calls + effect_timing->setDelay(start_delay.ToV8Delay()); + effect_timing->setEndDelay(end_delay.ToV8Delay()); + effect_timing->setFill(FillModeString(fill_mode)); + effect_timing->setIterationStart(iteration_start); + effect_timing->setIterations(iteration_count); + V8UnionCSSNumericValueOrStringOrUnrestrictedDouble* duration; + if (iteration_duration) { + duration = + MakeGarbageCollected(iteration_duration->InMillisecondsF()); + } else { + duration = MakeGarbageCollected("auto"); + } + effect_timing->setDuration(duration); + effect_timing->setDirection(PlaybackDirectionString(direction)); + effect_timing->setEasing(timing_function->ToString()); + + return effect_timing; +} + +// Converts values to CSSNumberish based on corresponding timeline type +V8CSSNumberish* Timing::ToComputedValue(std::optional time, + std::optional max_time) const { + if (time) { + // A valid timeline_duration indicates use of progress based timeline. We + // need to convert values to percentages using timeline_duration as 100% + if (max_time) { + return MakeGarbageCollected(CSSUnitValues::percent((time.value() / max_time.value()) * 100)); + } else { + // For time based timeline, simply return the value in milliseconds. + return MakeGarbageCollected(time.value().InMillisecondsF()); + } + } + return nullptr; +} + +ComputedEffectTiming* Timing::getComputedTiming(const CalculatedTiming& calculated_timing, + const NormalizedTiming& normalized_timing, + bool is_keyframe_effect) const { + ComputedEffectTiming* computed_timing = ComputedEffectTiming::Create(); + + // ComputedEffectTiming members. + computed_timing->setEndTime(ToComputedValue(normalized_timing.end_time, normalized_timing.timeline_duration)); + computed_timing->setActiveDuration( + ToComputedValue(normalized_timing.active_duration, normalized_timing.timeline_duration)); + computed_timing->setLocalTime(ToComputedValue(calculated_timing.local_time, normalized_timing.timeline_duration)); + + if (calculated_timing.is_in_effect) { + assert(calculated_timing.current_iteration); + assert(calculated_timing.progress); + computed_timing->setProgress(calculated_timing.progress.value()); + computed_timing->setCurrentIteration(calculated_timing.current_iteration.value()); + } else { + computed_timing->setProgress(std::nullopt); + computed_timing->setCurrentIteration(std::nullopt); + } + + // For the EffectTiming members, getComputedTiming is equivalent to getTiming + // except that the fill and duration must be resolved. + // + // https://w3.org/TR/web-animations-1/#dom-animationeffect-getcomputedtiming + + // TODO(crbug.com/1216527): Animation effect timing members start_delay and + // end_delay should be CSSNumberish + computed_timing->setDelay(start_delay.ToV8Delay()); + computed_timing->setEndDelay(end_delay.ToV8Delay()); + computed_timing->setFill(Timing::FillModeString(ResolvedFillMode(is_keyframe_effect))); + computed_timing->setIterationStart(iteration_start); + computed_timing->setIterations(iteration_count); + + V8CSSNumberish* computed_duration = + ToComputedValue(normalized_timing.iteration_duration, normalized_timing.timeline_duration); + if (computed_duration->IsCSSNumericValue()) { + if (normalized_timing.timeline_duration) { + computed_timing->setDuration(MakeGarbageCollected( + computed_duration->GetAsCSSNumericValue())); + } + } else { + computed_timing->setDuration( + MakeGarbageCollected(computed_duration->GetAsDouble())); + } + + computed_timing->setDirection(Timing::PlaybackDirectionString(direction)); + computed_timing->setEasing(timing_function->ToString()); + + return computed_timing; +} + +Timing::CalculatedTiming Timing::CalculateTimings(std::optional local_time, + bool is_idle, + const NormalizedTiming& normalized_timing, + AnimationDirection animation_direction, + bool is_keyframe_effect, + std::optional playback_rate) const { + const AnimationTimeDelta active_duration = normalized_timing.active_duration; + const AnimationTimeDelta duration = normalized_timing.iteration_duration; + + Timing::Phase current_phase = TimingCalculations::CalculatePhase(normalized_timing, local_time, animation_direction); + + const std::optional active_time = TimingCalculations::CalculateActiveTime( + normalized_timing, ResolvedFillMode(is_keyframe_effect), local_time, current_phase); + + std::optional progress; + + const std::optional overall_progress = TimingCalculations::CalculateOverallProgress( + current_phase, active_time, duration, iteration_count, iteration_start); + const std::optional simple_iteration_progress = TimingCalculations::CalculateSimpleIterationProgress( + current_phase, overall_progress, iteration_start, active_time, active_duration, iteration_count); + const std::optional current_iteration = TimingCalculations::CalculateCurrentIteration( + current_phase, active_time, iteration_count, overall_progress, simple_iteration_progress); + const bool current_direction_is_forwards = + TimingCalculations::IsCurrentDirectionForwards(current_iteration, direction); + const std::optional directed_progress = + TimingCalculations::CalculateDirectedProgress(simple_iteration_progress, current_iteration, direction); + + progress = TimingCalculations::CalculateTransformedProgress(current_phase, directed_progress, + current_direction_is_forwards, timing_function); + + AnimationTimeDelta time_to_next_iteration = AnimationTimeDelta::Max(); + // Conditionally compute the time to next iteration, which is only + // applicable if the iteration duration is non-zero. + if (!duration.is_zero()) { + const AnimationTimeDelta start_offset = TimingCalculations::MultiplyZeroAlwaysGivesZero(duration, iteration_start); + DCHECK_GE(start_offset, AnimationTimeDelta()); + const std::optional offset_active_time = + TimingCalculations::CalculateOffsetActiveTime(active_duration, active_time, start_offset); + const std::optional iteration_time = TimingCalculations::CalculateIterationTime( + duration, active_duration, offset_active_time, start_offset, current_phase, *this); + if (iteration_time) { + // active_time cannot be null if iteration_time is not null. + DCHECK(active_time); + time_to_next_iteration = duration - iteration_time.value(); + if (active_duration - active_time.value() < time_to_next_iteration) + time_to_next_iteration = AnimationTimeDelta::Max(); + } + } + + CalculatedTiming calculated = CalculatedTiming(); + calculated.phase = current_phase; + calculated.current_iteration = current_iteration; + calculated.progress = progress; + calculated.is_in_effect = active_time.has_value(); + // If active_time is not null then current_iteration and (transformed) + // progress are also non-null). + assert(!calculated.is_in_effect || (current_iteration.has_value() && progress.has_value())); + calculated.is_in_play = calculated.phase == Timing::kPhaseActive; + + // https://w3.org/TR/web-animations-1/#current + calculated.is_current = + calculated.is_in_play || + (playback_rate.has_value() && playback_rate > 0 && calculated.phase == Timing::kPhaseBefore) || + (playback_rate.has_value() && playback_rate < 0 && calculated.phase == Timing::kPhaseAfter) || + (!is_idle && normalized_timing.timeline_duration); + + calculated.local_time = local_time; + calculated.time_to_next_iteration = time_to_next_iteration; + + return calculated; +} + +} // namespace webf diff --git a/bridge/core/animation/timing.h b/bridge/core/animation/timing.h new file mode 100644 index 0000000000..c34b423e46 --- /dev/null +++ b/bridge/core/animation/timing.h @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2013 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#ifndef WEBF_CORE_ANIMATION_TIMING_H_ +#define WEBF_CORE_ANIMATION_TIMING_H_ + +#include +// #include "cc/animation/keyframe_model.h" +#include "core/animation/animation_time_delta.h" + +// Undefine Windows macros that conflict with enum values +#ifdef ALTERNATE +#undef ALTERNATE +#endif +#include "core/base/memory/values_equivalent.h" +#include "core/css/css_value.h" +#include "core/platform/geometry/length.h" +#include "core/platform/animation/timing_function.h" +#include "core/platform/math_extras.h" +// #include "foundation/webf_malloc.h" +// #include "third_party/blink/renderer/bindings/core/v8/v8_timeline_range.h" +// #include "third_party/blink/renderer/bindings/core/v8/v8_typedefs.h" +// #include "third_party/blink/renderer/bindings/core/v8/v8_union_cssnumericvalue_double.h" +// #include "third_party/blink/renderer/bindings/core/v8/v8_union_string_timelinerangeoffset.h" + +namespace webf { + +// Stub for cc::KeyframeModel::FillMode and Direction +namespace cc { +class KeyframeModel { + public: + enum FillMode { + FILL_MODE_NONE, + FILL_MODE_FORWARDS, + FILL_MODE_BACKWARDS, + FILL_MODE_BOTH, + FILL_MODE_AUTO + }; + + enum Direction { + NORMAL, + REVERSE, + ALTERNATE, + ALTERNATE_REVERSE + }; +}; +} // namespace cc + +// V8 type stubs +class V8CSSNumberish {}; +class V8UnionCSSNumericValueOrDouble {}; + +// Use the ValuesEquivalent from core/base/memory/values_equivalent.h instead + +class EffectTiming; +class ComputedEffectTiming; +enum class TimelinePhase; + +struct Timing { + // USING_FAST_MALLOC(Timing); + + public: + // Note that logic in CSSAnimations depends on the order of these values. + enum Phase { + kPhaseBefore, + kPhaseActive, + kPhaseAfter, + kPhaseNone, + }; + // Represents the animation direction from the Web Animations spec, see + // https://drafts.csswg.org/web-animations-1/#animation-direction. + enum class AnimationDirection { + kForwards, + kBackwards, + }; + + // Timing properties set via AnimationEffect.updateTiming override their + // corresponding CSS properties. + enum AnimationTimingOverride { + kOverrideNode = 0, + kOverrideDirection = 1, + kOverrideDuration = 1 << 1, + kOverrideEndDelay = 1 << 2, + kOverideFillMode = 1 << 3, + kOverrideIterationCount = 1 << 4, + kOverrideIterationStart = 1 << 5, + kOverrideStartDelay = 1 << 6, + kOverrideTimingFunction = 1 << 7, + kOverrideRangeStart = 1 << 8, + kOverrideRangeEnd = 1 << 9, + kOverrideAll = (1 << 10) - 1 + }; + + using V8Delay = V8UnionCSSNumericValueOrDouble; + + // Delay can be directly expressed as time delays or calculated based on a + // position on a view timeline. As part of the normalization process, a + // timeline offsets are converted to time-based delays. + struct Delay { + // TODO(crbug.com/7575): Support percent delays in addition to time-based + // delays. + AnimationTimeDelta time_delay; + std::optional relative_delay; + + Delay() = default; + + explicit Delay(AnimationTimeDelta time) : time_delay(time) {} + + bool IsInfinite() const { return time_delay.is_inf(); } + + bool operator==(const Delay& other) const { + return time_delay == other.time_delay && relative_delay == other.relative_delay; + } + + bool operator!=(const Delay& other) const { return !(*this == other); } + + bool IsNonzeroTimeBasedDelay() const { return !relative_delay && !time_delay.is_zero(); } + + // Scaling only affects time based delays. + void Scale(double scale_factor) { time_delay *= scale_factor; } + + AnimationTimeDelta AsTimeValue() const { return time_delay; } + + V8Delay* ToV8Delay() const; + }; + + using FillMode = cc::KeyframeModel::FillMode; + using PlaybackDirection = cc::KeyframeModel::Direction; + + static double NullValue() { return std::numeric_limits::quiet_NaN(); } + + static std::string FillModeString(FillMode); + static FillMode StringToFillMode(const std::string&); + static std::string PlaybackDirectionString(PlaybackDirection); + + Timing() = default; + + void AssertValid() const { + assert(!start_delay.IsInfinite()); + assert(!end_delay.IsInfinite()); + assert(std::isfinite(iteration_start)); + assert(iteration_start >= 0); + assert(iteration_count >= 0); + assert(!iteration_duration || iteration_duration.value() >= AnimationTimeDelta()); + assert(timing_function); + } + + Timing::FillMode ResolvedFillMode(bool is_animation) const; + EffectTiming* ConvertToEffectTiming() const; + + bool operator==(const Timing& other) const { + return start_delay == other.start_delay && end_delay == other.end_delay && fill_mode == other.fill_mode && + iteration_start == other.iteration_start && iteration_count == other.iteration_count && + iteration_duration == other.iteration_duration && direction == other.direction && + webf::ValuesEquivalent(timing_function.get(), other.timing_function.get()); + } + + bool operator!=(const Timing& other) const { return !(*this == other); } + + // Explicit changes to animation timing through the web animations API, + // override timing changes due to CSS style. + void SetTimingOverride(AnimationTimingOverride override) { timing_overrides |= override; } + bool HasTimingOverride(AnimationTimingOverride override) { return timing_overrides & override; } + bool HasTimingOverrides() { return timing_overrides != kOverrideNode; } + + V8CSSNumberish* ToComputedValue(std::optional, std::optional) const; + + Delay start_delay; + Delay end_delay; + FillMode fill_mode = FillMode::FILL_MODE_AUTO; + double iteration_start = 0; + double iteration_count = 1; + // If empty, indicates the 'auto' value. + std::optional iteration_duration = std::nullopt; + + PlaybackDirection direction = PlaybackDirection::NORMAL; + std::shared_ptr timing_function = LinearTimingFunction::Shared(); + // Mask of timing attributes that are set by calls to + // AnimationEffect.updateTiming. Once set, these attributes ignore changes + // based on the CSS style. + uint16_t timing_overrides = kOverrideNode; + + struct CalculatedTiming { + WEBF_DISALLOW_NEW(); + Phase phase = Phase::kPhaseNone; + std::optional current_iteration = 0; + std::optional progress = 0; + bool is_current = false; + bool is_in_effect = false; + bool is_in_play = false; + std::optional local_time; + AnimationTimeDelta time_to_forwards_effect_change = AnimationTimeDelta::Max(); + AnimationTimeDelta time_to_reverse_effect_change = AnimationTimeDelta::Max(); + AnimationTimeDelta time_to_next_iteration = AnimationTimeDelta::Max(); + }; + + // Normalized values contain specified timing values after normalizing to + // timeline. + struct NormalizedTiming { + WEBF_DISALLOW_NEW(); + // Value used in normalization math. Stored so that we can convert back if + // needed. At present, only scroll-linked animations have a timeline + // duration. If this changes, we need to update the is_current calculation. + std::optional timeline_duration; + // Though timing delays may be expressed as either times or (phase,offset) + // pairs, post normalization, delays is expressed in time. + AnimationTimeDelta start_delay; + AnimationTimeDelta end_delay; + AnimationTimeDelta iteration_duration; + // Calculated as (iteration_duration * iteration_count) + AnimationTimeDelta active_duration; + // Calculated as (start_delay + active_duration + end_delay) + AnimationTimeDelta end_time; + // Indicates if the before-active phase boundary aligns with the minimum + // scroll position. + bool is_start_boundary_aligned = false; + // Indicates if the active-after phase boundary aligns with the maximum + // scroll position. + bool is_end_boundary_aligned = false; + }; + + // TODO(crbug.com/1394434): Cleanup method signature by passing in + // AnimationEffectOwner. + CalculatedTiming CalculateTimings(std::optional local_time, + bool is_idle, + const NormalizedTiming& normalized_timing, + AnimationDirection animation_direction, + bool is_keyframe_effect, + std::optional playback_rate) const; + ComputedEffectTiming* getComputedTiming(const CalculatedTiming& calculated, + const NormalizedTiming& normalized, + bool is_keyframe_effect) const; +}; + +} // namespace webf + +#endif // WEBF_CORE_ANIMATION_TIMING_H_ diff --git a/bridge/core/animation/timing_calculations.cc b/bridge/core/animation/timing_calculations.cc new file mode 100644 index 0000000000..dd3cffc9c2 --- /dev/null +++ b/bridge/core/animation/timing_calculations.cc @@ -0,0 +1,408 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "core/animation/timing_calculations.h" + +#include "base/metrics/histogram_macros.h" + +namespace blink { + +namespace { + +inline bool EndsOnIterationBoundary(double iteration_count, double iteration_start) { + assert(std::isfinite(iteration_count)); + return !fmod(iteration_count + iteration_start, 1); +} + +void RecordBoundaryMisalignment(AnimationTimeDelta misalignment) { + // Animations require 1 microsecond precision. For a scroll-based animation, + // percentages are internally converted to time. The animation duration in + // microseconds is 16 * (range in pixels). + // Refer to cc/animations/scroll_timeline.h for details. + // + // It is not particularly meaningful to report the misalignment as a time + // since there is no dependency on having a high resolution timer. Instead, + // we convert back to 16ths of a pixel by scaling accordingly. + int sample = std::round(misalignment.InMicrosecondsF()); + UMA_HISTOGRAM_EXACT_LINEAR("Blink.Animation.SDA.BoundaryMisalignment", sample, 64); +} + +} // namespace + +double TimingCalculations::TimingCalculationEpsilon() { + // Permit 2-bits of quantization error. Threshold based on experimentation + // with accuracy of fmod. + return 2.0 * std::numeric_limits::epsilon(); +} + +AnimationTimeDelta TimingCalculations::TimeTolerance() { + return ANIMATION_TIME_DELTA_FROM_SECONDS(0.000001 /*one microsecond*/); +} + +bool TimingCalculations::IsWithinAnimationTimeEpsilon(double a, double b) { + return std::abs(a - b) <= TimingCalculationEpsilon(); +} + +bool TimingCalculations::IsWithinAnimationTimeTolerance(AnimationTimeDelta a, AnimationTimeDelta b) { + if (a.is_inf() || b.is_inf()) { + return a == b; + } + AnimationTimeDelta difference = a >= b ? a - b : b - a; + return difference <= TimeTolerance(); +} + +bool TimingCalculations::LessThanOrEqualToWithinEpsilon(double a, double b) { + return a <= b + TimingCalculationEpsilon(); +} + +bool TimingCalculations::LessThanOrEqualToWithinTimeTolerance(AnimationTimeDelta a, AnimationTimeDelta b) { + return a <= b + TimeTolerance(); +} + +bool TimingCalculations::GreaterThanOrEqualToWithinEpsilon(double a, double b) { + return a >= b - TimingCalculationEpsilon(); +} + +bool TimingCalculations::GreaterThanOrEqualToWithinTimeTolerance(AnimationTimeDelta a, AnimationTimeDelta b) { + return a >= b - TimeTolerance(); +} + +bool TimingCalculations::GreaterThanWithinTimeTolerance(AnimationTimeDelta a, AnimationTimeDelta b) { + return a > b - TimeTolerance(); +} + +double TimingCalculations::MultiplyZeroAlwaysGivesZero(double x, double y) { + DCHECK(!std::isnan(x)); + DCHECK(!std::isnan(y)); + return x && y ? x * y : 0; +} + +AnimationTimeDelta TimingCalculations::MultiplyZeroAlwaysGivesZero(AnimationTimeDelta x, double y) { + DCHECK(!std::isnan(y)); + return x.is_zero() || y == 0 ? AnimationTimeDelta() : (x * y); +} + +// https://w3.org/TR/web-animations-1/#animation-effect-phases-and-states +Timing::Phase TimingCalculations::CalculatePhase(const Timing::NormalizedTiming& normalized, + std::optional& local_time, + Timing::AnimationDirection direction) { + DCHECK(GreaterThanOrEqualToWithinTimeTolerance(normalized.active_duration, AnimationTimeDelta())); + if (!local_time) { + return Timing::kPhaseNone; + } + + AnimationTimeDelta before_active_boundary_time = + std::max(std::min(normalized.start_delay, normalized.end_time), AnimationTimeDelta()); + if (IsWithinAnimationTimeTolerance(local_time.value(), before_active_boundary_time)) { + local_time = before_active_boundary_time; + } + + if (local_time.value() < before_active_boundary_time) { + if (normalized.is_start_boundary_aligned) { + RecordBoundaryMisalignment(before_active_boundary_time - local_time.value()); + } + return Timing::kPhaseBefore; + } + if ((direction == Timing::AnimationDirection::kBackwards && local_time.value() == before_active_boundary_time && + !normalized.is_start_boundary_aligned)) { + return Timing::kPhaseBefore; + } + + AnimationTimeDelta active_after_boundary_time = std::max( + std::min(normalized.start_delay + normalized.active_duration, normalized.end_time), AnimationTimeDelta()); + if (IsWithinAnimationTimeTolerance(local_time.value(), active_after_boundary_time)) { + local_time = active_after_boundary_time; + } + if (local_time.value() > active_after_boundary_time) { + if (normalized.is_end_boundary_aligned) { + RecordBoundaryMisalignment(local_time.value() - active_after_boundary_time); + } + return Timing::kPhaseAfter; + } + if ((direction == Timing::AnimationDirection::kForwards && local_time.value() == active_after_boundary_time && + !normalized.is_end_boundary_aligned)) { + return Timing::kPhaseAfter; + } + return Timing::kPhaseActive; +} + +// https://w3.org/TR/web-animations-1/#calculating-the-active-time +std::optional TimingCalculations::CalculateActiveTime(const Timing::NormalizedTiming& normalized, + Timing::FillMode fill_mode, + std::optional local_time, + Timing::Phase phase) { + DCHECK(GreaterThanOrEqualToWithinTimeTolerance(normalized.active_duration, AnimationTimeDelta())); + switch (phase) { + case Timing::kPhaseBefore: + if (fill_mode == Timing::FillMode::BACKWARDS || fill_mode == Timing::FillMode::BOTH) { + DCHECK(local_time.has_value()); + return std::max(local_time.value() - normalized.start_delay, AnimationTimeDelta()); + } + return std::nullopt; + case Timing::kPhaseActive: + DCHECK(local_time.has_value()); + return local_time.value() - normalized.start_delay; + case Timing::kPhaseAfter: + if (fill_mode == Timing::FillMode::FORWARDS || fill_mode == Timing::FillMode::BOTH) { + DCHECK(local_time.has_value()); + return std::max(AnimationTimeDelta(), + std::min(normalized.active_duration, local_time.value() - normalized.start_delay)); + } + return std::nullopt; + case Timing::kPhaseNone: + DCHECK(!local_time.has_value()); + return std::nullopt; + default: + NOTREACHED_IN_MIGRATION(); + return std::nullopt; + } +} + +// Calculates the overall progress, which describes the number of iterations +// that have completed (including partial iterations). +// https://w3.org/TR/web-animations-1/#calculating-the-overall-progress +std::optional TimingCalculations::CalculateOverallProgress(Timing::Phase phase, + std::optional active_time, + AnimationTimeDelta iteration_duration, + double iteration_count, + double iteration_start) { + // 1. If the active time is unresolved, return unresolved. + if (!active_time) { + return std::nullopt; + } + + // 2. Calculate an initial value for overall progress. + double overall_progress = 0; + if (IsWithinAnimationTimeTolerance(iteration_duration, AnimationTimeDelta())) { + if (phase != Timing::kPhaseBefore) { + overall_progress = iteration_count; + } + } else { + overall_progress = (active_time.value() / iteration_duration); + } + + return overall_progress + iteration_start; +} + +// Calculates the simple iteration progress, which is a fraction of the progress +// through the current iteration that ignores transformations to the time +// introduced by the playback direction or timing functions applied to the +// effect. +// https://w3.org/TR/web-animations-1/#calculating-the-simple-iteration-progress +std::optional TimingCalculations::CalculateSimpleIterationProgress( + Timing::Phase phase, + std::optional overall_progress, + double iteration_start, + std::optional active_time, + AnimationTimeDelta active_duration, + double iteration_count) { + // 1. If the overall progress is unresolved, return unresolved. + if (!overall_progress) { + return std::nullopt; + } + + // 2. If overall progress is infinity, let the simple iteration progress be + // iteration start % 1.0, otherwise, let the simple iteration progress be + // overall progress % 1.0. + double simple_iteration_progress = + std::isinf(overall_progress.value()) ? fmod(iteration_start, 1.0) : fmod(overall_progress.value(), 1.0); + + // active_time is not null is because overall_progress != null and + // CalculateOverallProgress() only returns null when active_time is null. + DCHECK(active_time); + + // 3. If all of the following conditions are true, + // * the simple iteration progress calculated above is zero, and + // * the animation effect is in the active phase or the after phase, and + // * the active time is equal to the active duration, and + // * the iteration count is not equal to zero. + // let the simple iteration progress be 1.0. + if (IsWithinAnimationTimeEpsilon(simple_iteration_progress, 0.0) && + (phase == Timing::kPhaseActive || phase == Timing::kPhaseAfter) && + IsWithinAnimationTimeTolerance(active_time.value(), active_duration) && + !IsWithinAnimationTimeEpsilon(iteration_count, 0.0)) { + simple_iteration_progress = 1.0; + } + + // 4. Return simple iteration progress. + return simple_iteration_progress; +} + +// https://w3.org/TR/web-animations-1/#calculating-the-current-iteration +std::optional TimingCalculations::CalculateCurrentIteration(Timing::Phase phase, + std::optional active_time, + double iteration_count, + std::optional overall_progress, + std::optional simple_iteration_progress) { + // 1. If the active time is unresolved, return unresolved. + if (!active_time) { + return std::nullopt; + } + + // 2. If the animation effect is in the after phase and the iteration count + // is infinity, return infinity. + if (phase == Timing::kPhaseAfter && std::isinf(iteration_count)) { + return std::numeric_limits::infinity(); + } + + if (!overall_progress) { + return std::nullopt; + } + + // simple iteration progress can only be null if overall progress is null. + DCHECK(simple_iteration_progress); + + // 3. If the simple iteration progress is 1.0, return floor(overall progress) + // - 1. + if (simple_iteration_progress.value() == 1.0) { + // Safeguard for zero duration animation (crbug.com/954558). + return fmax(0, floor(overall_progress.value()) - 1); + } + + // 4. Otherwise, return floor(overall progress). + return floor(overall_progress.value()); +} + +// https://w3.org/TR/web-animations-1/#calculating-the-directed-progress +bool TimingCalculations::IsCurrentDirectionForwards(std::optional current_iteration, + Timing::PlaybackDirection direction) { + const bool current_iteration_is_even = + !current_iteration ? false + : (std::isinf(current_iteration.value()) + ? true + : IsWithinAnimationTimeEpsilon(fmod(current_iteration.value(), 2), 0)); + + switch (direction) { + case Timing::PlaybackDirection::NORMAL: + return true; + + case Timing::PlaybackDirection::REVERSE: + return false; + + case Timing::PlaybackDirection::ALTERNATE_NORMAL: + return current_iteration_is_even; + + case Timing::PlaybackDirection::ALTERNATE_REVERSE: + return !current_iteration_is_even; + } +} + +// https://w3.org/TR/web-animations-1/#calculating-the-directed-progress +std::optional TimingCalculations::CalculateDirectedProgress(std::optional simple_iteration_progress, + std::optional current_iteration, + Timing::PlaybackDirection direction) { + // 1. If the simple progress is unresolved, return unresolved. + if (!simple_iteration_progress) { + return std::nullopt; + } + + // 2. Calculate the current direction. + bool current_direction_is_forwards = IsCurrentDirectionForwards(current_iteration, direction); + + // 3. If the current direction is forwards then return the simple iteration + // progress. Otherwise return 1 - simple iteration progress. + return current_direction_is_forwards ? simple_iteration_progress.value() : 1 - simple_iteration_progress.value(); +} + +// https://w3.org/TR/web-animations-1/#calculating-the-transformed-progress +std::optional TimingCalculations::CalculateTransformedProgress(Timing::Phase phase, + std::optional directed_progress, + bool is_current_direction_forward, + scoped_refptr timing_function) { + if (!directed_progress) { + return std::nullopt; + } + + // Set the before flag to indicate if at the leading edge of an iteration. + // This is used to determine if the left or right limit should be used if at a + // discontinuity in the timing function. + bool before = is_current_direction_forward ? phase == Timing::kPhaseBefore : phase == Timing::kPhaseAfter; + TimingFunction::LimitDirection limit_direction = + before ? TimingFunction::LimitDirection::LEFT : TimingFunction::LimitDirection::RIGHT; + + // Snap boundaries to correctly render step timing functions at 0 and 1. + // (crbug.com/949373) + if (phase == Timing::kPhaseAfter) { + if (is_current_direction_forward && IsWithinAnimationTimeEpsilon(directed_progress.value(), 1)) { + directed_progress = 1; + } else if (!is_current_direction_forward && IsWithinAnimationTimeEpsilon(directed_progress.value(), 0)) { + directed_progress = 0; + } + } + + // Return the result of evaluating the animation effect’s timing function + // passing directed progress as the input progress value. + return timing_function->Evaluate(directed_progress.value(), limit_direction); +} + +// Offsets the active time by how far into the animation we start (i.e. the +// product of the iteration start and iteration duration). This is not part of +// the Web Animations spec; it is used for calculating the time until the next +// iteration to optimize scheduling. +std::optional TimingCalculations::CalculateOffsetActiveTime( + AnimationTimeDelta active_duration, + std::optional active_time, + AnimationTimeDelta start_offset) { + DCHECK(GreaterThanOrEqualToWithinTimeTolerance(active_duration, AnimationTimeDelta())); + DCHECK(GreaterThanOrEqualToWithinTimeTolerance(start_offset, AnimationTimeDelta())); + + if (!active_time) { + return std::nullopt; + } + + DCHECK(GreaterThanOrEqualToWithinTimeTolerance(active_time.value(), AnimationTimeDelta()) && + LessThanOrEqualToWithinTimeTolerance(active_time.value(), active_duration)); + + if (active_time->is_max()) { + return AnimationTimeDelta::Max(); + } + + return active_time.value() + start_offset; +} + +// Maps the offset active time into 'iteration time space'[0], aka the offset +// into the current iteration. This is not part of the Web Animations spec (note +// that the section linked below is non-normative); it is used for calculating +// the time until the next iteration to optimize scheduling. +// +// [0] https://w3.org/TR/web-animations-1/#iteration-time-space +std::optional TimingCalculations::CalculateIterationTime( + AnimationTimeDelta iteration_duration, + AnimationTimeDelta active_duration, + std::optional offset_active_time, + AnimationTimeDelta start_offset, + Timing::Phase phase, + const Timing& specified) { + DCHECK(GreaterThanWithinTimeTolerance(iteration_duration, AnimationTimeDelta())); + DCHECK(IsWithinAnimationTimeTolerance(active_duration, + MultiplyZeroAlwaysGivesZero(iteration_duration, specified.iteration_count))); + + if (!offset_active_time) { + return std::nullopt; + } + + DCHECK(GreaterThanWithinTimeTolerance(offset_active_time.value(), AnimationTimeDelta())); + DCHECK(LessThanOrEqualToWithinTimeTolerance(offset_active_time.value(), (active_duration + start_offset))); + + if (offset_active_time->is_max() || + (IsWithinAnimationTimeTolerance(offset_active_time.value() - start_offset, active_duration) && + specified.iteration_count && EndsOnIterationBoundary(specified.iteration_count, specified.iteration_start))) { + return std::make_optional(iteration_duration); + } + + DCHECK(!offset_active_time->is_max()); + AnimationTimeDelta iteration_time = + ANIMATION_TIME_DELTA_FROM_SECONDS(fmod(offset_active_time->InSecondsF(), iteration_duration.InSecondsF())); + + // This implements step 3 of + // https://w3.org/TR/web-animations-1/#calculating-the-simple-iteration-progress + if (iteration_time.is_zero() && phase == Timing::kPhaseAfter && !active_duration.is_zero() && + !offset_active_time.value().is_zero()) { + return std::make_optional(iteration_duration); + } + + return iteration_time; +} + +} // namespace blink diff --git a/bridge/core/animation/timing_calculations.h b/bridge/core/animation/timing_calculations.h new file mode 100644 index 0000000000..4ca39f4bfb --- /dev/null +++ b/bridge/core/animation/timing_calculations.h @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2013 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#ifndef WEBF_CORE_ANIMATION_TIMING_CALCULATIONS_H_ +#define WEBF_CORE_ANIMATION_TIMING_CALCULATIONS_H_ + +#include + +#include "core/animation/timing.h" +#include "core/platform/math_extras.h" + +namespace webf { + +class TimingCalculations { + public: + static double TimingCalculationEpsilon(); + + static AnimationTimeDelta TimeTolerance(); + + static bool IsWithinAnimationTimeEpsilon(double a, double b); + + static bool IsWithinAnimationTimeTolerance(AnimationTimeDelta a, AnimationTimeDelta b); + + static bool LessThanOrEqualToWithinEpsilon(double a, double b); + + static bool LessThanOrEqualToWithinTimeTolerance(AnimationTimeDelta a, AnimationTimeDelta b); + + static bool GreaterThanOrEqualToWithinEpsilon(double a, double b); + + static bool GreaterThanOrEqualToWithinTimeTolerance(AnimationTimeDelta a, AnimationTimeDelta b); + + static bool GreaterThanWithinTimeTolerance(AnimationTimeDelta a, AnimationTimeDelta b); + + static double MultiplyZeroAlwaysGivesZero(double x, double y); + + static AnimationTimeDelta MultiplyZeroAlwaysGivesZero(AnimationTimeDelta x, double y); + + // https://w3.org/TR/web-animations-1/#animation-effect-phases-and-states + static Timing::Phase CalculatePhase(const Timing::NormalizedTiming& normalized, + std::optional& local_time, + Timing::AnimationDirection direction); + + // https://w3.org/TR/web-animations-1/#calculating-the-active-time + static std::optional CalculateActiveTime(const Timing::NormalizedTiming& normalized, + Timing::FillMode fill_mode, + std::optional local_time, + Timing::Phase phase); + + // Calculates the overall progress, which describes the number of iterations + // that have completed (including partial iterations). + // https://w3.org/TR/web-animations-1/#calculating-the-overall-progress + static std::optional CalculateOverallProgress(Timing::Phase phase, + std::optional active_time, + AnimationTimeDelta iteration_duration, + double iteration_count, + double iteration_start); + + // Calculates the simple iteration progress, which is a fraction of the + // progress through the current iteration that ignores transformations to the + // time introduced by the playback direction or timing functions applied to + // the effect. + // https://w3.org/TR/web-animations-1/#calculating-the-simple-iteration-progress + static std::optional CalculateSimpleIterationProgress(Timing::Phase phase, + std::optional overall_progress, + double iteration_start, + std::optional active_time, + AnimationTimeDelta active_duration, + double iteration_count); + + // https://w3.org/TR/web-animations-1/#calculating-the-current-iteration + static std::optional CalculateCurrentIteration(Timing::Phase phase, + std::optional active_time, + double iteration_count, + std::optional overall_progress, + std::optional simple_iteration_progress); + + // https://w3.org/TR/web-animations-1/#calculating-the-directed-progress + static bool IsCurrentDirectionForwards(std::optional current_iteration, Timing::PlaybackDirection direction); + + // https://w3.org/TR/web-animations-1/#calculating-the-directed-progress + static std::optional CalculateDirectedProgress(std::optional simple_iteration_progress, + std::optional current_iteration, + Timing::PlaybackDirection direction); + + // https://w3.org/TR/web-animations-1/#calculating-the-transformed-progress + static std::optional CalculateTransformedProgress(Timing::Phase phase, + std::optional directed_progress, + bool is_current_direction_forward, + scoped_refptr timing_function); + + // Offsets the active time by how far into the animation we start (i.e. the + // product of the iteration start and iteration duration). This is not part of + // the Web Animations spec; it is used for calculating the time until the next + // iteration to optimize scheduling. + static std::optional CalculateOffsetActiveTime(AnimationTimeDelta active_duration, + std::optional active_time, + AnimationTimeDelta start_offset); + + // Maps the offset active time into 'iteration time space'[0], aka the offset + // into the current iteration. This is not part of the Web Animations spec + // (note that the section linked below is non-normative); it is used for + // calculating the time until the next iteration to optimize scheduling. + // + // [0] https://w3.org/TR/web-animations-1/#iteration-time-space + static std::optional CalculateIterationTime(AnimationTimeDelta iteration_duration, + AnimationTimeDelta active_duration, + std::optional offset_active_time, + AnimationTimeDelta start_offset, + Timing::Phase phase, + const Timing& specified); +}; + +} // namespace webf + +#endif // WEBF_CORE_ANIMATION_TIMING_CALCULATIONS_H_ diff --git a/bridge/core/api/character_data.cc b/bridge/core/api/character_data.cc new file mode 100644 index 0000000000..443faa5425 --- /dev/null +++ b/bridge/core/api/character_data.cc @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "plugin_api/character_data.h" +#include "core/dom/node.h" + +namespace webf {} // namespace webf \ No newline at end of file diff --git a/bridge/core/api/comment.cc b/bridge/core/api/comment.cc new file mode 100644 index 0000000000..bd1f64b3cb --- /dev/null +++ b/bridge/core/api/comment.cc @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "plugin_api/comment.h" +#include "core/dom/character_data.h" + +namespace webf {} // namespace webf diff --git a/bridge/core/api/container_node.cc b/bridge/core/api/container_node.cc new file mode 100644 index 0000000000..03147fe0d0 --- /dev/null +++ b/bridge/core/api/container_node.cc @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "plugin_api/container_node.h" +#include "core/dom/node.h" + +namespace webf {} // namespace webf \ No newline at end of file diff --git a/bridge/core/api/document.cc b/bridge/core/api/document.cc new file mode 100644 index 0000000000..da294695b3 --- /dev/null +++ b/bridge/core/api/document.cc @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "plugin_api/document.h" +#include "binding_call_methods.h" +#include "core/api/exception_state.h" +#include "core/dom/comment.h" +#include "core/dom/document.h" +#include "core/dom/document_fragment.h" +#include "core/dom/events/event.h" +#include "core/dom/text.h" +#include "core/html/html_body_element.h" +#include "core/html/html_head_element.h" +#include "core/html/html_html_element.h" +#include "foundation/native_value_converter.h" + +namespace webf { + +WebFValue DocumentPublicMethods::CreateElement( + webf::Document* ptr, + const UTF8Char* tag_name, + webf::SharedExceptionState* shared_exception_state) { + auto* document = static_cast(ptr); + MemberMutationScope scope{document->GetExecutingContext()}; + webf::AtomicString tag_name_atomic = webf::AtomicString::CreateFromUTF8(tag_name); + Element* new_element = document->createElement(tag_name_atomic, shared_exception_state->exception_state); + if (shared_exception_state->exception_state.HasException()) { + return WebFValue::Null(); + } + + // Hold the reference until rust side notify this element was released. + WebFValueStatus* status_block = new_element->KeepAlive(); + return WebFValue(new_element, new_element->elementPublicMethods(), status_block); +} + +WebFValue DocumentPublicMethods::CreateElementWithElementCreationOptions( + webf::Document* ptr, + const char* tag_name, + WebFElementCreationOptions& options, + webf::SharedExceptionState* shared_exception_state) { + auto* document = static_cast(ptr); + MemberMutationScope scope{document->GetExecutingContext()}; + webf::AtomicString tag_name_atomic = webf::AtomicString::CreateFromUTF8(tag_name); + + std::string value = std::string(R"({"is":")") + options.is + "\"}"; + const char* value_cstr = value.c_str(); + webf::ScriptValue options_value = webf::ScriptValue::CreateJsonObject(document->ctx(), value_cstr, value.length()); + + Element* new_element = + document->createElement(tag_name_atomic, options_value, shared_exception_state->exception_state); + if (shared_exception_state->exception_state.HasException()) { + return WebFValue::Null(); + } + + // Hold the reference until rust side notify this element was released. + WebFValueStatus* status_block = new_element->KeepAlive(); + return WebFValue(new_element, new_element->elementPublicMethods(), status_block); +} + +WebFValue DocumentPublicMethods::CreateElementNS( + webf::Document* ptr, + const UTF8Char* uri, + const UTF8Char* tag_name, + webf::SharedExceptionState* shared_exception_state) { + auto* document = static_cast(ptr); + MemberMutationScope scope{document->GetExecutingContext()}; + webf::AtomicString uri_atomic = webf::AtomicString::CreateFromUTF8(uri); + webf::AtomicString tag_name_atomic = webf::AtomicString::CreateFromUTF8(tag_name); + Element* new_element = + document->createElementNS(uri_atomic, tag_name_atomic, shared_exception_state->exception_state); + if (shared_exception_state->exception_state.HasException()) { + return WebFValue::Null(); + } + + // Hold the reference until rust side notify this element was released. + WebFValueStatus* status_block = new_element->KeepAlive(); + return WebFValue(new_element, new_element->elementPublicMethods(), status_block); +} + +WebFValue DocumentPublicMethods::CreateElementNSWithElementCreationOptions( + webf::Document* ptr, + const UTF8Char* uri, + const UTF8Char* tag_name, + WebFElementCreationOptions& options, + webf::SharedExceptionState* shared_exception_state) { + auto* document = static_cast(ptr); + MemberMutationScope scope{document->GetExecutingContext()}; + webf::AtomicString uri_atomic = webf::AtomicString::CreateFromUTF8(uri); + webf::AtomicString tag_name_atomic = webf::AtomicString::CreateFromUTF8(tag_name); + + Element* new_element = + document->createElementNS(uri_atomic, tag_name_atomic, nullptr, shared_exception_state->exception_state); + if (shared_exception_state->exception_state.HasException()) { + return WebFValue::Null(); + } + + // Hold the reference until rust side notify this element was released. + WebFValueStatus* status_block = new_element->KeepAlive(); + return WebFValue(new_element, new_element->elementPublicMethods(), status_block); +} + +WebFValue DocumentPublicMethods::CreateTextNode( + webf::Document* ptr, + const UTF8Char* data, + webf::SharedExceptionState* shared_exception_state) { + auto* document = static_cast(ptr); + MemberMutationScope scope{document->GetExecutingContext()}; + webf::AtomicString data_atomic = webf::AtomicString::CreateFromUTF8(data); + Text* text_node = document->createTextNode(data_atomic, shared_exception_state->exception_state); + + if (shared_exception_state->exception_state.HasException()) { + return WebFValue::Null(); + } + + WebFValueStatus* status_block = text_node->KeepAlive(); + + return WebFValue(text_node, text_node->textNodePublicMethods(), status_block); +} + +WebFValue DocumentPublicMethods::CreateDocumentFragment( + webf::Document* ptr, + webf::SharedExceptionState* shared_exception_state) { + auto* document = static_cast(ptr); + MemberMutationScope scope{document->GetExecutingContext()}; + DocumentFragment* document_fragment = document->createDocumentFragment(shared_exception_state->exception_state); + + if (shared_exception_state->exception_state.HasException()) { + return WebFValue::Null(); + } + + WebFValueStatus* status_block = document_fragment->KeepAlive(); + + return WebFValue( + document_fragment, document_fragment->documentFragmentPublicMethods(), status_block); +} + +WebFValue DocumentPublicMethods::CreateComment( + webf::Document* ptr, + const UTF8Char* data, + webf::SharedExceptionState* shared_exception_state) { + auto* document = static_cast(ptr); + MemberMutationScope scope{document->GetExecutingContext()}; + webf::AtomicString data_atomic = webf::AtomicString::CreateFromUTF8(data); + Comment* comment = document->createComment(data_atomic, shared_exception_state->exception_state); + + if (shared_exception_state->exception_state.HasException()) { + return WebFValue::Null(); + } + + WebFValueStatus* status_block = comment->KeepAlive(); + + return WebFValue(comment, comment->commentPublicMethods(), status_block); +} + +WebFValue DocumentPublicMethods::CreateEvent( + webf::Document* ptr, + const UTF8Char* type, + webf::SharedExceptionState* shared_exception_state) { + auto* document = static_cast(ptr); + MemberMutationScope scope{document->GetExecutingContext()}; + webf::AtomicString type_atomic = webf::AtomicString::CreateFromUTF8(type); + Event* event = document->createEvent(type_atomic, shared_exception_state->exception_state); + + if (shared_exception_state->exception_state.HasException()) { + return WebFValue::Null(); + } + + WebFValueStatus* status_block = event->KeepAlive(); + + return WebFValue(event, event->eventPublicMethods(), status_block); +} + +WebFValue DocumentPublicMethods::QuerySelector( + webf::Document* ptr, + const UTF8Char* selectors, + webf::SharedExceptionState* shared_exception_state) { + auto* document = static_cast(ptr); + MemberMutationScope scope{document->GetExecutingContext()}; + webf::AtomicString selectors_atomic = webf::AtomicString::CreateFromUTF8(selectors); + Element* element = document->querySelector(selectors_atomic, shared_exception_state->exception_state); + + if (shared_exception_state->exception_state.HasException()) { + return WebFValue::Null(); + } + + WebFValueStatus* status_block = element->KeepAlive(); + + return WebFValue(element, element->elementPublicMethods(), status_block); +} + +WebFValue DocumentPublicMethods::GetElementById( + webf::Document* ptr, + const UTF8Char* id, + webf::SharedExceptionState* shared_exception_state) { + auto* document = static_cast(ptr); + MemberMutationScope scope{document->GetExecutingContext()}; + webf::AtomicString id_atomic = webf::AtomicString::CreateFromUTF8(id); + Element* element = document->getElementById(id_atomic, shared_exception_state->exception_state); + + if (shared_exception_state->exception_state.HasException()) { + return WebFValue::Null(); + } + + WebFValueStatus* status_block = element->KeepAlive(); + + return WebFValue(element, element->elementPublicMethods(), status_block); +} + +WebFValue DocumentPublicMethods::ElementFromPoint( + webf::Document* ptr, + double x, + double y, + webf::SharedExceptionState* shared_exception_state) { + auto* document = static_cast(ptr); + MemberMutationScope scope{document->GetExecutingContext()}; + Element* element = document->elementFromPoint(x, y, shared_exception_state->exception_state); + + if (shared_exception_state->exception_state.HasException()) { + return WebFValue::Null(); + } + + WebFValueStatus* status_block = element->KeepAlive(); + + return WebFValue(element, element->elementPublicMethods(), status_block); +} + +WebFValue DocumentPublicMethods::DocumentElement(webf::Document* document) { + auto* document_element = DynamicTo(document->documentElement()); + WebFValueStatus* status_block = document_element->KeepAlive(); + return WebFValue{document_element, document_element->htmlElementPublicMethods(), + status_block}; +} + +WebFValue DocumentPublicMethods::Head(webf::Document* document) { + auto* head = document->head(); + WebFValueStatus* status_block = head->KeepAlive(); + return WebFValue{head, head->htmlElementPublicMethods(), status_block}; +} + +WebFValue DocumentPublicMethods::Body(webf::Document* document) { + auto* body = document->body(); + WebFValueStatus* status_block = body->KeepAlive(); + return WebFValue{body, body->htmlElementPublicMethods(), status_block}; +} + +NativeValue DocumentPublicMethods::Cookie(webf::Document* document, + webf::SharedExceptionState* shared_exception_state) { + auto cookie = document->GetBindingProperty(binding_call_methods::kcookie, FlushUICommandReason::kDependentsOnElement, + shared_exception_state->exception_state); + if (shared_exception_state->exception_state.HasException()) { + return Native_NewNull(); + } + return cookie; +} + +void DocumentPublicMethods::SetCookie(webf::Document* document, + const UTF8Char* value, + webf::SharedExceptionState* shared_exception_state) { + webf::AtomicString value_atomic = webf::AtomicString::CreateFromUTF8(value); + document->SetBindingProperty(binding_call_methods::kcookie, + NativeValueConverter::ToNativeValue(document->ctx(), value_atomic), + shared_exception_state->exception_state); +} + +void DocumentPublicMethods::ClearCookie(webf::Document* document, webf::SharedExceptionState* shared_exception_state) { + document->InvokeBindingMethod(binding_call_methods::k___clear_cookies__, 0, nullptr, + FlushUICommandReason::kDependentsOnElement, shared_exception_state->exception_state); +} + +} // namespace webf diff --git a/bridge/core/api/document_fragment.cc b/bridge/core/api/document_fragment.cc new file mode 100644 index 0000000000..e2620ffa45 --- /dev/null +++ b/bridge/core/api/document_fragment.cc @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "plugin_api/document_fragment.h" +#include "core/dom/container_node.h" + +namespace webf {} // namespace webf diff --git a/bridge/core/api/element.cc b/bridge/core/api/element.cc new file mode 100644 index 0000000000..ca51e175e3 --- /dev/null +++ b/bridge/core/api/element.cc @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "plugin_api/element.h" +#include "core/api/exception_state.h" +#include "core/css/legacy/legacy_inline_css_style_declaration.h" +#include "core/dom/container_node.h" +#include "core/dom/element.h" +#include "foundation/utility/make_visitor.h" + +namespace webf { + +WebFValue ElementPublicMethods::Style(Element* ptr) { + auto* element = static_cast(ptr); + MemberMutationScope member_mutation_scope{element->GetExecutingContext()}; + auto style = element->style(); + + return std::visit( + MakeVisitor( + [&](legacy::LegacyInlineCssStyleDeclaration* styleDeclaration) { + WebFValueStatus* status_block = styleDeclaration->KeepAlive(); + return WebFValue( + styleDeclaration, styleDeclaration->legacyCssStyleDeclarationPublicMethods(), status_block); + }, + [](auto&&) { return WebFValue::Null(); }), + style); +} + +void ElementPublicMethods::ToBlob(Element* ptr, + WebFNativeFunctionContext* callback_context, + SharedExceptionState* shared_exception_state) { + auto* element = static_cast(ptr); + auto callback_impl = WebFNativeFunction::Create(callback_context, shared_exception_state); + return element->toBlob(callback_impl, shared_exception_state->exception_state); +} + +void ElementPublicMethods::ToBlobWithDevicePixelRatio(Element* ptr, + double device_pixel_ratio, + WebFNativeFunctionContext* callback_context, + SharedExceptionState* shared_exception_state) { + auto* element = static_cast(ptr); + auto callback_impl = WebFNativeFunction::Create(callback_context, shared_exception_state); + return element->toBlob(device_pixel_ratio, callback_impl, shared_exception_state->exception_state); +} + +} // namespace webf diff --git a/bridge/core/api/event_target.cc b/bridge/core/api/event_target.cc new file mode 100644 index 0000000000..68046382c5 --- /dev/null +++ b/bridge/core/api/event_target.cc @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "plugin_api/event_target.h" +#include "core/api/exception_state.h" +#include "core/dom/comment.h" +#include "core/dom/container_node.h" +#include "core/dom/document.h" +#include "core/dom/document_fragment.h" +#include "core/dom/element.h" +#include "core/dom/events/event.h" +#include "core/dom/events/event_target.h" +#include "core/dom/node.h" +#include "core/dom/text.h" +#include "core/frame/window.h" +#include "core/html/canvas/html_canvas_element.h" +#include "core/html/html_element.h" +#include "core/html/html_image_element.h" +#include "html_element_type_helper.h" +#include "plugin_api/add_event_listener_options.h" +#include "plugin_api/webf_event_listener.h" + +namespace webf { + +void EventTargetPublicMethods::AddEventListener(EventTarget* event_target, + const UTF8Char* event_name_str, + WebFEventListenerContext* callback_context, + WebFAddEventListenerOptions* options, + SharedExceptionState* shared_exception_state) { + AtomicString event_name = AtomicString::CreateFromUTF8(event_name_str); + std::shared_ptr event_listener_options = AddEventListenerOptions::Create(); + + // Preparing for the event listener options. + event_listener_options->setOnce(options->once); + event_listener_options->setPassive(options->passive); + event_listener_options->setCapture(options->capture); + + auto listener_impl = WebFPublicPluginEventListener::Create(callback_context, shared_exception_state); + + event_target->addEventListener(event_name, listener_impl, event_listener_options, + shared_exception_state->exception_state); +} + +void EventTargetPublicMethods::RemoveEventListener(EventTarget* event_target, + const char* event_name_str, + WebFEventListenerContext* callback_context, + SharedExceptionState* shared_exception_state) { + AtomicString event_name = AtomicString::CreateFromUTF8(event_name_str); + auto listener_impl = WebFPublicPluginEventListener::Create(callback_context, shared_exception_state); + + event_target->removeEventListener(event_name, listener_impl, shared_exception_state->exception_state); +} + +bool EventTargetPublicMethods::DispatchEvent(EventTarget* event_target, + Event* event, + SharedExceptionState* shared_exception_state) { + return event_target->dispatchEvent(event, shared_exception_state->exception_state); +} + +void EventTargetPublicMethods::Release(EventTarget* event_target) { + event_target->ReleaseAlive(); +} + +WebFValue EventTargetPublicMethods::DynamicTo(webf::EventTarget* event_target, + webf::EventTargetType event_target_type) { + switch (event_target_type) { + case EventTargetType::kEventTarget: { + WebFValueStatus* status_block = event_target->KeepAlive(); + return WebFValue(event_target, event_target->eventTargetPublicMethods(), + status_block); + } + case EventTargetType::kNode: { + auto* node = webf::DynamicTo(event_target); + if (node == nullptr) { + return WebFValue::Null(); + } + WebFValueStatus* status_block = node->KeepAlive(); + return WebFValue(node, node->nodePublicMethods(), status_block); + } + case EventTargetType::kContainerNode: { + auto* container_node = webf::DynamicTo(event_target); + if (container_node == nullptr) { + return WebFValue::Null(); + } + WebFValueStatus* status_block = container_node->KeepAlive(); + return WebFValue(container_node, container_node->containerNodePublicMethods(), + status_block); + } + case EventTargetType::kWindow: { + auto* window = webf::DynamicTo(event_target); + if (window == nullptr) { + return WebFValue::Null(); + } + WebFValueStatus* status_block = window->KeepAlive(); + return WebFValue(window, window->windowPublicMethods(), status_block); + } + case EventTargetType::kDocument: { + auto* document = webf::DynamicTo(event_target); + if (document == nullptr) { + return WebFValue::Null(); + } + WebFValueStatus* status_block = static_cast(document)->KeepAlive(); + return WebFValue(static_cast(document), document->documentPublicMethods(), status_block); + } + case EventTargetType::kElement: { + auto* element = webf::DynamicTo(event_target); + if (element == nullptr) { + return WebFValue::Null(); + } + WebFValueStatus* status_block = element->KeepAlive(); + return WebFValue(element, element->elementPublicMethods(), status_block); + } + case EventTargetType::kHTMLDivElement: + case EventTargetType::kHTMLScriptElement: + case EventTargetType::HTMLElement: { + auto* html_element = webf::DynamicTo(event_target); + if (html_element == nullptr) { + return WebFValue::Null(); + } + WebFValueStatus* status_block = html_element->KeepAlive(); + return WebFValue(html_element, html_element->htmlElementPublicMethods(), + status_block); + } + case EventTargetType::kHTMLImageElement: { + auto* html_image_element = webf::DynamicTo(event_target); + if (html_image_element == nullptr) { + return WebFValue::Null(); + } + WebFValueStatus* status_block = html_image_element->KeepAlive(); + return WebFValue( + html_image_element, html_image_element->htmlImageElementPublicMethods(), status_block); + } + case EventTargetType::kDocumentFragment: { + auto* document_fragment = webf::DynamicTo(event_target); + if (document_fragment == nullptr) { + return WebFValue::Null(); + } + WebFValueStatus* status_block = document_fragment->KeepAlive(); + return WebFValue( + document_fragment, document_fragment->documentFragmentPublicMethods(), status_block); + } + case EventTargetType::kText: { + auto* text = webf::DynamicTo(event_target); + if (text == nullptr) { + return WebFValue::Null(); + } + WebFValueStatus* status_block = text->KeepAlive(); + return WebFValue(text, text->textNodePublicMethods(), status_block); + } + case EventTargetType::kComment: { + auto* comment = webf::DynamicTo(event_target); + if (comment == nullptr) { + return WebFValue::Null(); + } + WebFValueStatus* status_block = comment->KeepAlive(); + return WebFValue(comment, comment->commentPublicMethods(), status_block); + } + case EventTargetType::kHTMLCanvasElement: { + auto* canvas_element = webf::DynamicTo(event_target); + if (canvas_element == nullptr) { + return WebFValue::Null(); + } + WebFValueStatus* status_block = canvas_element->KeepAlive(); + return WebFValue(canvas_element, canvas_element->htmlCanvasElementPublicMethods(), + status_block); + } + default: + assert_m(false, ("Unknown event_target_type " + std::to_string(static_cast(event_target_type))).c_str()); + } +} + +} // namespace webf diff --git a/bridge/core/api/exception_state.cc b/bridge/core/api/exception_state.cc new file mode 100644 index 0000000000..9e08067136 --- /dev/null +++ b/bridge/core/api/exception_state.cc @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "plugin_api/exception_state.h" +#include "bindings/qjs/exception_state.h" +#include "core/api/exception_state.h" +#include "core/executing_context.h" + +namespace webf { + +SharedExceptionState::SharedExceptionState() = default; + +bool ExceptionStatePublicMethods::HasException(SharedExceptionState* shared_exception_state) { + return shared_exception_state->exception_state.HasException(); +} + +void ExceptionStatePublicMethods::Stringify(webf::ExecutingContext* context, + webf::SharedExceptionState* shared_exception_state, + char** errmsg, + uint32_t* strlen) { + context->HandleException(shared_exception_state->exception_state, errmsg, strlen); +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/api/exception_state.h b/bridge/core/api/exception_state.h new file mode 100644 index 0000000000..4ee3abc31c --- /dev/null +++ b/bridge/core/api/exception_state.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef EXCEPTION_STATE_H +#define EXCEPTION_STATE_H + +#include "bindings/qjs/exception_state.h" +#include "plugin_api/rust_readable.h" + +namespace webf { + +class SharedExceptionState : public RustReadable { + public: + SharedExceptionState(); + + ExceptionState exception_state; +}; + +} // namespace webf + +#endif // EXCEPTION_STATE_H diff --git a/bridge/core/api/executing_context.cc b/bridge/core/api/executing_context.cc new file mode 100644 index 0000000000..54cbcd7d35 --- /dev/null +++ b/bridge/core/api/executing_context.cc @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "plugin_api/executing_context.h" +#include "bindings/qjs/exception_state.h" +#include "core/api/exception_state.h" +#include "core/dom/document.h" +#include "core/executing_context.h" +#include "core/frame/legacy/location.h" +#include "core/frame/module_manager.h" +#include "core/frame/window.h" +#include "core/frame/window_or_worker_global_scope.h" +#include "core/timing/performance.h" +#include "foundation/native_value_converter.h" + +namespace webf { + +WebFValue ExecutingContextWebFMethods::document(webf::ExecutingContext* context) { + auto* document = context->document(); + WebFValueStatus* status_block = document->KeepAlive(); + return WebFValue(document, document->documentPublicMethods(), status_block); +} + +WebFValue ExecutingContextWebFMethods::window(webf::ExecutingContext* context) { + return WebFValue(context->window(), context->window()->windowPublicMethods(), + context->window()->KeepAlive()); +} + +WebFValue ExecutingContextWebFMethods::performance( + webf::ExecutingContext* context) { + return WebFValue( + context->performance(), context->performance()->performancePublicMethods(), context->performance()->KeepAlive()); +} + +WebFValue ExecutingContextWebFMethods::CreateExceptionState() { + return WebFValue(new SharedExceptionState(), + ExceptionState::publicMethodPointer(), nullptr); +} + +void ExecutingContextWebFMethods::FinishRecordingUIOperations(webf::ExecutingContext* context) { + context->uiCommandBuffer()->AddCommand(UICommand::kFinishRecordingCommand, nullptr, nullptr, nullptr, false); +} + +void ExecutingContextWebFMethods::WebFSyncBuffer(webf::ExecutingContext* context) { + context->uiCommandBuffer()->SyncAllPackages(); + context->dartMethodPtr()->requestBatchUpdate(context->isDedicated(), context->contextId()); +} + +struct ImageSnapshotNativeFunctionContext { + ExecutingContext* context_; + std::shared_ptr function_; +}; + +void ExecutingContextWebFMethods::WebFMatchImageSnapshot(webf::ExecutingContext* context, + NativeValue* bytes, + NativeValue* filename, + WebFNativeFunctionContext* callback_context, + SharedExceptionState* shared_exception_state) { + auto callback_impl = WebFNativeFunction::Create(callback_context, shared_exception_state); + auto imageBytes = static_cast(bytes->u.ptr); + auto filenameNativeString = static_cast(filename->u.ptr); + auto* nativeFunctionContext = new ImageSnapshotNativeFunctionContext{context, callback_impl}; + + context->FlushUICommand(context->window(), FlushUICommandReason::kDependentsAll); + + auto fn = [](void* ptr, double contextId, int8_t result, char* errmsg) { + auto* reader = static_cast(ptr); + auto* context = reader->context_; + + reader->context_->dartIsolateContext()->dispatcher()->PostToJs( + context->isDedicated(), context->contextId(), + [](ImageSnapshotNativeFunctionContext* reader, int8_t result, char* errmsg) { + if (errmsg != nullptr) { + NativeValue error_object = Native_NewCString(errmsg); + reader->function_->Invoke(reader->context_, 1, &error_object); + dart_free(errmsg); + } else { + auto params = new NativeValue[2]; + params[0] = Native_NewNull(); + params[1] = Native_NewInt64(result); + reader->function_->Invoke(reader->context_, 2, params); + } + + reader->context_->RunRustFutureTasks(); + delete reader; + }, + reader, result, errmsg); + }; + + context->dartMethodPtr()->matchImageSnapshot(context->isDedicated(), nativeFunctionContext, context->contextId(), + imageBytes, bytes->uint32, filenameNativeString, fn); +} + +void ExecutingContextWebFMethods::WebFMatchImageSnapshotBytes(webf::ExecutingContext* context, + NativeValue* imageA, + NativeValue* imageB, + WebFNativeFunctionContext* callback_context, + SharedExceptionState* shared_exception_state) { + auto callback_impl = WebFNativeFunction::Create(callback_context, shared_exception_state); + auto imageABytes = static_cast(imageA->u.ptr); + auto imageBBytes = static_cast(imageB->u.ptr); + + auto* nativeFunctionContext = new ImageSnapshotNativeFunctionContext{context, callback_impl}; + + context->FlushUICommand(context->window(), FlushUICommandReason::kDependentsAll); + + auto fn = [](void* ptr, double contextId, int8_t result, char* errmsg) { + auto* reader = static_cast(ptr); + auto* context = reader->context_; + + reader->context_->dartIsolateContext()->dispatcher()->PostToJs( + context->isDedicated(), context->contextId(), + [](ImageSnapshotNativeFunctionContext* reader, int8_t result, char* errmsg) { + if (errmsg != nullptr) { + NativeValue error_object = Native_NewCString(errmsg); + reader->function_->Invoke(reader->context_, 1, &error_object); + dart_free(errmsg); + } else { + auto params = new NativeValue[2]; + params[0] = Native_NewNull(); + params[1] = Native_NewInt64(result); + reader->function_->Invoke(reader->context_, 2, params); + } + + reader->context_->RunRustFutureTasks(); + delete reader; + }, + reader, result, errmsg); + }; + + context->dartMethodPtr()->matchImageSnapshotBytes(context->isDedicated(), nativeFunctionContext, context->contextId(), + imageABytes, imageA->uint32, imageBBytes, imageB->uint32, fn); +} + +NativeValue ExecutingContextWebFMethods::WebFInvokeModule(ExecutingContext* context, + const char* module_name, + const char* method, + SharedExceptionState* shared_exception_state) { + AtomicString module_name_atomic = AtomicString::CreateFromUTF8(module_name); + AtomicString method_atomic = AtomicString::CreateFromUTF8(method); + + ScriptValue result = ModuleManager::__webf_invoke_module__(context, module_name_atomic, method_atomic, + shared_exception_state->exception_state); + NativeValue return_result = result.ToNative(context->ctx(), shared_exception_state->exception_state); + + if (shared_exception_state->exception_state.HasException()) { + return Native_NewNull(); + } + + return return_result; +} + +NativeValue ExecutingContextWebFMethods::WebFInvokeModuleWithParams(ExecutingContext* context, + const UTF8Char* module_name, + const UTF8Char* method, + NativeValue* params, + SharedExceptionState* shared_exception_state) { + AtomicString module_name_atomic = AtomicString::CreateFromUTF8(module_name); + AtomicString method_atomic = AtomicString::CreateFromUTF8(method); + + const NativeValue* result = ModuleManager::__webf_invoke_module__(context, module_name_atomic, method_atomic, *params, + nullptr, shared_exception_state->exception_state); + + if (shared_exception_state->exception_state.HasException() || result == nullptr) { + return Native_NewNull(); + } + + NativeValue return_result = *result; + return return_result; +} + +NativeValue ExecutingContextWebFMethods::WebFInvokeModuleWithParamsAndCallback( + ExecutingContext* context, + const UTF8Char* module_name, + const UTF8Char* method, + NativeValue* params, + WebFNativeFunctionContext* callback_context, + SharedExceptionState* shared_exception_state) { + AtomicString module_name_atomic = AtomicString::CreateFromUTF8(module_name); + AtomicString method_atomic = webf::AtomicString::CreateFromUTF8(method); + + auto callback_impl = WebFNativeFunction::Create(callback_context, shared_exception_state); + + const NativeValue* result = ModuleManager::__webf_invoke_module__( + context, module_name_atomic, method_atomic, *params, callback_impl, shared_exception_state->exception_state); + + if (shared_exception_state->exception_state.HasException()) { + return Native_NewNull(); + } + + NativeValue return_result = *result; + return return_result; +} + +void ExecutingContextWebFMethods::WebFLocationReload(ExecutingContext* context, + SharedExceptionState* shared_exception_state) { + Location::__webf_location_reload__(context, shared_exception_state->exception_state); +} + +int32_t ExecutingContextWebFMethods::SetTimeout(ExecutingContext* context, + WebFNativeFunctionContext* callback_context, + int32_t timeout, + SharedExceptionState* shared_exception_state) { + auto callback_impl = WebFNativeFunction::Create(callback_context, shared_exception_state); + + return WindowOrWorkerGlobalScope::setTimeout(context, callback_impl, timeout, + shared_exception_state->exception_state); +} + +int32_t ExecutingContextWebFMethods::SetInterval(ExecutingContext* context, + WebFNativeFunctionContext* callback_context, + int32_t timeout, + SharedExceptionState* shared_exception_state) { + auto callback_impl = WebFNativeFunction::Create(callback_context, shared_exception_state); + + return WindowOrWorkerGlobalScope::setInterval(context, callback_impl, timeout, + shared_exception_state->exception_state); +} + +void ExecutingContextWebFMethods::ClearTimeout(ExecutingContext* context, + int32_t timeout_id, + SharedExceptionState* shared_exception_state) { + WindowOrWorkerGlobalScope::clearTimeout(context, timeout_id, shared_exception_state->exception_state); +} + +void ExecutingContextWebFMethods::ClearInterval(ExecutingContext* context, + int32_t interval_id, + SharedExceptionState* shared_exception_state) { + WindowOrWorkerGlobalScope::clearInterval(context, interval_id, shared_exception_state->exception_state); +} + +int32_t ExecutingContextWebFMethods::AddRustFutureTask(ExecutingContext* context, + WebFNativeFunctionContext* callback_context, + NativeLibraryMetaData* meta_data, + SharedExceptionState* shared_exception_state) { + auto callback_impl = WebFNativeFunction::Create(callback_context, shared_exception_state); + + return context->AddRustFutureTask(callback_impl, meta_data); +} + +void ExecutingContextWebFMethods::RemoveRustFutureTask(ExecutingContext* context, + int32_t callback_id, + NativeLibraryMetaData* meta_data, + SharedExceptionState* shared_exception_state) { + context->RemoveRustFutureTask(callback_id, meta_data); +} + +} // namespace webf diff --git a/bridge/core/api/html_canvas_element.cc b/bridge/core/api/html_canvas_element.cc new file mode 100644 index 0000000000..53b7b5f158 --- /dev/null +++ b/bridge/core/api/html_canvas_element.cc @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "plugin_api/html_canvas_element.h" + +namespace webf {} // namespace webf \ No newline at end of file diff --git a/bridge/core/api/html_element.cc b/bridge/core/api/html_element.cc new file mode 100644 index 0000000000..35bdbdfef0 --- /dev/null +++ b/bridge/core/api/html_element.cc @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "plugin_api/html_element.h" + +namespace webf {} // namespace webf \ No newline at end of file diff --git a/bridge/core/api/html_image_element.cc b/bridge/core/api/html_image_element.cc new file mode 100644 index 0000000000..044c874116 --- /dev/null +++ b/bridge/core/api/html_image_element.cc @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "plugin_api/html_image_element.h" + +namespace webf {} // namespace webf \ No newline at end of file diff --git a/bridge/core/api/node.cc b/bridge/core/api/node.cc new file mode 100644 index 0000000000..4bb0e67c4a --- /dev/null +++ b/bridge/core/api/node.cc @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "plugin_api/node.h" +#include "core/api/exception_state.h" +#include "core/dom/events/event_target.h" +#include "core/dom/node.h" + +namespace webf { + +NodePublicMethods::NodePublicMethods() {} + +WebFValue NodePublicMethods::AppendChild(Node* self_node, + Node* new_node, + SharedExceptionState* shared_exception_state) { + MemberMutationScope member_mutation_scope{self_node->GetExecutingContext()}; + Node* returned_node = self_node->appendChild(new_node, shared_exception_state->exception_state); + if (shared_exception_state->exception_state.HasException()) { + return WebFValue::Null(); + } + + WebFValueStatus* status_block = returned_node->KeepAlive(); + return WebFValue(returned_node, returned_node->nodePublicMethods(), status_block); +} + +WebFValue NodePublicMethods::RemoveChild(webf::Node* self_node, + webf::Node* target_node, + webf::SharedExceptionState* shared_exception_state) { + MemberMutationScope member_mutation_scope{self_node->GetExecutingContext()}; + Node* returned_node = self_node->removeChild(target_node, shared_exception_state->exception_state); + if (shared_exception_state->exception_state.HasException()) { + return WebFValue::Null(); + } + + WebFValueStatus* status_block = returned_node->KeepAlive(); + return WebFValue(returned_node, returned_node->nodePublicMethods(), status_block); +} + +} // namespace webf diff --git a/bridge/core/api/script_value_ref.cc b/bridge/core/api/script_value_ref.cc new file mode 100644 index 0000000000..2a1b8a845b --- /dev/null +++ b/bridge/core/api/script_value_ref.cc @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "plugin_api/script_value_ref.h" +#include "core/api/exception_state.h" +#include "core/native/script_value_ref.h" + +#include "string/wtf_string.h" + +namespace webf { + +// TODO(CGQAQ): this need allocate memory +const UTF8Char* ScriptValueRefPublicMethods::ToString(webf::ScriptValueRef* script_value_ref, + webf::SharedExceptionState* shared_exception_state) { + if (script_value_ref->script_value.IsString()) { + auto value = script_value_ref->script_value.ToAtomicString(script_value_ref->context->ctx()); + // TODO(CGQAQ): this is not right at all, UAF + auto str =value.ToUTF8String(); + auto* leak = new UTF8Char[str.size()]; + memcpy(leak, str.c_str(), str.size() + 1); + return leak; + } + shared_exception_state->exception_state.ThrowException(script_value_ref->context->ctx(), webf::ErrorType::TypeError, + "Value is not a string."); + return nullptr; +} + +void ScriptValueRefPublicMethods::SetAsString(webf::ScriptValueRef* script_value_ref, + const UTF8Char* value, + webf::SharedExceptionState* shared_exception_state) { + webf::AtomicString value_atomic = webf::AtomicString::CreateFromUTF8(value); + script_value_ref->script_value = webf::ScriptValue(script_value_ref->context->ctx(), value_atomic); +} + +void ScriptValueRefPublicMethods::Release(webf::ScriptValueRef* script_value_ref) { + delete script_value_ref; +} + +} // namespace webf diff --git a/bridge/core/api/text.cc b/bridge/core/api/text.cc new file mode 100644 index 0000000000..3b364518f6 --- /dev/null +++ b/bridge/core/api/text.cc @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "plugin_api/text.h" +#include "core/dom/character_data.h" + +namespace webf {} // namespace webf \ No newline at end of file diff --git a/bridge/core/api/window.cc b/bridge/core/api/window.cc new file mode 100644 index 0000000000..8b63090369 --- /dev/null +++ b/bridge/core/api/window.cc @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024-present The OpenWebF Company. All rights reserved. + * Licensed under GNU GPL with Enterprise exception. + */ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "plugin_api/window.h" +#include "core/api/exception_state.h" +#include "core/dom/events/event_target.h" +#include "core/frame/window.h" +#include "core/frame/window_or_worker_global_scope.h" + +namespace webf { + +void WindowPublicMethods::ScrollToWithXAndY(Window* window, + double x, + double y, + SharedExceptionState* shared_exception_state) { + window->scrollTo(x, y, shared_exception_state->exception_state); +} + +double WindowPublicMethods::RequestAnimationFrame(Window* window, + WebFNativeFunctionContext* callback_context, + SharedExceptionState* shared_exception_state) { + auto callback_impl = WebFNativeFunction::Create(callback_context, shared_exception_state); + return WindowOrWorkerGlobalScope::requestAnimationFrame(window->GetExecutingContext(), callback_impl, shared_exception_state->exception_state); +} + +} // namespace webf diff --git a/bridge/core/base/auto_reset.h b/bridge/core/base/auto_reset.h new file mode 100644 index 0000000000..40c6bc77b5 --- /dev/null +++ b/bridge/core/base/auto_reset.h @@ -0,0 +1,54 @@ +// Copyright 2011 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#ifndef WEBF_AUTO_RESET_H +#define WEBF_AUTO_RESET_H + +#include + +namespace webf { + +template +class [[maybe_unused, nodiscard]] AutoReset { + public: + template + AutoReset(T * scoped_variable, U && new_value) + : scoped_variable_(scoped_variable), + original_value_(std::exchange(*scoped_variable_, std::forward(new_value))) {} + + // A constructor that's useful for asserting the old value of + // `scoped_variable`, especially when it's inconvenient to check this before + // constructing the AutoReset object (e.g. in a class member initializer + // list). + template + AutoReset(T * scoped_variable, U && new_value, const T& expected_old_value) : AutoReset(scoped_variable, new_value) { + DCHECK_EQ(original_value_, expected_old_value); + } + + AutoReset(AutoReset && other) + : scoped_variable_(std::exchange(other.scoped_variable_, nullptr)), + original_value_(std::move(other.original_value_)) {} + + AutoReset& operator=(AutoReset&& rhs) { + scoped_variable_ = std::exchange(rhs.scoped_variable_, nullptr); + original_value_ = std::move(rhs.original_value_); + return *this; + } + + ~AutoReset() { + if (scoped_variable_) + *scoped_variable_ = std::move(original_value_); + } + + private: + T* scoped_variable_; + + T original_value_; +}; + +} // namespace webf + +#endif // WEBF_AUTO_RESET_H diff --git a/bridge/core/base/bit_cast.h b/bridge/core/base/bit_cast.h new file mode 100644 index 0000000000..f5ec029451 --- /dev/null +++ b/bridge/core/base/bit_cast.h @@ -0,0 +1,38 @@ +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#ifndef WEBF_BIT_CAST_H_ +#define WEBF_BIT_CAST_H_ + +#include + +namespace webf { + +// This is an equivalent to C++20's std::bit_cast<>(), but with additional +// warnings. It morally does what `*reinterpret_cast(&source)` does, but +// the cast/deref pair is undefined behavior, while bit_cast<>() isn't. +// +// This is not a magic "get out of UB free" card. This must only be used on +// values, not on references or pointers. For pointers, use +// reinterpret_cast<>(), and then look at https://eel.is/c++draft/basic.lval#11 +// as that's probably UB also. + +template +constexpr Dest bit_cast(const Source& source) { + static_assert(!std::is_pointer_v, "bit_cast must not be used on pointer types"); + static_assert(!std::is_pointer_v, "bit_cast must not be used on pointer types"); + static_assert(!std::is_reference_v, "bit_cast must not be used on reference types"); + static_assert(!std::is_reference_v, "bit_cast must not be used on reference types"); + static_assert(sizeof(Dest) == sizeof(Source), "bit_cast requires source and destination types to be the same size"); + static_assert(std::is_trivially_copyable_v, "bit_cast requires the source type to be trivially copyable"); + static_assert(std::is_trivially_copyable_v, "bit_cast requires the destination type to be trivially copyable"); + + return __builtin_bit_cast(Dest, source); +} + +} // namespace webf + +#endif // WEBF_BIT_CAST_H_ diff --git a/bridge/core/base/bit_field.h b/bridge/core/base/bit_field.h new file mode 100644 index 0000000000..dd5791c729 --- /dev/null +++ b/bridge/core/base/bit_field.h @@ -0,0 +1,147 @@ +// Copyright 2020 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#ifndef WEBF_BIT_FIELD_H +#define WEBF_BIT_FIELD_H + +#include +#include +#include +#include +#include "foundation/macros.h" + +namespace webf { + +// TOOD(omerkatz): Replace these casts with std::atomic_ref (C++20) once it +// becomes available. +template +FORCE_INLINE std::atomic* AsAtomicPtr(T* t) { + return reinterpret_cast*>(t); +} + +template +FORCE_INLINE const std::atomic* AsAtomicPtr(const T* t) { + return reinterpret_cast*>(t); +} + +enum class BitFieldValueConstness { + kNonConst, + kConst, +}; + +namespace internal { + +template +class BitFieldBase; + +// Helper class for defining values in a bit field. This helper provides +// utilities to read, write and update the value in the bit field. +template +class BitFieldValue final { + static_assert(std::is_fundamental::value, "Fields in a bit field must be of a primitive type."); + static_assert(std::is_fundamental::value, "Bit fields must be of a primitive type."); + static_assert(std::is_unsigned::value, "Bit field must be of an unsigned type"); + static_assert(sizeof(ValueType) <= sizeof(BitFieldType), "Value in bit field cannot be bigger than the bit field"); + static_assert(offset < 8 * sizeof(BitFieldType), "Field offset in bit field must be smaller than the bit field size"); + static_assert(size < 8 * sizeof(BitFieldType), "Field size in bit field must be smaller than the bit field size"); + static_assert(offset + size <= 8 * sizeof(BitFieldType), "Field in bit field cannot overflow the bit field"); + static_assert(size > 0, "Bit field fields cannot have 0 size."); + + public: + using Type = ValueType; + + template + using DefineNextValue = BitFieldValue; + + // Create a bit field with the given value. + static constexpr BitFieldType encode(ValueType value) { + assert(is_valid(value)); + return static_cast(value) << offset; + } + + // Update a bit field with the given value. + static constexpr BitFieldType update(BitFieldType previous, ValueType value) { + return (previous & ~kMask) | encode(value); + } + + // Read the value from the bit field. + static constexpr ValueType decode(BitFieldType value) { return static_cast((value & kMask) >> offset); } + + private: + static constexpr BitFieldValueConstness kIsConst = is_const; + + static constexpr BitFieldType kValidationMask = (BitFieldType{1} << size) - BitFieldType{1}; + static constexpr BitFieldType kMask = (kValidationMask) << offset; + static_assert(kMask != 0, "Mask in which all bits are 0 is not allowed."); + static_assert(~kMask != 0, "Mask in which all bits are 1 is not allowed."); + + // Confirm that the provided value fits into the bit field. + static constexpr bool is_valid(ValueType value) { return (static_cast(value) & ~kValidationMask) == 0; } + + friend class BitFieldBase; +}; + +} // namespace internal + +// BitField intended to be used by a single thread. +template +class SingleThreadedBitField { + static_assert(std::is_fundamental::value, "Bit fields must be of a primitive type."); + static_assert(std::is_unsigned::value, "Bit field must be of an unsigned type"); + + public: + template + using DefineFirstValue = internal::BitFieldValue; + + explicit SingleThreadedBitField() : SingleThreadedBitField(0) {} + explicit SingleThreadedBitField(BitFieldType bits) : bits_(bits) {} + + template + typename Value::Type get() const { + return Value::decode(bits_); + } + + template + void set(typename Value::Type value) { + bits_ = Value::update(bits_, value); + } + + BitFieldType bits() const { return bits_; } + + protected: + BitFieldType bits_; +}; + +// BitField that can be written by a single thread but read by multiple threads. +template +class ConcurrentlyReadBitField : public SingleThreadedBitField { + using Base = SingleThreadedBitField; + using Base::bits_; + + public: + explicit ConcurrentlyReadBitField() : Base(0) {} + explicit ConcurrentlyReadBitField(BitFieldType bits) : Base(bits) {} + + template + typename Value::Type get_concurrently() const { + return Value::decode(AsAtomicPtr(&bits_)->load(std::memory_order_relaxed)); + } + + template + void set(typename Value::Type value) { + AsAtomicPtr(&bits_)->store(Value::update(bits_, value), std::memory_order_relaxed); + } +}; + +} // namespace webf + +#endif // WEBF_BIT_FIELD_H diff --git a/bridge/core/base/bits.h b/bridge/core/base/bits.h new file mode 100644 index 0000000000..604ec94dce --- /dev/null +++ b/bridge/core/base/bits.h @@ -0,0 +1,215 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file defines some bit utilities. + +#ifndef BASE_BITS_H_ +#define BASE_BITS_H_ + +#include +#include +#include +#include + +#include + +#include "core/base/build_config.h" +#include "core/base/compiler_specific.h" +#include "foundation/macros.h" + +#if defined(COMPILER_MSVC) +#include +#endif + +namespace base { +namespace bits { + +// Returns true iff |value| is a power of 2. +template ::value>> +constexpr bool IsPowerOfTwo(T value) { + // From "Hacker's Delight": Section 2.1 Manipulating Rightmost Bits. + // + // Only positive integers with a single bit set are powers of two. If only one + // bit is set in x (e.g. 0b00000100000000) then |x-1| will have that bit set + // to zero and all bits to its right set to 1 (e.g. 0b00000011111111). Hence + // |x & (x-1)| is 0 iff x is a power of two. + return value > 0 && (value & (value - 1)) == 0; +} + +// Round down |size| to a multiple of alignment, which must be a power of two. +inline constexpr size_t AlignDown(size_t size, size_t alignment) { + DCHECK(IsPowerOfTwo(alignment)); + return size & ~(alignment - 1); +} + +// Move |ptr| back to the previous multiple of alignment, which must be a power +// of two. Defined for types where sizeof(T) is one byte. +template ::type> +inline T* AlignDown(T* ptr, size_t alignment) { + return reinterpret_cast(AlignDown(reinterpret_cast(ptr), alignment)); +} + +// Round up |size| to a multiple of alignment, which must be a power of two. +inline constexpr size_t AlignUp(size_t size, size_t alignment) { + DCHECK(IsPowerOfTwo(alignment)); + return (size + alignment - 1) & ~(alignment - 1); +} + +// Advance |ptr| to the next multiple of alignment, which must be a power of +// two. Defined for types where sizeof(T) is one byte. +template ::type> +inline T* AlignUp(T* ptr, size_t alignment) { + return reinterpret_cast(AlignUp(reinterpret_cast(ptr), alignment)); +} + +// CountLeadingZeroBits(value) returns the number of zero bits following the +// most significant 1 bit in |value| if |value| is non-zero, otherwise it +// returns {sizeof(T) * 8}. +// Example: 00100010 -> 2 +// +// CountTrailingZeroBits(value) returns the number of zero bits preceding the +// least significant 1 bit in |value| if |value| is non-zero, otherwise it +// returns {sizeof(T) * 8}. +// Example: 00100010 -> 1 +// +// C does not have an operator to do this, but fortunately the various +// compilers have built-ins that map to fast underlying processor instructions. +// +// Prefer the clang path on Windows, as _BitScanReverse() and friends are not +// constexpr. +#if defined(COMPILER_MSVC) && !defined(__clang__) + +template +ALWAYS_INLINE typename std::enable_if::value && sizeof(T) <= 4, unsigned>::type +CountLeadingZeroBits(T x) { + static_assert(bits > 0, "invalid instantiation"); + unsigned long index; + return LIKELY(_BitScanReverse(&index, static_cast(x))) ? (31 - index - (32 - bits)) : bits; +} + +template +ALWAYS_INLINE typename std::enable_if::value && sizeof(T) == 8, unsigned>::type +CountLeadingZeroBits(T x) { + static_assert(bits > 0, "invalid instantiation"); + unsigned long index; +// MSVC only supplies _BitScanReverse64 when building for a 64-bit target. +#if defined(ARCH_CPU_64_BITS) + return LIKELY(_BitScanReverse64(&index, static_cast(x))) ? (63 - index) : 64; +#else + uint32_t left = static_cast(x >> 32); + if (LIKELY(_BitScanReverse(&index, left))) + return 31 - index; + + uint32_t right = static_cast(x); + if (LIKELY(_BitScanReverse(&index, right))) + return 63 - index; + + return 64; +#endif +} + +template +ALWAYS_INLINE typename std::enable_if::value && sizeof(T) <= 4, unsigned>::type +CountTrailingZeroBits(T x) { + static_assert(bits > 0, "invalid instantiation"); + unsigned long index; + return LIKELY(_BitScanForward(&index, static_cast(x))) ? index : bits; +} + +template +ALWAYS_INLINE typename std::enable_if::value && sizeof(T) == 8, unsigned>::type +CountTrailingZeroBits(T x) { + static_assert(bits > 0, "invalid instantiation"); + unsigned long index; +// MSVC only supplies _BitScanForward64 when building for a 64-bit target. +#if defined(ARCH_CPU_64_BITS) + return LIKELY(_BitScanForward64(&index, static_cast(x))) ? index : 64; +#else + uint32_t right = static_cast(x); + if (LIKELY(_BitScanForward(&index, right))) + return index; + + uint32_t left = static_cast(x >> 32); + if (LIKELY(_BitScanForward(&index, left))) + return 32 + index; + + return 64; +#endif +} + +ALWAYS_INLINE uint32_t CountLeadingZeroBits32(uint32_t x) { + return CountLeadingZeroBits(x); +} + +ALWAYS_INLINE uint64_t CountLeadingZeroBits64(uint64_t x) { + return CountLeadingZeroBits(x); +} + +#elif defined(COMPILER_GCC) || defined(__clang__) + +// __builtin_clz has undefined behaviour for an input of 0, even though there's +// clearly a return value that makes sense, and even though some processor clz +// instructions have defined behaviour for 0. We could drop to raw __asm__ to +// do better, but we'll avoid doing that unless we see proof that we need to. +template +ALWAYS_INLINE constexpr typename std::enable_if::value && sizeof(T) <= 8, unsigned>::type +CountLeadingZeroBits(T value) { + static_assert(bits > 0, "invalid instantiation"); + return LIKELY(value) ? bits == 64 ? __builtin_clzll(static_cast(value)) + : __builtin_clz(static_cast(value)) - (32 - bits) + : bits; +} + +template +ALWAYS_INLINE constexpr typename std::enable_if::value && sizeof(T) <= 8, unsigned>::type +CountTrailingZeroBits(T value) { + return LIKELY(value) + ? bits == 64 ? __builtin_ctzll(static_cast(value)) : __builtin_ctz(static_cast(value)) + : bits; +} + +ALWAYS_INLINE constexpr uint32_t CountLeadingZeroBits32(uint32_t x) { + return CountLeadingZeroBits(x); +} + +ALWAYS_INLINE constexpr uint64_t CountLeadingZeroBits64(uint64_t x) { + return CountLeadingZeroBits(x); +} + +#endif + +ALWAYS_INLINE constexpr size_t CountLeadingZeroBitsSizeT(size_t x) { + return CountLeadingZeroBits(x); +} + +ALWAYS_INLINE constexpr size_t CountTrailingZeroBitsSizeT(size_t x) { + return CountTrailingZeroBits(x); +} + +// Returns the integer i such as 2^i <= n < 2^(i+1) +constexpr int Log2Floor(uint32_t n) { + return 31 - CountLeadingZeroBits(n); +} + +// Returns the integer i such as 2^(i-1) < n <= 2^i +constexpr int Log2Ceiling(uint32_t n) { + // When n == 0, we want the function to return -1. + // When n == 0, (n - 1) will underflow to 0xFFFFFFFF, which is + // why the statement below starts with (n ? 32 : -1). + return (n ? 32 : -1) - CountLeadingZeroBits(n - 1); +} + +// Returns a value of type T with a single bit set in the left-most position. +// Can be used instead of manually shifting a 1 to the left. +template +constexpr T LeftmostBit() { + static_assert(std::is_integral::value, "This function can only be used with integral types."); + T one(1u); + return one << ((CHAR_BIT * sizeof(T) - 1)); +} + +} // namespace bits +} // namespace base + +#endif // BASE_BITS_H_ \ No newline at end of file diff --git a/bridge/core/base/build_config.h b/bridge/core/base/build_config.h new file mode 100644 index 0000000000..1b6dfcd262 --- /dev/null +++ b/bridge/core/base/build_config.h @@ -0,0 +1,408 @@ +// Copyright 2012 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file doesn't belong to any GN target by design for faster build and +// less developer overhead. + +// This file adds build flags about the OS we're currently building on. They are +// defined directly in this file instead of via a `buildflag_header` target in a +// GN file for faster build. They are defined using the corresponding OS defines +// (e.g. OS_WIN) which are also defined in this file (except for OS_CHROMEOS, +// which is set by the build system). These defines are deprecated and should +// NOT be used directly. For example: +// Please Use: #if BUILDFLAG(IS_WIN) +// Deprecated: #if defined(OS_WIN) +// +// Operating System: +// IS_AIX / IS_ANDROID / IS_ASMJS / IS_CHROMEOS / IS_FREEBSD / IS_FUCHSIA / +// IS_IOS / IS_IOS_MACCATALYST / IS_IOS_VISION / IS_IOS_WATCH / IS_LINUX / +// IS_MAC / IS_NACL / IS_NETBSD / IS_OPENBSD / IS_QNX / IS_SOLARIS / IS_WIN +// Operating System family: +// IS_APPLE: MAC or IOS or IOS_MACCATALYST or IOS_VISION or IOS_WATCH +// IS_IOS: IOS or IOS_MACCATALYST or IOS_VISION or IOS_WATCH +// IS_BSD: FREEBSD or NETBSD or OPENBSD +// IS_POSIX: AIX or ANDROID or ASMJS or CHROMEOS or FREEBSD or IOS +// or IOS_MACCATALYST or IOS_VISION or IOS_WATCH or LINUX or MAC +// or NACL or NETBSD or OPENBSD or QNX or SOLARIS + +// This file also adds defines specific to the platform, architecture etc. +// +// Platform: +// IS_OZONE +// +// Compiler: +// COMPILER_MSVC / COMPILER_GCC +// +// Processor: +// ARCH_CPU_ARM64 / ARCH_CPU_ARMEL / ARCH_CPU_LOONGARCH32 / +// ARCH_CPU_LOONGARCH64 / ARCH_CPU_MIPS / ARCH_CPU_MIPS64 / +// ARCH_CPU_MIPS64EL / ARCH_CPU_MIPSEL / ARCH_CPU_PPC64 / ARCH_CPU_S390 / +// ARCH_CPU_S390X / ARCH_CPU_X86 / ARCH_CPU_X86_64 / ARCH_CPU_RISCV64 +// Processor family: +// ARCH_CPU_ARM_FAMILY: ARMEL or ARM64 +// ARCH_CPU_LOONGARCH_FAMILY: LOONGARCH32 or LOONGARCH64 +// ARCH_CPU_MIPS_FAMILY: MIPS64EL or MIPSEL or MIPS64 or MIPS +// ARCH_CPU_PPC64_FAMILY: PPC64 +// ARCH_CPU_S390_FAMILY: S390 or S390X +// ARCH_CPU_X86_FAMILY: X86 or X86_64 +// ARCH_CPU_RISCV_FAMILY: Riscv64 +// Processor features: +// ARCH_CPU_31_BITS / ARCH_CPU_32_BITS / ARCH_CPU_64_BITS +// ARCH_CPU_BIG_ENDIAN / ARCH_CPU_LITTLE_ENDIAN + +#ifndef BUILD_BUILD_CONFIG_H_ +#define BUILD_BUILD_CONFIG_H_ + +#include "core/base/buildflag.h" // IWYU pragma: export + +// Clangd does not detect BUILDFLAG_INTERNAL_* indirect usage, so mark the +// header as "always_keep" to avoid "unused include" warning. +// +// IWYU pragma: always_keep + +// A set of macros to use for platform detection. +#if defined(__native_client__) +// __native_client__ must be first, so that other OS_ defines are not set. +#define OS_NACL 1 +#elif defined(ANDROID) +#define OS_ANDROID 1 +#elif defined(__APPLE__) +// Only include TargetConditionals after testing ANDROID as some Android builds +// on the Mac have this header available and it's not needed unless the target +// is really an Apple platform. +#include +#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE +#define OS_IOS 1 +// Catalyst is the technology that allows running iOS apps on macOS. These +// builds are both OS_IOS and OS_IOS_MACCATALYST. +#if defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST +#define OS_IOS_MACCATALYST +#endif // defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST +#if defined(TARGET_OS_VISION) && TARGET_OS_VISION +#define OS_IOS_VISION 1 +#endif // defined(TARGET_OS_VISION) && TARGET_OS_VISION +#if defined(TARGET_OS_WATCH) && TARGET_OS_WATCH +#define OS_IOS_WATCH 1 +#endif // defined(TARGET_OS_WATCH) && TARGET_OS_WATCH +#else +#define OS_MAC 1 +#endif // defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE +#elif defined(__linux__) +#if !defined(OS_CHROMEOS) +// Do not define OS_LINUX on Chrome OS build. +// The OS_CHROMEOS macro is defined in GN. +#define OS_LINUX 1 +#endif // !defined(OS_CHROMEOS) +// Include a system header to pull in features.h for glibc/uclibc macros. +#include +#if defined(__GLIBC__) && !defined(__UCLIBC__) +// We really are using glibc, not uClibc pretending to be glibc. +#define LIBC_GLIBC 1 +#endif +#elif defined(_WIN32) +#define OS_WIN 1 +#elif defined(__Fuchsia__) +#define OS_FUCHSIA 1 +#elif defined(__FreeBSD__) +#define OS_FREEBSD 1 +#elif defined(__NetBSD__) +#define OS_NETBSD 1 +#elif defined(__OpenBSD__) +#define OS_OPENBSD 1 +#elif defined(__sun) +#define OS_SOLARIS 1 +#elif defined(__QNXNTO__) +#define OS_QNX 1 +#elif defined(_AIX) +#define OS_AIX 1 +#elif defined(__asmjs__) || defined(__wasm__) +#define OS_ASMJS 1 +#elif defined(__MVS__) +#define OS_ZOS 1 +#else +#error Please add support for your platform in build/build_config.h +#endif +// NOTE: Adding a new port? Please follow +// https://chromium.googlesource.com/chromium/src/+/main/docs/new_port_policy.md + +#if defined(OS_MAC) || defined(OS_IOS) +#define OS_APPLE 1 +#endif + +// For access to standard BSD features, use OS_BSD instead of a +// more specific macro. +#if defined(OS_FREEBSD) || defined(OS_NETBSD) || defined(OS_OPENBSD) +#define OS_BSD 1 +#endif + +// For access to standard POSIXish features, use OS_POSIX instead of a +// more specific macro. +#if defined(OS_AIX) || defined(OS_ANDROID) || defined(OS_ASMJS) || defined(OS_FREEBSD) || defined(OS_IOS) || \ + defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_MAC) || defined(OS_NACL) || defined(OS_NETBSD) || \ + defined(OS_OPENBSD) || defined(OS_QNX) || defined(OS_SOLARIS) || defined(OS_ZOS) +#define OS_POSIX 1 +#endif + +// OS build flags +#if defined(OS_AIX) +#define BUILDFLAG_INTERNAL_IS_AIX() (1) +#else +#define BUILDFLAG_INTERNAL_IS_AIX() (0) +#endif + +#if defined(OS_ANDROID) +#define BUILDFLAG_INTERNAL_IS_ANDROID() (1) +#else +#define BUILDFLAG_INTERNAL_IS_ANDROID() (0) +#endif + +#if defined(OS_APPLE) +#define BUILDFLAG_INTERNAL_IS_APPLE() (1) +#else +#define BUILDFLAG_INTERNAL_IS_APPLE() (0) +#endif + +#if defined(OS_ASMJS) +#define BUILDFLAG_INTERNAL_IS_ASMJS() (1) +#else +#define BUILDFLAG_INTERNAL_IS_ASMJS() (0) +#endif + +#if defined(OS_BSD) +#define BUILDFLAG_INTERNAL_IS_BSD() (1) +#else +#define BUILDFLAG_INTERNAL_IS_BSD() (0) +#endif + +#if defined(OS_CHROMEOS) +#define BUILDFLAG_INTERNAL_IS_CHROMEOS() (1) +#else +#define BUILDFLAG_INTERNAL_IS_CHROMEOS() (0) +#endif + +#if defined(OS_FREEBSD) +#define BUILDFLAG_INTERNAL_IS_FREEBSD() (1) +#else +#define BUILDFLAG_INTERNAL_IS_FREEBSD() (0) +#endif + +#if defined(OS_FUCHSIA) +#define BUILDFLAG_INTERNAL_IS_FUCHSIA() (1) +#else +#define BUILDFLAG_INTERNAL_IS_FUCHSIA() (0) +#endif + +#if defined(OS_IOS) +#define BUILDFLAG_INTERNAL_IS_IOS() (1) +#else +#define BUILDFLAG_INTERNAL_IS_IOS() (0) +#endif + +#if defined(OS_IOS_MACCATALYST) +#define BUILDFLAG_INTERNAL_IS_IOS_MACCATALYST() (1) +#else +#define BUILDFLAG_INTERNAL_IS_IOS_MACCATALYST() (0) +#endif + +#if defined(OS_IOS_VISION) +#define BUILDFLAG_INTERNAL_IS_IOS_VISION() (1) +#else +#define BUILDFLAG_INTERNAL_IS_IOS_VISION() (0) +#endif + +#if defined(OS_IOS_WATCH) +#define BUILDFLAG_INTERNAL_IS_IOS_WATCH() (1) +#else +#define BUILDFLAG_INTERNAL_IS_IOS_WATCH() (0) +#endif + +#if defined(OS_LINUX) +#define BUILDFLAG_INTERNAL_IS_LINUX() (1) +#else +#define BUILDFLAG_INTERNAL_IS_LINUX() (0) +#endif + +#if defined(OS_MAC) +#define BUILDFLAG_INTERNAL_IS_MAC() (1) +#else +#define BUILDFLAG_INTERNAL_IS_MAC() (0) +#endif + +#if defined(OS_NACL) +#define BUILDFLAG_INTERNAL_IS_NACL() (1) +#else +#define BUILDFLAG_INTERNAL_IS_NACL() (0) +#endif + +#if defined(OS_NETBSD) +#define BUILDFLAG_INTERNAL_IS_NETBSD() (1) +#else +#define BUILDFLAG_INTERNAL_IS_NETBSD() (0) +#endif + +#if defined(OS_OPENBSD) +#define BUILDFLAG_INTERNAL_IS_OPENBSD() (1) +#else +#define BUILDFLAG_INTERNAL_IS_OPENBSD() (0) +#endif + +#if defined(OS_POSIX) +#define BUILDFLAG_INTERNAL_IS_POSIX() (1) +#else +#define BUILDFLAG_INTERNAL_IS_POSIX() (0) +#endif + +#if defined(OS_QNX) +#define BUILDFLAG_INTERNAL_IS_QNX() (1) +#else +#define BUILDFLAG_INTERNAL_IS_QNX() (0) +#endif + +#if defined(OS_SOLARIS) +#define BUILDFLAG_INTERNAL_IS_SOLARIS() (1) +#else +#define BUILDFLAG_INTERNAL_IS_SOLARIS() (0) +#endif + +#if defined(OS_WIN) +#define BUILDFLAG_INTERNAL_IS_WIN() (1) +#else +#define BUILDFLAG_INTERNAL_IS_WIN() (0) +#endif + +#if defined(USE_OZONE) +#define BUILDFLAG_INTERNAL_IS_OZONE() (1) +#else +#define BUILDFLAG_INTERNAL_IS_OZONE() (0) +#endif + +// Compiler detection. Note: clang masquerades as GCC on POSIX and as MSVC on +// Windows. +#if defined(__GNUC__) +#define COMPILER_GCC 1 +#elif defined(_MSC_VER) +#define COMPILER_MSVC 1 +#else +#error Please add support for your compiler in build/build_config.h +#endif + +// Processor architecture detection. For more info on what's defined, see: +// http://msdn.microsoft.com/en-us/library/b0084kay.aspx +// http://www.agner.org/optimize/calling_conventions.pdf +// or with gcc, run: "echo | gcc -E -dM -" +#if defined(_M_X64) || defined(__x86_64__) +#define ARCH_CPU_X86_FAMILY 1 +#define ARCH_CPU_X86_64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(_M_IX86) || defined(__i386__) +#define ARCH_CPU_X86_FAMILY 1 +#define ARCH_CPU_X86 1 +#define ARCH_CPU_32_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(__s390x__) +#define ARCH_CPU_S390_FAMILY 1 +#define ARCH_CPU_S390X 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#elif defined(__s390__) +#define ARCH_CPU_S390_FAMILY 1 +#define ARCH_CPU_S390 1 +#define ARCH_CPU_31_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#elif (defined(__PPC64__) || defined(__PPC__)) && defined(__BIG_ENDIAN__) +#define ARCH_CPU_PPC64_FAMILY 1 +#define ARCH_CPU_PPC64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#elif defined(__PPC64__) +#define ARCH_CPU_PPC64_FAMILY 1 +#define ARCH_CPU_PPC64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(__ARMEL__) +#define ARCH_CPU_ARM_FAMILY 1 +#define ARCH_CPU_ARMEL 1 +#define ARCH_CPU_32_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(__aarch64__) || defined(_M_ARM64) +#define ARCH_CPU_ARM_FAMILY 1 +#define ARCH_CPU_ARM64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(__pnacl__) || defined(__asmjs__) || defined(__wasm__) +#define ARCH_CPU_32_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(__MIPSEL__) +#if defined(__LP64__) +#define ARCH_CPU_MIPS_FAMILY 1 +#define ARCH_CPU_MIPS64EL 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#else +#define ARCH_CPU_MIPS_FAMILY 1 +#define ARCH_CPU_MIPSEL 1 +#define ARCH_CPU_32_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#endif +#elif defined(__MIPSEB__) +#if defined(__LP64__) +#define ARCH_CPU_MIPS_FAMILY 1 +#define ARCH_CPU_MIPS64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#else +#define ARCH_CPU_MIPS_FAMILY 1 +#define ARCH_CPU_MIPS 1 +#define ARCH_CPU_32_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#endif +#elif defined(__loongarch__) +#define ARCH_CPU_LOONGARCH_FAMILY 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#if __loongarch_grlen == 64 +#define ARCH_CPU_LOONGARCH64 1 +#define ARCH_CPU_64_BITS 1 +#else +#define ARCH_CPU_LOONGARCH32 1 +#define ARCH_CPU_32_BITS 1 +#endif +#elif defined(__riscv) && (__riscv_xlen == 64) +#define ARCH_CPU_RISCV_FAMILY 1 +#define ARCH_CPU_RISCV64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#else +#error Please add support for your architecture in build/build_config.h +#endif + +// Type detection for wchar_t. +#if defined(OS_WIN) +#define WCHAR_T_IS_16_BIT +#elif defined(OS_FUCHSIA) +#define WCHAR_T_IS_32_BIT +#elif defined(OS_POSIX) && defined(COMPILER_GCC) && defined(__WCHAR_MAX__) && \ + (__WCHAR_MAX__ == 0x7fffffff || __WCHAR_MAX__ == 0xffffffff) +#define WCHAR_T_IS_32_BIT +#elif defined(OS_POSIX) && defined(COMPILER_GCC) && defined(__WCHAR_MAX__) && \ + (__WCHAR_MAX__ == 0x7fff || __WCHAR_MAX__ == 0xffff) +// On Posix, we'll detect short wchar_t, but projects aren't guaranteed to +// compile in this mode (in particular, Chrome doesn't). This is intended for +// other projects using base who manage their own dependencies and make sure +// short wchar works for them. +#define WCHAR_T_IS_16_BIT +#else +#error Please add support for your compiler in build/build_config.h +#endif + +#if defined(OS_ANDROID) +// The compiler thinks std::string::const_iterator and "const char*" are +// equivalent types. +#define STD_STRING_ITERATOR_IS_CHAR_POINTER +// The compiler thinks std::u16string::const_iterator and "char16*" are +// equivalent types. +#define BASE_STRING16_ITERATOR_IS_CHAR16_POINTER +#endif + +#endif // BUILD_BUILD_CONFIG_H_ \ No newline at end of file diff --git a/bridge/core/base/buildflag.h b/bridge/core/base/buildflag.h new file mode 100644 index 0000000000..865901af72 --- /dev/null +++ b/bridge/core/base/buildflag.h @@ -0,0 +1,47 @@ +// Copyright 2015 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUILD_BUILDFLAG_H_ +#define BUILD_BUILDFLAG_H_ + +// These macros un-mangle the names of the build flags in a way that looks +// natural, and gives errors if the flag is not defined. Normally in the +// preprocessor it's easy to make mistakes that interpret "you haven't done +// the setup to know what the flag is" as "flag is off". Normally you would +// include the generated header rather than include this file directly. +// +// This is for use with generated headers. See build/buildflag_header.gni. + +// This dance of two macros does a concatenation of two preprocessor args using +// ## doubly indirectly because using ## directly prevents macros in that +// parameter from being expanded. +#define BUILDFLAG_CAT_INDIRECT(a, b) a##b +#define BUILDFLAG_CAT(a, b) BUILDFLAG_CAT_INDIRECT(a, b) + +// Accessor for build flags. +// +// To test for a value, if the build file specifies: +// +// ENABLE_FOO=true +// +// Then you would check at build-time in source code with: +// +// #include "foo_flags.h" // The header the build file specified. +// +// #if BUILDFLAG(ENABLE_FOO) +// ... +// #endif +// +// There will no #define called ENABLE_FOO so if you accidentally test for +// whether that is defined, it will always be negative. You can also use +// the value in expressions: +// +// const char kSpamServerName[] = BUILDFLAG(SPAM_SERVER_NAME); +// +// Because the flag is accessed as a preprocessor macro with (), an error +// will be thrown if the proper header defining the internal flag value has +// not been included. +#define BUILDFLAG(flag) (BUILDFLAG_CAT(BUILDFLAG_INTERNAL_, flag)()) + +#endif // BUILD_BUILDFLAG_H_ \ No newline at end of file diff --git a/bridge/core/base/compiler_specific.h b/bridge/core/base/compiler_specific.h new file mode 100644 index 0000000000..e6bbe51854 --- /dev/null +++ b/bridge/core/base/compiler_specific.h @@ -0,0 +1,635 @@ +// Copyright 2012 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_COMPILER_SPECIFIC_H_ +#define BASE_COMPILER_SPECIFIC_H_ + +#include "core/base/build_config.h" + +#if defined(COMPILER_MSVC) && !defined(__clang__) +#error "Only clang-cl is supported on Windows, see https://crbug.com/988071" +#endif + +// This is a wrapper around `__has_cpp_attribute`, which can be used to test for +// the presence of an attribute. In case the compiler does not support this +// macro it will simply evaluate to 0. +// +// References: +// https://wg21.link/sd6#testing-for-the-presence-of-an-attribute-__has_cpp_attribute +// https://wg21.link/cpp.cond#:__has_cpp_attribute +#if defined(__has_cpp_attribute) +#define HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) +#else +#define HAS_CPP_ATTRIBUTE(x) 0 +#endif + +// A wrapper around `__has_attribute`, similar to HAS_CPP_ATTRIBUTE. +#if defined(__has_attribute) +#define HAS_ATTRIBUTE(x) __has_attribute(x) +#else +#define HAS_ATTRIBUTE(x) 0 +#endif + +// A wrapper around `__has_builtin`, similar to HAS_CPP_ATTRIBUTE. +#if defined(__has_builtin) +#define HAS_BUILTIN(x) __has_builtin(x) +#else +#define HAS_BUILTIN(x) 0 +#endif + +// Annotate a function indicating it should not be inlined. +// Use like: +// NOINLINE void DoStuff() { ... } +#if defined(__clang__) && HAS_ATTRIBUTE(noinline) +#define NOINLINE [[clang::noinline]] +#elif defined(COMPILER_GCC) && HAS_ATTRIBUTE(noinline) +#define NOINLINE __attribute__((noinline)) +#elif defined(COMPILER_MSVC) +#define NOINLINE __declspec(noinline) +#else +#define NOINLINE +#endif + +// Annotate a function indicating it should not be optimized. +#if defined(__clang__) && HAS_ATTRIBUTE(optnone) +#define NOOPT [[clang::optnone]] +#elif defined(COMPILER_GCC) && HAS_ATTRIBUTE(optimize) +#define NOOPT __attribute__((optimize(0))) +#else +#define NOOPT +#endif + +#if defined(__clang__) && defined(NDEBUG) && HAS_ATTRIBUTE(always_inline) +#define ALWAYS_INLINE [[clang::always_inline]] inline +#elif defined(COMPILER_GCC) && defined(NDEBUG) && HAS_ATTRIBUTE(always_inline) +#define ALWAYS_INLINE inline __attribute__((__always_inline__)) +#elif defined(COMPILER_MSVC) && defined(NDEBUG) +#define ALWAYS_INLINE __forceinline +#else +#define ALWAYS_INLINE inline +#endif + +// Annotate a function indicating it should never be tail called. Useful to make +// sure callers of the annotated function are never omitted from call-stacks. +// To provide the complementary behavior (prevent the annotated function from +// being omitted) look at NOINLINE. Also note that this doesn't prevent code +// folding of multiple identical caller functions into a single signature. To +// prevent code folding, see NO_CODE_FOLDING() in base/debug/alias.h. +// Use like: +// NOT_TAIL_CALLED void FooBar(); +#if defined(__clang__) && HAS_ATTRIBUTE(not_tail_called) +#define NOT_TAIL_CALLED [[clang::not_tail_called]] +#else +#define NOT_TAIL_CALLED +#endif + +// Annotate a function indicating it must be tail called. +// Can be used only on return statements, even for functions returning void. +// Caller and callee must have the same number of arguments and its types must +// be "similar". +#if defined(__clang__) && HAS_ATTRIBUTE(musttail) +#define MUSTTAIL [[clang::musttail]] +#else +#define MUSTTAIL +#endif + +// Specify memory alignment for structs, classes, etc. +// Use like: +// class ALIGNAS(16) MyClass { ... } +// ALIGNAS(16) int array[4]; +// +// In most places you can use the C++11 keyword "alignas", which is preferred. +// +// Historically, compilers had trouble mixing __attribute__((...)) syntax with +// alignas(...) syntax. However, at least Clang is very accepting nowadays. It +// may be that this macro can be removed entirely. +#if defined(__clang__) +#define ALIGNAS(byte_alignment) alignas(byte_alignment) +#elif defined(COMPILER_MSVC) +#define ALIGNAS(byte_alignment) __declspec(align(byte_alignment)) +#elif defined(COMPILER_GCC) && HAS_ATTRIBUTE(aligned) +#define ALIGNAS(byte_alignment) __attribute__((aligned(byte_alignment))) +#endif + +// In case the compiler supports it NO_UNIQUE_ADDRESS evaluates to the C++20 +// attribute [[no_unique_address]]. This allows annotating data members so that +// they need not have an address distinct from all other non-static data members +// of its class. +// +// References: +// * https://en.cppreference.com/w/cpp/language/attributes/no_unique_address +// * https://wg21.link/dcl.attr.nouniqueaddr +#if defined(COMPILER_MSVC) && HAS_CPP_ATTRIBUTE(msvc::no_unique_address) +// Unfortunately MSVC ignores [[no_unique_address]] (see +// https://devblogs.microsoft.com/cppblog/msvc-cpp20-and-the-std-cpp20-switch/#msvc-extensions-and-abi), +// and clang-cl matches it for ABI compatibility reasons. We need to prefer +// [[msvc::no_unique_address]] when available if we actually want any effect. +#define NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] +#elif HAS_CPP_ATTRIBUTE(no_unique_address) +#define NO_UNIQUE_ADDRESS [[no_unique_address]] +#else +#define NO_UNIQUE_ADDRESS +#endif + +// Tells the compiler a function is using a printf-style format string. +// |format_param| is the one-based index of the format string parameter; +// |dots_param| is the one-based index of the "..." parameter. +// For v*printf functions (which take a va_list), pass 0 for dots_param. +// (This is undocumented but matches what the system C headers do.) +// For member functions, the implicit this parameter counts as index 1. +#if (defined(COMPILER_GCC) || defined(__clang__)) && HAS_ATTRIBUTE(format) +#define PRINTF_FORMAT(format_param, dots_param) __attribute__((format(printf, format_param, dots_param))) +#else +#define PRINTF_FORMAT(format_param, dots_param) +#endif + +// WPRINTF_FORMAT is the same, but for wide format strings. +// This doesn't appear to yet be implemented in any compiler. +// See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=38308 . +#define WPRINTF_FORMAT(format_param, dots_param) +// If available, it would look like: +// __attribute__((format(wprintf, format_param, dots_param))) + +// Sanitizers annotations. +#if HAS_ATTRIBUTE(no_sanitize) +#define NO_SANITIZE(what) __attribute__((no_sanitize(what))) +#endif +#if !defined(NO_SANITIZE) +#define NO_SANITIZE(what) +#endif + +// MemorySanitizer annotations. +#if defined(MEMORY_SANITIZER) && !BUILDFLAG(IS_NACL) +#include + +// Mark a memory region fully initialized. +// Use this to annotate code that deliberately reads uninitialized data, for +// example a GC scavenging root set pointers from the stack. +#define MSAN_UNPOISON(p, size) __msan_unpoison(p, size) + +// Check a memory region for initializedness, as if it was being used here. +// If any bits are uninitialized, crash with an MSan report. +// Use this to sanitize data which MSan won't be able to track, e.g. before +// passing data to another process via shared memory. +#define MSAN_CHECK_MEM_IS_INITIALIZED(p, size) __msan_check_mem_is_initialized(p, size) +#else // MEMORY_SANITIZER +#define MSAN_UNPOISON(p, size) +#define MSAN_CHECK_MEM_IS_INITIALIZED(p, size) +#endif // MEMORY_SANITIZER + +// DISABLE_CFI_PERF -- Disable Control Flow Integrity for perf reasons. +#if !defined(DISABLE_CFI_PERF) +#if defined(__clang__) && defined(OFFICIAL_BUILD) +#define DISABLE_CFI_PERF NO_SANITIZE("cfi") +#else +#define DISABLE_CFI_PERF +#endif +#endif + +// DISABLE_CFI_ICALL -- Disable Control Flow Integrity indirect call checks. +// Security Note: if you just need to allow calling of dlsym functions use +// DISABLE_CFI_DLSYM. +#if !defined(DISABLE_CFI_ICALL) +#if BUILDFLAG(IS_WIN) +// Windows also needs __declspec(guard(nocf)). +#define DISABLE_CFI_ICALL NO_SANITIZE("cfi-icall") __declspec(guard(nocf)) +#else +#define DISABLE_CFI_ICALL NO_SANITIZE("cfi-icall") +#endif +#endif +#if !defined(DISABLE_CFI_ICALL) +#define DISABLE_CFI_ICALL +#endif + +// DISABLE_CFI_DLSYM -- applies DISABLE_CFI_ICALL on platforms where dlsym +// functions must be called. Retains CFI checks on platforms where loaded +// modules participate in CFI (e.g. Windows). +#if !defined(DISABLE_CFI_DLSYM) +#if BUILDFLAG(IS_WIN) +// Windows modules register functions when loaded so can be checked by CFG. +#define DISABLE_CFI_DLSYM +#else +#define DISABLE_CFI_DLSYM DISABLE_CFI_ICALL +#endif +#endif +#if !defined(DISABLE_CFI_DLSYM) +#define DISABLE_CFI_DLSYM +#endif + +// Macro useful for writing cross-platform function pointers. +#if !defined(CDECL) +#if BUILDFLAG(IS_WIN) +#define CDECL __cdecl +#else // BUILDFLAG(IS_WIN) +#define CDECL +#endif // BUILDFLAG(IS_WIN) +#endif // !defined(CDECL) + +// Macro for hinting that an expression is likely to be false. +#if !defined(UNLIKELY) +#if defined(COMPILER_GCC) || defined(__clang__) +#define UNLIKELY(x) __builtin_expect(!!(x), 0) +#else +#define UNLIKELY(x) (x) +#endif // defined(COMPILER_GCC) +#endif // !defined(UNLIKELY) + +#if !defined(LIKELY) +#if defined(COMPILER_GCC) || defined(__clang__) +#define LIKELY(x) __builtin_expect(!!(x), 1) +#else +#define LIKELY(x) (x) +#endif // defined(COMPILER_GCC) +#endif // !defined(LIKELY) + +// Compiler feature-detection. +// clang.llvm.org/docs/LanguageExtensions.html#has-feature-and-has-extension +#if defined(__has_feature) +#define HAS_FEATURE(FEATURE) __has_feature(FEATURE) +#else +#define HAS_FEATURE(FEATURE) 0 +#endif + +#if defined(COMPILER_GCC) +#define PRETTY_FUNCTION __PRETTY_FUNCTION__ +#elif defined(COMPILER_MSVC) +#define PRETTY_FUNCTION __FUNCSIG__ +#else +// See https://en.cppreference.com/w/c/language/function_definition#func +#define PRETTY_FUNCTION __func__ +#endif + +#if !defined(CPU_ARM_NEON) +#if defined(__arm__) +#if !defined(__ARMEB__) && !defined(__ARM_EABI__) && !defined(__EABI__) && !defined(__VFP_FP__) && \ + !defined(_WIN32_WCE) && !defined(ANDROID) +#error Chromium does not support middle endian architecture +#endif +#if defined(__ARM_NEON__) +#define CPU_ARM_NEON 1 +#endif +#endif // defined(__arm__) +#endif // !defined(CPU_ARM_NEON) + +#if !defined(HAVE_MIPS_MSA_INTRINSICS) +#if defined(__mips_msa) && defined(__mips_isa_rev) && (__mips_isa_rev >= 5) +#define HAVE_MIPS_MSA_INTRINSICS 1 +#endif +#endif + +#if defined(__clang__) && HAS_ATTRIBUTE(uninitialized) +// Attribute "uninitialized" disables -ftrivial-auto-var-init=pattern for +// the specified variable. +// Library-wide alternative is +// 'configs -= [ "//build/config/compiler:default_init_stack_vars" ]' in .gn +// file. +// +// See "init_stack_vars" in build/config/compiler/BUILD.gn and +// http://crbug.com/977230 +// "init_stack_vars" is enabled for non-official builds and we hope to enable it +// in official build in 2020 as well. The flag writes fixed pattern into +// uninitialized parts of all local variables. In rare cases such initialization +// is undesirable and attribute can be used: +// 1. Degraded performance +// In most cases compiler is able to remove additional stores. E.g. if memory is +// never accessed or properly initialized later. Preserved stores mostly will +// not affect program performance. However if compiler failed on some +// performance critical code we can get a visible regression in a benchmark. +// 2. memset, memcpy calls +// Compiler may replaces some memory writes with memset or memcpy calls. This is +// not -ftrivial-auto-var-init specific, but it can happen more likely with the +// flag. It can be a problem if code is not linked with C run-time library. +// +// Note: The flag is security risk mitigation feature. So in future the +// attribute uses should be avoided when possible. However to enable this +// mitigation on the most of the code we need to be less strict now and minimize +// number of exceptions later. So if in doubt feel free to use attribute, but +// please document the problem for someone who is going to cleanup it later. +// E.g. platform, bot, benchmark or test name in patch description or next to +// the attribute. +#define STACK_UNINITIALIZED [[clang::uninitialized]] +#else +#define STACK_UNINITIALIZED +#endif + +// Attribute "no_stack_protector" disables -fstack-protector for the specified +// function. +// +// "stack_protector" is enabled on most POSIX builds. The flag adds a canary +// to each stack frame, which on function return is checked against a reference +// canary. If the canaries do not match, it's likely that a stack buffer +// overflow has occurred, so immediately crashing will prevent exploitation in +// many cases. +// +// In some cases it's desirable to remove this, e.g. on hot functions, or if +// we have purposely changed the reference canary. +#if defined(COMPILER_GCC) || defined(__clang__) +#if HAS_ATTRIBUTE(__no_stack_protector__) +#define NO_STACK_PROTECTOR __attribute__((__no_stack_protector__)) +#else +#define NO_STACK_PROTECTOR __attribute__((__optimize__("-fno-stack-protector"))) +#endif +#else +#define NO_STACK_PROTECTOR +#endif + +// The ANALYZER_ASSUME_TRUE(bool arg) macro adds compiler-specific hints +// to Clang which control what code paths are statically analyzed, +// and is meant to be used in conjunction with assert & assert-like functions. +// The expression is passed straight through if analysis isn't enabled. +// +// ANALYZER_SKIP_THIS_PATH() suppresses static analysis for the current +// codepath and any other branching codepaths that might follow. +#if defined(__clang_analyzer__) + +inline constexpr bool AnalyzerNoReturn() __attribute__((analyzer_noreturn)) { + return false; +} + +inline constexpr bool AnalyzerAssumeTrue(bool arg) { + // AnalyzerNoReturn() is invoked and analysis is terminated if |arg| is + // false. + return arg || AnalyzerNoReturn(); +} + +#define ANALYZER_ASSUME_TRUE(arg) ::AnalyzerAssumeTrue(!!(arg)) +#define ANALYZER_SKIP_THIS_PATH() static_cast(::AnalyzerNoReturn()) + +#else // !defined(__clang_analyzer__) + +#define ANALYZER_ASSUME_TRUE(arg) (arg) +#define ANALYZER_SKIP_THIS_PATH() + +#endif // defined(__clang_analyzer__) + +// Use nomerge attribute to disable optimization of merging multiple same calls. +#if defined(__clang__) && HAS_ATTRIBUTE(nomerge) +#define NOMERGE [[clang::nomerge]] +#else +#define NOMERGE +#endif + +// Marks a type as being eligible for the "trivial" ABI despite having a +// non-trivial destructor or copy/move constructor. Such types can be relocated +// after construction by simply copying their memory, which makes them eligible +// to be passed in registers. The canonical example is std::unique_ptr. +// +// Use with caution; this has some subtle effects on constructor/destructor +// ordering and will be very incorrect if the type relies on its address +// remaining constant. When used as a function argument (by value), the value +// may be constructed in the caller's stack frame, passed in a register, and +// then used and destructed in the callee's stack frame. A similar thing can +// occur when values are returned. +// +// TRIVIAL_ABI is not needed for types which have a trivial destructor and +// copy/move constructors, such as base::TimeTicks and other POD. +// +// It is also not likely to be effective on types too large to be passed in one +// or two registers on typical target ABIs. +// +// See also: +// https://clang.llvm.org/docs/AttributeReference.html#trivial-abi +// https://libcxx.llvm.org/docs/DesignDocs/UniquePtrTrivialAbi.html +#if defined(__clang__) && HAS_ATTRIBUTE(trivial_abi) +#define TRIVIAL_ABI [[clang::trivial_abi]] +#else +#define TRIVIAL_ABI +#endif + +// Detect whether a type is trivially relocatable, ie. a move-and-destroy +// sequence can replaced with memmove(). This can be used to optimise the +// implementation of containers. This is automatically true for types that were +// defined with TRIVIAL_ABI such as scoped_refptr. +// +// See also: +// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p1144r8.html +// https://clang.llvm.org/docs/LanguageExtensions.html#:~:text=__is_trivially_relocatable +#if defined(__clang__) && HAS_BUILTIN(__is_trivially_relocatable) +#define IS_TRIVIALLY_RELOCATABLE(t) __is_trivially_relocatable(t) +#else +#define IS_TRIVIALLY_RELOCATABLE(t) false +#endif + +// Marks a member function as reinitializing a moved-from variable. +// See also +// https://clang.llvm.org/extra/clang-tidy/checks/bugprone/use-after-move.html#reinitialization +#if defined(__clang__) && HAS_ATTRIBUTE(reinitializes) +#define REINITIALIZES_AFTER_MOVE [[clang::reinitializes]] +#else +#define REINITIALIZES_AFTER_MOVE +#endif + +#if defined(__clang__) +#define GSL_OWNER [[gsl::Owner]] +#define GSL_POINTER [[gsl::Pointer]] +#else +#define GSL_OWNER +#define GSL_POINTER +#endif + +// Adds the "logically_const" tag to a symbol's mangled name. The "Mutable +// Constants" check [1] detects instances of constants that aren't in .rodata, +// e.g. due to a missing `const`. Using this tag suppresses the check for this +// symbol, allowing it to live outside .rodata without a warning. +// +// [1]: +// https://crsrc.org/c/docs/speed/binary_size/android_binary_size_trybot.md#Mutable-Constants +#if defined(COMPILER_GCC) || defined(__clang__) +#define LOGICALLY_CONST [[gnu::abi_tag("logically_const")]] +#else +#define LOGICALLY_CONST +#endif + +// preserve_most clang's calling convention. Reduces register pressure for the +// caller and as such can be used for cold calls. Support for the +// "preserve_most" attribute is limited: +// - 32-bit platforms do not implement it, +// - component builds fail because _dl_runtime_resolve() clobbers registers, +// - there are crashes on arm64 on Windows (https://crbug.com/v8/14065), which +// can hopefully be fixed in the future. +// Additionally, the initial implementation in clang <= 16 overwrote the return +// register(s) in the epilogue of a preserve_most function, so we only use +// preserve_most in clang >= 17 (see https://reviews.llvm.org/D143425). +// Clang only supports preserve_most on X86-64 and AArch64 for now. +// See https://clang.llvm.org/docs/AttributeReference.html#preserve-most for +// more details. +#if (defined(ARCH_CPU_ARM64) || defined(ARCH_CPU_X86_64)) && !(BUILDFLAG(IS_WIN) && defined(ARCH_CPU_ARM64)) && \ + !defined(COMPONENT_BUILD) && defined(__clang__) && __clang_major__ >= 17 && HAS_ATTRIBUTE(preserve_most) +#define PRESERVE_MOST __attribute__((preserve_most)) +#else +#define PRESERVE_MOST +#endif + +// Mark parameters or return types as having a lifetime attached to the class. +// +// When used to mark a method's pointer/reference parameter, the compiler is +// made aware that it will be stored internally in the class and the pointee +// must outlive the class. Typically used on constructor arguments. It should +// appear to the right of the parameter's variable name. +// +// Example: +// ``` +// struct S { +// S(int* p LIFETIME_BOUND) : ptr_(p) {} +// +// int* ptr_; +// }; +// ``` +// +// When used on a method with a return value, the compiler is made aware that +// the returned type is/has a pointer to the internals of the class, and must +// not outlive the class object. It should appear after any method qualifiers. +// +// Example: +// ``` +// struct S { +// int* GetPtr() const LIFETIME_BOUND { return i_; }; +// +// int i_; +// }; +// ``` +// +// This allows the compiler to warn in (a limited set of) cases where the +// pointer would otherwise be left dangling, especially in cases where the +// pointee would be a destroyed temporary. +// +// Docs: https://clang.llvm.org/docs/AttributeReference.html#lifetimebound +#if defined(__clang__) +#define LIFETIME_BOUND [[clang::lifetimebound]] +#else +#define LIFETIME_BOUND +#endif + +// Mark a function as pure, meaning that it does not have side effects, meaning +// that it does not write anything external to the function's local variables +// and return value. +// +// WARNING: If this attribute is mis-used it will result in UB and +// miscompilation, as the optimizator may fold multiple calls into one and +// reorder them inappropriately. This shouldn't appear outside of key vocabulary +// types. It allows callers to work with the vocab type directly, and call its +// methods without having to worry about caching things into local variables in +// hot code. +// +// This attribute must not appear on functions that make use of function +// pointers, virtual methods, or methods of templates (including operators like +// comparison), as the "pure" function can not know what those functions do and +// can not guarantee there will never be sideeffects. +#if defined(COMPILER_GCC) || defined(__clang__) +#define PURE_FUNCTION [[gnu::pure]] +#else +#define PURE_FUNCTION +#endif + +// Functions should be marked with UNSAFE_BUFFER_USAGE when they lead to +// out-of-bounds bugs when called with incorrect inputs. +// +// Ideally such functions should be paired with a safer version that works with +// safe primitives like `base::span`. Otherwise, another safer coding pattern +// should be documented along side the use of `UNSAFE_BUFFER_USAGE`. +// +// All functions marked with UNSAFE_BUFFER_USAGE should come with a safety +// comment that explains the requirements of the function to prevent an +// out-of-bounds bug. For example: +// ``` +// // Function to do things between `input` and `end`. +// // +// // # Safety +// // The `input` must point to an array with size at least 5. The `end` must +// // point within the same allocation of `input` and not come before `input`. +// ``` +// +// The requirements described in the safety comment must be sufficient to +// guarantee that the function never goes out of bounds. Annotating a function +// in this way means that all callers will be required to wrap the call in an +// `UNSAFE_BUFFERS()` macro (see below), with a comment justifying how it meets +// the requirements. +#if defined(__clang__) && HAS_ATTRIBUTE(unsafe_buffer_usage) +#define UNSAFE_BUFFER_USAGE [[clang::unsafe_buffer_usage]] +#else +#define UNSAFE_BUFFER_USAGE +#endif + +// UNSAFE_BUFFERS() wraps code that violates the -Wunsafe-buffer-usage warning, +// such as: +// - pointer arithmetic, +// - pointer subscripting, and +// - calls to functions annotated with UNSAFE_BUFFER_USAGE. +// +// This indicates code whose bounds correctness cannot be ensured +// systematically, and thus requires manual review. +// +// ** USE OF THIS MACRO SHOULD BE VERY RARE.** This should only be used when +// strictly necessary. Prefer to use `base::span` instead of pointers, or other +// safer coding patterns (like std containers) that avoid the opportunity for +// out-of-bounds bugs to creep into the code. Any use of UNSAFE_BUFFERS() can +// lead to a critical security bug if any assumptions are wrong, or ever become +// wrong in the future. +// +// The macro should be used to wrap the minimum necessary code, to make it clear +// what is unsafe, and prevent accidentally opting extra things out of the +// warning. +// +// All usage of UNSAFE_BUFFERS() should come with a `// SAFETY: ...` comment +// that explains how we have guaranteed that the pointer usage can never go +// out-of-bounds, or that the requirements of the UNSAFE_BUFFER_USAGE function +// are met. The safety comment should allow a reader to check that all +// requirements have been met, using only local invariants. Examples of local +// invariants include: +// - Runtime conditions or CHECKs near the UNSAFE_BUFFERS macros +// - Invariants guaranteed by types in the surrounding code +// - Invariants guaranteed by function calls in the surrounding code +// - Caller requirements, if the containing function is itself marked with +// UNSAFE_BUFFER_USAGE +// +// The last case should be an option of last resort. It is less safe and will +// require the caller also use the UNSAFE_BUFFERS() macro. Prefer directly +// capturing such invariants in types like `base::span`. +// +// Safety explanations may not rely on invariants that are not fully +// encapsulated close to the UNSAFE_BUFFERS() usage. Instead, use safer coding +// patterns or stronger invariants. +#if defined(__clang__) +// clang-format off +// Formatting is off so that we can put each _Pragma on its own line, as +// recommended by the gcc docs. +#define UNSAFE_BUFFERS(...) \ + _Pragma("clang unsafe_buffer_usage begin") \ + __VA_ARGS__ \ + _Pragma("clang unsafe_buffer_usage end") +// clang-format on +#else +#define UNSAFE_BUFFERS(...) __VA_ARGS__ +#endif + +// Defines a condition for a function to be checked at compile time if the +// parameter's value is known at compile time. If the condition is failed, the +// function is omitted from the overload set resolution, much like `requires`. +// +// If the parameter is a runtime value, then the condition is unable to be +// checked and the function will be omitted from the overload set resolution. +// This ensures the function can only be called with values known at compile +// time. This is a clang extension. +// +// Example: +// ``` +// void f(int a) ENABLE_IF_ATTR(a > 0) {} +// f(1); // Ok. +// f(0); // Error: no valid f() found. +// ``` +// +// The `ENABLE_IF_ATTR` annotation is preferred over `consteval` with a check +// that breaks compile because metaprogramming does not observe such checks. So +// with `consteval`, the function looks callable to concepts/type_traits but is +// not and will fail to compile even though it reports it's usable. Whereas +// `ENABLE_IF_ATTR` interacts correctly with metaprogramming. This is especially +// painful for constructors. See also +// https://github.com/chromium/subspace/issues/266. +#if defined(__clang__) +#define ENABLE_IF_ATTR(cond, msg) __attribute__((enable_if(cond, msg))) +#else +#define ENABLE_IF_ATTR(cond, msg) +#endif + +#endif // BASE_COMPILER_SPECIFIC_H_ \ No newline at end of file diff --git a/bridge/core/base/containers/contains.h b/bridge/core/base/containers/contains.h new file mode 100644 index 0000000000..2ddf1b531f --- /dev/null +++ b/bridge/core/base/containers/contains.h @@ -0,0 +1,110 @@ +// Copyright 2020 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef WEBF_CONTAINS_H +#define WEBF_CONTAINS_H + +#include +#include +#include + +namespace webf { + +namespace internal { + +// Small helper to detect whether a given type has a nested `key_type` typedef. +// Used below to catch misuses of the API for associative containers. +template +struct HasKeyType : std::false_type {}; + +template +struct HasKeyType> : std::true_type {}; + +} // namespace internal + +// A general purpose utility to check whether `container` contains `value`. This +// will probe whether a `contains` or `find` member function on `container` +// exists, and fall back to a generic linear search over `container`. +// Helper to check if container has a `contains` method. +template +struct has_contains_method : std::false_type {}; + +template +struct has_contains_method().contains(std::declval()))>> + : std::true_type {}; + +template +constexpr bool has_contains_v = has_contains_method::value; + +// Helper to check if container has a `find` method that returns `npos`. +template +struct has_find_npos_method : std::false_type {}; + +template +struct has_find_npos_method< + Container, + Value, + std::void_t().find(std::declval()) != Container::npos)>> : std::true_type { +}; + +template +constexpr bool has_find_npos_v = has_find_npos_method::value; + +// Helper to check if container has a `find` method that returns an iterator. +template +struct has_find_iterator_method : std::false_type {}; + +template +struct has_find_iterator_method< + Container, + Value, + std::void_t().find(std::declval()) != std::declval().end())>> + : std::true_type {}; + +template +constexpr bool has_find_iterator_v = has_find_iterator_method::value; + +// Helper to check if linear search is necessary. +template +struct is_associative_container : std::false_type {}; + +template +struct is_associative_container> : std::true_type {}; + +template +constexpr bool Contains(const Container& container, const Value& value) { + if constexpr (has_contains_v) { + return container.contains(value); + } else if constexpr (has_find_npos_v) { + return container.find(value) != Container::npos; + } else if constexpr (has_find_iterator_v) { + return container.find(value) != container.end(); + } else { + static_assert(!is_associative_container::value, + "Error: About to perform linear search on an associative container. " + "Either use a more generic comparator (e.g. std::less<>) or, if a " + "linear search is desired, provide an explicit projection parameter."); + return std::find(std::begin(container), std::end(container), value) != std::end(container); + } +} + +// Overload that allows to provide an additional projection invocable. This +// projection will be applied to every element in `container` before comparing +// it with `value`. This will always perform a linear search. +template +constexpr bool Contains(const Container& container, const Value& value, Proj proj) { + // Use std::find_if with a custom predicate based on the projection + return std::find_if(container.begin(), container.end(), + [&value, &proj](const auto& elem) { return proj(elem) == value; }) != container.end(); +} + +} // namespace webf + +#endif // WEBF_CONTAINS_H diff --git a/bridge/core/base/containers/enum_set.h b/bridge/core/base/containers/enum_set.h new file mode 100644 index 0000000000..e62a8dd79a --- /dev/null +++ b/bridge/core/base/containers/enum_set.h @@ -0,0 +1,371 @@ +// Copyright 2012 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BASE_CONTAINERS_ENUM_SET_H_ +#define BASE_CONTAINERS_ENUM_SET_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "foundation/macros.h" + +namespace webf { + +// Forward declarations needed for friend declarations. +template +class EnumSet; + +template +EnumSet Union(EnumSet set1, EnumSet set2); + +template +EnumSet Intersection(EnumSet set1, EnumSet set2); + +template +EnumSet Difference(EnumSet set1, EnumSet set2); + +// An EnumSet is a set that can hold enum values between a min and a +// max value (inclusive of both). It's essentially a wrapper around +// std::bitset<> with stronger type enforcement, more descriptive +// member function names, and an iterator interface. +// +// If you're working with enums with a small number of possible values +// (say, fewer than 64), you can efficiently pass around an EnumSet +// for that enum around by value. + +template +class EnumSet { + private: + static_assert(std::is_enum_v, "First template parameter of EnumSet must be an enumeration type"); + using enum_underlying_type = std::underlying_type_t; + + static constexpr bool InRange(E value) { return (value >= MinEnumValue) && (value <= MaxEnumValue); } + + static constexpr enum_underlying_type GetUnderlyingValue(E value) { return static_cast(value); } + + public: + using EnumType = E; + static const E kMinValue = MinEnumValue; + static const E kMaxValue = MaxEnumValue; + static const size_t kValueCount = GetUnderlyingValue(kMaxValue) - GetUnderlyingValue(kMinValue) + 1; + + static_assert(kMinValue <= kMaxValue, "min value must be no greater than max value"); + + private: + // Declaration needed by Iterator. + using EnumBitSet = std::bitset; + + public: + // Iterator is a forward-only read-only iterator for EnumSet. It follows the + // common STL input iterator interface (like std::unordered_set). + // + // Example usage, using a range-based for loop: + // + // EnumSet enums; + // for (SomeType val : enums) { + // Process(val); + // } + // + // Or using an explicit iterator (not recommended): + // + // for (EnumSet<...>::Iterator it = enums.begin(); it != enums.end(); it++) { + // Process(*it); + // } + // + // The iterator must not be outlived by the set. In particular, the following + // is an error: + // + // EnumSet<...> SomeFn() { ... } + // + // /* ERROR */ + // for (EnumSet<...>::Iterator it = SomeFun().begin(); ... + // + // Also, there are no guarantees as to what will happen if you + // modify an EnumSet while traversing it with an iterator. + class Iterator { + public: + using value_type = EnumType; + using size_type = size_t; + using difference_type = ptrdiff_t; + using pointer = EnumType*; + using reference = EnumType&; + using iterator_category = std::forward_iterator_tag; + + Iterator() : enums_(nullptr), i_(kValueCount) {} + ~Iterator() = default; + + Iterator(const Iterator&) = default; + Iterator& operator=(const Iterator&) = default; + + Iterator(Iterator&&) = default; + Iterator& operator=(Iterator&&) = default; + + friend bool operator==(const Iterator& lhs, const Iterator& rhs) { return lhs.i_ == rhs.i_; } + + value_type operator*() const { + assert(Good()); + return FromIndex(i_); + } + + Iterator& operator++() { + assert(Good()); + // If there are no more set elements in the bitset, this will result in an + // index equal to kValueCount, which is equivalent to EnumSet.end(). + i_ = FindNext(i_ + 1); + + return *this; + } + + Iterator operator++(int) { + assert(Good()); + Iterator old(*this); + + // If there are no more set elements in the bitset, this will result in an + // index equal to kValueCount, which is equivalent to EnumSet.end(). + i_ = FindNext(i_ + 1); + + return std::move(old); + } + + private: + friend Iterator EnumSet::begin() const; + + explicit Iterator(const EnumBitSet& enums) : enums_(&enums), i_(FindNext(0)) {} + + // Returns true iff the iterator points to an EnumSet and it + // hasn't yet traversed the EnumSet entirely. + bool Good() const { return enums_ && i_ < kValueCount && enums_->test(i_); } + + size_t FindNext(size_t i) { + while ((i < kValueCount) && !enums_->test(i)) { + ++i; + } + return i; + } + + // TODO(guopengfei):代码迁移,使用std::unique_ptr替换base/memory/raw_ptr.h + std::unique_ptr enums_; + size_t i_; + }; + + EnumSet() = default; + + ~EnumSet() = default; + + constexpr EnumSet(std::initializer_list values) : EnumSet(EnumBitSet(bitstring(values))) {} + + // Returns an EnumSet with all values between kMinValue and kMaxValue, which + // also contains undefined enum values if the enum in question has gaps + // between kMinValue and kMaxValue. + static constexpr EnumSet All() { + if (kValueCount == 0) { + return EnumSet(); + } + // Since `1 << kValueCount` may trigger shift-count-overflow warning if + // the `kValueCount` is 64, instead of returning `(1 << kValueCount) - 1`, + // the bitmask will be constructed from two parts: the most significant bits + // and the remaining. + uint64_t mask = 1ULL << (kValueCount - 1); + return EnumSet(EnumBitSet(mask - 1 + mask)); + } + + // Returns an EnumSet with all the values from start to end, inclusive. + static constexpr EnumSet FromRange(E start, E end) { + CHECK_LE(start, end); + return EnumSet( + EnumBitSet(((single_val_bitstring(end)) - (single_val_bitstring(start))) | (single_val_bitstring(end)))); + } + + // Copy constructor and assignment welcome. + + // Bitmask operations. + // + // This bitmask is 0-based and the value of the Nth bit depends on whether + // the set contains an enum element of integer value N. + // + // These may only be used if Min >= 0 and Max < 64. + + // Returns an EnumSet constructed from |bitmask|. + static constexpr EnumSet FromEnumBitmask(const uint64_t bitmask) { + static_assert(GetUnderlyingValue(kMaxValue) < 64, "The highest enum value must be < 64 for FromEnumBitmask "); + static_assert(GetUnderlyingValue(kMinValue) >= 0, "The lowest enum value must be >= 0 for FromEnumBitmask "); + return EnumSet(EnumBitSet(bitmask >> GetUnderlyingValue(kMinValue))); + } + // Returns a bitmask for the EnumSet. + uint64_t ToEnumBitmask() const { + static_assert(GetUnderlyingValue(kMaxValue) < 64, "The highest enum value must be < 64 for ToEnumBitmask "); + static_assert(GetUnderlyingValue(kMinValue) >= 0, "The lowest enum value must be >= 0 for FromEnumBitmask "); + return enums_.to_ullong() << GetUnderlyingValue(kMinValue); + } + + // Returns a uint64_t bit mask representing the values within the range + // [64*n, 64*n + 63] of the EnumSet. + std::optional GetNth64bitWordBitmask(size_t n) const { + // If the EnumSet contains less than n 64-bit masks, return std::nullopt. + if (GetUnderlyingValue(kMaxValue) / 64 < n) { + return std::nullopt; + } + + std::bitset mask = ~uint64_t{0}; + std::bitset bits = enums_; + if (GetUnderlyingValue(kMinValue) < n * 64) { + bits >>= n * 64 - GetUnderlyingValue(kMinValue); + } + uint64_t result = (bits & mask).to_ullong(); + if (GetUnderlyingValue(kMinValue) > n * 64) { + result <<= GetUnderlyingValue(kMinValue) - n * 64; + } + return result; + } + + // Set operations. Put, Retain, and Remove are basically + // self-mutating versions of Union, Intersection, and Difference + // (defined below). + + // Adds the given value (which must be in range) to our set. + void Put(E value) { enums_.set(ToIndex(value)); } + + // Adds all values in the given set to our set. + void PutAll(EnumSet other) { enums_ |= other.enums_; } + + // Adds all values in the given range to our set, inclusive. + void PutRange(E start, E end) { + CHECK_LE(start, end); + size_t endIndexInclusive = ToIndex(end); + for (size_t current = ToIndex(start); current <= endIndexInclusive; ++current) { + enums_.set(current); + } + } + + // There's no real need for a Retain(E) member function. + + // Removes all values not in the given set from our set. + void RetainAll(EnumSet other) { enums_ &= other.enums_; } + + // If the given value is in range, removes it from our set. + void Remove(E value) { + if (InRange(value)) { + enums_.reset(ToIndex(value)); + } + } + + // Removes all values in the given set from our set. + void RemoveAll(EnumSet other) { enums_ &= ~other.enums_; } + + // Removes all values from our set. + void Clear() { enums_.reset(); } + + // Conditionally puts or removes `value`, based on `should_be_present`. + void PutOrRemove(E value, bool should_be_present) { + if (should_be_present) { + Put(value); + } else { + Remove(value); + } + } + + // Returns true iff the given value is in range and a member of our set. + constexpr bool Has(E value) const { return InRange(value) && enums_[ToIndex(value)]; } + + // Returns true iff the given set is a subset of our set. + bool HasAll(EnumSet other) const { return (enums_ & other.enums_) == other.enums_; } + + // Returns true if the given set contains any value of our set. + bool HasAny(EnumSet other) const { return (enums_ & other.enums_).count() > 0; } + + // Returns true iff our set is empty. + bool empty() const { return !enums_.any(); } + + // Returns how many values our set has. + size_t size() const { return enums_.count(); } + + // Returns an iterator pointing to the first element (if any). + Iterator begin() const { return Iterator(enums_); } + + // Returns an iterator that does not point to any element, but to the position + // that follows the last element in the set. + Iterator end() const { return Iterator(); } + + std::string ToString() const { return enums_.to_string(); } + + private: + friend EnumSet Union(EnumSet set1, EnumSet set2); + friend EnumSet Intersection(EnumSet set1, EnumSet set2); + friend EnumSet Difference(EnumSet set1, EnumSet set2); + + static constexpr uint64_t bitstring(const std::initializer_list& values) { + uint64_t result = 0; + for (E value : values) { + result |= single_val_bitstring(value); + } + return result; + } + + static constexpr uint64_t single_val_bitstring(E val) { + const uint64_t bitstring = 1; + const size_t shift_amount = ToIndex(val); + assert(shift_amount < sizeof(bitstring) * 8); + return bitstring << shift_amount; + } + + // A bitset can't be constexpr constructed if it has size > 64, since the + // constexpr constructor uses a uint64_t. If your EnumSet has > 64 values, you + // can safely remove the constepxr qualifiers from this file, at the cost of + // some minor optimizations. + explicit constexpr EnumSet(EnumBitSet enums) : enums_(enums) { + static_assert(kValueCount <= 64, "Max number of enum values is 64 for constexpr constructor"); + } + + // Converts a value to/from an index into |enums_|. + static constexpr size_t ToIndex(E value) { + assert(InRange(value)); + return static_cast(GetUnderlyingValue(value)) - static_cast(GetUnderlyingValue(MinEnumValue)); + } + + static E FromIndex(size_t i) { + assert(i < kValueCount); + return static_cast(GetUnderlyingValue(MinEnumValue) + i); + } + + EnumBitSet enums_; +}; + +template +const E EnumSet::kMinValue; + +template +const E EnumSet::kMaxValue; + +template +const size_t EnumSet::kValueCount; + +// The usual set operations. + +template +EnumSet Union(EnumSet set1, EnumSet set2) { + return EnumSet(set1.enums_ | set2.enums_); +} + +template +EnumSet Intersection(EnumSet set1, EnumSet set2) { + return EnumSet(set1.enums_ & set2.enums_); +} + +template +EnumSet Difference(EnumSet set1, EnumSet set2) { + return EnumSet(set1.enums_ & ~set2.enums_); +} + +} // namespace webf + +#endif // BASE_CONTAINERS_ENUM_SET_H_ diff --git a/bridge/core/base/containers/span.h b/bridge/core/base/containers/span.h new file mode 100644 index 0000000000..8feb77c1b4 --- /dev/null +++ b/bridge/core/base/containers/span.h @@ -0,0 +1,519 @@ +/* +This is an implementation of C++20's tcb::span +http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/n4820.pdf +*/ + +// Copyright Tristan Brindle 2018. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file ../../LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef TCB_SPAN_HPP_INCLUDED +#define TCB_SPAN_HPP_INCLUDED + +#include +#include +#include +#include +#include + +#ifndef TCB_SPAN_NO_EXCEPTIONS +// Attempt to discover whether we're being compiled with exception support +#if !(defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) +#define TCB_SPAN_NO_EXCEPTIONS +#endif +#endif + +#ifndef TCB_SPAN_NO_EXCEPTIONS +#include +#include +#endif + +// Various feature test macros + +#ifndef TCB_SPAN_NAMESPACE_NAME +#define TCB_SPAN_NAMESPACE_NAME tcb +#endif + +#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +#define TCB_SPAN_HAVE_CPP17 +#endif + +#if __cplusplus >= 201402L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +#define TCB_SPAN_HAVE_CPP14 +#endif + +namespace TCB_SPAN_NAMESPACE_NAME { + +// Establish default contract checking behavior +#if !defined(TCB_SPAN_THROW_ON_CONTRACT_VIOLATION) && !defined(TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION) && \ + !defined(TCB_SPAN_NO_CONTRACT_CHECKING) +#if defined(NDEBUG) || !defined(TCB_SPAN_HAVE_CPP14) +#define TCB_SPAN_NO_CONTRACT_CHECKING +#else +#define TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION +#endif +#endif + +#if defined(TCB_SPAN_THROW_ON_CONTRACT_VIOLATION) +struct contract_violation_error : std::logic_error { + explicit contract_violation_error(const char* msg) : std::logic_error(msg) {} +}; + +inline void contract_violation(const char* msg) { + throw contract_violation_error(msg); +} + +#elif defined(TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION) +[[noreturn]] inline void contract_violation(const char* /*unused*/) { + std::terminate(); +} +#endif + +#if !defined(TCB_SPAN_NO_CONTRACT_CHECKING) +#define TCB_SPAN_STRINGIFY(cond) #cond +#define TCB_SPAN_EXPECT(cond) cond ? (void)0 : contract_violation("Expected " TCB_SPAN_STRINGIFY(cond)) +#else +#define TCB_SPAN_EXPECT(cond) +#endif + +#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_inline_variables) +#define TCB_SPAN_INLINE_VAR inline +#else +#define TCB_SPAN_INLINE_VAR +#endif + +#if defined(TCB_SPAN_HAVE_CPP14) || (defined(__cpp_constexpr) && __cpp_constexpr >= 201304) +#define TCB_SPAN_HAVE_CPP14_CONSTEXPR +#endif + +#if defined(TCB_SPAN_HAVE_CPP14_CONSTEXPR) +#define TCB_SPAN_CONSTEXPR14 constexpr +#else +#define TCB_SPAN_CONSTEXPR14 +#endif + +#if defined(TCB_SPAN_HAVE_CPP14_CONSTEXPR) && (!defined(_MSC_VER) || _MSC_VER > 1900) +#define TCB_SPAN_CONSTEXPR_ASSIGN constexpr +#else +#define TCB_SPAN_CONSTEXPR_ASSIGN +#endif + +#if defined(TCB_SPAN_NO_CONTRACT_CHECKING) +#define TCB_SPAN_CONSTEXPR11 constexpr +#else +#define TCB_SPAN_CONSTEXPR11 TCB_SPAN_CONSTEXPR14 +#endif + +#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_deduction_guides) +#define TCB_SPAN_HAVE_DEDUCTION_GUIDES +#endif + +#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_byte) +#define TCB_SPAN_HAVE_STD_BYTE +#endif + +#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_array_constexpr) +#define TCB_SPAN_HAVE_CONSTEXPR_STD_ARRAY_ETC +#endif + +#if defined(TCB_SPAN_HAVE_CONSTEXPR_STD_ARRAY_ETC) +#define TCB_SPAN_ARRAY_CONSTEXPR constexpr +#else +#define TCB_SPAN_ARRAY_CONSTEXPR +#endif + +#ifdef TCB_SPAN_HAVE_STD_BYTE +using byte = std::byte; +#else +using byte = unsigned char; +#endif + +#if defined(TCB_SPAN_HAVE_CPP17) +#define TCB_SPAN_NODISCARD [[nodiscard]] +#else +#define TCB_SPAN_NODISCARD +#endif + +TCB_SPAN_INLINE_VAR constexpr std::size_t dynamic_extent = SIZE_MAX; + +template +class span; + +namespace detail { + +template +struct span_storage { + constexpr span_storage() noexcept = default; + + constexpr span_storage(E* p_ptr, std::size_t /*unused*/) noexcept : ptr(p_ptr) {} + + E* ptr = nullptr; + static constexpr std::size_t size = S; +}; + +template +struct span_storage { + constexpr span_storage() noexcept = default; + + constexpr span_storage(E* p_ptr, std::size_t p_size) noexcept : ptr(p_ptr), size(p_size) {} + + E* ptr = nullptr; + std::size_t size = 0; +}; + +// Reimplementation of C++17 std::size() and std::data() +#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_nonmember_container_access) +using std::data; +using std::size; +#else +template +constexpr auto size(const C& c) -> decltype(c.size()) { + return c.size(); +} + +template +constexpr std::size_t size(const T (&)[N]) noexcept { + return N; +} + +template +constexpr auto data(C& c) -> decltype(c.data()) { + return c.data(); +} + +template +constexpr auto data(const C& c) -> decltype(c.data()) { + return c.data(); +} + +template +constexpr T* data(T (&array)[N]) noexcept { + return array; +} + +template +constexpr const E* data(std::initializer_list il) noexcept { + return il.begin(); +} +#endif // TCB_SPAN_HAVE_CPP17 + +#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_void_t) +using std::void_t; +#else +template +using void_t = void; +#endif + +template +using uncvref_t = typename std::remove_cv::type>::type; + +template +struct is_span : std::false_type {}; + +template +struct is_span> : std::true_type {}; + +template +struct is_std_array : std::false_type {}; + +template +struct is_std_array> : std::true_type {}; + +template +struct has_size_and_data : std::false_type {}; + +template +struct has_size_and_data())), decltype(detail::data(std::declval()))>> + : std::true_type {}; + +template > +struct is_container { + static constexpr bool value = + !is_span::value && !is_std_array::value && !std::is_array::value && has_size_and_data::value; +}; + +template +using remove_pointer_t = typename std::remove_pointer::type; + +template +struct is_container_element_type_compatible : std::false_type {}; + +template +struct is_container_element_type_compatible< + T, + E, + typename std::enable_if< + !std::is_same()))>::type, void>::value && + std::is_convertible()))> (*)[], E (*)[]>::value>::type> + : std::true_type {}; + +template +struct is_complete : std::false_type {}; + +template +struct is_complete : std::true_type {}; + +} // namespace detail + +template +class span { + static_assert(std::is_object::value, + "A span's ElementType must be an object type (not a " + "reference type or void)"); + static_assert(detail::is_complete::value, + "A span's ElementType must be a complete type (not a forward " + "declaration)"); + static_assert(!std::is_abstract::value, "A span's ElementType cannot be an abstract class type"); + + using storage_type = detail::span_storage; + + public: + // constants and types + using element_type = ElementType; + using value_type = typename std::remove_cv::type; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = element_type*; + using const_pointer = const element_type*; + using reference = element_type&; + using const_reference = const element_type&; + using iterator = pointer; + using reverse_iterator = std::reverse_iterator; + + static constexpr size_type extent = Extent; + + // [span.cons], span constructors, copy, assignment, and destructor + template ::type = 0> + constexpr span() noexcept {} + + TCB_SPAN_CONSTEXPR11 span(pointer ptr, size_type count) : storage_(ptr, count) { + TCB_SPAN_EXPECT(extent == dynamic_extent || count == extent); + } + + TCB_SPAN_CONSTEXPR11 span(pointer first_elem, pointer last_elem) : storage_(first_elem, last_elem - first_elem) { + TCB_SPAN_EXPECT(extent == dynamic_extent || last_elem - first_elem == static_cast(extent)); + } + + template < + std::size_t N, + std::size_t E = Extent, + typename std::enable_if<(E == dynamic_extent || N == E) && + detail::is_container_element_type_compatible::value, + int>::type = 0> + constexpr span(element_type (&arr)[N]) noexcept : storage_(arr, N) {} + + template < + typename T, + std::size_t N, + std::size_t E = Extent, + typename std::enable_if<(E == dynamic_extent || N == E) && + detail::is_container_element_type_compatible&, ElementType>::value, + int>::type = 0> + TCB_SPAN_ARRAY_CONSTEXPR span(std::array& arr) noexcept : storage_(arr.data(), N) {} + + template &, ElementType>::value, + int>::type = 0> + TCB_SPAN_ARRAY_CONSTEXPR span(const std::array& arr) noexcept : storage_(arr.data(), N) {} + + template ::value && + detail::is_container_element_type_compatible::value, + int>::type = 0> + constexpr span(Container& cont) : storage_(detail::data(cont), detail::size(cont)) {} + + template < + typename Container, + std::size_t E = Extent, + typename std::enable_if::value && + detail::is_container_element_type_compatible::value, + int>::type = 0> + constexpr span(const Container& cont) : storage_(detail::data(cont), detail::size(cont)) {} + + constexpr span(const span& other) noexcept = default; + + template < + typename OtherElementType, + std::size_t OtherExtent, + typename std::enable_if<(Extent == dynamic_extent || OtherExtent == dynamic_extent || Extent == OtherExtent) && + std::is_convertible::value, + int>::type = 0> + constexpr span(const span& other) noexcept : storage_(other.data(), other.size()) {} + + ~span() noexcept = default; + + TCB_SPAN_CONSTEXPR_ASSIGN span& operator=(const span& other) noexcept = default; + + // [span.sub], span subviews + template + TCB_SPAN_CONSTEXPR11 span first() const { + TCB_SPAN_EXPECT(Count <= size()); + return {data(), Count}; + } + + template + TCB_SPAN_CONSTEXPR11 span last() const { + TCB_SPAN_EXPECT(Count <= size()); + return {data() + (size() - Count), Count}; + } + + template + using subspan_return_t = + span; + + template + TCB_SPAN_CONSTEXPR11 subspan_return_t subspan() const { + TCB_SPAN_EXPECT(Offset <= size() && (Count == dynamic_extent || Offset + Count <= size())); + return {data() + Offset, Count != dynamic_extent ? Count : size() - Offset}; + } + + TCB_SPAN_CONSTEXPR11 span first(size_type count) const { + TCB_SPAN_EXPECT(count <= size()); + return {data(), count}; + } + + TCB_SPAN_CONSTEXPR11 span last(size_type count) const { + TCB_SPAN_EXPECT(count <= size()); + return {data() + (size() - count), count}; + } + + TCB_SPAN_CONSTEXPR11 span subspan(size_type offset, + size_type count = dynamic_extent) const { + TCB_SPAN_EXPECT(offset <= size() && (count == dynamic_extent || offset + count <= size())); + return {data() + offset, count == dynamic_extent ? size() - offset : count}; + } + + // [span.obs], span observers + constexpr size_type size() const noexcept { return storage_.size; } + + constexpr size_type size_bytes() const noexcept { return size() * sizeof(element_type); } + + TCB_SPAN_NODISCARD constexpr bool empty() const noexcept { return size() == 0; } + + // [span.elem], span element access + TCB_SPAN_CONSTEXPR11 reference operator[](size_type idx) const { + TCB_SPAN_EXPECT(idx < size()); + return *(data() + idx); + } + + TCB_SPAN_CONSTEXPR11 reference front() const { + TCB_SPAN_EXPECT(!empty()); + return *data(); + } + + TCB_SPAN_CONSTEXPR11 reference back() const { + TCB_SPAN_EXPECT(!empty()); + return *(data() + (size() - 1)); + } + + constexpr pointer data() const noexcept { return storage_.ptr; } + + // [span.iterators], span iterator support + constexpr iterator begin() const noexcept { return data(); } + + constexpr iterator end() const noexcept { return data() + size(); } + + TCB_SPAN_ARRAY_CONSTEXPR reverse_iterator rbegin() const noexcept { return reverse_iterator(end()); } + + TCB_SPAN_ARRAY_CONSTEXPR reverse_iterator rend() const noexcept { return reverse_iterator(begin()); } + + private: + storage_type storage_{}; +}; + +#ifdef TCB_SPAN_HAVE_DEDUCTION_GUIDES + +/* Deduction Guides */ +template +span(T (&)[N]) -> span; + +template +span(std::array&) -> span; + +template +span(const std::array&) -> span; + +template +span(Container&) -> span()))>::type>; + +template +span(const Container&) -> span; + +#endif // TCB_HAVE_DEDUCTION_GUIDES + +template +constexpr span make_span(span s) noexcept { + return s; +} + +template +constexpr span make_span(T (&arr)[N]) noexcept { + return {arr}; +} + +template +TCB_SPAN_ARRAY_CONSTEXPR span make_span(std::array& arr) noexcept { + return {arr}; +} + +template +TCB_SPAN_ARRAY_CONSTEXPR span make_span(const std::array& arr) noexcept { + return {arr}; +} + +template +constexpr span()))>::type> make_span( + Container& cont) { + return {cont}; +} + +template +constexpr span make_span(const Container& cont) { + return {cont}; +} + +template +span as_bytes( + span s) noexcept { + return {reinterpret_cast(s.data()), s.size_bytes()}; +} + +template ::value, int>::type = 0> +span as_writable_bytes( + span s) noexcept { + return {reinterpret_cast(s.data()), s.size_bytes()}; +} + +template +constexpr auto get(span s) -> decltype(s[N]) { + return s[N]; +} + +} // namespace TCB_SPAN_NAMESPACE_NAME + +namespace std { + +template +class tuple_size> : public integral_constant {}; + +template +class tuple_size>; // not defined + +template +class tuple_element> { + public: + static_assert(Extent != TCB_SPAN_NAMESPACE_NAME::dynamic_extent && I < Extent, ""); + using type = ElementType; +}; + +} // end namespace std + +#endif // TCB_SPAN_HPP_INCLUDED \ No newline at end of file diff --git a/bridge/core/base/hash/hash.cc b/bridge/core/base/hash/hash.cc new file mode 100644 index 0000000000..9086bd587f --- /dev/null +++ b/bridge/core/base/hash/hash.cc @@ -0,0 +1,71 @@ +// Copyright 2014 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#include "core/base/hash/hash.h" + +#include +#include +#include +#include +#include + +namespace webf { + +#define get16bits(d) ((((uint32_t)(((const uint8_t*)(d))[1])) << 8) + (uint32_t)(((const uint8_t*)(d))[0])) + +// Definition in base/third_party/superfasthash/superfasthash.c. (Third-party +// code did not come with its own header file, so declaring the function here.) +// Note: This algorithm is also in Blink under Source/wtf/StringHasher.h. +uint32_t SuperFastHash(const char* data, int len) { + uint32_t hash = (uint32_t)len, tmp; + int rem; + + if (len <= 0 || data == NULL) + return 0; + + rem = len & 3; + len >>= 2; + + /* Main loop */ + for (; len > 0; len--) { + hash += get16bits(data); + tmp = (uint32_t)(get16bits(data + 2) << 11) ^ hash; + hash = (hash << 16) ^ tmp; + data += 2 * sizeof(uint16_t); + hash += hash >> 11; + } + + /* Handle end cases */ + switch (rem) { + case 3: + hash += get16bits(data); + hash ^= hash << 16; + hash ^= (uint32_t)(signed char)data[sizeof(uint16_t)] << 18; + hash += hash >> 11; + break; + case 2: + hash += get16bits(data); + hash ^= hash << 11; + hash += hash >> 17; + break; + case 1: + hash += (uint32_t)((signed char)*data); + hash ^= hash << 10; + hash += hash >> 1; + } + + /* Force "avalanching" of final 127 bits */ + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + + return hash; +} + +} // namespace webf diff --git a/bridge/core/base/hash/hash.h b/bridge/core/base/hash/hash.h new file mode 100644 index 0000000000..229f44a4ef --- /dev/null +++ b/bridge/core/base/hash/hash.h @@ -0,0 +1,26 @@ +// Copyright 2011 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#ifndef BASE_HASH_HASH_H_ +#define BASE_HASH_HASH_H_ + +#include +#include + +#include +#include +#include +#include + +//#include "base/containers/span.h" + +namespace webf { + +uint32_t SuperFastHash(const char* data, int len); + +} // namespace webf + +#endif // BASE_HASH_HASH_H_ diff --git a/bridge/core/base/memory/ref_counted.h b/bridge/core/base/memory/ref_counted.h new file mode 100644 index 0000000000..f6b8b0e0c9 --- /dev/null +++ b/bridge/core/base/memory/ref_counted.h @@ -0,0 +1,211 @@ +// Copyright 2012 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef WEBF_CORE_BASE_MEMORY_REF_COUNTED_H_ +#define WEBF_CORE_BASE_MEMORY_REF_COUNTED_H_ + +#include +#include +#include + +#include "core/base/memory/scoped_refptr.h" + +namespace webf { + +namespace subtle { + +class RefCountedBase { + public: + bool HasOneRef() const { return ref_count_ == 1; } + bool HasAtLeastOneRef() const { return ref_count_ >= 1; } + + void Adopted() { + // In WebF, we start with ref_count_ = 1 + assert(ref_count_ == 1); + } + + protected: + RefCountedBase() : ref_count_(1) {} + + ~RefCountedBase() { + assert(ref_count_ == 0); + } + + void AddRef() const { + assert(ref_count_ >= 0); + assert(ref_count_ < std::numeric_limits::max()); + ++ref_count_; + } + + // Returns true if the object should self-delete. + bool Release() const { + assert(ref_count_ > 0); + if (--ref_count_ == 0) { + return true; + } + return false; + } + + private: + mutable int ref_count_; + + RefCountedBase(const RefCountedBase&) = delete; + RefCountedBase& operator=(const RefCountedBase&) = delete; +}; + +class RefCountedThreadSafeBase { + public: + bool HasOneRef() const { return ref_count_.load(std::memory_order_acquire) == 1; } + bool HasAtLeastOneRef() const { return ref_count_.load(std::memory_order_acquire) >= 1; } + + void Adopted() { + // In WebF, we start with ref_count_ = 1 + assert(ref_count_.load(std::memory_order_acquire) == 1); + } + + protected: + RefCountedThreadSafeBase() : ref_count_(1) {} + + ~RefCountedThreadSafeBase() { + assert(ref_count_.load(std::memory_order_acquire) == 0); + } + + void AddRef() const { + ref_count_.fetch_add(1, std::memory_order_relaxed); + } + + // Returns true if the object should self-delete. + bool Release() const { + if (ref_count_.fetch_sub(1, std::memory_order_acq_rel) == 1) { + return true; + } + return false; + } + + private: + mutable std::atomic ref_count_; + + RefCountedThreadSafeBase(const RefCountedThreadSafeBase&) = delete; + RefCountedThreadSafeBase& operator=(const RefCountedThreadSafeBase&) = delete; +}; + +} // namespace subtle + +// A base class for reference counted classes. Otherwise, known as a cheap +// knock-off of WebKit's RefCounted class. To use this, just extend your +// class from it like so: +// +// class MyFoo : public RefCounted { +// ... +// private: +// friend class RefCounted; +// ~MyFoo(); +// }; +// +// Usage Notes: +// 1. You should always make your destructor non-public, to avoid any code +// deleting the object accidentally while there are references to it. +// 2. You should always make the ref-counted base class a friend of your class, +// so that it can access the destructor. +// +// The ref count manipulation to RefCounted is NOT thread safe and has DCHECKs +// to trap unsafe cross thread usage. A subclass instance of RefCounted can be +// passed to another execution sequence only when its ref count is 1. If the ref +// count is more than 1, the RefCounted class verifies the ref count +// manipulation is on the same execution sequence as the previous ones. The +// subclass can also manually call IsOnValidSequence to trap other non-thread +// safe accesses; see the documentation for that method. +// +template +class RefCounted : public subtle::RefCountedBase { + public: + static constexpr subtle::StartRefCountFromOneTag kRefCountPreference = + subtle::kStartRefCountFromOneTag; + + RefCounted() = default; + + RefCounted(const RefCounted&) = delete; + RefCounted& operator=(const RefCounted&) = delete; + + void AddRef() const { + subtle::RefCountedBase::AddRef(); + } + + void Release() const { + if (subtle::RefCountedBase::Release()) { + // Prune the code paths which the static analyzer may take to simulate + // object destruction. Use-after-free errors aren't possible given the + // lifetime guarantees of the refcounting system. + delete static_cast(this); + } + } + + protected: + ~RefCounted() = default; +}; + +// Forward declaration. +template class RefCountedThreadSafe; + +// Default traits for RefCountedThreadSafe. Deletes the object when its ref +// count reaches 0. Overload to delete it on a different thread etc. +template +struct DefaultRefCountedThreadSafeTraits { + static void Destruct(const T* x) { + delete x; + } +}; + +// +// A thread-safe variant of RefCounted +// +// class MyFoo : public RefCountedThreadSafe { +// ... +// }; +// +// If you're using the default trait, then you should add compile time +// asserts that no one else is deleting your object. i.e. +// private: +// friend class RefCountedThreadSafe; +// ~MyFoo(); +// +// We can use REFCOUNTED_VIRTUAL_DTOR() with RefCountedThreadSafe in the +// presence of virtual inheritance. For more details, see the comment above +// the REFCOUNTED_VIRTUAL_DTOR() macro below. +template > +class RefCountedThreadSafe : public subtle::RefCountedThreadSafeBase { + public: + static constexpr subtle::StartRefCountFromOneTag kRefCountPreference = + subtle::kStartRefCountFromOneTag; + + RefCountedThreadSafe() = default; + + RefCountedThreadSafe(const RefCountedThreadSafe&) = delete; + RefCountedThreadSafe& operator=(const RefCountedThreadSafe&) = delete; + + void AddRef() const { + subtle::RefCountedThreadSafeBase::AddRef(); + } + + void Release() const { + if (subtle::RefCountedThreadSafeBase::Release()) { + Traits::Destruct(static_cast(this)); + } + } + + protected: + ~RefCountedThreadSafe() = default; + + private: + friend struct DefaultRefCountedThreadSafeTraits; + static void DeleteInternal(const T* x) { delete x; } +}; + +} // namespace webf + +#endif // WEBF_CORE_BASE_MEMORY_REF_COUNTED_H_ \ No newline at end of file diff --git a/bridge/core/base/memory/scoped_refptr.h b/bridge/core/base/memory/scoped_refptr.h new file mode 100644 index 0000000000..67668650f0 --- /dev/null +++ b/bridge/core/base/memory/scoped_refptr.h @@ -0,0 +1,275 @@ +// Copyright 2017 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef WEBF_CORE_BASE_MEMORY_SCOPED_REFPTR_H_ +#define WEBF_CORE_BASE_MEMORY_SCOPED_REFPTR_H_ + +#include +#include +#include +#include +#include + +namespace webf { + +template +class scoped_refptr; + +namespace subtle { + +enum AdoptRefTag { kAdoptRefTag }; + +} // namespace subtle + +// Creates a scoped_refptr from a raw pointer without incrementing the reference +// count. Use this only for a newly created object whose reference count starts +// from 1 instead of 0. +template +scoped_refptr AdoptRef(T* obj) { + // In WebF, we assume reference counts start from 1 + return scoped_refptr(obj, subtle::kAdoptRefTag); +} + +// Constructs an instance of T, which is a ref counted type, and wraps the +// object into a scoped_refptr. +template +scoped_refptr MakeRefCounted(Args&&... args) { + T* obj = new T(std::forward(args)...); + return AdoptRef(obj); +} + +// Takes an instance of T, which is a ref counted type, and wraps the object +// into a scoped_refptr. +template +scoped_refptr WrapRefCounted(T* t) { + return scoped_refptr(t); +} + +// +// A smart pointer class for reference counted objects. Use this class instead +// of calling AddRef and Release manually on a reference counted object to +// avoid common memory leaks caused by forgetting to Release an object +// reference. Sample usage: +// +// class MyFoo : public RefCounted { +// ... +// private: +// friend class RefCounted; // Allow destruction by RefCounted<>. +// ~MyFoo(); // Destructor must be private/protected. +// }; +// +// void some_function() { +// scoped_refptr foo = MakeRefCounted(); +// foo->Method(param); +// // |foo| is released when this function returns +// } +// +// void some_other_function() { +// scoped_refptr foo = MakeRefCounted(); +// ... +// foo.reset(); // explicitly releases |foo| +// ... +// if (foo) +// foo->Method(param); +// } +// +template +class scoped_refptr { + public: + typedef T element_type; + + constexpr scoped_refptr() = default; + + // Allow implicit construction from nullptr. + constexpr scoped_refptr(std::nullptr_t) {} + + // Constructs from a raw pointer. Note that this constructor allows implicit + // conversion from T* to scoped_refptr which is strongly discouraged. If + // you are creating a new ref-counted object please use + // MakeRefCounted() or WrapRefCounted(). + scoped_refptr(T* p) : ptr_(p) { + if (ptr_) { + AddRef(ptr_); + } + } + + // Copy constructor. + scoped_refptr(const scoped_refptr& r) : scoped_refptr(r.ptr_) {} + + // Copy conversion constructor. + template + scoped_refptr(const scoped_refptr& r, + typename std::enable_if::value>::type* = nullptr) + : scoped_refptr(r.ptr_) {} + + // Move constructor. + scoped_refptr(scoped_refptr&& r) noexcept : ptr_(r.ptr_) { r.ptr_ = nullptr; } + + // Move conversion constructor. + template + scoped_refptr(scoped_refptr&& r, + typename std::enable_if::value>::type* = nullptr) noexcept + : ptr_(r.ptr_) { + r.ptr_ = nullptr; + } + + ~scoped_refptr() { + if (ptr_) { + Release(ptr_); + } + } + + T* get() const { return ptr_; } + + T& operator*() const { + assert(ptr_); + return *ptr_; + } + + T* operator->() const { + assert(ptr_); + return ptr_; + } + + scoped_refptr& operator=(std::nullptr_t) { + reset(); + return *this; + } + + scoped_refptr& operator=(T* p) { return *this = scoped_refptr(p); } + + // Unified assignment operator. + scoped_refptr& operator=(scoped_refptr r) noexcept { + swap(r); + return *this; + } + + // Sets managed object to null and releases reference to the previous managed + // object, if it existed. + void reset() { scoped_refptr().swap(*this); } + + // Returns the owned pointer (if any), releasing ownership to the caller. The + // caller is responsible for managing the lifetime of the reference. + [[nodiscard]] T* release(); + + void swap(scoped_refptr& r) noexcept { std::swap(ptr_, r.ptr_); } + + explicit operator bool() const { return ptr_ != nullptr; } + + template + friend bool operator==(const scoped_refptr& lhs, + const scoped_refptr& rhs) { + return lhs.ptr_ == rhs.ptr_; + } + + template + friend bool operator==(const scoped_refptr& lhs, const U* rhs) { + return lhs.ptr_ == rhs; + } + + friend bool operator==(const scoped_refptr& lhs, std::nullptr_t null) { + return !static_cast(lhs); + } + + template + friend bool operator!=(const scoped_refptr& lhs, + const scoped_refptr& rhs) { + return !(lhs == rhs); + } + + template + friend bool operator!=(const scoped_refptr& lhs, const U* rhs) { + return !(lhs == rhs); + } + + friend bool operator!=(const scoped_refptr& lhs, std::nullptr_t null) { + return static_cast(lhs); + } + + template + friend bool operator<(const scoped_refptr& lhs, + const scoped_refptr& rhs) { + return lhs.ptr_ < rhs.ptr_; + } + + template + friend bool operator>(const scoped_refptr& lhs, + const scoped_refptr& rhs) { + return lhs.ptr_ > rhs.ptr_; + } + + template + friend bool operator<=(const scoped_refptr& lhs, + const scoped_refptr& rhs) { + return lhs.ptr_ <= rhs.ptr_; + } + + template + friend bool operator>=(const scoped_refptr& lhs, + const scoped_refptr& rhs) { + return lhs.ptr_ >= rhs.ptr_; + } + + protected: + T* ptr_ = nullptr; + + private: + template + friend scoped_refptr AdoptRef(U*); + + scoped_refptr(T* p, subtle::AdoptRefTag) : ptr_(p) {} + + // Friend required for move constructors that set r.ptr_ to null. + template + friend class scoped_refptr; + + // Non-inline helpers to allow: + // class Opaque; + // extern template class scoped_refptr; + // Otherwise the compiler will complain that Opaque is an incomplete type. + static void AddRef(T* ptr); + static void Release(T* ptr); +}; + +template +T* scoped_refptr::release() { + T* ptr = ptr_; + ptr_ = nullptr; + return ptr; +} + +// static +template +void scoped_refptr::AddRef(T* ptr) { + ptr->AddRef(); +} + +// static +template +void scoped_refptr::Release(T* ptr) { + ptr->Release(); +} + +template +std::ostream& operator<<(std::ostream& out, const scoped_refptr& p) { + return out << p.get(); +} + +// Handy utility for swapping scoped_refptr objects. +template +void swap(scoped_refptr& lhs, scoped_refptr& rhs) noexcept { + lhs.swap(rhs); +} + +} // namespace webf + +// Temporary alias for migration +template +using scoped_refptr = webf::scoped_refptr; + +#endif // WEBF_CORE_BASE_MEMORY_SCOPED_REFPTR_H_ \ No newline at end of file diff --git a/bridge/core/base/memory/shared_ptr.h b/bridge/core/base/memory/shared_ptr.h new file mode 100644 index 0000000000..1b21e96040 --- /dev/null +++ b/bridge/core/base/memory/shared_ptr.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BASE_MEMORY_SHARED_PTR_H_ +#define BASE_MEMORY_SHARED_PTR_H_ + +#include +#include + +#ifdef _WIN32 +#include // For CoTaskMemAlloc and CoTaskMemFree +#endif + +namespace std { + +// Polyfill for std::reinterpret_pointer_cast(shared_ptr) on older toolchains. +// The C++20 standard library defines this; guard to avoid conflicts. +#if ANDROID && (__cplusplus < 202002L) +template +std::shared_ptr reinterpret_pointer_cast(const std::shared_ptr& r) noexcept { + auto p = reinterpret_cast::element_type*>(r.get()); + return std::shared_ptr(r, p); +} + +#endif + +} // namespace std + +namespace webf { + +namespace { + +inline void* AllocateMemory(size_t size) { +#ifdef _WIN32 + return CoTaskMemAlloc(size); +#else + return malloc(size); +#endif +} + +inline void FreeMemory(void* ptr) { +#ifdef _WIN32 + CoTaskMemFree(ptr); +#else + free(ptr); +#endif +} + +// Custom deleter for objects allocated with platform-specific allocators +template +struct ObjectDeleter { + void operator()(T* ptr) { + if (ptr) { + ptr->~T(); // Call destructor + FreeMemory(ptr); // Free memory with matching allocator + } + } +}; + +} // anonymous namespace + +template +std::shared_ptr MakeSharedPtrWithAdditionalBytes(size_t additional_bytes, Args&&... args) { + void* memory = AllocateMemory(sizeof(T) + additional_bytes); + memset(memory, 0, sizeof(T) + additional_bytes); + return std::shared_ptr(::new (memory) T(std::forward(args)...), ObjectDeleter{}); +} + +} // namespace webf + +#endif diff --git a/bridge/core/base/memory/stack_allocated.h b/bridge/core/base/memory/stack_allocated.h new file mode 100644 index 0000000000..063cea1852 --- /dev/null +++ b/bridge/core/base/memory/stack_allocated.h @@ -0,0 +1,38 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MEMORY_STACK_ALLOCATED_H_ +#define BASE_MEMORY_STACK_ALLOCATED_H_ + +namespace webf { + +// NotNullTag was originally added to WebKit here: +// https://trac.webkit.org/changeset/103243/webkit +// ...with the stated goal of improving the performance of the placement new +// operator and potentially enabling the -fomit-frame-pointer compiler flag. +// +// TODO(szager): The placement new operator which uses this tag is currently +// defined in third_party/blink/renderer/platform/wtf/allocator/allocator.h, +// in the global namespace. It should probably move to /base. +// +// It's unknown at the time of writing whether it still provides any benefit +// (or if it ever did). It is used by placing the kNotNull tag before the +// address of the object when calling placement new. +// +// If the kNotNull tag is specified to placement new for a null pointer, +// Undefined Behaviour can result. +// +// Example: +// +// union { int i; } u; +// +// // Typically placement new looks like this. +// new (&u.i) int(3); +// // But we can promise `&u.i` is not null like this. +// new (base::NotNullTag::kNotNull, &u.i) int(3); +enum class NotNullTag { kNotNull }; + +} // namespace webf + +#endif // BASE_MEMORY_STACK_ALLOCATED_H_ diff --git a/bridge/core/base/memory/values_equivalent.h b/bridge/core/base/memory/values_equivalent.h new file mode 100644 index 0000000000..565ba58cd8 --- /dev/null +++ b/bridge/core/base/memory/values_equivalent.h @@ -0,0 +1,67 @@ +// Copyright 2022 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2022-present The WebF authors. All rights reserved. + +#ifndef WEBF_VALUES_EQUIVALENT_H +#define WEBF_VALUES_EQUIVALENT_H + +#include +#include +#include + +namespace webf { + +// Compares two pointers for equality, returns the dereferenced value comparison +// if both are non-null. +// Behaves like std::optional::operator==(const std::optional&) but for +// pointers, with an optional predicate. +// If `p` is specified, `p(const T& x, const T& y)` should return whether `x` +// and `y` are equal. It's called with `(*a, *b)` when `a != b && a && b`. +template > +bool ValuesEquivalent(const T* a, const T* b, Predicate p = {}) { + if (a == b) + return true; + if (!a || !b) + return false; + return p(*a, *b); +} + +// Specialize for smart pointers like std::unique_ptr and base::scoped_refptr +// that provide a T* get() method. +// Example usage: +// struct Example { +// std::unique_ptr child; +// bool operator==(const Example& other) const { +// return base::ValuesEquivalent(child, other.child); +// } +// }; +template , + std::enable_if_t().get())>>* = nullptr> +bool ValuesEquivalent(const T& x, const T& y, Predicate p = {}) { + return ValuesEquivalent(x.get(), y.get(), std::move(p)); +} + +// Specialize for smart pointers like blink::Persistent and blink::Member that +// provide a T* Get() method. +// Example usage: +// namespace blink { +// struct Example : public GarbageCollected { +// Member child; +// bool operator==(const Example& other) const { +// return base::ValuesEquivalent(child, other.child); +// } +// void Trace(Visitor*) const; +// }; +// } // namespace blink +template , + std::enable_if_t().Get())>>* = nullptr> +bool ValuesEquivalent(const T& x, const T& y, Predicate p = {}) { + return ValuesEquivalent(x.Get(), y.Get(), std::move(p)); +} + +} // namespace webf +#endif // WEBF_VALUES_EQUIVALENT_H diff --git a/bridge/core/base/no_destructor.h b/bridge/core/base/no_destructor.h new file mode 100644 index 0000000000..be7a919b42 --- /dev/null +++ b/bridge/core/base/no_destructor.h @@ -0,0 +1,145 @@ +// Copyright 2018 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef WEBF_NO_DESTRUCTOR_H +#define WEBF_NO_DESTRUCTOR_H + +#include +#include +#include + +namespace webf { + +// Helper type to create a function-local static variable of type `T` when `T` +// has a non-trivial destructor. Storing a `T` in a `base::NoDestructor` will +// prevent `~T()` from running, even when the variable goes out of scope. +// +// Useful when a variable has static storage duration but its type has a +// non-trivial destructor. Chromium bans global constructors and destructors: +// using a function-local static variable prevents the former, while using +// `base::NoDestructor` prevents the latter. +// +// ## Caveats +// +// - Must not be used for locals or fields; by definition, this does not run +// destructors, and this will likely lead to memory leaks and other +// surprising and undesirable behaviour. +// +// - If `T` is not constexpr constructible, must be a function-local static +// variable, since a global `NoDestructor` will still generate a static +// initializer. +// +// - If `T` is constinit constructible, may be used as a global, but mark the +// global `constinit`. +// +// - If the data is rarely used, consider creating it on demand rather than +// caching it for the lifetime of the program. Though `base::NoDestructor` +// does not heap allocate, the compiler still reserves space in bss for +// storing `T`, which costs memory at runtime. +// +// - If `T` is trivially destructible, do not use `base::NoDestructor`: +// +// const uint64_t GetUnstableSessionSeed() { +// // No need to use `base::NoDestructor` as `uint64_t` is trivially +// // destructible and does not require a global destructor. +// static const uint64_t kSessionSeed = base::RandUint64(); +// return kSessionSeed; +// } +// +// ## Example Usage +// +// const std::string& GetDefaultText() { +// // Required since `static const std::string` requires a global destructor. +// static const base::NoDestructor s("Hello world!"); +// return *s; +// } +// +// More complex initialization using a lambda: +// +// const std::string& GetRandomNonce() { +// // `nonce` is initialized with random data the first time this function is +// // called, but its value is fixed thereafter. +// static const base::NoDestructor nonce([] { +// std::string s(16); +// crypto::RandString(s.data(), s.size()); +// return s; +// }()); +// return *nonce; +// } +// +// ## Thread safety +// +// Initialisation of function-local static variables is thread-safe since C++11. +// The standard guarantees that: +// +// - function-local static variables will be initialised the first time +// execution passes through the declaration. +// +// - if another thread's execution concurrently passes through the declaration +// in the middle of initialisation, that thread will wait for the in-progress +// initialisation to complete. +template +class NoDestructor { + public: + static_assert(!(std::is_trivially_constructible_v && std::is_trivially_destructible_v), + "T is trivially constructible and destructible; please use a " + "constinit object of type T directly instead"); + + static_assert(!std::is_trivially_destructible_v, + "T is trivially destructible; please use a function-local static " + "of type T directly instead"); + + // Not constexpr; just write static constexpr T x = ...; if the value should + // be a constexpr. + template + explicit NoDestructor(Args&&... args) { + new (storage_) T(std::forward(args)...); + } + + // Allows copy and move construction of the contained type, to allow + // construction from an initializer list, e.g. for std::vector. + explicit NoDestructor(const T& x) { new (storage_) T(x); } + explicit NoDestructor(T&& x) { new (storage_) T(std::move(x)); } + + NoDestructor(const NoDestructor&) = delete; + NoDestructor& operator=(const NoDestructor&) = delete; + + ~NoDestructor() = default; + + const T& operator*() const { return *get(); } + T& operator*() { return *get(); } + + const T* operator->() const { return get(); } + T* operator->() { return get(); } + + const T* get() const { return reinterpret_cast(storage_); } + T* get() { return reinterpret_cast(storage_); } + + private: + alignas(T) char storage_[sizeof(T)]; + +#if defined(LEAK_SANITIZER) + // TODO(crbug.com/40562930): This is a hack to work around the fact + // that LSan doesn't seem to treat NoDestructor as a root for reachability + // analysis. This means that code like this: + // static base::NoDestructor> v({1, 2, 3}); + // is considered a leak. Using the standard leak sanitizer annotations to + // suppress leaks doesn't work: std::vector is implicitly constructed before + // calling the base::NoDestructor constructor. + // + // Unfortunately, I haven't been able to demonstrate this issue in simpler + // reproductions: until that's resolved, hold an explicit pointer to the + // placement-new'd object in leak sanitizer mode to help LSan realize that + // objects allocated by the contained type are still reachable. + T* storage_ptr_ = reinterpret_cast(storage_); +#endif // defined(LEAK_SANITIZER) +}; + +} // namespace webf + +#endif // WEBF_NO_DESTRUCTOR_H diff --git a/bridge/core/base/notreached.h b/bridge/core/base/notreached.h new file mode 100644 index 0000000000..0526fc1150 --- /dev/null +++ b/bridge/core/base/notreached.h @@ -0,0 +1,16 @@ +// Copyright 2020 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBF_CORE_BASE_NOTREACHED_H_ +#define WEBF_CORE_BASE_NOTREACHED_H_ + +#include + +// NOTREACHED() annotates code that should not be reached. +// This is a simplified version for WebF compatibility with Blink code. +#define NOTREACHED() assert(false) + +// TODO: Add more sophisticated implementation with logging if needed + +#endif // WEBF_CORE_BASE_NOTREACHED_H_ \ No newline at end of file diff --git a/bridge/core/base/numerics/angle_conversion.h b/bridge/core/base/numerics/angle_conversion.h new file mode 100644 index 0000000000..41679952ce --- /dev/null +++ b/bridge/core/base/numerics/angle_conversion.h @@ -0,0 +1,26 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBF_BASE_NUMERICS_ANGLE_CONVERSIONS_H_ +#define WEBF_BASE_NUMERICS_ANGLE_CONVERSIONS_H_ + +#include + +namespace base { + +template +constexpr typename std::enable_if::value, T>::type DegToRad(T deg) { + constexpr T pi = static_cast(3.14159265358979323846); // Manually define pi + return deg * pi / 180; +} + +template +constexpr typename std::enable_if::value, T>::type RadToDeg(T rad) { + constexpr T pi = static_cast(3.14159265358979323846); // Manually define pi + return rad * 180 / pi; +} + +} // namespace base + +#endif // WEBF_BASE_NUMERICS_ANGLE_CONVERSIONS_H_ \ No newline at end of file diff --git a/bridge/core/base/numerics/checked_math.h b/bridge/core/base/numerics/checked_math.h new file mode 100644 index 0000000000..3fad1dd2d3 --- /dev/null +++ b/bridge/core/base/numerics/checked_math.h @@ -0,0 +1,335 @@ +// Copyright 2017 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_NUMERICS_CHECKED_MATH_H_ +#define BASE_NUMERICS_CHECKED_MATH_H_ + +#include + +#include +#include + +#include "core/base/numerics/checked_math_impl.h" + +namespace base { +namespace internal { + +template +class CheckedNumeric { + static_assert(std::is_arithmetic_v, "CheckedNumeric: T must be a numeric type."); + + public: + template + friend class CheckedNumeric; + + using type = T; + + constexpr CheckedNumeric() = default; + + // Copy constructor. + template + constexpr CheckedNumeric(const CheckedNumeric& rhs) : state_(rhs.state_.value(), rhs.IsValid()) {} + + // Strictly speaking, this is not necessary, but declaring this allows class + // template argument deduction to be used so that it is possible to simply + // write `CheckedNumeric(777)` instead of `CheckedNumeric(777)`. + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr CheckedNumeric(T value) : state_(value) {} + + // This is not an explicit constructor because we implicitly upgrade regular + // numerics to CheckedNumerics to make them easier to use. + template >> + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr CheckedNumeric(Src value) : state_(value) {} + + // This is not an explicit constructor because we want a seamless conversion + // from StrictNumeric types. + template + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr CheckedNumeric(StrictNumeric value) : state_(static_cast(value)) {} + + // IsValid() - The public API to test if a CheckedNumeric is currently valid. + // A range checked destination type can be supplied using the Dst template + // parameter. + template + constexpr bool IsValid() const { + return state_.is_valid() && IsValueInRangeForNumericType(state_.value()); + } + + // AssignIfValid(Dst) - Assigns the underlying value if it is currently valid + // and is within the range supported by the destination type. Returns true if + // successful and false otherwise. + template +#if defined(__clang__) || defined(__GNUC__) + __attribute__((warn_unused_result)) +#elif defined(_MSC_VER) + _Check_return_ +#endif + constexpr bool + AssignIfValid(Dst* result) const { + return BASE_NUMERICS_LIKELY(IsValid()) ? ((*result = static_cast(state_.value())), true) : false; + } + + // ValueOrDie() - The primary accessor for the underlying value. If the + // current state is not valid it will CHECK and crash. + // A range checked destination type can be supplied using the Dst template + // parameter, which will trigger a CHECK if the value is not in bounds for + // the destination. + // The CHECK behavior can be overridden by supplying a handler as a + // template parameter, for test code, etc. However, the handler cannot access + // the underlying value, and it is not available through other means. + template + constexpr StrictNumeric ValueOrDie() const { + return BASE_NUMERICS_LIKELY(IsValid()) ? static_cast(state_.value()) + : CheckHandler::template HandleFailure(); + } + + // ValueOrDefault(T default_value) - A convenience method that returns the + // current value if the state is valid, and the supplied default_value for + // any other state. + // A range checked destination type can be supplied using the Dst template + // parameter. WARNING: This function may fail to compile or CHECK at runtime + // if the supplied default_value is not within range of the destination type. + template + constexpr StrictNumeric ValueOrDefault(const Src default_value) const { + return BASE_NUMERICS_LIKELY(IsValid()) ? static_cast(state_.value()) : checked_cast(default_value); + } + + // Returns a checked numeric of the specified type, cast from the current + // CheckedNumeric. If the current state is invalid or the destination cannot + // represent the result then the returned CheckedNumeric will be invalid. + template + constexpr CheckedNumeric::type> Cast() const { + return *this; + } + + // This friend method is available solely for providing more detailed logging + // in the tests. Do not implement it in production code, because the + // underlying values may change at any time. + template + friend U GetNumericValueForTest(const CheckedNumeric& src); + + // Prototypes for the supported arithmetic operator overloads. + template + constexpr CheckedNumeric& operator+=(const Src rhs); + template + constexpr CheckedNumeric& operator-=(const Src rhs); + template + constexpr CheckedNumeric& operator*=(const Src rhs); + template + constexpr CheckedNumeric& operator/=(const Src rhs); + template + constexpr CheckedNumeric& operator%=(const Src rhs); + template + constexpr CheckedNumeric& operator<<=(const Src rhs); + template + constexpr CheckedNumeric& operator>>=(const Src rhs); + template + constexpr CheckedNumeric& operator&=(const Src rhs); + template + constexpr CheckedNumeric& operator|=(const Src rhs); + template + constexpr CheckedNumeric& operator^=(const Src rhs); + + constexpr CheckedNumeric operator-() const { + // Use an optimized code path for a known run-time variable. + if (!IsConstantEvaluated() && std::is_signed_v && std::is_floating_point_v) { + return FastRuntimeNegate(); + } + // The negation of two's complement int min is int min. + const bool is_valid = IsValid() && (!std::is_signed_v || std::is_floating_point_v || + NegateWrapper(state_.value()) != std::numeric_limits::lowest()); + return CheckedNumeric(NegateWrapper(state_.value()), is_valid); + } + + constexpr CheckedNumeric operator~() const { + return CheckedNumeric(InvertWrapper(state_.value()), IsValid()); + } + + constexpr CheckedNumeric Abs() const { return !IsValueNegative(state_.value()) ? *this : -*this; } + + template + constexpr CheckedNumeric::type> Max(const U rhs) const { + return CheckMax(*this, rhs); + } + + template + constexpr CheckedNumeric::type> Min(const U rhs) const { + return CheckMin(*this, rhs); + } + + // This function is available only for integral types. It returns an unsigned + // integer of the same width as the source type, containing the absolute value + // of the source, and properly handling signed min. + constexpr CheckedNumeric::type> UnsignedAbs() const { + return CheckedNumeric::type>(SafeUnsignedAbs(state_.value()), state_.is_valid()); + } + + constexpr CheckedNumeric& operator++() { + *this += 1; + return *this; + } + + constexpr CheckedNumeric operator++(int) { + CheckedNumeric value = *this; + *this += 1; + return value; + } + + constexpr CheckedNumeric& operator--() { + *this -= 1; + return *this; + } + + constexpr CheckedNumeric operator--(int) { + // TODO(pkasting): Consider std::exchange() once it's constexpr in C++20. + const CheckedNumeric value = *this; + *this -= 1; + return value; + } + + // These perform the actual math operations on the CheckedNumerics. + // Binary arithmetic operations. + template