day-piece-webview 0.4.4

An embedded native web view for Day apps: remote pages and bundled sites
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
name: ci

# Four questions, answered on every push and once a day:
#   host        does the crate build, lint, and pass its tests, with each desktop toolkit's arm
#               compiled in on a host that has that toolkit's web engine?
#   cross-check do the mobile and web arms compile for their targets?
#   renderer-comparison do native and Servo pages pass and produce matched captures?
#   demo        do the walkthroughs pass on all eight primary platform-toolkit pairs,
#               against day's main or newest release?
#
# No Cargo.lock is committed, here or in demo/: every run resolves day afresh. A push builds against the
# head of day's main (and of every other Day repository it uses), and the nightly run against
# the newest releases on crates.io (daybrite/actions' day-deps). A red push run after a day push
# is the signal, not a nuisance; a red nightly run means this crate needs a day that is not
# released yet. There is no pinned lock for a local `day patch` table to rewrite and commit by
# accident.
on:
  push:
    branches: ["**"]
    tags: ["v[0-9]+.[0-9]+.[0-9]+*"]
  pull_request:
  workflow_dispatch:
    inputs:
      day-main:
        description: Build against day's main branch instead of the newest release
        type: boolean
        default: false
      measure-sizes:
        description: Compare native-only and Servo release artifact sizes
        type: boolean
        default: true
  schedule:
    # Daily: day's main moves on weeks nobody pushes here.
    - cron: "0 11 * * *"

# The repository's own jobs run this checkout's code with a read-only token. The write grants
# sit on the job that calls the shared workflow, whose release and Pages jobs alone use them;
# its build legs, which run this code too, narrow their token to read-only themselves.
permissions:
  contents: read

jobs:
  # One row per desktop toolkit, because each links a different engine: WebKitGTK 6 and Qt
  # WebEngine on Linux, WKWebView through objc2 on macOS, and WebView2 through the XAML shim on
  # Windows (build.rs fetches the pinned WebView2 SDK package there). The feature-less clippy and
  # the tests cover the front end: the JavaScript envelope, the reply codec, and the link policy.
  host:
    strategy:
      fail-fast: false
      matrix:
        include:
          - { os: ubuntu-latest, features: gtk }
          - { os: ubuntu-latest, features: qt }
          - { os: macos-15, features: appkit }
          - { os: xcode-27, features: appkit }
          - { os: windows-latest, features: winui }
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v7
      - uses: dtolnay/rust-toolchain@stable
      # Day from crates.io, or from main on a push (daybrite/actions day-deps).
      - uses: daybrite/actions/.github/actions/day-deps@v1
        with:
          day-deps: ${{ inputs.day-main && 'main' || 'auto' }}
      - name: Install the toolkit and web engine libraries
        if: runner.os == 'Linux'
        uses: daybrite/actions/.github/actions/setup-day-deps@v1
        with:
          target: linux-${{ matrix.features }}
          pack: false
          extras: true
          java: false
      - run: cargo fmt --all -- --check
        if: matrix.features == 'gtk'
      - run: cargo fmt --manifest-path servo/Cargo.toml --all -- --check
        if: matrix.features == 'gtk'
      - name: Verify native consumer dependency isolation
        if: matrix.features == 'gtk'
        run: python3 scripts/check-native-resolution.py
      - run: cargo clippy --all-targets -- -D warnings
      - run: cargo clippy --features ${{ matrix.features }} -- -D warnings
      - run: cargo test
      - name: Test AppKit renderer registration
        if: matrix.features == 'appkit'
        run: cargo test --features appkit --lib
      - name: Test Windows evaluation payload conversion
        if: runner.os == 'Windows'
        run: |
          cmake -S tests/xaml -B target/xaml-tests
          cmake --build target/xaml-tests --config Release
          ctest --test-dir target/xaml-tests -C Release --output-on-failure
      - uses: actions/setup-node@v6
        if: matrix.features == 'gtk'
        with:
          node-version: '22'
      - run: node --test tests/*.mjs
        if: matrix.features == 'gtk'
      - run: python3 -m unittest discover -s scripts -p test_renderer_comparison.py -v
        if: matrix.features == 'gtk'
      - run: python3 -m unittest discover -s scripts -p test_windows_angle.py -v
        if: matrix.features == 'gtk'
      - run: python3 -m unittest discover -s scripts -p test_renderer_size.py -v
        if: matrix.features == 'gtk'

  # The mobile and web arms compile for their targets. A link is not attempted here (the demo
  # below does that inside a real app), but an objc2 call that no longer type-checks, a JNI
  # signature drift in the Android arm, or a wasm-only API change fails here in a minute.
  cross-check:
    strategy:
      fail-fast: false
      matrix:
        include:
          - { os: macos-15, target: aarch64-apple-ios-sim, features: uikit }
          - { os: ubuntu-latest, target: aarch64-linux-android, features: material }
          # The getrandom backend cfg is what `day build` sets for every web app; without it
          # getrandom refuses to compile for wasm32-unknown-unknown.
          - { os: ubuntu-latest, target: wasm32-unknown-unknown, features: dom, rustflags: '--cfg getrandom_backend="custom"' }
    runs-on: ${{ matrix.os }}
    env:
      RUSTFLAGS: ${{ matrix.rustflags }}
    steps:
      - uses: actions/checkout@v7
      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}
      # Day from crates.io, or from main on a push (daybrite/actions day-deps).
      - uses: daybrite/actions/.github/actions/day-deps@v1
        with:
          day-deps: ${{ inputs.day-main && 'main' || 'auto' }}
      - uses: actions/setup-java@v5
        if: matrix.features == 'material'
        with:
          distribution: temurin
          java-version: '17'
      - name: Test Android resource content-type handling
        if: matrix.features == 'material'
        run: |
          javac -d target/android-resource-tests src/DayWebContentType.java tests/android/ContentTypeTest.java
          java -cp target/android-resource-tests dev.daybrite.day.piece.webview.ContentTypeTest
      - run: cargo clippy --target ${{ matrix.target }} --features ${{ matrix.features }} -- -D warnings

  # Only implemented Servo hosts belong here. Both engines render the same fixtures in one
  # process; the ordinary native-only matrix below still tests every supported toolkit.
  # harmony-arkui is paused (docs/servo.md, "Paused: OpenHarmony"): the prebuilt SpiderMonkey
  # needs the external LLVM 19 toolchain's libc++ and its env interposer crashes on OHOS. The
  # `matrix.target == 'harmony-arkui'` steps below stay so resuming is one matrix row again.
  renderer-comparison:
    name: renderer comparison (${{ matrix.target }})
    strategy:
      fail-fast: false
      matrix:
        include:
          - { os: xcode-27, target: macos-appkit }
          - { os: macos-15, target: macos-gtk }
          - { os: macos-15, target: macos-qt }
          - { os: ubuntu-24.04, target: linux-gtk }
          - { os: ubuntu-24.04, target: linux-qt }
          - { os: windows-latest, target: windows-winui }
          - { os: ubuntu-24.04, target: android-material }
    runs-on: ${{ matrix.os }}
    timeout-minutes: 240
    permissions:
      contents: read
    env:
      CARGO_BUILD_JOBS: 2
      CARGO_INCREMENTAL: 0
      CARGO_PROFILE_DEV_DEBUG: 0
      DAY_TARGET: ${{ matrix.target }}
      DAY_LOG: warn
      # Windows uses a physically short checkout below (subst is undone by canonicalize).
      RENDERER_ROOT: .
      DEMO_DIR: demo
    steps:
      - uses: actions/checkout@v7
      - uses: dtolnay/rust-toolchain@stable
      # Day from crates.io, or from main on a push (daybrite/actions day-deps).
      - uses: daybrite/actions/.github/actions/day-deps@v1
        with:
          day-deps: ${{ inputs.day-main && 'main' || 'auto' }}
      - uses: actions/setup-python@v6
        with:
          python-version: '3.12'
      - uses: daybrite/actions/.github/actions/setup-day-cli@v1
      - uses: daybrite/actions/.github/actions/setup-day-deps@v1
        with:
          target: ${{ matrix.target }}
          pack: false
          extras: true
          java: ${{ matrix.target == 'android-material' || matrix.target == 'harmony-arkui' }}
          packages: >-
            ${{ runner.os == 'macOS' && 'python@3.12 llvm'
            || runner.os == 'Windows' && 'webview2-runtime llvm cmake ninja'
            || runner.os == 'Linux' && 'clang-19 libclang-19-dev llvm-19-dev cmake ninja-build
            pkg-config libudev-dev libegl1-mesa-dev libgles2-mesa-dev libgl1-mesa-dev
            libfontconfig1-dev libfreetype6-dev libx11-dev libxrandr-dev libxcursor-dev libxi-dev xvfb'
            || '' }}
      - name: Configure macOS Servo tools
        if: runner.os == 'macOS'
        shell: bash
        run: |
          echo "DAY_BUILD_TOOL_PATH=$(brew --prefix python@3.12)/libexec/bin:$(brew --prefix llvm)/bin" >> "$GITHUB_ENV"
          echo "$(brew --prefix python@3.12)/libexec/bin" >> "$GITHUB_PATH"
          echo "$(brew --prefix llvm)/bin" >> "$GITHUB_PATH"
          test "$(uname -m)" = arm64
      - name: Configure Linux Servo tools
        if: runner.os == 'Linux'
        shell: bash
        run: |
          # setup-day-deps installs the toolkit and the extra Servo packages above.
          # Check before compiling Servo so a missing library fails in setup.
          case "${{ matrix.target }}" in
            linux-gtk)
              pkg-config --print-errors --exists 'librsvg-2.0 >= 2.52'
              ;;
            linux-qt)
              pkg-config --print-errors --exists Qt6Svg
              ;;
          esac
          echo /usr/lib/llvm-19/bin >> "$GITHUB_PATH"
          echo LIBCLANG_PATH=/usr/lib/llvm-19/lib >> "$GITHUB_ENV"
          echo LIBGL_ALWAYS_SOFTWARE=1 >> "$GITHUB_ENV"
      - uses: ilammy/msvc-dev-cmd@v1
        if: runner.os == 'Windows'
      - name: Test Linux graphics context switching
        if: startsWith(matrix.target, 'linux-')
        shell: bash
        run: |
          rustc --edition 2024 --test tests/linux/gl-context.rs -D warnings -o "$RUNNER_TEMP/linux-gl-context"
          xvfb-run -a "$RUNNER_TEMP/linux-gl-context"
      - name: Configure Windows Servo tools
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          'C:/Program Files/LLVM/bin' >> $env:GITHUB_PATH
          'LIBCLANG_PATH=C:/Program Files/LLVM/bin' >> $env:GITHUB_ENV
          # Git Bash prepends its own link.exe, even after msvc-dev-cmd sets PATH.
          $linker = Join-Path $env:VCToolsInstallDir 'bin/Hostx64/x64/link.exe'
          if (!(Test-Path $linker)) { throw "MSVC linker missing: $linker" }
          "CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER=$linker" >> $env:GITHUB_ENV
          clang --version
          # mozangle 0.7.1's build script links libGLESv2.dll by passing all 196 object paths to
          # cl.exe on one command line. Under the runner's D:\a\<repo>\<repo>\demo\build\day\
          # flavors\servo\cargo\<target>\debug\debug\build\mozangle-<hash>\out prefix that is
          # ~36k characters, past Windows' 32,767 limit: CreateProcess fails with error 206 ("The
          # filename or extension is too long") and the script panics. Day canonicalizes its
          # project root, so subst/junction aliases resolve back to the long workspace path.
          # Clone the checked-out commit to a physically short path on this ephemeral runner.
          # Remove once mozangle links through a response file.
          $shortRoot = Join-Path ([IO.Path]::GetPathRoot($env:GITHUB_WORKSPACE)) 'wv'
          if (Test-Path $shortRoot) { throw "Short checkout already exists: $shortRoot" }
          git clone --local --no-hardlinks $env:GITHUB_WORKSPACE $shortRoot
          if ($LASTEXITCODE -ne 0) { throw 'Creating the short checkout failed' }
          $revision = git -C $env:GITHUB_WORKSPACE rev-parse HEAD
          git -C $shortRoot checkout --detach $revision
          if ($LASTEXITCODE -ne 0) { throw 'Selecting the CI revision failed' }
          "RENDERER_ROOT=$shortRoot" >> $env:GITHUB_ENV
          "DEMO_DIR=$shortRoot/demo" >> $env:GITHUB_ENV
      - name: Install Servo Android NDK
        if: matrix.target == 'android-material'
        shell: bash
        run: |
          "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" 'ndk;28.2.13676358'
          echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/28.2.13676358" >> "$GITHUB_ENV"
          echo DAY_ANDROID_ABI=x86_64 >> "$GITHUB_ENV"
      - name: Cache Servo OpenHarmony native SDK
        if: matrix.target == 'harmony-arkui'
        uses: actions/cache@v6
        with:
          path: ${{ runner.temp }}/servo-ohos-sdk-6.0.0.1/native
          key: servo-ohos-native-linux-6.0.0.1-42955c8106ca
      - name: Install Servo OpenHarmony native SDK (API 20)
        if: matrix.target == 'harmony-arkui'
        shell: bash
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          # Keep the API-18 packaging SDK installed by setup-day-deps intact.
          bash scripts/install-servo-ohos-sdk.sh
          {
            echo DAY_OHOS_ARCH=x86_64
            echo DAY_OHOS_SHOT_SETTLE_MS=2500
          } >> "$GITHUB_ENV"
      - uses: Swatinem/rust-cache@v2
        with:
          workspaces: ${{ env.DEMO_DIR }} -> build/day/flavors/servo/cargo/${{ matrix.target }}/debug
          shared-key: servo-${{ matrix.target }}
      - name: Build ARM64 mobile application
        if: matrix.target == 'android-material' || matrix.target == 'harmony-arkui'
        working-directory: ${{ env.DEMO_DIR }}
        env:
          DAY_ANDROID_ABI: arm64-v8a
          DAY_OHOS_ARCH: arm64
        shell: bash
        run: '"$DAY_BIN" build -t "$DAY_TARGET" --flavor servo'
      - name: Build the mixed-renderer application
        id: mixed-build
        working-directory: ${{ env.DEMO_DIR }}
        shell: bash
        run: '"$DAY_BIN" build -t "$DAY_TARGET" --flavor servo'
      - name: Stage and check Windows ANGLE runtime
        if: runner.os == 'Windows'
        working-directory: ${{ env.RENDERER_ROOT }}
        shell: bash
        run: python scripts/stage-windows-angle.py "demo/build/day/flavors/servo/cargo/$DAY_TARGET/debug/debug"
      - name: Enable emulator acceleration
        if: matrix.target == 'android-material' || matrix.target == 'harmony-arkui'
        shell: bash
        run: sudo chmod a+rw /dev/kvm
      - name: Boot Android comparison device
        if: matrix.target == 'android-material'
        shell: bash
        run: |
          "$DAY_BIN" devices setup -t android-material --name day-servo-ci --device pixel_7 --os 36 --arch x86_64
          SERIAL="$("$DAY_BIN" devices boot -t android-material --device day-servo-ci --wait --headless)"
          echo "ANDROID_SERIAL=$SERIAL" >> "$GITHUB_ENV"
          "$ANDROID_HOME/platform-tools/adb" -s "$SERIAL" shell settings put global hide_error_dialogs 1
          "$ANDROID_HOME/platform-tools/adb" -s "$SERIAL" shell settings put secure immersive_mode_confirmations confirmed
      - uses: actions/cache@v6
        if: matrix.target == 'harmony-arkui'
        with:
          path: ~/.cache/ohos-qemu/v20260919
          key: ohos-qemu-v20260919-x86_64_virt-phone
      - name: Boot OpenHarmony comparison device
        if: matrix.target == 'harmony-arkui'
        shell: bash
        run: bash scripts/boot-servo-ohos-ci.sh
      - name: Prepare native ArkWeb for comparison
        if: matrix.target == 'harmony-arkui'
        uses: daybrite/actions/.github/actions/setup-harmony-webview@main
      - name: Run mixed-renderer regression and comparison scripts
        working-directory: ${{ env.DEMO_DIR }}
        shell: bash
        env:
          # Ubuntu hosted runners prohibit WebKit's bubblewrap network namespace.
          # This exception is confined to the CI process running bundled fixtures.
          WEBKIT_DISABLE_SANDBOX_THIS_IS_DANGEROUS: ${{ matrix.target == 'linux-gtk' && '1' || '' }}
        run: |
          set -euo pipefail
          run_day() {
            if [[ "$DAY_TARGET" == linux-* ]]; then
              xvfb-run -a -s '-screen 0 1600x1200x24' "$DAY_BIN" "$@"
            else
              "$DAY_BIN" "$@"
            fi
          }
          run_day launch -t "$DAY_TARGET" --flavor servo --script experiments/servo.yaml
          run_day launch -t "$DAY_TARGET" --flavor servo --script experiments/renderer-comparison.yaml
      - name: Verify matched captures and browser pixels
        working-directory: ${{ env.RENDERER_ROOT }}
        shell: bash
        run: |
          python -m venv "$RUNNER_TEMP/renderer-checks"
          if [[ "$RUNNER_OS" == Windows ]]; then
            CHECK_PYTHON="$RUNNER_TEMP/renderer-checks/Scripts/python.exe"
          else
            CHECK_PYTHON="$RUNNER_TEMP/renderer-checks/bin/python"
          fi
          "$CHECK_PYTHON" -m pip install 'Pillow==11.3.0'
          "$CHECK_PYTHON" scripts/verify-renderer-comparison.py demo/build/day/flavors/servo/screenshots --target "$DAY_TARGET"
          "$CHECK_PYTHON" scripts/verify-webview-render.py demo/build/day/flavors/servo/screenshots --pattern 'renderer-*-proof.png' --color 00ff00
      # The shared website/release jobs collect screenshots-* after this matrix succeeds.
      - uses: actions/upload-artifact@v7
        if: ${{ !cancelled() && steps.mixed-build.outcome == 'success' }}
        with:
          name: screenshots-renderer-comparison-${{ matrix.target }}
          path: ${{ env.DEMO_DIR }}/build/day/flavors/servo/screenshots/
          if-no-files-found: warn
      - name: Upload script diagnostics
        if: always()
        uses: actions/upload-artifact@v7
        with:
          name: renderer-comparison-reports-${{ matrix.target }}
          path: ${{ env.DEMO_DIR }}/build/day/flavors/servo/reports/dayscript/
          if-no-files-found: warn

      - name: Compare native and Servo release sizes
        if: ${{ !cancelled() && inputs.measure-sizes == true && steps.mixed-build.outcome == 'success' }}
        working-directory: ${{ env.RENDERER_ROOT }}
        env:
          # Rebuild every staged Android ABI so neither variant retains the other engine.
          DAY_ANDROID_ABI: arm64-v8a,x86_64
          DAY_OHOS_ARCH: arm64
          MACOSX_DEPLOYMENT_TARGET: '15.5'
        shell: bash
        run: python scripts/measure-renderer-size.py --target "$DAY_TARGET" --profile release
      - name: Upload renderer size reports
        if: always()
        uses: actions/upload-artifact@v7
        with:
          name: renderer-sizes-${{ matrix.target }}
          path: ${{ env.DEMO_DIR }}/build/renderer-sizes/
          if-no-files-found: ignore

  # The proof: the demo app, driven end to end by the shared Day workflow. It builds against day's
  # newest main, runs dayscript/webview.yaml, and fails on a failed assertion or a blank capture.
  # The script loads a site bundled with the app, so no step waits on the network. Screenshots are
  # uploaded per target, and a tag build attaches them to the release.
  demo:
    # Ensures comparison captures exist before the reusable workflow assembles the website.
    needs: renderer-comparison
    uses: daybrite/actions/.github/workflows/dayapp.yml@v1
    permissions:
      contents: write # release-asset upload on tag builds
      pages: write    # project website, screenshots, and web demo
      id-token: write # deploy-pages OIDC token
    secrets: inherit
    with:
      project-path: demo
      targets: macos-appkit, linux-gtk, linux-qt, windows-winui, ios-uikit, android-material,
        harmony-arkui, web-dom
      target-packages: |
        windows-winui: webview2-runtime
      day-deps: ${{ inputs.day-main && 'main' || 'auto' }}
      # On a version tag, after every leg passed: the library crates to crates.io.
      publish-crates: true
      # The piece, then the Servo backend, which depends on the piece.
      publish-crates-paths: . servo
      scripts: dayscript/webview.yaml
      script-setup: |
        if [[ "$DAY_SCRIPT_TARGET" == android-material ]]; then
          # The normal Day stream omits activity/configuration events. Preserve those on
          # failure so a startup remount can be distinguished from a lost/repeated action.
          android_script_diagnostics() {
            local diagnostic_status=$?
            if (( diagnostic_status != 0 )); then
              echo '::group::Android activity and application diagnostics'
              timeout 10s "$ANDROID_HOME/platform-tools/adb" -s "$ANDROID_SERIAL" \
                logcat -d -v threadtime ActivityTaskManager:I ActivityManager:I \
                WindowManager:I AndroidRuntime:V Day:V day:V '*:S' || true
              echo '::endgroup::'
            fi
            return "$diagnostic_status"
          }
          trap android_script_diagnostics EXIT
        fi
      preflight-checks: fmt, clippy, test
      # website/site.toml opts into daysite: landing page, gallery, and /webapp/.
      deploy-web: true
      ios-devices: |
        device=iPhone, orientation=portrait
      android-devices: |
        device=pixel_7,     os=36, orientation=portrait

  # The same walkthrough paints a marker on every target. Checking the Harmony captures
  # separately catches a working JS engine with a blank GPU surface, without a second suite.
  harmony-rendering:
    needs: demo
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/download-artifact@v8
        with:
          pattern: screenshots-harmony-arkui*
          path: harmony-screenshots
          merge-multiple: true
      - run: python3 -m pip install 'Pillow==11.3.0'
      - run: python3 -m unittest discover -s scripts -p 'test_*.py' -v
      - run: python3 scripts/verify-webview-render.py harmony-screenshots