diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8d0fbf3309f..ca278ff2576 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -31,6 +31,9 @@ updates: servo-media-related: patterns: - "servo-media*" + objc2-related: + patterns: + - "objc2*" ignore: # Ignore all stylo crates as their upgrades are coordinated via companion PRs. - dependency-name: selectors diff --git a/.github/workflows/dispatch-workflow.yml b/.github/workflows/dispatch-workflow.yml index c938b1acd39..2e4b8ef7076 100644 --- a/.github/workflows/dispatch-workflow.yml +++ b/.github/workflows/dispatch-workflow.yml @@ -8,12 +8,18 @@ on: profile: required: true type: string + build-args: + required: true + type: string wpt-args: required: true type: string wpt: required: true type: boolean + number-of-wpt-chunks: + required: true + type: number unit-tests: required: true type: boolean @@ -32,6 +38,7 @@ jobs: secrets: inherit with: profile: ${{ inputs.profile }} + build-args: ${{ inputs.build-args }} unit-tests: ${{ inputs.unit-tests }} build-libservo: ${{ inputs.build-libservo }} bencher: ${{ inputs.bencher }} @@ -43,6 +50,7 @@ jobs: secrets: inherit with: profile: ${{ inputs.profile }} + build-args: ${{ inputs.build-args }} wpt: ${{ inputs.wpt }} unit-tests: ${{ inputs.unit-tests }} build-libservo: ${{ inputs.build-libservo }} @@ -56,7 +64,9 @@ jobs: secrets: inherit with: profile: ${{ inputs.profile }} + build-args: ${{ inputs.build-args }} wpt: ${{ inputs.wpt }} + number-of-wpt-chunks: ${{ inputs.number-of-wpt-chunks }} unit-tests: ${{ inputs.unit-tests }} build-libservo: ${{ inputs.build-libservo }} wpt-args: ${{ inputs.wpt-args }} diff --git a/.github/workflows/linux-wpt.yml b/.github/workflows/linux-wpt.yml index 03dce7456b2..e3425a39575 100644 --- a/.github/workflows/linux-wpt.yml +++ b/.github/workflows/linux-wpt.yml @@ -13,6 +13,10 @@ on: default: false required: false type: boolean + number-of-wpt-chunks: + default: 20 + required: false + type: number env: RUST_BACKTRACE: 1 @@ -24,15 +28,25 @@ env: WPT_ALWAYS_SUCCEED_ARG: "${{ inputs.wpt-sync-from-upstream && '--always-succeed' || '' }}" jobs: + chunks: + name: Generate chunks array + runs-on: ubuntu-22.04 + outputs: + chunks-array: ${{ steps.generate-chunks-array.outputs.result }} + steps: + - uses: actions/github-script@v7 + id: generate-chunks-array + with: + script: | + return Array.from({length: ${{ inputs.number-of-wpt-chunks }}}, (_, i) => i + 1) linux-wpt: name: WPT runs-on: ubuntu-22.04 - env: - max_chunk_id: 20 + needs: chunks strategy: fail-fast: false matrix: - chunk_id: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] + chunk_id: ${{ fromJson(needs.chunks.outputs.chunks-array) }} steps: - uses: actions/checkout@v4 if: github.event_name != 'pull_request_target' @@ -66,7 +80,7 @@ jobs: ./mach test-wpt \ $WPT_ALWAYS_SUCCEED_ARG \ --${{ inputs.profile }} --processes $(nproc) --timeout-multiplier 2 \ - --total-chunks ${{ env.max_chunk_id }} --this-chunk ${{ matrix.chunk_id }} \ + --total-chunks ${{ inputs.number-of-wpt-chunks }} --this-chunk ${{ matrix.chunk_id }} \ --log-raw wpt-full-logs/linux/raw/${{ matrix.chunk_id }}.log \ --log-wptreport wpt-full-logs/linux/wptreport/${{ matrix.chunk_id }}.json \ --log-raw-unexpected wpt-filtered-logs/linux/${{ matrix.chunk_id }}.log \ diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index b8914a2e810..b47f1906160 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -6,6 +6,10 @@ on: required: false default: "release" type: string + build-args: + default: "" + required: false + type: string wpt-args: default: "" required: false @@ -17,6 +21,10 @@ on: wpt: required: false type: boolean + number-of-wpt-chunks: + default: 20 + required: false + type: number unit-tests: required: false default: false @@ -58,6 +66,10 @@ on: wpt: required: false type: boolean + number-of-wpt-chunks: + default: 20 + required: false + type: number unit-tests: required: false default: false @@ -90,10 +102,10 @@ jobs: uses: ./.github/workflows/self-hosted-runner-select.yml secrets: inherit with: - # Ubuntu 22.04 has glibc 2.34 so the binaries produced - # won't run on systems with older glibc e.g wpt.fyi - # runners which still use 20.04. - github-hosted-runner-label: ubuntu-${{ inputs.upload && '20.04' || '22.04' }} + # Before updating the GH action runner image for the nightly job, ensure + # that the system has a glibc version that is compatible with the one + # used by the wpt.fyi runners. + github-hosted-runner-label: ubuntu-22.04 self-hosted-image-name: servo-ubuntu2204 force-github-hosted-runner: ${{ inputs.upload || inputs.force-github-hosted-runner }} runner-timeout: @@ -138,16 +150,8 @@ jobs: tool-cache: false large-packages: false swap-storage: false - - name: Install LLVM and Clang - # Expliclity install Clang 14 on Ubuntu 20.04 used by the nightly job (#34713). - if: ${{ runner.environment != 'self-hosted' && inputs.upload }} - uses: KyleMayes/install-llvm-action@v2 - with: - version: 14 - # FIXME: It would be better to use the above step for regular Linux jobs - # and remove the following step, but currenty that is failing and needs investigation. - name: Set LIBCLANG_PATH env # needed for bindgen in mozangle - if: ${{ runner.environment != 'self-hosted' && !inputs.upload }} # not needed on ubuntu 20.04 used for nightly + if: ${{ runner.environment != 'self-hosted' }} run: echo "LIBCLANG_PATH=/usr/lib/llvm-14/lib" >> $GITHUB_ENV - name: Setup Python if: ${{ runner.environment != 'self-hosted' }} @@ -166,7 +170,7 @@ jobs: - name: Build (${{ inputs.profile }}) run: | - ./mach build --use-crown --locked --${{ inputs.profile }} + ./mach build --use-crown --locked --${{ inputs.profile }} ${{ inputs.build-args }} cp -r target/cargo-timings target/cargo-timings-linux - name: Smoketest run: xvfb-run ./mach smoketest --${{ inputs.profile }} @@ -223,6 +227,7 @@ jobs: wpt-args: ${{ inputs.wpt-args }} profile: ${{ inputs.profile }} wpt-sync-from-upstream: ${{ inputs.wpt-sync-from-upstream }} + number-of-wpt-chunks: ${{ inputs. number-of-wpt-chunks }} secrets: inherit bencher: diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index e27c0a54180..4c19def57d2 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -7,6 +7,10 @@ on: required: false default: "release" type: string + build-args: + default: "" + required: false + type: string wpt-args: default: "" required: false @@ -146,7 +150,7 @@ jobs: brew install gnu-tar - name: Build (${{ inputs.profile }}) run: | - ./mach build --use-crown --locked --${{ inputs.profile }} + ./mach build --use-crown --locked --${{ inputs.profile }} ${{ inputs.build-args }} cp -r target/cargo-timings target/cargo-timings-macos - name: Smoketest uses: nick-fields/retry@v3 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c2bda1a7c2c..1d11033a326 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -56,6 +56,8 @@ jobs: unit-tests: ${{ matrix.unit_tests }} build-libservo: ${{ matrix.build_libservo }} wpt-args: ${{ matrix.wpt_args }} + build-args: ${{ matrix.build_args }} + number-of-wpt-chunks: ${{ matrix.number_of_wpt_chunks }} bencher: ${{ matrix.bencher }} build-result: diff --git a/.github/workflows/ohos.yml b/.github/workflows/ohos.yml index 8728d31865c..505186903b0 100644 --- a/.github/workflows/ohos.yml +++ b/.github/workflows/ohos.yml @@ -223,10 +223,8 @@ jobs: hdc shell snapshot_display -f /data/local/tmp/servo.jpeg hdc file recv /data/local/tmp/servo.jpeg test_output/servo_hos_screenshot.jpeg hdc file recv /data/local/tmp/ohtrace.txt test_output/servo.ftrace - # To limit the logsize we only save logs from servo. - # Todo: investigate giving servo a custom domain tag, so we can also log appspawn etc, - # since another common error might be the dynamic loader failing to relocate libservoshell.so - hdc shell hilog --exit --pid=${servo_pid} > test_output/servo.log + # To limit the logsize we only save logs from servo. + hdc shell hilog --exit -D 0xE0C3 > test_output/servo.log # todo: Also benchmark some other websites.... - name: Upload artifacts uses: actions/upload-artifact@v4 diff --git a/.github/workflows/try-label.yml b/.github/workflows/try-label.yml index 5444b75a4bd..d4ce3f944f6 100644 --- a/.github/workflows/try-label.yml +++ b/.github/workflows/try-label.yml @@ -128,6 +128,8 @@ jobs: unit-tests: ${{ matrix.unit_tests }} build-libservo: ${{ matrix.build_libservo }} wpt-args: ${{ matrix.wpt_args }} + build-args: ${{ matrix.build_args }} + number-of-wpt-chunks: ${{ matrix.number_of_wpt_chunks }} bencher: ${{ matrix.bencher }} results: diff --git a/.github/workflows/try.yml b/.github/workflows/try.yml index 274ad7483ff..c3a49af8857 100644 --- a/.github/workflows/try.yml +++ b/.github/workflows/try.yml @@ -10,6 +10,10 @@ on: default: "release" type: choice options: ["release", "debug", "production"] + build-args: + default: "" + required: false + type: string wpt-args: default: "" required: false @@ -79,6 +83,7 @@ jobs: // WPT-related overrides only affect Linux currently, as tests don't run by default on other platforms. configuration.matrix[0].wpt = Boolean(${{ inputs.wpt }}); configuration.matrix[0].wpt_args = "${{ inputs.wpt-args }}" || ""; + configuration.matrix[0].build_args = "${{ inputs.build-args }}" || ""; let unit_tests = Boolean(${{ inputs.unit-tests }}); let profile = '${{ inputs.profile }}'; @@ -107,7 +112,9 @@ jobs: profile: ${{ matrix.profile }} unit-tests: ${{ matrix.unit_tests }} build-libservo: ${{ matrix.build_libservo }} + build-args: ${{ matrix.build_args }} wpt-args: ${{ matrix.wpt_args }} + number-of-wpt-chunks: ${{ matrix.number_of_wpt_chunks }} bencher: ${{ matrix.bencher }} build-result: diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 5cf1e50b853..93e1710e464 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -7,6 +7,10 @@ on: required: false default: "release" type: string + build-args: + default: "" + required: false + type: string unit-tests: required: false default: false @@ -161,7 +165,7 @@ jobs: - name: Build (${{ inputs.profile }}) run: | - .\mach build --use-crown --locked --${{ inputs.profile }} + .\mach build --use-crown --locked --${{ inputs.profile }} ${{ inputs.build-args }} cp C:\a\servo\servo\target\cargo-timings C:\a\servo\servo\target\cargo-timings-windows -Recurse - name: Copy resources if: ${{ runner.environment != 'self-hosted' }} diff --git a/.gitignore b/.gitignore index 73c2fce6cda..630eb63b9bb 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,9 @@ webrender-captures/ Session.vim Sessionx.vim +# Zed +/.zed + /unminified-js /unminified-css diff --git a/Cargo.lock b/Cargo.lock index 83a6eb2e358..363ca82cfb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -249,9 +249,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.97" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "app_units" @@ -282,6 +282,12 @@ dependencies = [ "x11rb", ] +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "arkui-sys" version = "0.2.4" @@ -324,9 +330,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a194f9d963d8099596278594b3107448656ba73831c9d8c783e613ce86da64" +checksum = "b37fc50485c4f3f736a4fb14199f6d5f5ba008d7f28fe710306c92780f004c07" dependencies = [ "brotli", "flate2", @@ -387,9 +393,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-lc-rs" -version = "1.12.6" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dabb68eb3a7aa08b46fddfd59a3d55c978243557a90ab804769f7e20e67d2b01" +checksum = "93fcc8f365936c834db5514fc45aee5b1202d677e6b40e48468aaaa8183ca8c7" dependencies = [ "aws-lc-sys", "zeroize", @@ -397,9 +403,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.27.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77926887776171ced7d662120a75998e444d3750c951abfe07f90da130514b1f" +checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079" dependencies = [ "bindgen 0.69.5", "cc", @@ -436,9 +442,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -520,7 +526,7 @@ dependencies = [ "bitflags 2.9.0", "cexpr", "clang-sys", - "itertools 0.13.0", + "itertools 0.10.5", "proc-macro2", "quote", "regex", @@ -595,7 +601,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" dependencies = [ - "objc2", + "objc2 0.5.2", ] [[package]] @@ -663,9 +669,9 @@ dependencies = [ [[package]] name = "brotli" -version = "7.0.0" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -674,9 +680,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.2" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -707,18 +713,18 @@ checksum = "28346c117b50270785fbc123bd6e4ecad20d0c6d5f43d081dc80a3abcc62be64" [[package]] name = "bytemuck" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" +checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ff22c2722516255d1823ce3cc4bc0b154dbc9364be5c905d6baa6eccbbc8774" +checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" dependencies = [ "proc-macro2", "quote", @@ -778,17 +784,13 @@ name = "canvas" version = "0.0.1" dependencies = [ "app_units", - "bitflags 2.9.0", - "byteorder", "canvas_traits", + "compositing_traits", "crossbeam-channel", "cssparser", "euclid", - "fnv", "font-kit", "fonts", - "glow", - "half", "ipc-channel", "log", "lyon_geom", @@ -798,14 +800,10 @@ dependencies = [ "range", "raqote", "servo_arc", + "snapshot", "stylo", - "surfman", "unicode-script", - "webrender", "webrender_api", - "webrender_traits", - "webxr", - "webxr-api", ] [[package]] @@ -823,6 +821,7 @@ dependencies = [ "serde_bytes", "servo_config", "servo_malloc_size_of", + "snapshot", "stylo", "webrender_api", "webxr-api", @@ -845,9 +844,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.17" +version = "1.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" +checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" dependencies = [ "jobserver", "libc", @@ -902,9 +901,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", @@ -965,18 +964,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.32" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" +checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.32" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" +checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" dependencies = [ "anstyle", "clap_lex", @@ -1038,10 +1037,11 @@ dependencies = [ [[package]] name = "codespan-reporting" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" dependencies = [ + "serde", "termcolor", "unicode-width", ] @@ -1083,6 +1083,7 @@ name = "compositing" version = "0.0.1" dependencies = [ "base", + "bincode", "bitflags 2.9.0", "compositing_traits", "constellation_traits", @@ -1099,6 +1100,7 @@ dependencies = [ "pixels", "profile_traits", "script_traits", + "servo_allocator", "servo_config", "servo_geometry", "stylo_traits", @@ -1106,8 +1108,8 @@ dependencies = [ "tracing", "webrender", "webrender_api", - "webrender_traits", "webxr", + "wr_malloc_size_of", ] [[package]] @@ -1115,17 +1117,28 @@ name = "compositing_traits" version = "0.0.1" dependencies = [ "base", + "bincode", "crossbeam-channel", + "dpi", "embedder_traits", "euclid", + "gleam", + "glow", + "image", "ipc-channel", "log", + "malloc_size_of_derive", "pixels", - "script_traits", + "profile_traits", + "raw-window-handle", + "serde", + "servo_geometry", + "servo_malloc_size_of", "strum_macros", + "stylo", "stylo_traits", + "surfman", "webrender_api", - "webrender_traits", ] [[package]] @@ -1162,6 +1175,7 @@ dependencies = [ "net", "net_traits", "parking_lot", + "profile", "profile_traits", "script_layout_interface", "script_traits", @@ -1172,9 +1186,9 @@ dependencies = [ "stylo_traits", "tracing", "webgpu", + "webgpu_traits", "webrender", "webrender_api", - "webrender_traits", "webxr-api", ] @@ -1184,23 +1198,32 @@ version = "0.0.1" dependencies = [ "base", "bitflags 2.9.0", + "canvas_traits", + "devtools_traits", "embedder_traits", "euclid", + "http 1.3.1", + "hyper_serde", "ipc-channel", + "log", "malloc_size_of_derive", + "net_traits", + "profile_traits", "serde", "servo_malloc_size_of", "servo_url", + "strum", "strum_macros", - "stylo_traits", + "uuid", + "webgpu_traits", "webrender_api", + "wgpu-core", ] [[package]] name = "content-security-policy" version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f33b8ed5a7a80fdf6b7b1946f0d804c08ba348d72725b09a58fe804c48b7354f" +source = "git+https://github.com/servo/rust-content-security-policy/?branch=servo-csp#334bfcbf0a3f503b21c90aee6aee30d4b8c9558a" dependencies = [ "base64 0.22.1", "bitflags 2.9.0", @@ -1368,9 +1391,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] @@ -1468,9 +1491,9 @@ checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" [[package]] name = "darling" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", @@ -1478,23 +1501,22 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim", "syn", ] [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", @@ -1503,9 +1525,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "data-url" @@ -1548,42 +1570,11 @@ dependencies = [ "serde", ] -[[package]] -name = "derive_builder" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "derive_builder_macro" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" -dependencies = [ - "derive_builder_core", - "syn", -] - [[package]] name = "derive_more" -version = "0.99.19" +version = "0.99.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ "proc-macro2", "quote", @@ -1618,6 +1609,7 @@ version = "0.0.1" dependencies = [ "base", "bitflags 2.9.0", + "embedder_traits", "http 1.3.1", "ipc-channel", "malloc_size_of_derive", @@ -1722,6 +1714,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.1", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -1776,9 +1778,9 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dpi" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" [[package]] name = "dtoa" @@ -1803,9 +1805,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "dwrote" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70182709525a3632b2ba96b6569225467b18ecb4a77f46d255f713a6bebf05fd" +checksum = "bfe1f192fcce01590bd8d839aca53ce0d11d803bf291b2a6c4ad925a8f0024be" dependencies = [ "lazy_static", "libc", @@ -1931,7 +1933,6 @@ dependencies = [ "url", "webdriver", "webrender_api", - "webxr-api", ] [[package]] @@ -1983,9 +1984,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.7" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ "anstream", "anstyle", @@ -2026,9 +2027,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", "windows-sys 0.59.0", @@ -2036,9 +2037,9 @@ dependencies = [ [[package]] name = "error-code" -version = "3.3.1" +version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" [[package]] name = "etagere" @@ -2123,9 +2124,9 @@ checksum = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" [[package]] name = "flate2" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ "crc32fast", "miniz_oxide", @@ -2183,6 +2184,7 @@ dependencies = [ "base", "bitflags 2.9.0", "byteorder", + "compositing_traits", "core-foundation 0.9.4", "core-graphics", "core-text", @@ -2194,7 +2196,7 @@ dependencies = [ "freetype-sys", "harfbuzz-sys", "ipc-channel", - "itertools 0.13.0", + "itertools 0.14.0", "libc", "log", "malloc_size_of_derive", @@ -2202,6 +2204,7 @@ dependencies = [ "net_traits", "num-traits", "parking_lot", + "profile_traits", "range", "serde", "servo_allocator", @@ -2218,7 +2221,6 @@ dependencies = [ "unicode-script", "url", "webrender_api", - "webrender_traits", "xml-rs", "yeslogic-fontconfig-sys", ] @@ -2236,7 +2238,7 @@ dependencies = [ [[package]] name = "fontsan" version = "0.5.2" -source = "git+https://github.com/servo/fontsan#8fbc406506cfd1f8ab60e625d1e926a0e72e1d8a" +source = "git+https://github.com/servo/fontsan#c0d0b5333117901e1c31bc3c502c384115b93e6f" dependencies = [ "cc", "glob", @@ -2478,9 +2480,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", @@ -2522,9 +2524,9 @@ dependencies = [ [[package]] name = "gilrs-core" -version = "0.6.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed2326d21aa97752d41b2c195aee1d99cd84456ff4d5a7f5e6e1cdbd3dcb0b8" +checksum = "a6d95ae10ce5aa99543a28cf74e41c11f3b9e3c14f0452bbde46024753cd683e" dependencies = [ "core-foundation 0.10.0", "inotify", @@ -2549,9 +2551,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "gio-sys" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "160eb5250a26998c3e1b54e6a3d4ea15c6c7762a6062a19a7b63eff6e2b33f9e" +checksum = "521e93a7e56fc89e84aea9a52cfc9436816a4b363b030260b699950ff1336c83" dependencies = [ "glib-sys", "gobject-sys", @@ -2560,19 +2562,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "git2" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fda788993cc341f69012feba8bf45c0ba4f3291fcc08e214b4d5a7332d88aff" -dependencies = [ - "bitflags 2.9.0", - "libc", - "libgit2-sys", - "log", - "url", -] - [[package]] name = "gl_generator" version = "0.14.0" @@ -2595,9 +2584,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707b819af8059ee5395a2de9f2317d87a53dbad8846a2f089f0bb44703f37686" +checksum = "c501c495842c2b23cdacead803a5a343ca2a5d7a7ddaff14cc5f6cf22cfb92c2" dependencies = [ "bitflags 2.9.0", "futures-channel", @@ -2616,9 +2605,9 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.20.7" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715601f8f02e71baef9c1f94a657a9a77c192aea6097cf9ae7e5e177cd8cde68" +checksum = "ebe6dc9ce29887c4b3b74d78d5ba473db160a258ae7ed883d23632ac7fed7bc9" dependencies = [ "heck", "proc-macro-crate", @@ -2629,9 +2618,9 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8928869a44cfdd1fccb17d6746e4ff82c8f82e41ce705aa026a52ca8dc3aefb" +checksum = "8ab79e1ed126803a8fb827e3de0e2ff95191912b8db65cee467edb56fc4cc215" dependencies = [ "libc", "system-deps", @@ -2675,9 +2664,9 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c773a3cb38a419ad9c26c81d177d96b4b08980e8bdbbf32dace883e96e96e7e3" +checksum = "ec9aca94bb73989e3cfdbf8f2e0f1f6da04db4d291c431f444838925c4c63eda" dependencies = [ "glib-sys", "libc", @@ -2723,7 +2712,7 @@ checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" dependencies = [ "bitflags 2.9.0", "gpu-descriptor-types", - "hashbrown 0.15.2", + "hashbrown", ] [[package]] @@ -2743,9 +2732,9 @@ checksum = "36119f3a540b086b4e436bb2b588cf98a68863470e0e880f4d0842f112a3183a" [[package]] name = "gstreamer" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2188fe829b0ebe12e4cf2bbcf6658470a936269daba7afae92847a2af32c9105" +checksum = "50ab4c88f731596a2511a6f14cabdd666e0d8efab62a1d58e6ddb57faa96e22e" dependencies = [ "cfg-if", "futures-channel", @@ -2753,7 +2742,7 @@ dependencies = [ "futures-util", "glib", "gstreamer-sys", - "itertools 0.13.0", + "itertools 0.14.0", "libc", "muldiv", "num-integer", @@ -2796,9 +2785,9 @@ dependencies = [ [[package]] name = "gstreamer-audio" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49118ca684e2fc42207509fcac8497d91079c2ffe8ff2b4ae99e71dbafef1ede" +checksum = "2e7ec7e0374298897e669db7c79544bc44df12011985e7dd5f38644edaf2caf4" dependencies = [ "cfg-if", "glib", @@ -2812,9 +2801,9 @@ dependencies = [ [[package]] name = "gstreamer-audio-sys" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d469526ecf30811b50a6460fd285ee40d189c46048b3d0c69b67a04b414fb51" +checksum = "2b5f3e09e7c04ec91d78c2a6ca78d50b574b9ed49fdf5e72f3693adca4306a87" dependencies = [ "glib-sys", "gobject-sys", @@ -2826,9 +2815,9 @@ dependencies = [ [[package]] name = "gstreamer-base" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad33dd444db0d215ac363164f900f800ffb93361ad8a60840e95e14b7de985e8" +checksum = "f19a74fd04ffdcb847dd322640f2cf520897129d00a7bcb92fd62a63f3e27404" dependencies = [ "atomic_refcell", "cfg-if", @@ -2840,9 +2829,9 @@ dependencies = [ [[package]] name = "gstreamer-base-sys" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "114b2a704f19a70f20c54b00e54f5d5376bbf78bd2791e6beb0776c997d8bf24" +checksum = "87f2fb0037b6d3c5b51f60dea11e667910f33be222308ca5a101450018a09840" dependencies = [ "glib-sys", "gobject-sys", @@ -2853,9 +2842,9 @@ dependencies = [ [[package]] name = "gstreamer-gl" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02818bd81028abc4ee7b0106c21625be9a2f86ba5fd41ccff58359537637db59" +checksum = "34aa19feafc4da2c7635abce0e0768892ff97ad73586bef02d9a60b251d9fe09" dependencies = [ "glib", "gstreamer", @@ -2868,9 +2857,9 @@ dependencies = [ [[package]] name = "gstreamer-gl-egl" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5fad98961d18ed5dba4be44787d4735b78a1f53b7db392be004b96f1f2430b" +checksum = "8de1f4247cf2d009b41ab5efb03e4d826b7ccaafb9a75d3ea10e68e46f65e8aa" dependencies = [ "glib", "gstreamer", @@ -2881,9 +2870,9 @@ dependencies = [ [[package]] name = "gstreamer-gl-egl-sys" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7ebdc94dc34e2b135b2610676b47d30ce88b80862f01e2acf7e29b9b42a14e4" +checksum = "dda4d852ed107cc48692af4e109e5e4775b6ce1044d13df79f6f431c195096d7" dependencies = [ "glib-sys", "gstreamer-gl-sys", @@ -2893,9 +2882,9 @@ dependencies = [ [[package]] name = "gstreamer-gl-sys" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2984f8c246407fabbecf852c4595dd1487f4cc495386e17ad31acb69db7d39" +checksum = "a832c21d4522ed5e1b8dfc676a45361969216b144fc03af413a38c471f38bcf7" dependencies = [ "glib-sys", "gobject-sys", @@ -2983,9 +2972,9 @@ dependencies = [ [[package]] name = "gstreamer-sys" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe159238834058725808cf6604a7c5d9e4a50e1eacd7b0c63bce2fe3a067dbd1" +checksum = "feea73b4d92dbf9c24a203c9cd0bcc740d584f6b5960d5faf359febf288919b2" dependencies = [ "glib-sys", "gobject-sys", @@ -2995,9 +2984,9 @@ dependencies = [ [[package]] name = "gstreamer-video" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad242d388b63c91652c8157de3b0c1f709e49c941a0aae1952455f6ee326ca2d" +checksum = "1318b599d77ca4f7702ecbdeac1672d6304cb16b7e5752fabb3ee8260449a666" dependencies = [ "cfg-if", "futures-channel", @@ -3012,9 +3001,9 @@ dependencies = [ [[package]] name = "gstreamer-video-sys" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465ff496889fb38be47f5e821163c2e83414d87c4aa55f5aae62dc7200971d4d" +checksum = "0a70f0947f12d253b9de9bc3fd92f981e4d025336c18389c7f08cdf388a99f5c" dependencies = [ "glib-sys", "gobject-sys", @@ -3090,12 +3079,13 @@ dependencies = [ [[package]] name = "half" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", + "num-traits", ] [[package]] @@ -3115,21 +3105,12 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "serde", -] - -[[package]] -name = "hashbrown" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" dependencies = [ "foldhash", + "serde", ] [[package]] @@ -3218,10 +3199,11 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] name = "hilog" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e862eccf1f9bf3ec24a45b65f1369b2dfe053f9066c591623e0b1e58937178d" +checksum = "ae00913bdb22425089eab7ff3307019717b5f948172c0cf8cceac49ada086ce4" dependencies = [ + "arc-swap", "env_filter", "hilog-sys", "log", @@ -3262,9 +3244,9 @@ dependencies = [ [[package]] name = "html5ever" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5bf3413d61499f71fe4f627bbecfbec2977ce280525701df788f47370b0c507" +checksum = "953cbbe631aae7fc0a112702ad5d3aaf09da38beaf45ea84610d6e1c358f569c" dependencies = [ "log", "mac", @@ -3406,9 +3388,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" dependencies = [ "bytes", "futures-channel", @@ -3416,6 +3398,7 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "hyper 1.6.0", + "libc", "pin-project-lite", "socket2", "tokio", @@ -3439,9 +3422,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.62" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -3449,7 +3432,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.52.0", + "windows-core 0.58.0", ] [[package]] @@ -3480,9 +3463,9 @@ dependencies = [ [[package]] name = "icu_calendar_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e009b7f0151ee6fb28c40b1283594397e0b7183820793e9ace3dcd13db126d0" +checksum = "820499e77e852162190608b4f444e7b4552619150eafc39a9e39333d9efae9e1" [[package]] name = "icu_capi" @@ -3535,9 +3518,9 @@ dependencies = [ [[package]] name = "icu_casemap_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d57966d5ab748f74513be4046867f9a20e801e2775d41f91d04a0f560b61f08" +checksum = "02bd9f6276270c85a5cd54611adbbf94e993ec464a2a86a452a6c565b7ded5d9" [[package]] name = "icu_collator" @@ -3560,9 +3543,9 @@ dependencies = [ [[package]] name = "icu_collator_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee3f88741364b7d6269cce6827a3e6a8a2cf408a78f766c9224ab479d5e4ae5" +checksum = "7b353986d77d28991eca4dea5ef2b8982f639342ae19ca81edc44f048bc38ebb" [[package]] name = "icu_collections" @@ -3601,9 +3584,9 @@ dependencies = [ [[package]] name = "icu_datetime_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ba7e7f7a01269b9afb0a39eff4f8676f693b55f509b3120e43a0350a9f88bea" +checksum = "bef5f04076123cab1b7a926a7083db27fe0d7a0e575adb984854aae3f3a6507d" [[package]] name = "icu_decimal" @@ -3621,9 +3604,9 @@ dependencies = [ [[package]] name = "icu_decimal_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d424c994071c6f5644f999925fc868c85fec82295326e75ad5017bc94b41523" +checksum = "67c95dd97f5ccf6d837a9c115496ec7d36646fa86ca18e7f1412115b4c820ae2" [[package]] name = "icu_experimental" @@ -3657,9 +3640,9 @@ dependencies = [ [[package]] name = "icu_experimental_data" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c178b9a34083fca5bd70d61f647575335e9c197d0f30c38e8ccd187babc69d0" +checksum = "121df92eafb8f5286d4e8ff401c1e7db8384377f806db3f8db77b91e5b7bd4dd" [[package]] name = "icu_list" @@ -3677,9 +3660,9 @@ dependencies = [ [[package]] name = "icu_list_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1825170d2c6679cb20dbd96a589d034e49f698aed9a2ef4fafc9a0101ed298f" +checksum = "52b1a7fbdbf3958f1be8354cb59ac73f165b7b7082d447ff2090355c9a069120" [[package]] name = "icu_locid" @@ -3710,9 +3693,9 @@ dependencies = [ [[package]] name = "icu_locid_transform_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" [[package]] name = "icu_normalizer" @@ -3734,9 +3717,9 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_pattern" @@ -3767,9 +3750,9 @@ dependencies = [ [[package]] name = "icu_plurals_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3e8f775b215d45838814a090a2227247a7431d74e9156407d9c37f6ef0f208" +checksum = "a483403238cb7d6a876a77a5f8191780336d80fe7b8b00bfdeb20be6abbfd112" [[package]] name = "icu_properties" @@ -3789,9 +3772,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" @@ -3853,9 +3836,9 @@ dependencies = [ [[package]] name = "icu_segmenter_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f739ee737260d955e330bc83fdeaaf1631f7fb7ed218761d3c04bb13bb7d79df" +checksum = "a1e52775179941363cc594e49ce99284d13d6948928d8e72c755f55e98caa1eb" [[package]] name = "icu_timezone" @@ -3874,9 +3857,9 @@ dependencies = [ [[package]] name = "icu_timezone_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c588878c508a3e2ace333b3c50296053e6483c6a7541251b546cc59dcd6ced8e" +checksum = "1adcf7b613a268af025bc2a2532b4b9ee294e6051c5c0832d8bff20ac0232e68" [[package]] name = "ident_case" @@ -3931,12 +3914,12 @@ checksum = "76a49eaebc8750bcba241df1e1e47ebb51b81eb35c65e8f11ffa0aebac353f7f" [[package]] name = "indexmap" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown", ] [[package]] @@ -4038,9 +4021,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] @@ -4053,9 +4036,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.5" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c102670231191d07d37a35af3eb77f1f0dbf7a71be51a962dcd57ea607be7260" +checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806" dependencies = [ "jiff-static", "log", @@ -4066,9 +4049,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.5" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cdde31a9d349f1b1f51a0b3714a5940ac022976f4b49485fc04be052b183b4c" +checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48" dependencies = [ "proc-macro2", "quote", @@ -4163,7 +4146,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] -name = "layout_2020" +name = "layout" version = "0.0.1" dependencies = [ "app_units", @@ -4171,6 +4154,8 @@ dependencies = [ "base", "bitflags 2.9.0", "canvas_traits", + "compositing_traits", + "constellation_traits", "data-url", "embedder_traits", "euclid", @@ -4182,21 +4167,28 @@ dependencies = [ "icu_locid", "icu_segmenter", "ipc-channel", - "itertools 0.13.0", + "itertools 0.14.0", "log", + "malloc_size_of_derive", "net_traits", + "num-traits", "parking_lot", "pixels", + "profile_traits", "quickcheck", "range", "rayon", + "script", "script_layout_interface", + "script_traits", "selectors", "servo_arc", "servo_config", "servo_geometry", + "servo_malloc_size_of", "servo_url", "stylo", + "stylo_atoms", "stylo_traits", "taffy", "tracing", @@ -4204,47 +4196,9 @@ dependencies = [ "unicode-script", "url", "webrender_api", - "webrender_traits", "xi-unicode", ] -[[package]] -name = "layout_thread_2020" -version = "0.0.1" -dependencies = [ - "app_units", - "base", - "constellation_traits", - "embedder_traits", - "euclid", - "fnv", - "fonts", - "fonts_traits", - "fxhash", - "ipc-channel", - "layout_2020", - "log", - "metrics", - "net_traits", - "parking_lot", - "profile_traits", - "script", - "script_layout_interface", - "script_traits", - "servo_allocator", - "servo_arc", - "servo_config", - "servo_malloc_size_of", - "servo_url", - "stylo", - "stylo_atoms", - "stylo_traits", - "tracing", - "url", - "webrender_api", - "webrender_traits", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -4280,9 +4234,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.171" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libdbus-sys" @@ -4293,33 +4247,21 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "libgit2-sys" -version = "0.18.1+1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1dcb20f84ffcdd825c7a311ae347cce604a6f084a767dec4a4929829645290e" -dependencies = [ - "cc", - "libc", - "libz-sys", - "pkg-config", -] - [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] name = "libm" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" @@ -4336,6 +4278,7 @@ dependencies = [ name = "libservo" version = "0.0.1" dependencies = [ + "anyhow", "arboard", "background_hang_monitor", "base", @@ -4354,7 +4297,7 @@ dependencies = [ "devtools_traits", "dpi", "embedder_traits", - "env_logger 0.11.7", + "env_logger 0.11.8", "euclid", "fonts", "gaol", @@ -4363,7 +4306,7 @@ dependencies = [ "http 1.3.1", "ipc-channel", "keyboard-types", - "layout_thread_2020", + "layout", "libservo", "log", "media", @@ -4382,6 +4325,8 @@ dependencies = [ "servo-media", "servo-media-dummy", "servo-media-gstreamer", + "servo-tracing", + "servo_allocator", "servo_config", "servo_geometry", "servo_url", @@ -4391,10 +4336,10 @@ dependencies = [ "tracing", "url", "webdriver_server", + "webgl", "webgpu", "webrender", "webrender_api", - "webrender_traits", "webxr", "webxr-api", "winit", @@ -4512,16 +4457,13 @@ dependencies = [ [[package]] name = "markup5ever" -version = "0.15.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a7b81dfb91586d0677086d40a6d755070e0799b71bb897485bac408dfd5c69" +checksum = "d0a8096766c229e8c88a3900c9b44b7e06aa7f7343cc229158c3e58ef8f9973a" dependencies = [ "log", - "phf", - "phf_codegen", - "string_cache", - "string_cache_codegen", "tendril", + "web_atoms", ] [[package]] @@ -4554,6 +4496,7 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" name = "media" version = "0.0.1" dependencies = [ + "compositing_traits", "euclid", "fnv", "ipc-channel", @@ -4562,7 +4505,6 @@ dependencies = [ "servo-media", "servo_config", "webrender_api", - "webrender_traits", ] [[package]] @@ -4623,6 +4565,7 @@ name = "metrics" version = "0.0.1" dependencies = [ "base", + "constellation_traits", "ipc-channel", "log", "malloc_size_of_derive", @@ -4667,9 +4610,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", "simd-adler32", @@ -4703,7 +4646,7 @@ dependencies = [ [[package]] name = "mozjs" version = "0.14.1" -source = "git+https://github.com/servo/mozjs#39db84352d79ffa3e4fe756c8034af9def788f22" +source = "git+https://github.com/servo/mozjs#728acdf3d4ce0604e9f75dd1d539dc6f291ccec7" dependencies = [ "bindgen 0.71.1", "cc", @@ -4714,8 +4657,8 @@ dependencies = [ [[package]] name = "mozjs_sys" -version = "0.128.9-0" -source = "git+https://github.com/servo/mozjs#39db84352d79ffa3e4fe756c8034af9def788f22" +version = "0.128.9-2" +source = "git+https://github.com/servo/mozjs#728acdf3d4ce0604e9f75dd1d539dc6f291ccec7" dependencies = [ "bindgen 0.71.1", "cc", @@ -4737,22 +4680,25 @@ checksum = "956787520e75e9bd233246045d19f42fb73242759cc57fba9611d940ae96d4b0" [[package]] name = "naga" -version = "24.0.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=2f255edc60e9669c8c737464c59af10d59a31126#2f255edc60e9669c8c737464c59af10d59a31126" +version = "25.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632" dependencies = [ "arrayvec", "bit-set", "bitflags 2.9.0", "cfg_aliases", "codespan-reporting", - "hashbrown 0.14.5", + "half", + "hashbrown", "hexf-parse", "indexmap", "log", + "num-traits", + "once_cell", "rustc-hash 1.1.0", "spirv", "strum", - "termcolor", "thiserror 2.0.9", "unicode-ident", ] @@ -4854,6 +4800,7 @@ dependencies = [ "base64 0.22.1", "bytes", "chrono", + "compositing_traits", "content-security-policy", "cookie 0.18.1", "crossbeam-channel", @@ -4874,6 +4821,7 @@ dependencies = [ "hyper_serde", "imsz", "ipc-channel", + "itertools 0.14.0", "log", "malloc_size_of_derive", "mime", @@ -4904,7 +4852,6 @@ dependencies = [ "uuid", "webpki-roots", "webrender_api", - "webrender_traits", ] [[package]] @@ -4912,6 +4859,7 @@ name = "net_traits" version = "0.0.1" dependencies = [ "base", + "compositing_traits", "content-security-policy", "cookie 0.18.1", "crossbeam-channel", @@ -4927,6 +4875,7 @@ dependencies = [ "num-traits", "percent-encoding", "pixels", + "profile_traits", "rustls-pki-types", "serde", "servo_arc", @@ -4935,7 +4884,6 @@ dependencies = [ "servo_url", "url", "uuid", - "webrender_traits", ] [[package]] @@ -5126,6 +5074,15 @@ dependencies = [ "objc2-encode", ] +[[package]] +name = "objc2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +dependencies = [ + "objc2-encode", +] + [[package]] name = "objc2-app-kit" version = "0.2.2" @@ -5135,13 +5092,24 @@ dependencies = [ "bitflags 2.9.0", "block2", "libc", - "objc2", + "objc2 0.5.2", "objc2-core-data", "objc2-core-image", - "objc2-foundation", + "objc2-foundation 0.2.2", "objc2-quartz-core", ] +[[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.1", + "objc2-foundation 0.3.1", +] + [[package]] name = "objc2-cloud-kit" version = "0.2.2" @@ -5150,9 +5118,9 @@ checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", + "objc2 0.5.2", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -5162,8 +5130,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" dependencies = [ "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -5174,8 +5142,19 @@ checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags 2.9.0", + "dispatch2", + "objc2 0.6.1", ] [[package]] @@ -5185,8 +5164,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", "objc2-metal", ] @@ -5197,9 +5176,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" dependencies = [ "block2", - "objc2", + "objc2 0.5.2", "objc2-contacts", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -5218,7 +5197,18 @@ dependencies = [ "block2", "dispatch", "libc", - "objc2", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.1", + "objc2-core-foundation", ] [[package]] @@ -5228,9 +5218,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ "block2", - "objc2", - "objc2-app-kit", - "objc2-foundation", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -5241,8 +5231,8 @@ checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -5253,8 +5243,8 @@ checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", "objc2-metal", ] @@ -5264,8 +5254,8 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" dependencies = [ - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -5276,12 +5266,12 @@ checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", + "objc2 0.5.2", "objc2-cloud-kit", "objc2-core-data", "objc2-core-image", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", "objc2-link-presentation", "objc2-quartz-core", "objc2-symbols", @@ -5296,8 +5286,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" dependencies = [ "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -5308,9 +5298,9 @@ checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", + "objc2 0.5.2", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -5388,9 +5378,9 @@ checksum = "93e60dacb893ed4eb57e8fd9eff81b8ad0153842661b3093927bdfd861506652" [[package]] name = "once_cell" -version = "1.21.1" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oorandom" @@ -5530,7 +5520,7 @@ dependencies = [ [[package]] name = "peek-poke" version = "0.3.0" -source = "git+https://github.com/servo/webrender?branch=0.66#88462530746749163bcf1dc89be20a19f2394e71" +source = "git+https://github.com/servo/webrender?branch=0.67#ae2477d9a6da403e5b5dce8a17415a2cd1563074" dependencies = [ "euclid", "peek-poke-derive", @@ -5539,13 +5529,12 @@ dependencies = [ [[package]] name = "peek-poke-derive" version = "0.3.0" -source = "git+https://github.com/servo/webrender?branch=0.66#88462530746749163bcf1dc89be20a19f2394e71" +source = "git+https://github.com/servo/webrender?branch=0.67#ae2477d9a6da403e5b5dce8a17415a2cd1563074" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", - "unicode-xid", ] [[package]] @@ -5792,9 +5781,9 @@ checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" [[package]] name = "prettyplease" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb" +checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" dependencies = [ "proc-macro2", "syn", @@ -5811,9 +5800,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -5825,6 +5814,7 @@ dependencies = [ "base", "ipc-channel", "libc", + "log", "parking_lot", "profile_traits", "regex", @@ -5857,6 +5847,7 @@ dependencies = [ "log", "malloc_size_of_derive", "serde", + "servo_allocator", "servo_config", "servo_malloc_size_of", "signpost", @@ -5905,9 +5896,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.37.3" +version = "0.37.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf763ab1c7a3aa408be466efc86efe35ed1bd3dd74173ed39d6b0d0a6f0ba148" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" dependencies = [ "memchr", ] @@ -6143,14 +6134,15 @@ dependencies = [ [[package]] name = "ron" -version = "0.8.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +checksum = "beceb6f7bf81c73e73aeef6dd1356d9a1b2b4909e1f0fc3e59b034f9572d7b7f" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "bitflags 2.9.0", "serde", "serde_derive", + "unicode-ident", ] [[package]] @@ -6195,9 +6187,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.25" +version = "0.23.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" +checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" dependencies = [ "aws-lc-rs", "log", @@ -6219,15 +6211,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] [[package]] name = "rustls-webpki" -version = "0.103.1" +version = "0.103.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" dependencies = [ "aws-lc-rs", "ring", @@ -6291,6 +6286,7 @@ dependencies = [ "cbc", "chrono", "cipher", + "compositing_traits", "constellation_traits", "content-security-policy", "cookie 0.18.1", @@ -6317,12 +6313,13 @@ dependencies = [ "image", "indexmap", "ipc-channel", - "itertools 0.13.0", + "itertools 0.14.0", "jstraceable_derive", "keyboard-types", "libc", "log", "malloc_size_of_derive", + "markup5ever", "media", "metrics", "mime", @@ -6356,6 +6353,7 @@ dependencies = [ "servo_rand", "servo_url", "smallvec", + "snapshot", "strum", "strum_macros", "stylo", @@ -6372,13 +6370,15 @@ dependencies = [ "unicode-bidi", "unicode-segmentation", "url", + "urlpattern", "utf-8", "uuid", "webdriver", - "webgpu", + "webgpu_traits", "webrender_api", - "webrender_traits", "webxr-api", + "wgpu-core", + "wgpu-types", "xml5ever", ] @@ -6389,6 +6389,9 @@ dependencies = [ "bitflags 2.9.0", "crossbeam-channel", "cssparser", + "deny_public_fields", + "dom_struct", + "domobject_derive", "html5ever", "indexmap", "jstraceable_derive", @@ -6398,6 +6401,7 @@ dependencies = [ "mozjs", "num-traits", "parking_lot", + "phf", "phf_codegen", "phf_shared", "regex", @@ -6405,10 +6409,12 @@ dependencies = [ "servo_arc", "servo_config", "servo_malloc_size_of", + "servo_url", "smallvec", "stylo", "stylo_atoms", "tendril", + "tracing", "webxr-api", "xml5ever", ] @@ -6421,11 +6427,14 @@ dependencies = [ "atomic_refcell", "base", "canvas_traits", + "compositing_traits", "constellation_traits", + "embedder_traits", "euclid", "fnv", "fonts", "fonts_traits", + "fxhash", "html5ever", "ipc-channel", "libc", @@ -6444,7 +6453,6 @@ dependencies = [ "stylo", "stylo_traits", "webrender_api", - "webrender_traits", ] [[package]] @@ -6463,21 +6471,17 @@ version = "0.0.1" dependencies = [ "background_hang_monitor_api", "base", - "bitflags 2.9.0", "bluetooth_traits", "canvas_traits", + "compositing_traits", "constellation_traits", - "cookie 0.18.1", "crossbeam-channel", "devtools_traits", "embedder_traits", "euclid", "http 1.3.1", - "hyper_serde", "ipc-channel", "keyboard-types", - "libc", - "log", "malloc_size_of_derive", "media", "net_traits", @@ -6490,11 +6494,8 @@ dependencies = [ "strum_macros", "stylo_atoms", "stylo_traits", - "uuid", - "webdriver", - "webgpu", + "webgpu_traits", "webrender_api", - "webrender_traits", "webxr-api", ] @@ -6513,8 +6514,8 @@ dependencies = [ [[package]] name = "selectors" -version = "0.27.0" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#bc4717c7842ad59243f00ae76ba23f998c749b94" +version = "0.28.0" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" dependencies = [ "bitflags 2.9.0", "cssparser", @@ -6624,7 +6625,7 @@ dependencies = [ [[package]] name = "servo-media" version = "0.1.0" -source = "git+https://github.com/servo/media#c7eab1ae326b8b95b938741660553342f7cd94b7" +source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" dependencies = [ "once_cell", "servo-media-audio", @@ -6637,7 +6638,7 @@ dependencies = [ [[package]] name = "servo-media-audio" version = "0.2.0" -source = "git+https://github.com/servo/media#c7eab1ae326b8b95b938741660553342f7cd94b7" +source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" dependencies = [ "byte-slice-cast", "euclid", @@ -6658,7 +6659,7 @@ dependencies = [ [[package]] name = "servo-media-derive" version = "0.1.0" -source = "git+https://github.com/servo/media#c7eab1ae326b8b95b938741660553342f7cd94b7" +source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" dependencies = [ "proc-macro2", "quote", @@ -6668,7 +6669,7 @@ dependencies = [ [[package]] name = "servo-media-dummy" version = "0.1.0" -source = "git+https://github.com/servo/media#c7eab1ae326b8b95b938741660553342f7cd94b7" +source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" dependencies = [ "ipc-channel", "servo-media", @@ -6682,7 +6683,7 @@ dependencies = [ [[package]] name = "servo-media-gstreamer" version = "0.1.0" -source = "git+https://github.com/servo/media#c7eab1ae326b8b95b938741660553342f7cd94b7" +source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" dependencies = [ "byte-slice-cast", "glib", @@ -6715,7 +6716,7 @@ dependencies = [ [[package]] name = "servo-media-gstreamer-render" version = "0.1.0" -source = "git+https://github.com/servo/media#c7eab1ae326b8b95b938741660553342f7cd94b7" +source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" dependencies = [ "gstreamer", "gstreamer-video", @@ -6725,7 +6726,7 @@ dependencies = [ [[package]] name = "servo-media-gstreamer-render-android" version = "0.1.0" -source = "git+https://github.com/servo/media#c7eab1ae326b8b95b938741660553342f7cd94b7" +source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" dependencies = [ "glib", "gstreamer", @@ -6739,7 +6740,7 @@ dependencies = [ [[package]] name = "servo-media-gstreamer-render-unix" version = "0.1.0" -source = "git+https://github.com/servo/media#c7eab1ae326b8b95b938741660553342f7cd94b7" +source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" dependencies = [ "glib", "gstreamer", @@ -6754,7 +6755,7 @@ dependencies = [ [[package]] name = "servo-media-player" version = "0.1.0" -source = "git+https://github.com/servo/media#c7eab1ae326b8b95b938741660553342f7cd94b7" +source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" dependencies = [ "ipc-channel", "serde", @@ -6766,7 +6767,7 @@ dependencies = [ [[package]] name = "servo-media-streams" version = "0.1.0" -source = "git+https://github.com/servo/media#c7eab1ae326b8b95b938741660553342f7cd94b7" +source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" dependencies = [ "uuid", ] @@ -6774,18 +6775,28 @@ dependencies = [ [[package]] name = "servo-media-traits" version = "0.1.0" -source = "git+https://github.com/servo/media#c7eab1ae326b8b95b938741660553342f7cd94b7" +source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" [[package]] name = "servo-media-webrtc" version = "0.1.0" -source = "git+https://github.com/servo/media#c7eab1ae326b8b95b938741660553342f7cd94b7" +source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" dependencies = [ "log", "servo-media-streams", "uuid", ] +[[package]] +name = "servo-tracing" +version = "0.0.1" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "servo_allocator" version = "0.0.1" @@ -6798,8 +6809,8 @@ dependencies = [ [[package]] name = "servo_arc" -version = "0.4.0" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#bc4717c7842ad59243f00ae76ba23f998c749b94" +version = "0.4.1" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" dependencies = [ "serde", "stable_deref_trait", @@ -6843,6 +6854,7 @@ version = "0.0.1" dependencies = [ "accountable-refcell", "app_units", + "atomic_refcell", "content-security-policy", "crossbeam-channel", "euclid", @@ -6858,9 +6870,13 @@ dependencies = [ "stylo", "stylo_dom", "stylo_malloc_size_of", + "taffy", "thin-vec", "tokio", + "unicode-bidi", + "unicode-script", "url", + "urlpattern", "uuid", "webrender_api", "wr_malloc_size_of", @@ -6931,14 +6947,15 @@ dependencies = [ "net", "net_traits", "nix", - "objc2-app-kit", - "objc2-foundation", + "objc2-app-kit 0.3.1", + "objc2-foundation 0.3.1", "ohos-ime", "ohos-ime-sys", "ohos-vsync", "raw-window-handle", "rustls", "serde_json", + "servo-tracing", "servo_allocator", "shellwords", "sig", @@ -6948,7 +6965,6 @@ dependencies = [ "tracing-perfetto", "tracing-subscriber", "url", - "vergen-git2", "windows-sys 0.59.0", "winit", "winres", @@ -6968,9 +6984,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -7066,9 +7082,9 @@ checksum = "d31d263dd118560e1a492922182ab6ca6dc1d03a3bf54e7699993f31a4150e3f" [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" dependencies = [ "serde", ] @@ -7118,11 +7134,21 @@ dependencies = [ "serde", ] +[[package]] +name = "snapshot" +version = "0.0.1" +dependencies = [ + "euclid", + "ipc-channel", + "pixels", + "serde", +] + [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", "windows-sys 0.52.0", @@ -7179,9 +7205,9 @@ checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" [[package]] name = "string_cache" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938d512196766101d333398efde81bc1f37b00cb42c2f8350e5df639f040bbbe" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", "parking_lot", @@ -7202,12 +7228,6 @@ dependencies = [ "quote", ] -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - [[package]] name = "strum" version = "0.26.3" @@ -7250,8 +7270,8 @@ dependencies = [ [[package]] name = "stylo" -version = "0.2.0" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#bc4717c7842ad59243f00ae76ba23f998c749b94" +version = "0.3.0" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" dependencies = [ "app_units", "arrayvec", @@ -7265,12 +7285,11 @@ dependencies = [ "fxhash", "icu_segmenter", "indexmap", - "itertools 0.10.5", + "itertools 0.14.0", "itoa", "lazy_static", "log", "malloc_size_of_derive", - "markup5ever", "matches", "mime", "new_debug_unreachable", @@ -7304,12 +7323,13 @@ dependencies = [ "url", "void", "walkdir", + "web_atoms", ] [[package]] name = "stylo_atoms" -version = "0.2.0" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#bc4717c7842ad59243f00ae76ba23f998c749b94" +version = "0.3.0" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" dependencies = [ "string_cache", "string_cache_codegen", @@ -7317,13 +7337,13 @@ dependencies = [ [[package]] name = "stylo_config" -version = "0.2.0" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#bc4717c7842ad59243f00ae76ba23f998c749b94" +version = "0.3.0" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" [[package]] name = "stylo_derive" -version = "0.2.0" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#bc4717c7842ad59243f00ae76ba23f998c749b94" +version = "0.3.0" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" dependencies = [ "darling", "proc-macro2", @@ -7334,8 +7354,8 @@ dependencies = [ [[package]] name = "stylo_dom" -version = "0.2.0" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#bc4717c7842ad59243f00ae76ba23f998c749b94" +version = "0.3.0" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" dependencies = [ "bitflags 2.9.0", "stylo_malloc_size_of", @@ -7343,8 +7363,8 @@ dependencies = [ [[package]] name = "stylo_malloc_size_of" -version = "0.2.0" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#bc4717c7842ad59243f00ae76ba23f998c749b94" +version = "0.3.0" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" dependencies = [ "app_units", "cssparser", @@ -7360,13 +7380,13 @@ dependencies = [ [[package]] name = "stylo_static_prefs" -version = "0.2.0" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#bc4717c7842ad59243f00ae76ba23f998c749b94" +version = "0.3.0" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" [[package]] name = "stylo_traits" -version = "0.2.0" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#bc4717c7842ad59243f00ae76ba23f998c749b94" +version = "0.3.0" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" dependencies = [ "app_units", "bitflags 2.9.0", @@ -7421,9 +7441,9 @@ dependencies = [ [[package]] name = "svg_fmt" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce5d813d71d82c4cbc1742135004e4a79fd870214c155443451c139c9470a0aa" +checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" [[package]] name = "sw-composite" @@ -7439,9 +7459,9 @@ checksum = "e454d048db5527d000bfddb77bd072bbf3a1e2ae785f16d9bd116e07c2ab45eb" [[package]] name = "syn" -version = "2.0.100" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -7450,9 +7470,9 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", @@ -7749,7 +7769,7 @@ dependencies = [ [[package]] name = "to_shmem" version = "0.2.0" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#bc4717c7842ad59243f00ae76ba23f998c749b94" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" dependencies = [ "cssparser", "servo_arc", @@ -7762,7 +7782,7 @@ dependencies = [ [[package]] name = "to_shmem_derive" version = "0.1.0" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#bc4717c7842ad59243f00ae76ba23f998c749b94" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" dependencies = [ "darling", "proc-macro2", @@ -7773,9 +7793,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.44.1" +version = "1.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" +checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" dependencies = [ "backtrace", "bytes", @@ -7821,9 +7841,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", @@ -7855,18 +7875,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap", "serde", @@ -8039,6 +8059,47 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + [[package]] name = "unicase" version = "2.8.1" @@ -8081,12 +8142,6 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "universal-hash" version = "0.5.1" @@ -8125,6 +8180,18 @@ dependencies = [ "serde", ] +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + [[package]] name = "utf-8" version = "0.7.6" @@ -8177,45 +8244,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" -[[package]] -name = "vergen" -version = "9.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0d2f179f8075b805a43a2a21728a46f0cc2921b3c58695b28fa8817e103cd9a" -dependencies = [ - "anyhow", - "derive_builder", - "rustversion", - "time", - "vergen-lib", -] - -[[package]] -name = "vergen-git2" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d86bae87104cb2790cdee615c2bb54729804d307191732ab27b1c5357ea6ddc5" -dependencies = [ - "anyhow", - "derive_builder", - "git2", - "rustversion", - "time", - "vergen", - "vergen-lib", -] - -[[package]] -name = "vergen-lib" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b07e6010c0f3e59fcb164e0163834597da68d1f864e2b8ca49f74de01e9c166" -dependencies = [ - "anyhow", - "derive_builder", - "rustversion", -] - [[package]] name = "version-compare" version = "0.2.0" @@ -8356,9 +8384,9 @@ checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" [[package]] name = "wayland-backend" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7208998eaa3870dad37ec8836979581506e0c5c64c20c9e79e9d2a10d6f47bf" +checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" dependencies = [ "cc", "downcast-rs", @@ -8370,9 +8398,9 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.8" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2120de3d33638aaef5b9f4472bff75f07c56379cf76ea320bd3a3d65ecaf73f" +checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" dependencies = [ "bitflags 2.9.0", "rustix", @@ -8393,9 +8421,9 @@ dependencies = [ [[package]] name = "wayland-cursor" -version = "0.31.8" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a93029cbb6650748881a00e4922b076092a6a08c11e7fbdb923f064b23968c5d" +checksum = "a65317158dec28d00416cb16705934070aef4f8393353d41126c54264ae0f182" dependencies = [ "rustix", "wayland-client", @@ -8404,9 +8432,9 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.32.6" +version = "0.32.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0781cf46869b37e36928f7b432273c0995aa8aed9552c556fb18754420541efc" +checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" dependencies = [ "bitflags 2.9.0", "wayland-backend", @@ -8416,9 +8444,9 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ccaacc76703fefd6763022ac565b590fcade92202492381c95b2edfdf7d46b3" +checksum = "4fd38cdad69b56ace413c6bcc1fbf5acc5e2ef4af9d5f8f1f9570c0c83eae175" dependencies = [ "bitflags 2.9.0", "wayland-backend", @@ -8429,9 +8457,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248a02e6f595aad796561fa82d25601bd2c8c3b145b1c7453fc8f94c1a58f8b2" +checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" dependencies = [ "bitflags 2.9.0", "wayland-backend", @@ -8496,12 +8524,24 @@ dependencies = [ ] [[package]] -name = "webdriver" -version = "0.51.0" +name = "web_atoms" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310ce9d3648c5ff1915ca7dd09c44eabb7eb17f9ff4a6e7e5f4a902c8d1e269f" +checksum = "0b9c5f0bc545ea3b20b423e33b9b457764de0b3730cd957f6c6aa6c301785f6e" dependencies = [ - "base64 0.21.7", + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", +] + +[[package]] +name = "webdriver" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91d53921e1bef27512fa358179c9a22428d55778d2c2ae3c5c37a52b82ce6e92" +dependencies = [ + "base64 0.22.1", "bytes", "cookie 0.16.2", "http 0.2.12", @@ -8546,30 +8586,72 @@ dependencies = [ "webdriver", ] +[[package]] +name = "webgl" +version = "0.0.1" +dependencies = [ + "bitflags 2.9.0", + "byteorder", + "canvas_traits", + "compositing_traits", + "crossbeam-channel", + "euclid", + "fnv", + "glow", + "half", + "ipc-channel", + "itertools 0.14.0", + "log", + "pixels", + "snapshot", + "surfman", + "webrender", + "webrender_api", + "webxr", + "webxr-api", +] + [[package]] name = "webgpu" version = "0.0.1" dependencies = [ "arrayvec", "base", + "compositing_traits", "euclid", "ipc-channel", "log", "serde", "servo_config", "servo_malloc_size_of", + "snapshot", + "webgpu_traits", "webrender", "webrender_api", - "webrender_traits", + "wgpu-core", + "wgpu-types", +] + +[[package]] +name = "webgpu_traits" +version = "0.0.1" +dependencies = [ + "arrayvec", + "base", + "ipc-channel", + "serde", + "servo_malloc_size_of", + "snapshot", + "webrender_api", "wgpu-core", "wgpu-types", ] [[package]] name = "webpki-roots" -version = "0.26.8" +version = "0.26.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +checksum = "37493cadf42a2a939ed404698ded7fb378bf301b5011f973361779a3a74f8c93" dependencies = [ "rustls-pki-types", ] @@ -8577,7 +8659,7 @@ dependencies = [ [[package]] name = "webrender" version = "0.66.0" -source = "git+https://github.com/servo/webrender?branch=0.66#88462530746749163bcf1dc89be20a19f2394e71" +source = "git+https://github.com/servo/webrender?branch=0.67#ae2477d9a6da403e5b5dce8a17415a2cd1563074" dependencies = [ "allocator-api2", "bincode", @@ -8612,7 +8694,7 @@ dependencies = [ [[package]] name = "webrender_api" version = "0.66.0" -source = "git+https://github.com/servo/webrender?branch=0.66#88462530746749163bcf1dc89be20a19f2394e71" +source = "git+https://github.com/servo/webrender?branch=0.67#ae2477d9a6da403e5b5dce8a17415a2cd1563074" dependencies = [ "app_units", "bitflags 2.9.0", @@ -8633,41 +8715,18 @@ dependencies = [ [[package]] name = "webrender_build" version = "0.0.2" -source = "git+https://github.com/servo/webrender?branch=0.66#88462530746749163bcf1dc89be20a19f2394e71" +source = "git+https://github.com/servo/webrender?branch=0.67#ae2477d9a6da403e5b5dce8a17415a2cd1563074" dependencies = [ "bitflags 2.9.0", "lazy_static", ] -[[package]] -name = "webrender_traits" -version = "0.0.1" -dependencies = [ - "base", - "constellation_traits", - "dpi", - "embedder_traits", - "euclid", - "gleam", - "glow", - "image", - "ipc-channel", - "libc", - "log", - "raw-window-handle", - "serde", - "servo-media", - "servo_geometry", - "stylo", - "surfman", - "webrender_api", -] - [[package]] name = "webxr" version = "0.0.1" dependencies = [ "crossbeam-channel", + "embedder_traits", "euclid", "glow", "log", @@ -8684,6 +8743,7 @@ dependencies = [ name = "webxr-api" version = "0.0.1" dependencies = [ + "embedder_traits", "euclid", "ipc-channel", "log", @@ -8698,15 +8758,17 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "wgpu-core" -version = "24.0.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=2f255edc60e9669c8c737464c59af10d59a31126#2f255edc60e9669c8c737464c59af10d59a31126" +version = "25.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19813e647da7aa3cdaa84f5846e2c64114970ea7c86b1e6aae8be08091f4bdc" dependencies = [ "arrayvec", + "bit-set", "bit-vec", "bitflags 2.9.0", "cfg_aliases", "document-features", - "hashbrown 0.14.5", + "hashbrown", "indexmap", "log", "naga", @@ -8726,32 +8788,36 @@ dependencies = [ [[package]] name = "wgpu-core-deps-apple" -version = "24.0.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=2f255edc60e9669c8c737464c59af10d59a31126#2f255edc60e9669c8c737464c59af10d59a31126" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd488b3239b6b7b185c3b045c39ca6bf8af34467a4c5de4e0b1a564135d093d" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-emscripten" -version = "24.0.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=2f255edc60e9669c8c737464c59af10d59a31126#2f255edc60e9669c8c737464c59af10d59a31126" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09ad7aceb3818e52539acc679f049d3475775586f3f4e311c30165cf2c00445" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-windows-linux-android" -version = "24.0.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=2f255edc60e9669c8c737464c59af10d59a31126#2f255edc60e9669c8c737464c59af10d59a31126" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cba5fb5f7f9c98baa7c889d444f63ace25574833df56f5b817985f641af58e46" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-hal" -version = "24.0.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=2f255edc60e9669c8c737464c59af10d59a31126#2f255edc60e9669c8c737464c59af10d59a31126" +version = "25.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb7c4a1dc42ff14c23c9b11ebf1ee85cde661a9b1cf0392f79c1faca5bc559fb" dependencies = [ "android_system_properties", "arrayvec", @@ -8760,6 +8826,7 @@ dependencies = [ "bitflags 2.9.0", "block", "bytemuck", + "cfg-if", "cfg_aliases", "core-graphics-types", "glow", @@ -8767,7 +8834,7 @@ dependencies = [ "gpu-alloc", "gpu-allocator", "gpu-descriptor", - "hashbrown 0.14.5", + "hashbrown", "js-sys", "khronos-egl", "libc", @@ -8777,7 +8844,6 @@ dependencies = [ "naga", "ndk-sys 0.5.0+25.2.9519653", "objc", - "once_cell", "ordered-float", "parking_lot", "profiling", @@ -8794,10 +8860,12 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "24.0.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=2f255edc60e9669c8c737464c59af10d59a31126#2f255edc60e9669c8c737464c59af10d59a31126" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aa49460c2a8ee8edba3fca54325540d904dd85b2e086ada762767e17d06e8bc" dependencies = [ "bitflags 2.9.0", + "bytemuck", "js-sys", "log", "serde", @@ -8868,15 +8936,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-core" version = "0.57.0" @@ -9196,9 +9255,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winit" -version = "0.30.9" +version = "0.30.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a809eacf18c8eca8b6635091543f02a5a06ddf3dad846398795460e6e0ae3cc0" +checksum = "b0d05bd8908e14618c9609471db04007e644fd9cce6529756046cfc577f9155e" dependencies = [ "ahash", "android-activity", @@ -9217,9 +9276,9 @@ dependencies = [ "libc", "memmap2", "ndk", - "objc2", - "objc2-app-kit", - "objc2-foundation", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", "objc2-ui-kit", "orbclient", "percent-encoding", @@ -9248,9 +9307,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.4" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] @@ -9276,7 +9335,7 @@ dependencies = [ [[package]] name = "wr_glyph_rasterizer" version = "0.1.0" -source = "git+https://github.com/servo/webrender?branch=0.66#88462530746749163bcf1dc89be20a19f2394e71" +source = "git+https://github.com/servo/webrender?branch=0.67#ae2477d9a6da403e5b5dce8a17415a2cd1563074" dependencies = [ "core-foundation 0.9.4", "core-graphics", @@ -9300,8 +9359,8 @@ dependencies = [ [[package]] name = "wr_malloc_size_of" -version = "0.0.3" -source = "git+https://github.com/servo/webrender?branch=0.66#88462530746749163bcf1dc89be20a19f2394e71" +version = "0.2.0" +source = "git+https://github.com/servo/webrender?branch=0.67#ae2477d9a6da403e5b5dce8a17415a2cd1563074" dependencies = [ "app_units", "euclid", @@ -9419,15 +9478,15 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "xml-rs" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" +checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" [[package]] name = "xml5ever" -version = "0.21.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9f61eba2105dcadbee9232cfcf75c96abea184bf3805a8bfe022cf7fe7fa11d" +checksum = "0a91563ba5a5ab749488164063f1317e327ca1daa80f00e5bd1e670ad0d78154" dependencies = [ "log", "mac", diff --git a/Cargo.toml b/Cargo.toml index 7e5aac53ed4..4045a0ffcb9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ arboard = "3" arrayvec = "0.7" async-tungstenite = { version = "0.28", features = ["tokio-rustls-webpki-roots"] } atomic_refcell = "0.1.13" -aws-lc-rs = { version = "1.12", default-features = false, features = ["aws-lc-sys"] } +aws-lc-rs = { version = "1.13", default-features = false, features = ["aws-lc-sys"] } background_hang_monitor_api = { path = "components/shared/background_hang_monitor" } backtrace = "0.3" base = { path = "components/shared/base" } @@ -41,7 +41,7 @@ chrono = { version = "0.4", features = ["serde"] } cipher = { version = "0.4.4", features = ["alloc"] } compositing_traits = { path = "components/shared/compositing" } constellation_traits = { path = "components/shared/constellation" } -content-security-policy = { version = "0.5", features = ["serde"] } +content-security-policy = { git = "https://github.com/servo/rust-content-security-policy/", branch = "servo-csp", features = ["serde"] } cookie = { package = "cookie", version = "0.18" } crossbeam-channel = "0.5" cssparser = { version = "0.35", features = ["serde"] } @@ -49,7 +49,7 @@ ctr = "0.9.2" darling = { version = "0.20", default-features = false } data-url = "0.3" devtools_traits = { path = "components/shared/devtools" } -dpi = { version = "0.1" } +dpi = "0.1" embedder_traits = { path = "components/shared/embedder" } encoding_rs = "0.8" env_logger = "0.11" @@ -71,7 +71,7 @@ gstreamer-video = "0.23" harfbuzz-sys = "0.6.1" headers = "0.4" hitrace = "0.1.4" -html5ever = "0.30" +html5ever = "0.31" http = "1.3" http-body-util = "0.1" hyper = "1.6" @@ -82,9 +82,9 @@ icu_locid = "1.5.0" icu_segmenter = "1.5.0" image = "0.24" imsz = "0.2" -indexmap = { version = "2.8.0", features = ["std"] } +indexmap = { version = "2.9.0", features = ["std"] } ipc-channel = "0.19" -itertools = "0.13" +itertools = "0.14" js = { package = "mozjs", git = "https://github.com/servo/mozjs" } keyboard-types = "0.7" libc = "0.2" @@ -92,7 +92,7 @@ log = "0.4" mach2 = "0.4" malloc_size_of = { package = "servo_malloc_size_of", path = "components/malloc_size_of" } malloc_size_of_derive = "0.1" -markup5ever = "0.15" +markup5ever = "0.16.1" memmap2 = "0.9.5" mime = "0.3.13" mime_guess = "2.0.5" @@ -115,30 +115,32 @@ rayon = "1" regex = "1.11" rustls = { version = "0.23", default-features = false, features = ["logging", "std", "tls12"] } rustls-pemfile = "2.0" -rustls-pki-types = "1.11" +rustls-pki-types = "1.12" script_layout_interface = { path = "components/shared/script_layout" } script_traits = { path = "components/shared/script" } -selectors = { git = "https://github.com/servo/stylo", branch = "2025-03-15" } +selectors = { git = "https://github.com/servo/stylo", branch = "2025-05-01" } serde = "1.0.219" serde_bytes = "0.11" serde_json = "1.0" servo-media = { git = "https://github.com/servo/media" } servo-media-dummy = { git = "https://github.com/servo/media" } servo-media-gstreamer = { git = "https://github.com/servo/media" } -servo_arc = { git = "https://github.com/servo/stylo", branch = "2025-03-15" } -stylo_atoms = { git = "https://github.com/servo/stylo", branch = "2025-03-15" } +servo-tracing = { path = "components/servo_tracing" } +servo_arc = { git = "https://github.com/servo/stylo", branch = "2025-05-01" } smallbitvec = "2.6.0" -smallvec = "1.14" +smallvec = "1.15" +snapshot = { path = "./components/shared/snapshot" } static_assertions = "1.1" string_cache = "0.8" string_cache_codegen = "0.5" strum = "0.26" strum_macros = "0.26" -stylo = { git = "https://github.com/servo/stylo", branch = "2025-03-15" } -stylo_config = { git = "https://github.com/servo/stylo", branch = "2025-03-15" } -stylo_dom = { git = "https://github.com/servo/stylo", branch = "2025-03-15" } -stylo_malloc_size_of = { git = "https://github.com/servo/stylo", branch = "2025-03-15" } -stylo_traits = { git = "https://github.com/servo/stylo", branch = "2025-03-15" } +stylo = { git = "https://github.com/servo/stylo", branch = "2025-05-01" } +stylo_atoms = { git = "https://github.com/servo/stylo", branch = "2025-05-01" } +stylo_config = { git = "https://github.com/servo/stylo", branch = "2025-05-01" } +stylo_dom = { git = "https://github.com/servo/stylo", branch = "2025-05-01" } +stylo_malloc_size_of = { git = "https://github.com/servo/stylo", branch = "2025-05-01" } +stylo_traits = { git = "https://github.com/servo/stylo", branch = "2025-05-01" } surfman = { git = "https://github.com/servo/surfman", rev = "f7688b4585f9e0b5d4bf8ee8e4a91e82349610b1", features = ["chains"] } syn = { version = "2", default-features = false, features = ["clone-impls", "derive", "parsing"] } synstructure = "0.13" @@ -160,21 +162,22 @@ unicode-properties = { version = "0.1.3", features = ["emoji"] } unicode-script = "0.5" unicode-segmentation = "1.12.0" url = "2.5" +urlpattern = "0.3" uuid = { version = "1.12.1", features = ["v4"] } -webdriver = "0.51.0" +webdriver = "0.53.0" +webgpu_traits = { path = "components/shared/webgpu" } webpki-roots = "0.26" -webrender = { git = "https://github.com/servo/webrender", branch = "0.66", features = ["capture"] } -webrender_api = { git = "https://github.com/servo/webrender", branch = "0.66" } -webrender_traits = { path = "components/shared/webrender" } +webrender = { git = "https://github.com/servo/webrender", branch = "0.67", features = ["capture"] } +webrender_api = { git = "https://github.com/servo/webrender", branch = "0.67" } webxr-api = { path = "components/shared/webxr" } -wgpu-core = { git = "https://github.com/gfx-rs/wgpu", rev = "2f255edc60e9669c8c737464c59af10d59a31126" } -wgpu-types = { git = "https://github.com/gfx-rs/wgpu", rev = "2f255edc60e9669c8c737464c59af10d59a31126" } +wgpu-core = "25" +wgpu-types = "25" winapi = "0.3" windows-sys = "0.59" wio = "0.2" -wr_malloc_size_of = { git = "https://github.com/servo/webrender", branch = "0.66" } +wr_malloc_size_of = { git = "https://github.com/servo/webrender", branch = "0.67" } xi-unicode = "0.3.0" -xml5ever = "0.21" +xml5ever = "0.22" [profile.release] opt-level = 3 @@ -214,8 +217,8 @@ codegen-units = 1 # # For html5ever: # -# markup5ever = { path = "../html5ever/markup5ever" } # html5ever = { path = "../html5ever/html5ever" } +# markup5ever = { path = "../html5ever/markup5ever" } # xml5ever = { path = "../html5ever/xml5ever" } # # Or for Stylo: @@ -223,8 +226,8 @@ codegen-units = 1 # [patch."https://github.com/servo/stylo"] # selectors = { path = "../stylo/selectors" } # servo_arc = { path = "../stylo/servo_arc" } -# stylo_atoms = { path = "../stylo/stylo_atoms" } # stylo = { path = "../stylo/style" } +# stylo_atoms = { path = "../stylo/stylo_atoms" } # stylo_config = { path = "../stylo/stylo_config" } # stylo_dom = { path = "../stylo/stylo_dom" } # stylo_malloc_size_of = { path = "../stylo/malloc_size_of" } @@ -241,3 +244,7 @@ codegen-units = 1 # # [patch."https://github.com/servo/"] # = { path = "/path/to/local/checkout" } +# +# [patch."https://github.com/servo/rust-content-security-policy"] +# content-security-policy = { path = "../rust-content-security-policy/" } +# content-security-policy = { git = "https://github.com/timvdlippe/rust-content-security-policy/", branch = "fix-report-checks", features = ["serde"] } diff --git a/LICENSE_WHATWG_SPECS b/LICENSE_WHATWG_SPECS new file mode 100644 index 00000000000..745d5b38e24 --- /dev/null +++ b/LICENSE_WHATWG_SPECS @@ -0,0 +1,15 @@ +BSD 3-Clause License + +Copyright WHATWG (Apple, Google, Mozilla, Microsoft) + +This license applies to portions of WHATWG specification incorporated into source code. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. 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. + +3. Neither the name of the copyright holder 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 HOLDER 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. diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 611ce0909c8..9e531904581 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -1,16 +1,4 @@ - +*Describe the changes that this pull request makes here. This will be the commit message.* - ---- - -- [ ] `./mach build -d` does not report any errors -- [ ] `./mach test-tidy` does not report any errors -- [ ] These changes fix #___ (GitHub issue number if applicable) - - -- [ ] There are tests for these changes OR -- [ ] These changes do not require tests because ___ - - - - +Testing: *Describe how this pull request is tested or why it doesn't require tests* +Fixes: *Link to an issue this pull requests fixes or remove this line if there is no issue* diff --git a/components/allocator/Cargo.toml b/components/allocator/Cargo.toml index ce66c31833b..ae38e78db82 100644 --- a/components/allocator/Cargo.toml +++ b/components/allocator/Cargo.toml @@ -14,9 +14,9 @@ path = "lib.rs" use-system-allocator = ["libc"] [target.'cfg(not(any(windows, target_env = "ohos")))'.dependencies] -tikv-jemallocator = { workspace = true } -tikv-jemalloc-sys = { workspace = true } libc = { workspace = true, optional = true } +tikv-jemalloc-sys = { workspace = true } +tikv-jemallocator = { workspace = true } [target.'cfg(windows)'.dependencies] windows-sys = { workspace = true, features = ["Win32_System_Memory"] } diff --git a/components/background_hang_monitor/Cargo.toml b/components/background_hang_monitor/Cargo.toml index 2fce92ec05d..20216ec78b7 100644 --- a/components/background_hang_monitor/Cargo.toml +++ b/components/background_hang_monitor/Cargo.toml @@ -23,12 +23,12 @@ libc = { workspace = true } log = { workspace = true } serde_json = { workspace = true } -[features] -sampler = ["unwind-sys", "mach2", "nix"] - [target.'cfg(target_os = "macos")'.dependencies] mach2 = { version = "0.4", optional = true } [target.'cfg(all(target_os = "linux", not(any(target_arch = "arm", target_arch = "aarch64", target_env = "ohos", target_env = "musl"))))'.dependencies] nix = { workspace = true, features = ["signal"], optional = true } unwind-sys = { version = "0.1.4", optional = true } + +[features] +sampler = ["unwind-sys", "mach2", "nix"] diff --git a/components/bluetooth/Cargo.toml b/components/bluetooth/Cargo.toml index 8b22ccbd137..4be0c04c6bf 100644 --- a/components/bluetooth/Cargo.toml +++ b/components/bluetooth/Cargo.toml @@ -23,11 +23,6 @@ servo_config = { path = "../config" } servo_rand = { path = "../rand" } uuid = { workspace = true } -[features] -default = ["bluetooth-test"] -native-bluetooth = ["blurz", "blurdroid", "blurmac", "bluetooth-test"] -bluetooth-test = ["blurmock"] - [target.'cfg(target_os = "linux")'.dependencies] blurz = { version = "0.3", optional = true } @@ -36,3 +31,8 @@ blurdroid = { version = "0.1.2", optional = true } [target.'cfg(target_os = "macos")'.dependencies] blurmac = { path = "../../third_party/blurmac", optional = true } + +[features] +default = ["bluetooth-test"] +native-bluetooth = ["blurz", "blurdroid", "blurmac", "bluetooth-test"] +bluetooth-test = ["blurmock"] diff --git a/components/bluetooth/README.md b/components/bluetooth/README.md index ec6e536d66a..1e3a42a2e1b 100644 --- a/components/bluetooth/README.md +++ b/components/bluetooth/README.md @@ -1,55 +1,165 @@ -# Bluetooth Rust lib using macOS CoreBluetooth +# Bluetooth -[![Build Status](https://travis-ci.org/akosthekiss/blurmac.svg?branch=master)](https://travis-ci.org/akosthekiss/blurmac) -[![Crates.io](https://img.shields.io/crates/v/blurmac.svg)](https://crates.io/crates/blurmac) +Servo-specific APIs to access Bluetooth devices. -The main aim of BlurMac is to enable [WebBluetooth](https://webbluetoothcg.github.io) -in [Servo](https://github.com/servo/servo) on macOS. Thus, API and implementation -decisions are affected by the encapsulating [Devices](https://github.com/servo/devices), -and the sibling [BlurZ](https://github.com/szeged/blurz) and [BlurDroid](https://github.com/szeged/blurdroid) -crates. +Bluetooth related code is located in `bluetooth.rs`. +### Implementation -## Run Servo with WebBluetooth Enabled +Underlying dependency crates: -Usually, you don't want to work with BlurMac on its own but use it within Servo. -So, most probably you'll want to run Servo with WebBluetooth enabled: +- Android platform: [blurdroid](https://crates.io/crates/blurdroid) +- Linux platform: [blurz](https://crates.io/crates/blurz) +- macOS platform: [blurmac](https://crates.io/crates/blurmac) +- `Fake` prefixed structures: [blurmock](https://crates.io/crates/blurmock) -``` -RUST_LOG=blurmac \ -./mach run \ - --dev \ - --pref=dom.bluetooth.enabled \ - --pref=dom.permissions.testing.allowed_in_nonsecure_contexts \ - URL +`Empty` prefixed structures are located in `empty.rs`. + +### Usage + +#### Without the *bluetooth-test* feature + +There are three supported platforms (Android, Linux, macOS), on other platforms we fall back to a default (`Empty` prefixed) implementation. Each enum (`BluetoothAdapter`, `BluetoothDevice`, etc.) will contain only one variant for each targeted platform. See the following `BluetoothAdapter` example: + +Android: + +```rust +pub enum BluetoothAdapter { + Android(Arc), +} ``` -Notes: -* The above command is actually not really BlurMac-specific (except for the `RUST_LOG` - part). It runs Servo with WBT enabled on any platform where WBT is supported. -* You don't need the `RUST_LOG=blurmac` part if you don't want to see BlurMac debug - messages on the console. -* You don't need the `--dev` part if you want to run a release build. -* You don't need the `--pref=dom.permissions.testing.allowed_in_nonsecure_contexts` - part if your `URL` is https (but you do need it if you test a local file). +Linux: +```rust +pub enum BluetoothAdapter { + Bluez(Arc), +} +``` -## Known Issues +macOS: -* Device RSSI can not be retrieved yet. -* Support for included services is incomplete. -* Descriptors are not supported yet. -* Notifications on characteristics are not supported yet (the limitation comes from - Devices). +```rust +pub enum BluetoothAdapter { + Mac(Arc), +} +``` +Unsupported platforms: -## Compatibility +```rust +pub enum BluetoothAdapter { + Empty(Arc), +} +``` -Tested on: +You will have a platform specific adapter, e.g. on Android target, `BluetoothAdapter::init()` will create a `BluetoothAdapter::Android` enum variant, which wraps an `Arc`. -* macOS Sierra 10.12. +```rust +pub fn init() -> Result> { + let blurdroid_adapter = try!(BluetoothAdapterAndroid::get_adapter()); + Ok(BluetoothAdapter::Android(Arc::new(blurdroid_adapter))) +} +``` +On each platform you can call the same functions to reach the same GATT hierarchy elements. The following code can access the same Bluetooth device on all supported platforms: -## Copyright and Licensing +```rust +use device::{BluetoothAdapter, BluetoothDevice}; -Licensed under the BSD 3-Clause [License](LICENSE.md). +fn main() { + // Get the bluetooth adapter. + let adapter = BluetoothAdpater::init().expect("No bluetooth adapter found!"); + // Get a device with the id 01:2A:00:4D:00:04 if it exists. + let device = adapter.get_device("01:2A:00:4D:00:04".to_owned() /*device address*/) + .expect("No bluetooth device found!"); +} +``` + +#### With the *bluetooth-test* feature + +Each enum (`BluetoothAdapter`, `BluetoothDevice`, etc.) will contain one variant of the three possible default target, and a `Mock` variant, which wraps a `Fake` structure. + +Android: + +```rust +pub enum BluetoothAdapter { + Android(Arc), + Mock(Arc), +} +``` + +Linux: + +```rust +pub enum BluetoothAdapter { + Bluez(Arc), + Mock(Arc), +} +``` + +macOS: + +```rust +pub enum BluetoothAdapter { + Mac(Arc), + Mock(Arc), +} +``` + +Unsupported platforms: + +```rust +pub enum BluetoothAdapter { + Empty(Arc), + Mock(Arc), +} +``` + +Beside the platform specific structures, you can create and access mock adapters, devices, services etc. These mock structures implements all the platform specific functions too. To create a mock GATT hierarchy, first you need to call the `BluetoothAdapter::init_mock()` function, insted of `BluetoothAdapter::init()`. + +```rust +use device::{BluetoothAdapter, BluetoothDevice}; +use std::String; + +// This function takes a BluetoothAdapter, +// and print the ids of the devices, which the adapter can find. +fn print_device_ids(adapter: &BluetoothAdpater) { + let devices = match adapter.get_devices().expect("No devices on the adapter!"); + for device in devices { + println!("{:?}", device.get_id()); + } +} + +fn main() { +// This code uses a real adapter. + // Get the bluetooth adapter. + let adapter = BluetoothAdpater::init().expect("No bluetooth adapter found!"); + // Get a device with the id 01:2A:00:4D:00:04 if it exists. + let device = adapter.get_device("01:2A:00:4D:00:04".to_owned() /*device address*/) + .expect("No bluetooth device found!"); + +// This code uses a mock adapter. + // Creating a mock adapter. + let mock_adapter = BluetoothAdpater::init_mock().unwrap(); + // Creating a mock device. + let mock_device = + BluetoothDevice::create_mock_device(mock_adapter, + "device_id_string_goes_here".to_owned()) + .unwrap(); + // Changing its device_id. + let new_device_id = String::from("new_device_id_string".to_owned()); + mock_device.set_id(new_device_id.clone()); + // Calling the get_id function must return the last id we set. + assert_equals!(new_device_id, mock_device.get_id()); + // Getting the mock_device with its id + // must return the same mock device object we created before. + assert_equals!(Some(mock_device), + mock_adapter.get_device(new_device_id.clone()).unwrap()); + // The print_device_ids function accept real and mock adapters too. + print_device_ids(&adapter); + print_device_ids(&mock_adapter); +} +``` + +Calling a test function on a not `Mock` structure, will result an error with the message: `Error! Test functions are not supported on real devices!`. diff --git a/components/canvas/Cargo.toml b/components/canvas/Cargo.toml index 10ac5a35cd4..6084fc6e434 100644 --- a/components/canvas/Cargo.toml +++ b/components/canvas/Cargo.toml @@ -11,23 +11,15 @@ rust-version.workspace = true name = "canvas" path = "lib.rs" -[features] -webgl_backtrace = ["canvas_traits/webgl_backtrace"] -webxr = ["dep:webxr", "dep:webxr-api"] - [dependencies] app_units = { workspace = true } -bitflags = { workspace = true } -byteorder = { workspace = true } canvas_traits = { workspace = true } +compositing_traits = { workspace = true } crossbeam-channel = { workspace = true } cssparser = { workspace = true } euclid = { workspace = true } -fnv = { workspace = true } font-kit = "0.14" fonts = { path = "../fonts" } -glow = { workspace = true } -half = "2" ipc-channel = { workspace = true } log = { workspace = true } lyon_geom = "1.0.4" @@ -37,11 +29,7 @@ pixels = { path = "../pixels" } range = { path = "../range" } raqote = "0.8.5" servo_arc = { workspace = true } +snapshot = { workspace = true } stylo = { workspace = true } -surfman = { workspace = true } unicode-script = { workspace = true } -webrender = { workspace = true } webrender_api = { workspace = true } -webrender_traits = { workspace = true } -webxr = { path = "../webxr", features = ["ipc"], optional = true } -webxr-api = { workspace = true, features = ["ipc"], optional = true } diff --git a/components/canvas/backend.rs b/components/canvas/backend.rs new file mode 100644 index 00000000000..7e348fbc9b9 --- /dev/null +++ b/components/canvas/backend.rs @@ -0,0 +1,270 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use canvas_traits::canvas::{ + CompositionOrBlending, FillOrStrokeStyle, LineCapStyle, LineJoinStyle, +}; +use euclid::Angle; +use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D}; +use lyon_geom::Arc; +use style::color::AbsoluteColor; + +use crate::canvas_data::{CanvasPaintState, Filter, TextRun}; + +pub(crate) trait Backend: Clone + Sized { + type Pattern<'a>: PatternHelpers + Clone; + type StrokeOptions: StrokeOptionsHelpers + Clone; + type Color: Clone; + type DrawOptions: DrawOptionsHelpers + Clone; + type CompositionOp; + type DrawTarget: GenericDrawTarget; + type PathBuilder: GenericPathBuilder; + type SourceSurface; + type Bytes<'a>: AsRef<[u8]>; + type Path: PathHelpers + Clone; + type GradientStop; + type GradientStops; + + fn get_composition_op(&self, opts: &Self::DrawOptions) -> Self::CompositionOp; + fn need_to_draw_shadow(&self, color: &Self::Color) -> bool; + fn set_shadow_color(&mut self, color: AbsoluteColor, state: &mut CanvasPaintState<'_, Self>); + fn set_fill_style( + &mut self, + style: FillOrStrokeStyle, + state: &mut CanvasPaintState<'_, Self>, + drawtarget: &Self::DrawTarget, + ); + fn set_stroke_style( + &mut self, + style: FillOrStrokeStyle, + state: &mut CanvasPaintState<'_, Self>, + drawtarget: &Self::DrawTarget, + ); + fn set_global_composition( + &mut self, + op: CompositionOrBlending, + state: &mut CanvasPaintState<'_, Self>, + ); + fn create_drawtarget(&self, size: Size2D) -> Self::DrawTarget; + fn new_paint_state<'a>(&self) -> CanvasPaintState<'a, Self>; +} + +// This defines required methods for a DrawTarget (currently only implemented for raqote). The +// prototypes are derived from the now-removed Azure backend's methods. +pub(crate) trait GenericDrawTarget { + fn clear_rect(&mut self, rect: &Rect); + fn copy_surface( + &mut self, + surface: B::SourceSurface, + source: Rect, + destination: Point2D, + ); + fn create_path_builder(&self) -> B::PathBuilder; + fn create_similar_draw_target(&self, size: &Size2D) -> Self; + fn create_source_surface_from_data(&self, data: &[u8]) -> Option; + fn draw_surface( + &mut self, + surface: B::SourceSurface, + dest: Rect, + source: Rect, + filter: Filter, + draw_options: &B::DrawOptions, + ); + fn draw_surface_with_shadow( + &self, + surface: B::SourceSurface, + dest: &Point2D, + color: &B::Color, + offset: &Vector2D, + sigma: f32, + operator: B::CompositionOp, + ); + fn fill(&mut self, path: &B::Path, pattern: B::Pattern<'_>, draw_options: &B::DrawOptions); + fn fill_text( + &mut self, + text_runs: Vec, + start: Point2D, + pattern: &B::Pattern<'_>, + draw_options: &B::DrawOptions, + ); + fn fill_rect( + &mut self, + rect: &Rect, + pattern: B::Pattern<'_>, + draw_options: Option<&B::DrawOptions>, + ); + fn get_size(&self) -> Size2D; + fn get_transform(&self) -> Transform2D; + fn pop_clip(&mut self); + fn push_clip(&mut self, path: &B::Path); + fn set_transform(&mut self, matrix: &Transform2D); + fn stroke( + &mut self, + path: &B::Path, + pattern: B::Pattern<'_>, + stroke_options: &B::StrokeOptions, + draw_options: &B::DrawOptions, + ); + fn stroke_line( + &mut self, + start: Point2D, + end: Point2D, + pattern: B::Pattern<'_>, + stroke_options: &B::StrokeOptions, + draw_options: &B::DrawOptions, + ); + fn stroke_rect( + &mut self, + rect: &Rect, + pattern: B::Pattern<'_>, + stroke_options: &B::StrokeOptions, + draw_options: &B::DrawOptions, + ); + fn surface(&self) -> B::SourceSurface; + fn bytes(&'_ self) -> B::Bytes<'_>; +} + +/// A generic PathBuilder that abstracts the interface for azure's and raqote's PathBuilder. +pub(crate) trait GenericPathBuilder { + fn arc( + &mut self, + origin: Point2D, + radius: f32, + start_angle: f32, + end_angle: f32, + anticlockwise: bool, + ) { + Self::ellipse( + self, + origin, + radius, + radius, + 0., + start_angle, + end_angle, + anticlockwise, + ); + } + fn bezier_curve_to( + &mut self, + control_point1: &Point2D, + control_point2: &Point2D, + control_point3: &Point2D, + ); + fn close(&mut self); + #[allow(clippy::too_many_arguments)] + fn ellipse( + &mut self, + origin: Point2D, + radius_x: f32, + radius_y: f32, + rotation_angle: f32, + start_angle: f32, + end_angle: f32, + anticlockwise: bool, + ) { + let mut start = Angle::radians(start_angle); + let mut end = Angle::radians(end_angle); + + // Wrap angles mod 2 * PI if necessary + if !anticlockwise && start > end + Angle::two_pi() || + anticlockwise && end > start + Angle::two_pi() + { + start = start.positive(); + end = end.positive(); + } + + // Calculate the total arc we're going to sweep. + let sweep = match anticlockwise { + true => { + if end - start == Angle::two_pi() { + -Angle::two_pi() + } else if end > start { + -(Angle::two_pi() - (end - start)) + } else { + -(start - end) + } + }, + false => { + if start - end == Angle::two_pi() { + Angle::two_pi() + } else if start > end { + Angle::two_pi() - (start - end) + } else { + end - start + } + }, + }; + + let arc: Arc = Arc { + center: origin, + radii: Vector2D::new(radius_x, radius_y), + start_angle: start, + sweep_angle: sweep, + x_rotation: Angle::radians(rotation_angle), + }; + + self.line_to(arc.from()); + + arc.for_each_quadratic_bezier(&mut |q| { + self.quadratic_curve_to(&q.ctrl, &q.to); + }); + } + fn get_current_point(&mut self) -> Option>; + fn line_to(&mut self, point: Point2D); + fn move_to(&mut self, point: Point2D); + fn quadratic_curve_to(&mut self, control_point: &Point2D, end_point: &Point2D); + fn svg_arc( + &mut self, + radius_x: f32, + radius_y: f32, + rotation_angle: f32, + large_arc: bool, + sweep: bool, + end_point: Point2D, + ) { + let Some(start) = self.get_current_point() else { + return; + }; + + let arc = lyon_geom::SvgArc { + from: start, + to: end_point, + radii: lyon_geom::vector(radius_x, radius_y), + x_rotation: lyon_geom::Angle::degrees(rotation_angle), + flags: lyon_geom::ArcFlags { large_arc, sweep }, + }; + + arc.for_each_quadratic_bezier(&mut |q| { + self.quadratic_curve_to(&q.ctrl, &q.to); + }); + } + fn finish(&mut self) -> B::Path; +} + +pub(crate) trait PatternHelpers { + fn is_zero_size_gradient(&self) -> bool; + fn draw_rect(&self, rect: &Rect) -> Rect; +} + +pub(crate) trait StrokeOptionsHelpers { + fn set_line_width(&mut self, _val: f32); + fn set_miter_limit(&mut self, _val: f32); + fn set_line_join(&mut self, val: LineJoinStyle); + fn set_line_cap(&mut self, val: LineCapStyle); + fn set_line_dash(&mut self, items: Vec); + fn set_line_dash_offset(&mut self, offset: f32); +} + +pub(crate) trait DrawOptionsHelpers { + fn set_alpha(&mut self, val: f32); +} + +pub(crate) trait PathHelpers { + fn transformed_copy_to_builder(&self, transform: &Transform2D) -> B::PathBuilder; + + fn contains_point(&self, x: f64, y: f64, path_transform: &Transform2D) -> bool; + + fn copy_to_builder(&self) -> B::PathBuilder; +} diff --git a/components/canvas/canvas_data.rs b/components/canvas/canvas_data.rs index 5462098abf5..ea30589d0af 100644 --- a/components/canvas/canvas_data.rs +++ b/components/canvas/canvas_data.rs @@ -2,11 +2,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use std::marker::PhantomData; use std::mem; use std::sync::Arc; use app_units::Au; use canvas_traits::canvas::*; +use compositing_traits::{CrossProcessCompositorApi, ImageUpdate, SerializableImageData}; use euclid::default::{Box2D, Point2D, Rect, Size2D, Transform2D, Vector2D}; use euclid::point2; use fonts::{ @@ -15,20 +17,26 @@ use fonts::{ }; use ipc_channel::ipc::{IpcSender, IpcSharedMemory}; use log::warn; -use num_traits::ToPrimitive; use range::Range; use servo_arc::Arc as ServoArc; +use snapshot::Snapshot; use style::color::AbsoluteColor; use style::properties::style_structs::Font as FontStyleStruct; use unicode_script::Script; use webrender_api::units::RectExt as RectExt_; use webrender_api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat, ImageKey}; -use webrender_traits::{CrossProcessCompositorApi, ImageUpdate, SerializableImageData}; -use crate::raqote_backend::Repetition; +use crate::backend::{ + Backend, DrawOptionsHelpers as _, GenericDrawTarget as _, GenericPathBuilder, PathHelpers, + PatternHelpers, StrokeOptionsHelpers as _, +}; -fn to_path(path: &[PathSegment], mut builder: Box) -> Path { - let mut build_ref = PathBuilderRef { +// Asserts on WR texture cache update for zero sized image with raw data. +// https://github.com/servo/webrender/blob/main/webrender/src/texture_cache.rs#L1475 +const MIN_WR_IMAGE_SIZE: Size2D = Size2D::new(1, 1); + +fn to_path(path: &[PathSegment], mut builder: B::PathBuilder) -> B::Path { + let mut build_ref = PathBuilderRef:: { builder: &mut builder, transform: Transform2D::identity(), }; @@ -107,20 +115,20 @@ fn to_path(path: &[PathSegment], mut builder: Box) -> Pa /// draw the path, we convert it back to userspace and draw it /// with the correct transform applied. /// TODO: De-abstract now that Azure is removed? -enum PathState { +enum PathState { /// Path builder in user-space. If a transform has been applied /// but no further path operations have occurred, it is stored /// in the optional field. - UserSpacePathBuilder(Box, Option>), + UserSpacePathBuilder(B::PathBuilder, Option>), /// Path builder in device-space. - DeviceSpacePathBuilder(Box), + DeviceSpacePathBuilder(B::PathBuilder), /// Path in user-space. If a transform has been applied but /// but no further path operations have occurred, it is stored /// in the optional field. - UserSpacePath(Path, Option>), + UserSpacePath(B::Path, Option>), } -impl PathState { +impl PathState { fn is_path(&self) -> bool { match *self { PathState::UserSpacePath(..) => true, @@ -128,7 +136,7 @@ impl PathState { } } - fn path(&self) -> &Path { + fn path(&self) -> &B::Path { match *self { PathState::UserSpacePath(ref p, _) => p, PathState::UserSpacePathBuilder(..) | PathState::DeviceSpacePathBuilder(..) => { @@ -138,84 +146,14 @@ impl PathState { } } -pub trait Backend { - fn get_composition_op(&self, opts: &DrawOptions) -> CompositionOp; - fn need_to_draw_shadow(&self, color: &Color) -> bool; - fn set_shadow_color(&mut self, color: AbsoluteColor, state: &mut CanvasPaintState<'_>); - fn set_fill_style( - &mut self, - style: FillOrStrokeStyle, - state: &mut CanvasPaintState<'_>, - drawtarget: &dyn GenericDrawTarget, - ); - fn set_stroke_style( - &mut self, - style: FillOrStrokeStyle, - state: &mut CanvasPaintState<'_>, - drawtarget: &dyn GenericDrawTarget, - ); - fn set_global_composition( - &mut self, - op: CompositionOrBlending, - state: &mut CanvasPaintState<'_>, - ); - fn create_drawtarget(&self, size: Size2D) -> Box; - fn recreate_paint_state<'a>(&self, state: &CanvasPaintState<'a>) -> CanvasPaintState<'a>; -} - -/// A generic PathBuilder that abstracts the interface for azure's and raqote's PathBuilder. -/// TODO: De-abstract now that Azure is removed? -pub trait GenericPathBuilder { - fn arc( - &mut self, - origin: Point2D, - radius: f32, - start_angle: f32, - end_angle: f32, - anticlockwise: bool, - ); - fn bezier_curve_to( - &mut self, - control_point1: &Point2D, - control_point2: &Point2D, - control_point3: &Point2D, - ); - fn close(&mut self); - #[allow(clippy::too_many_arguments)] - fn ellipse( - &mut self, - origin: Point2D, - radius_x: f32, - radius_y: f32, - rotation_angle: f32, - start_angle: f32, - end_angle: f32, - anticlockwise: bool, - ); - fn get_current_point(&mut self) -> Option>; - fn line_to(&mut self, point: Point2D); - fn move_to(&mut self, point: Point2D); - fn quadratic_curve_to(&mut self, control_point: &Point2D, end_point: &Point2D); - fn svg_arc( - &mut self, - radius_x: f32, - radius_y: f32, - rotation_angle: f32, - large_arc: bool, - sweep: bool, - end_point: Point2D, - ); - fn finish(&mut self) -> Path; -} - /// A wrapper around a stored PathBuilder and an optional transformation that should be /// applied to any points to ensure they are in the matching device space. -struct PathBuilderRef<'a> { - builder: &'a mut Box, +struct PathBuilderRef<'a, B: Backend> { + builder: &'a mut B::PathBuilder, transform: Transform2D, } -impl PathBuilderRef<'_> { +impl PathBuilderRef<'_, B> { fn line_to(&mut self, pt: &Point2D) { let pt = self.transform.transform_point(*pt); self.builder.line_to(pt); @@ -336,7 +274,7 @@ impl PathBuilderRef<'_> { } #[allow(clippy::too_many_arguments)] - pub fn ellipse( + pub(crate) fn ellipse( &mut self, center: &Point2D, radius_x: f32, @@ -425,9 +363,9 @@ impl UnshapedTextRun<'_> { } } -pub struct TextRun { - pub font: FontRef, - pub glyphs: Arc, +pub(crate) struct TextRun { + pub(crate) font: FontRef, + pub(crate) glyphs: Arc, } impl TextRun { @@ -453,149 +391,31 @@ impl TextRun { } } -// This defines required methods for a DrawTarget (currently only implemented for raqote). The -// prototypes are derived from the now-removed Azure backend's methods. -pub trait GenericDrawTarget { - fn clear_rect(&mut self, rect: &Rect); - fn copy_surface( - &mut self, - surface: SourceSurface, - source: Rect, - destination: Point2D, - ); - fn create_gradient_stops(&self, gradient_stops: Vec) -> GradientStops; - fn create_path_builder(&self) -> Box; - fn create_similar_draw_target(&self, size: &Size2D) -> Box; - fn create_source_surface_from_data(&self, data: &[u8]) -> Option; - fn draw_surface( - &mut self, - surface: SourceSurface, - dest: Rect, - source: Rect, - filter: Filter, - draw_options: &DrawOptions, - ); - fn draw_surface_with_shadow( - &self, - surface: SourceSurface, - dest: &Point2D, - color: &Color, - offset: &Vector2D, - sigma: f32, - operator: CompositionOp, - ); - fn fill(&mut self, path: &Path, pattern: Pattern, draw_options: &DrawOptions); - fn fill_text( - &mut self, - text_runs: Vec, - start: Point2D, - pattern: &Pattern, - draw_options: &DrawOptions, - ); - fn fill_rect(&mut self, rect: &Rect, pattern: Pattern, draw_options: Option<&DrawOptions>); - fn get_size(&self) -> Size2D; - fn get_transform(&self) -> Transform2D; - fn pop_clip(&mut self); - fn push_clip(&mut self, path: &Path); - fn set_transform(&mut self, matrix: &Transform2D); - fn snapshot(&self) -> SourceSurface; - fn stroke( - &mut self, - path: &Path, - pattern: Pattern, - stroke_options: &StrokeOptions, - draw_options: &DrawOptions, - ); - fn stroke_line( - &mut self, - start: Point2D, - end: Point2D, - pattern: Pattern, - stroke_options: &StrokeOptions, - draw_options: &DrawOptions, - ); - fn stroke_rect( - &mut self, - rect: &Rect, - pattern: Pattern, - stroke_options: &StrokeOptions, - draw_options: &DrawOptions, - ); - fn snapshot_data(&self, f: &dyn Fn(&[u8]) -> Vec) -> Vec; - fn snapshot_data_owned(&self) -> Vec; -} - -pub enum GradientStop { - Raqote(raqote::GradientStop), -} - -pub enum GradientStops { - Raqote(Vec), -} - -#[derive(Clone)] -pub enum Color { - Raqote(raqote::SolidSource), -} - -#[derive(Clone)] -pub enum CompositionOp { - Raqote(raqote::BlendMode), -} - -#[derive(Clone)] -pub enum SourceSurface { - Raqote(Vec), // TODO: See if we can avoid the alloc (probably?) -} - -#[derive(Clone)] -pub enum Path { - Raqote(raqote::Path), -} - -#[derive(Clone)] -pub enum Pattern<'a> { - Raqote(crate::raqote_backend::Pattern<'a>), -} - -#[derive(Clone)] -pub enum DrawOptions { - Raqote(raqote::DrawOptions), -} - -#[derive(Clone)] -pub enum StrokeOptions { - Raqote(raqote::StrokeStyle), -} - #[derive(Clone, Copy)] -pub enum Filter { +pub(crate) enum Filter { Bilinear, Nearest, } -pub struct CanvasData<'a> { - backend: Box, - drawtarget: Box, - path_state: Option, - state: CanvasPaintState<'a>, - saved_states: Vec>, +pub(crate) struct CanvasData<'a, B: Backend> { + backend: B, + drawtarget: B::DrawTarget, + path_state: Option>, + state: CanvasPaintState<'a, B>, + saved_states: Vec>, compositor_api: CrossProcessCompositorApi, image_key: ImageKey, font_context: Arc, } -fn create_backend() -> Box { - Box::new(crate::raqote_backend::RaqoteBackend) -} - -impl<'a> CanvasData<'a> { - pub fn new( +impl<'a, B: Backend> CanvasData<'a, B> { + pub(crate) fn new( size: Size2D, compositor_api: CrossProcessCompositorApi, font_context: Arc, - ) -> CanvasData<'a> { - let backend = create_backend(); + backend: B, + ) -> CanvasData<'a, B> { + let size = size.max(MIN_WR_IMAGE_SIZE); let draw_target = backend.create_drawtarget(size); let image_key = compositor_api.generate_image_key().unwrap(); let descriptor = ImageDescriptor { @@ -605,15 +425,14 @@ impl<'a> CanvasData<'a> { offset: 0, flags: ImageDescriptorFlags::empty(), }; - let data = SerializableImageData::Raw(IpcSharedMemory::from_bytes( - &draw_target.snapshot_data_owned(), - )); + let data = + SerializableImageData::Raw(IpcSharedMemory::from_bytes(draw_target.bytes().as_ref())); compositor_api.update_images(vec![ImageUpdate::AddImage(image_key, descriptor, data)]); CanvasData { + state: backend.new_paint_state(), backend, drawtarget: draw_target, path_state: None, - state: CanvasPaintState::default(), saved_states: vec![], compositor_api, image_key, @@ -621,14 +440,14 @@ impl<'a> CanvasData<'a> { } } - pub fn image_key(&self) -> ImageKey { + pub(crate) fn image_key(&self) -> ImageKey { self.image_key } - pub fn draw_image( + pub(crate) fn draw_image( &mut self, image_data: &[u8], - image_size: Size2D, + image_size: Size2D, dest_rect: Rect, source_rect: Rect, smoothing_enabled: bool, @@ -637,15 +456,15 @@ impl<'a> CanvasData<'a> { // We round up the floating pixel values to draw the pixels let source_rect = source_rect.ceil(); // It discards the extra pixels (if any) that won't be painted - let image_data = if Rect::from_size(image_size).contains_rect(&source_rect) { - pixels::rgba8_get_rect(image_data, image_size.to_u64(), source_rect.to_u64()).into() + let image_data = if Rect::from_size(image_size.to_f64()).contains_rect(&source_rect) { + pixels::rgba8_get_rect(image_data, image_size, source_rect.to_u64()).into() } else { image_data.into() }; let draw_options = self.state.draw_options.clone(); - let writer = |draw_target: &mut dyn GenericDrawTarget| { - write_image( + let writer = |draw_target: &mut B::DrawTarget| { + write_image::( draw_target, image_data, source_rect.size, @@ -665,15 +484,15 @@ impl<'a> CanvasData<'a> { // TODO(pylbrecht) pass another closure for raqote self.draw_with_shadow(&rect, writer); } else { - writer(&mut *self.drawtarget); + writer(&mut self.drawtarget); } } - pub fn save_context_state(&mut self) { + pub(crate) fn save_context_state(&mut self) { self.saved_states.push(self.state.clone()); } - pub fn restore_context_state(&mut self) { + pub(crate) fn restore_context_state(&mut self) { if let Some(state) = self.saved_states.pop() { let _ = mem::replace(&mut self.state, state); self.drawtarget.set_transform(&self.state.transform); @@ -681,7 +500,7 @@ impl<'a> CanvasData<'a> { } } - pub fn fill_text_with_size( + pub(crate) fn fill_text_with_size( &mut self, text: String, x: f64, @@ -760,7 +579,7 @@ impl<'a> CanvasData<'a> { } /// - pub fn fill_text( + pub(crate) fn fill_text( &mut self, text: String, x: f64, @@ -778,7 +597,7 @@ impl<'a> CanvasData<'a> { /// /// - pub fn measure_text(&mut self, text: String) -> TextMetrics { + pub(crate) fn measure_text(&mut self, text: String) -> TextMetrics { // > Step 2: Replace all ASCII whitespace in text with U+0020 SPACE characters. let text = replace_ascii_whitespace(text); let Some(ref font_style) = self.state.font_style else { @@ -930,49 +749,15 @@ impl<'a> CanvasData<'a> { point2(x + anchor_x, y + anchor_y) } - pub fn fill_rect(&mut self, rect: &Rect) { + pub(crate) fn fill_rect(&mut self, rect: &Rect) { if self.state.fill_style.is_zero_size_gradient() { return; // Paint nothing if gradient size is zero. } - let draw_rect = match &self.state.fill_style { - Pattern::Raqote(pattern) => match pattern { - crate::raqote_backend::Pattern::Surface(pattern) => { - let pattern_rect = Rect::new(Point2D::origin(), pattern.size()); - let mut draw_rect = rect.intersection(&pattern_rect).unwrap_or(Rect::zero()); - - match pattern.repetition() { - Repetition::NoRepeat => { - draw_rect.size.width = - draw_rect.size.width.min(pattern_rect.size.width); - draw_rect.size.height = - draw_rect.size.height.min(pattern_rect.size.height); - }, - Repetition::RepeatX => { - draw_rect.size.width = rect.size.width; - draw_rect.size.height = - draw_rect.size.height.min(pattern_rect.size.height); - }, - Repetition::RepeatY => { - draw_rect.size.height = rect.size.height; - draw_rect.size.width = - draw_rect.size.width.min(pattern_rect.size.width); - }, - Repetition::Repeat => { - draw_rect = *rect; - }, - } - - draw_rect - }, - crate::raqote_backend::Pattern::Color(..) | - crate::raqote_backend::Pattern::LinearGradient(..) | - crate::raqote_backend::Pattern::RadialGradient(..) => *rect, - }, - }; + let draw_rect = self.state.fill_style.draw_rect(rect); if self.need_to_draw_shadow() { - self.draw_with_shadow(&draw_rect, |new_draw_target: &mut dyn GenericDrawTarget| { + self.draw_with_shadow(&draw_rect, |new_draw_target: &mut B::DrawTarget| { new_draw_target.fill_rect( &draw_rect, self.state.fill_style.clone(), @@ -988,17 +773,17 @@ impl<'a> CanvasData<'a> { } } - pub fn clear_rect(&mut self, rect: &Rect) { + pub(crate) fn clear_rect(&mut self, rect: &Rect) { self.drawtarget.clear_rect(rect); } - pub fn stroke_rect(&mut self, rect: &Rect) { + pub(crate) fn stroke_rect(&mut self, rect: &Rect) { if self.state.stroke_style.is_zero_size_gradient() { return; // Paint nothing if gradient size is zero. } if self.need_to_draw_shadow() { - self.draw_with_shadow(rect, |new_draw_target: &mut dyn GenericDrawTarget| { + self.draw_with_shadow(rect, |new_draw_target: &mut B::DrawTarget| { new_draw_target.stroke_rect( rect, self.state.stroke_style.clone(), @@ -1026,12 +811,12 @@ impl<'a> CanvasData<'a> { } } - pub fn begin_path(&mut self) { + pub(crate) fn begin_path(&mut self) { // Erase any traces of previous paths that existed before this. self.path_state = None; } - pub fn close_path(&mut self) { + pub(crate) fn close_path(&mut self) { self.path_builder().close(); } @@ -1093,14 +878,14 @@ impl<'a> CanvasData<'a> { assert!(self.path_state.as_ref().unwrap().is_path()) } - fn path(&self) -> &Path { + fn path(&self) -> &B::Path { self.path_state .as_ref() .expect("Should have called ensure_path()") .path() } - pub fn fill(&mut self) { + pub(crate) fn fill(&mut self) { if self.state.fill_style.is_zero_size_gradient() { return; // Paint nothing if gradient size is zero. } @@ -1109,16 +894,16 @@ impl<'a> CanvasData<'a> { self.drawtarget.fill( &self.path().clone(), self.state.fill_style.clone(), - &self.state.draw_options, + &self.state.draw_options.clone(), ); } - pub fn fill_path(&mut self, path: &[PathSegment]) { + pub(crate) fn fill_path(&mut self, path: &[PathSegment]) { if self.state.fill_style.is_zero_size_gradient() { return; // Paint nothing if gradient size is zero. } - let path = to_path(path, self.drawtarget.create_path_builder()); + let path = to_path::(path, self.drawtarget.create_path_builder()); self.drawtarget.fill( &path, @@ -1127,7 +912,7 @@ impl<'a> CanvasData<'a> { ); } - pub fn stroke(&mut self) { + pub(crate) fn stroke(&mut self) { if self.state.stroke_style.is_zero_size_gradient() { return; // Paint nothing if gradient size is zero. } @@ -1141,12 +926,12 @@ impl<'a> CanvasData<'a> { ); } - pub fn stroke_path(&mut self, path: &[PathSegment]) { + pub(crate) fn stroke_path(&mut self, path: &[PathSegment]) { if self.state.stroke_style.is_zero_size_gradient() { return; // Paint nothing if gradient size is zero. } - let path = to_path(path, self.drawtarget.create_path_builder()); + let path = to_path::(path, self.drawtarget.create_path_builder()); self.drawtarget.stroke( &path, @@ -1156,18 +941,18 @@ impl<'a> CanvasData<'a> { ); } - pub fn clip(&mut self) { + pub(crate) fn clip(&mut self) { self.ensure_path(); let path = self.path().clone(); self.drawtarget.push_clip(&path); } - pub fn clip_path(&mut self, path: &[PathSegment]) { - let path = to_path(path, self.drawtarget.create_path_builder()); + pub(crate) fn clip_path(&mut self, path: &[PathSegment]) { + let path = to_path::(path, self.drawtarget.create_path_builder()); self.drawtarget.push_clip(&path); } - pub fn is_point_in_path( + pub(crate) fn is_point_in_path( &mut self, x: f64, y: f64, @@ -1186,7 +971,7 @@ impl<'a> CanvasData<'a> { chan.send(result).unwrap(); } - pub fn is_point_in_path_( + pub(crate) fn is_point_in_path_( &mut self, path: &[PathSegment], x: f64, @@ -1198,7 +983,7 @@ impl<'a> CanvasData<'a> { Some(PathState::UserSpacePath(_, Some(transform))) => transform, Some(_) | None => &self.drawtarget.get_transform(), }; - let result = to_path(path, self.drawtarget.create_path_builder()).contains_point( + let result = to_path::(path, self.drawtarget.create_path_builder()).contains_point( x, y, path_transform, @@ -1206,15 +991,15 @@ impl<'a> CanvasData<'a> { chan.send(result).unwrap(); } - pub fn move_to(&mut self, point: &Point2D) { + pub(crate) fn move_to(&mut self, point: &Point2D) { self.path_builder().move_to(point); } - pub fn line_to(&mut self, point: &Point2D) { + pub(crate) fn line_to(&mut self, point: &Point2D) { self.path_builder().line_to(point); } - fn path_builder(&mut self) -> PathBuilderRef { + fn path_builder(&mut self) -> PathBuilderRef { if self.path_state.is_none() { self.path_state = Some(PathState::UserSpacePathBuilder( self.drawtarget.create_path_builder(), @@ -1279,18 +1064,18 @@ impl<'a> CanvasData<'a> { } } - pub fn rect(&mut self, rect: &Rect) { + pub(crate) fn rect(&mut self, rect: &Rect) { self.path_builder().rect(rect); } - pub fn quadratic_curve_to(&mut self, cp: &Point2D, endpoint: &Point2D) { + pub(crate) fn quadratic_curve_to(&mut self, cp: &Point2D, endpoint: &Point2D) { if self.path_state.is_none() { self.move_to(cp); } self.path_builder().quadratic_curve_to(cp, endpoint); } - pub fn bezier_curve_to( + pub(crate) fn bezier_curve_to( &mut self, cp1: &Point2D, cp2: &Point2D, @@ -1302,7 +1087,7 @@ impl<'a> CanvasData<'a> { self.path_builder().bezier_curve_to(cp1, cp2, endpoint); } - pub fn arc( + pub(crate) fn arc( &mut self, center: &Point2D, radius: f32, @@ -1314,12 +1099,12 @@ impl<'a> CanvasData<'a> { .arc(center, radius, start_angle, end_angle, ccw); } - pub fn arc_to(&mut self, cp1: &Point2D, cp2: &Point2D, radius: f32) { + pub(crate) fn arc_to(&mut self, cp1: &Point2D, cp2: &Point2D, radius: f32) { self.path_builder().arc_to(cp1, cp2, radius); } #[allow(clippy::too_many_arguments)] - pub fn ellipse( + pub(crate) fn ellipse( &mut self, center: &Point2D, radius_x: f32, @@ -1340,37 +1125,45 @@ impl<'a> CanvasData<'a> { ); } - pub fn set_fill_style(&mut self, style: FillOrStrokeStyle) { + pub(crate) fn set_fill_style(&mut self, style: FillOrStrokeStyle) { self.backend - .set_fill_style(style, &mut self.state, &*self.drawtarget); + .set_fill_style(style, &mut self.state, &self.drawtarget); } - pub fn set_stroke_style(&mut self, style: FillOrStrokeStyle) { + pub(crate) fn set_stroke_style(&mut self, style: FillOrStrokeStyle) { self.backend - .set_stroke_style(style, &mut self.state, &*self.drawtarget); + .set_stroke_style(style, &mut self.state, &self.drawtarget); } - pub fn set_line_width(&mut self, width: f32) { + pub(crate) fn set_line_width(&mut self, width: f32) { self.state.stroke_opts.set_line_width(width); } - pub fn set_line_cap(&mut self, cap: LineCapStyle) { + pub(crate) fn set_line_cap(&mut self, cap: LineCapStyle) { self.state.stroke_opts.set_line_cap(cap); } - pub fn set_line_join(&mut self, join: LineJoinStyle) { + pub(crate) fn set_line_join(&mut self, join: LineJoinStyle) { self.state.stroke_opts.set_line_join(join); } - pub fn set_miter_limit(&mut self, limit: f32) { + pub(crate) fn set_miter_limit(&mut self, limit: f32) { self.state.stroke_opts.set_miter_limit(limit); } - pub fn get_transform(&self) -> Transform2D { + pub(crate) fn set_line_dash(&mut self, items: Vec) { + self.state.stroke_opts.set_line_dash(items); + } + + pub(crate) fn set_line_dash_offset(&mut self, offset: f32) { + self.state.stroke_opts.set_line_dash_offset(offset); + } + + pub(crate) fn get_transform(&self) -> Transform2D { self.drawtarget.get_transform() } - pub fn set_transform(&mut self, transform: &Transform2D) { + pub(crate) fn set_transform(&mut self, transform: &Transform2D) { // If there is an in-progress path, store the existing transformation required // to move between device and user space. match self.path_state.as_mut() { @@ -1386,34 +1179,28 @@ impl<'a> CanvasData<'a> { self.drawtarget.set_transform(transform) } - pub fn set_global_alpha(&mut self, alpha: f32) { + pub(crate) fn set_global_alpha(&mut self, alpha: f32) { self.state.draw_options.set_alpha(alpha); } - pub fn set_global_composition(&mut self, op: CompositionOrBlending) { + pub(crate) fn set_global_composition(&mut self, op: CompositionOrBlending) { self.backend.set_global_composition(op, &mut self.state); } - pub fn recreate(&mut self, size: Option>) { - let size = size.unwrap_or_else(|| self.drawtarget.get_size().to_u64()); + pub(crate) fn recreate(&mut self, size: Option>) { + let size = size + .unwrap_or_else(|| self.drawtarget.get_size().to_u64()) + .max(MIN_WR_IMAGE_SIZE); self.drawtarget = self .backend .create_drawtarget(Size2D::new(size.width, size.height)); - self.state = self.backend.recreate_paint_state(&self.state); + self.state = self.backend.new_paint_state(); self.saved_states.clear(); self.update_image_rendering(); } - pub fn send_pixels(&mut self, chan: IpcSender) { - self.drawtarget.snapshot_data(&|bytes| { - let data = IpcSharedMemory::from_bytes(bytes); - chan.send(data).unwrap(); - vec![] - }); - } - /// Update image in WebRender - pub fn update_image_rendering(&mut self) { + pub(crate) fn update_image_rendering(&mut self) { let descriptor = ImageDescriptor { size: self.drawtarget.get_size().cast_unit(), stride: None, @@ -1422,7 +1209,7 @@ impl<'a> CanvasData<'a> { flags: ImageDescriptorFlags::empty(), }; let data = SerializableImageData::Raw(IpcSharedMemory::from_bytes( - &self.drawtarget.snapshot_data_owned(), + self.drawtarget.bytes().as_ref(), )); self.compositor_api @@ -1434,7 +1221,7 @@ impl<'a> CanvasData<'a> { } // https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata - pub fn put_image_data(&mut self, mut imagedata: Vec, rect: Rect) { + pub(crate) fn put_image_data(&mut self, mut imagedata: Vec, rect: Rect) { assert_eq!(imagedata.len() % 4, 0); assert_eq!(rect.size.area() as usize, imagedata.len() / 4); pixels::rgba8_byte_swap_and_premultiply_inplace(&mut imagedata); @@ -1449,31 +1236,31 @@ impl<'a> CanvasData<'a> { ); } - pub fn set_shadow_offset_x(&mut self, value: f64) { + pub(crate) fn set_shadow_offset_x(&mut self, value: f64) { self.state.shadow_offset_x = value; } - pub fn set_shadow_offset_y(&mut self, value: f64) { + pub(crate) fn set_shadow_offset_y(&mut self, value: f64) { self.state.shadow_offset_y = value; } - pub fn set_shadow_blur(&mut self, value: f64) { + pub(crate) fn set_shadow_blur(&mut self, value: f64) { self.state.shadow_blur = value; } - pub fn set_shadow_color(&mut self, value: AbsoluteColor) { + pub(crate) fn set_shadow_color(&mut self, value: AbsoluteColor) { self.backend.set_shadow_color(value, &mut self.state); } - pub fn set_font(&mut self, font_style: FontStyleStruct) { + pub(crate) fn set_font(&mut self, font_style: FontStyleStruct) { self.state.font_style = Some(ServoArc::new(font_style)) } - pub fn set_text_align(&mut self, text_align: TextAlign) { + pub(crate) fn set_text_align(&mut self, text_align: TextAlign) { self.state.text_align = text_align; } - pub fn set_text_baseline(&mut self, text_baseline: TextBaseline) { + pub(crate) fn set_text_baseline(&mut self, text_baseline: TextBaseline) { self.state.text_baseline = text_baseline; } @@ -1485,7 +1272,7 @@ impl<'a> CanvasData<'a> { self.state.shadow_blur != 0.0f64) } - fn create_draw_target_for_shadow(&self, source_rect: &Rect) -> Box { + fn create_draw_target_for_shadow(&self, source_rect: &Rect) -> B::DrawTarget { let mut draw_target = self.drawtarget.create_similar_draw_target(&Size2D::new( source_rect.size.width as i32, source_rect.size.height as i32, @@ -1499,13 +1286,13 @@ impl<'a> CanvasData<'a> { fn draw_with_shadow(&self, rect: &Rect, draw_shadow_source: F) where - F: FnOnce(&mut dyn GenericDrawTarget), + F: FnOnce(&mut B::DrawTarget), { let shadow_src_rect = self.state.transform.outer_transformed_rect(rect); let mut new_draw_target = self.create_draw_target_for_shadow(&shadow_src_rect); - draw_shadow_source(&mut *new_draw_target); + draw_shadow_source(&mut new_draw_target); self.drawtarget.draw_surface_with_shadow( - new_draw_target.snapshot(), + new_draw_target.surface(), &Point2D::new(shadow_src_rect.origin.x, shadow_src_rect.origin.y), &self.state.shadow_color, &Vector2D::new( @@ -1521,22 +1308,40 @@ impl<'a> CanvasData<'a> { /// canvas_size: The size of the canvas we're reading from /// read_rect: The area of the canvas we want to read from #[allow(unsafe_code)] - pub fn read_pixels(&self, read_rect: Rect, canvas_size: Size2D) -> Vec { - let canvas_rect = Rect::from_size(canvas_size); - if canvas_rect - .intersection(&read_rect) - .is_none_or(|rect| rect.is_empty()) - { - return vec![]; - } + pub(crate) fn read_pixels( + &self, + read_rect: Option>, + canvas_size: Option>, + ) -> Snapshot { + let canvas_size = canvas_size.unwrap_or(self.drawtarget.get_size().cast()); - self.drawtarget.snapshot_data(&|bytes| { - pixels::rgba8_get_rect(bytes, canvas_size, read_rect).into_owned() - }) + let data = if let Some(read_rect) = read_rect { + let canvas_rect = Rect::from_size(canvas_size); + if canvas_rect + .intersection(&read_rect) + .is_none_or(|rect| rect.is_empty()) + { + vec![] + } else { + pixels::rgba8_get_rect(self.drawtarget.bytes().as_ref(), canvas_size, read_rect) + .to_vec() + } + } else { + self.drawtarget.bytes().as_ref().to_vec() + }; + + Snapshot::from_vec( + canvas_size, + snapshot::PixelFormat::BGRA, + snapshot::AlphaMode::Transparent { + premultiplied: true, + }, + data, + ) } } -impl Drop for CanvasData<'_> { +impl Drop for CanvasData<'_, B> { fn drop(&mut self) { self.compositor_api .update_images(vec![ImageUpdate::DeleteImage(self.image_key)]); @@ -1547,20 +1352,21 @@ const HANGING_BASELINE_DEFAULT: f32 = 0.8; const IDEOGRAPHIC_BASELINE_DEFAULT: f32 = 0.5; #[derive(Clone)] -pub struct CanvasPaintState<'a> { - pub draw_options: DrawOptions, - pub fill_style: Pattern<'a>, - pub stroke_style: Pattern<'a>, - pub stroke_opts: StrokeOptions, +pub(crate) struct CanvasPaintState<'a, B: Backend> { + pub(crate) draw_options: B::DrawOptions, + pub(crate) fill_style: B::Pattern<'a>, + pub(crate) stroke_style: B::Pattern<'a>, + pub(crate) stroke_opts: B::StrokeOptions, /// The current 2D transform matrix. - pub transform: Transform2D, - pub shadow_offset_x: f64, - pub shadow_offset_y: f64, - pub shadow_blur: f64, - pub shadow_color: Color, - pub font_style: Option>, - pub text_align: TextAlign, - pub text_baseline: TextBaseline, + pub(crate) transform: Transform2D, + pub(crate) shadow_offset_x: f64, + pub(crate) shadow_offset_y: f64, + pub(crate) shadow_blur: f64, + pub(crate) shadow_color: B::Color, + pub(crate) font_style: Option>, + pub(crate) text_align: TextAlign, + pub(crate) text_baseline: TextBaseline, + pub(crate) _backend: PhantomData, } /// It writes an image to the destination target @@ -1570,14 +1376,14 @@ pub struct CanvasPaintState<'a> { /// dest_rect: Area of the destination target where the pixels will be copied /// smoothing_enabled: It determines if smoothing is applied to the image result /// premultiply: Determines whenever the image data should be premultiplied or not -fn write_image( - draw_target: &mut dyn GenericDrawTarget, +fn write_image( + draw_target: &mut B::DrawTarget, mut image_data: Vec, image_size: Size2D, dest_rect: Rect, smoothing_enabled: bool, premultiply: bool, - draw_options: &DrawOptions, + draw_options: &B::DrawOptions, ) { if image_data.is_empty() { return; @@ -1606,25 +1412,11 @@ fn write_image( draw_target.draw_surface(source_surface, dest_rect, image_rect, filter, draw_options); } -pub trait RectToi32 { - fn to_i32(&self) -> Rect; +pub(crate) trait RectToi32 { fn ceil(&self) -> Rect; } impl RectToi32 for Rect { - fn to_i32(&self) -> Rect { - Rect::new( - Point2D::new( - self.origin.x.to_i32().unwrap(), - self.origin.y.to_i32().unwrap(), - ), - Size2D::new( - self.size.width.to_i32().unwrap(), - self.size.height.to_i32().unwrap(), - ), - ) - } - fn ceil(&self) -> Rect { Rect::new( Point2D::new(self.origin.x.ceil(), self.origin.y.ceil()), @@ -1633,22 +1425,6 @@ impl RectToi32 for Rect { } } -pub trait RectExt { - fn to_u64(&self) -> Rect; -} - -impl RectExt for Rect { - fn to_u64(&self) -> Rect { - self.cast() - } -} - -impl RectExt for Rect { - fn to_u64(&self) -> Rect { - self.cast() - } -} - fn replace_ascii_whitespace(text: String) -> String { text.chars() .map(|c| match c { diff --git a/components/canvas/canvas_paint_thread.rs b/components/canvas/canvas_paint_thread.rs index 09d670d5274..82a221d560d 100644 --- a/components/canvas/canvas_paint_thread.rs +++ b/components/canvas/canvas_paint_thread.rs @@ -9,20 +9,23 @@ use std::thread; use canvas_traits::ConstellationCanvasMsg; use canvas_traits::canvas::*; +use compositing_traits::CrossProcessCompositorApi; use crossbeam_channel::{Sender, select, unbounded}; -use euclid::default::Size2D; +use euclid::default::{Point2D, Rect, Size2D, Transform2D}; use fonts::{FontContext, SystemFontServiceProxy}; use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::router::ROUTER; use log::warn; use net_traits::ResourceThreads; +use style::color::AbsoluteColor; +use style::properties::style_structs::Font as FontStyleStruct; use webrender_api::ImageKey; -use webrender_traits::CrossProcessCompositorApi; use crate::canvas_data::*; +use crate::raqote_backend::RaqoteBackend; pub struct CanvasPaintThread<'a> { - canvases: HashMap>, + canvases: HashMap>, next_canvas_id: CanvasId, compositor_api: CrossProcessCompositorApi, font_context: Arc, @@ -76,7 +79,11 @@ impl<'a> CanvasPaintThread<'a> { }, Ok(CanvasMsg::FromScript(message, canvas_id)) => match message { FromScriptMsg::SendPixels(chan) => { - canvas_paint_thread.canvas(canvas_id).send_pixels(chan); + chan.send(canvas_paint_thread + .canvas(canvas_id) + .read_pixels(None, None) + .as_ipc() + ).unwrap(); }, }, Err(e) => { @@ -109,10 +116,14 @@ impl<'a> CanvasPaintThread<'a> { let canvas_id = self.next_canvas_id; self.next_canvas_id.0 += 1; - let canvas_data = - CanvasData::new(size, self.compositor_api.clone(), self.font_context.clone()); + let canvas_data = CanvasData::new( + size, + self.compositor_api.clone(), + self.font_context.clone(), + RaqoteBackend, + ); let image_key = canvas_data.image_key(); - self.canvases.insert(canvas_id, canvas_data); + self.canvases.insert(canvas_id, Canvas::Raqote(canvas_data)); (canvas_id, image_key) } @@ -159,24 +170,21 @@ impl<'a> CanvasPaintThread<'a> { Canvas2dMsg::IsPointInPath(path, x, y, fill_rule, chan) => self .canvas(canvas_id) .is_point_in_path_(&path[..], x, y, fill_rule, chan), - Canvas2dMsg::DrawImage( - ref image_data, - image_size, - dest_rect, - source_rect, - smoothing_enabled, - ) => self.canvas(canvas_id).draw_image( - image_data, - image_size, - dest_rect, - source_rect, - smoothing_enabled, - true, - ), + Canvas2dMsg::DrawImage(snapshot, dest_rect, source_rect, smoothing_enabled) => { + let snapshot = snapshot.to_owned(); + self.canvas(canvas_id).draw_image( + snapshot.data(), + snapshot.size(), + dest_rect, + source_rect, + smoothing_enabled, + !snapshot.alpha_mode().is_premultiplied(), + ) + }, Canvas2dMsg::DrawEmptyImage(image_size, dest_rect, source_rect) => { self.canvas(canvas_id).draw_image( &vec![0; image_size.area() as usize * 4], - image_size, + image_size.to_u64(), dest_rect, source_rect, false, @@ -192,10 +200,10 @@ impl<'a> CanvasPaintThread<'a> { ) => { let image_data = self .canvas(canvas_id) - .read_pixels(source_rect.to_u64(), image_size.to_u64()); + .read_pixels(Some(source_rect.to_u64()), Some(image_size.to_u64())); self.canvas(other_canvas_id).draw_image( - &image_data, - source_rect.size, + image_data.data(), + source_rect.size.to_u64(), dest_rect, source_rect, smoothing, @@ -230,6 +238,10 @@ impl<'a> CanvasPaintThread<'a> { Canvas2dMsg::SetLineCap(cap) => self.canvas(canvas_id).set_line_cap(cap), Canvas2dMsg::SetLineJoin(join) => self.canvas(canvas_id).set_line_join(join), Canvas2dMsg::SetMiterLimit(limit) => self.canvas(canvas_id).set_miter_limit(limit), + Canvas2dMsg::SetLineDash(items) => self.canvas(canvas_id).set_line_dash(items), + Canvas2dMsg::SetLineDashOffset(offset) => { + self.canvas(canvas_id).set_line_dash_offset(offset) + }, Canvas2dMsg::GetTransform(sender) => { let transform = self.canvas(canvas_id).get_transform(); sender.send(transform).unwrap(); @@ -240,8 +252,10 @@ impl<'a> CanvasPaintThread<'a> { self.canvas(canvas_id).set_global_composition(op) }, Canvas2dMsg::GetImageData(dest_rect, canvas_size, sender) => { - let pixels = self.canvas(canvas_id).read_pixels(dest_rect, canvas_size); - sender.send(&pixels).unwrap(); + let snapshot = self + .canvas(canvas_id) + .read_pixels(Some(dest_rect), Some(canvas_size)); + sender.send(snapshot.as_ipc()).unwrap(); }, Canvas2dMsg::PutImageData(rect, receiver) => { self.canvas(canvas_id) @@ -269,7 +283,347 @@ impl<'a> CanvasPaintThread<'a> { } } - fn canvas(&mut self, canvas_id: CanvasId) -> &mut CanvasData<'a> { + fn canvas(&mut self, canvas_id: CanvasId) -> &mut Canvas<'a> { self.canvases.get_mut(&canvas_id).expect("Bogus canvas id") } } + +enum Canvas<'a> { + Raqote(CanvasData<'a, RaqoteBackend>), +} + +impl Canvas<'_> { + fn set_fill_style(&mut self, style: FillOrStrokeStyle) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_fill_style(style), + } + } + + fn fill(&mut self) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.fill(), + } + } + + fn fill_text(&mut self, text: String, x: f64, y: f64, max_width: Option, is_rtl: bool) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.fill_text(text, x, y, max_width, is_rtl), + } + } + + fn fill_rect(&mut self, rect: &Rect) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.fill_rect(rect), + } + } + + fn set_stroke_style(&mut self, style: FillOrStrokeStyle) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_stroke_style(style), + } + } + + fn stroke_rect(&mut self, rect: &Rect) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.stroke_rect(rect), + } + } + + fn begin_path(&mut self) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.begin_path(), + } + } + + fn close_path(&mut self) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.close_path(), + } + } + + fn fill_path(&mut self, path: &[PathSegment]) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.fill_path(path), + } + } + + fn stroke(&mut self) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.stroke(), + } + } + + fn stroke_path(&mut self, path: &[PathSegment]) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.stroke_path(path), + } + } + + fn clip(&mut self) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.clip(), + } + } + + fn is_point_in_path(&mut self, x: f64, y: f64, fill_rule: FillRule, chan: IpcSender) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.is_point_in_path(x, y, fill_rule, chan), + } + } + + fn is_point_in_path_( + &mut self, + path: &[PathSegment], + x: f64, + y: f64, + fill_rule: FillRule, + chan: IpcSender, + ) { + match self { + Canvas::Raqote(canvas_data) => { + canvas_data.is_point_in_path_(path, x, y, fill_rule, chan) + }, + } + } + + fn clear_rect(&mut self, rect: &Rect) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.clear_rect(rect), + } + } + + fn draw_image( + &mut self, + data: &[u8], + size: Size2D, + dest_rect: Rect, + source_rect: Rect, + smoothing_enabled: bool, + is_premultiplied: bool, + ) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.draw_image( + data, + size, + dest_rect, + source_rect, + smoothing_enabled, + is_premultiplied, + ), + } + } + + fn read_pixels( + &mut self, + read_rect: Option>, + canvas_size: Option>, + ) -> snapshot::Snapshot { + match self { + Canvas::Raqote(canvas_data) => canvas_data.read_pixels(read_rect, canvas_size), + } + } + + fn move_to(&mut self, point: &Point2D) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.move_to(point), + } + } + + fn line_to(&mut self, point: &Point2D) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.line_to(point), + } + } + + fn rect(&mut self, rect: &Rect) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.rect(rect), + } + } + + fn quadratic_curve_to(&mut self, cp: &Point2D, pt: &Point2D) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.quadratic_curve_to(cp, pt), + } + } + + fn bezier_curve_to(&mut self, cp1: &Point2D, cp2: &Point2D, pt: &Point2D) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.bezier_curve_to(cp1, cp2, pt), + } + } + + fn arc(&mut self, center: &Point2D, radius: f32, start: f32, end: f32, ccw: bool) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.arc(center, radius, start, end, ccw), + } + } + + fn arc_to(&mut self, cp1: &Point2D, cp2: &Point2D, radius: f32) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.arc_to(cp1, cp2, radius), + } + } + + #[allow(clippy::too_many_arguments)] + fn ellipse( + &mut self, + center: &Point2D, + radius_x: f32, + radius_y: f32, + rotation: f32, + start: f32, + end: f32, + ccw: bool, + ) { + match self { + Canvas::Raqote(canvas_data) => { + canvas_data.ellipse(center, radius_x, radius_y, rotation, start, end, ccw) + }, + } + } + + fn restore_context_state(&mut self) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.restore_context_state(), + } + } + + fn save_context_state(&mut self) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.save_context_state(), + } + } + + fn set_line_width(&mut self, width: f32) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_line_width(width), + } + } + + fn set_line_cap(&mut self, cap: LineCapStyle) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_line_cap(cap), + } + } + + fn set_line_join(&mut self, join: LineJoinStyle) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_line_join(join), + } + } + + fn set_miter_limit(&mut self, limit: f32) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_miter_limit(limit), + } + } + + fn set_line_dash(&mut self, items: Vec) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_line_dash(items), + } + } + + fn set_line_dash_offset(&mut self, offset: f32) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_line_dash_offset(offset), + } + } + + fn set_transform(&mut self, matrix: &Transform2D) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_transform(matrix), + } + } + + fn set_global_alpha(&mut self, alpha: f32) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_global_alpha(alpha), + } + } + + fn set_global_composition(&mut self, op: CompositionOrBlending) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_global_composition(op), + } + } + + fn set_shadow_offset_x(&mut self, value: f64) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_shadow_offset_x(value), + } + } + + fn set_shadow_offset_y(&mut self, value: f64) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_shadow_offset_y(value), + } + } + + fn set_shadow_blur(&mut self, value: f64) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_shadow_blur(value), + } + } + + fn set_shadow_color(&mut self, color: AbsoluteColor) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_shadow_color(color), + } + } + + fn set_font(&mut self, font_style: FontStyleStruct) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_font(font_style), + } + } + + fn set_text_align(&mut self, text_align: TextAlign) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_text_align(text_align), + } + } + + fn set_text_baseline(&mut self, text_baseline: TextBaseline) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_text_baseline(text_baseline), + } + } + + fn measure_text(&mut self, text: String) -> TextMetrics { + match self { + Canvas::Raqote(canvas_data) => canvas_data.measure_text(text), + } + } + + fn clip_path(&mut self, path: &[PathSegment]) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.clip_path(path), + } + } + + fn get_transform(&self) -> Transform2D { + match self { + Canvas::Raqote(canvas_data) => canvas_data.get_transform(), + } + } + + fn put_image_data(&mut self, unwrap: Vec, rect: Rect) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.put_image_data(unwrap, rect), + } + } + + fn update_image_rendering(&mut self) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.update_image_rendering(), + } + } + + fn recreate(&mut self, size: Option>) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.recreate(size), + } + } +} diff --git a/components/canvas/lib.rs b/components/canvas/lib.rs index d2c62c1d8b6..91ab58b0e8b 100644 --- a/components/canvas/lib.rs +++ b/components/canvas/lib.rs @@ -4,14 +4,8 @@ #![deny(unsafe_code)] +mod backend; mod raqote_backend; -pub use webgl_mode::WebGLComm; - pub mod canvas_data; pub mod canvas_paint_thread; -mod webgl_limits; -mod webgl_mode; -pub mod webgl_thread; -#[cfg(feature = "webxr")] -mod webxr; diff --git a/components/canvas/raqote_backend.rs b/components/canvas/raqote_backend.rs index bc8d20e9dcf..ecf780c36d5 100644 --- a/components/canvas/raqote_backend.rs +++ b/components/canvas/raqote_backend.rs @@ -7,20 +7,19 @@ use std::collections::HashMap; use canvas_traits::canvas::*; use cssparser::color::clamp_unit_f32; -use euclid::Angle; use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D}; use font_kit::font::Font; use fonts::{ByteIndex, FontIdentifier, FontTemplateRefMethods}; use log::warn; -use lyon_geom::Arc; use range::Range; use raqote::PathOp; use style::color::AbsoluteColor; -use crate::canvas_data::{ - self, Backend, CanvasPaintState, Color, CompositionOp, DrawOptions, Filter, GenericDrawTarget, - GenericPathBuilder, GradientStop, GradientStops, Path, SourceSurface, StrokeOptions, TextRun, +use crate::backend::{ + Backend, DrawOptionsHelpers, GenericDrawTarget, GenericPathBuilder, PathHelpers, + PatternHelpers, StrokeOptionsHelpers, }; +use crate::canvas_data::{CanvasPaintState, Filter, TextRun}; thread_local! { /// The shared font cache used by all canvases that render on a thread. It would be nicer @@ -30,80 +29,85 @@ thread_local! { static SHARED_FONT_CACHE: RefCell> = RefCell::default(); } -#[derive(Default)] -pub struct RaqoteBackend; +#[derive(Clone, Default)] +pub(crate) struct RaqoteBackend; impl Backend for RaqoteBackend { - fn get_composition_op(&self, opts: &DrawOptions) -> CompositionOp { - CompositionOp::Raqote(opts.as_raqote().blend_mode) + type Pattern<'a> = Pattern<'a>; + type StrokeOptions = raqote::StrokeStyle; + type Color = raqote::SolidSource; + type DrawOptions = raqote::DrawOptions; + type CompositionOp = raqote::BlendMode; + type DrawTarget = raqote::DrawTarget; + type PathBuilder = PathBuilder; + type SourceSurface = Vec; // TODO: See if we can avoid the alloc (probably?) + type Bytes<'a> = &'a [u8]; + type Path = raqote::Path; + type GradientStop = raqote::GradientStop; + type GradientStops = Vec; + + fn get_composition_op(&self, opts: &Self::DrawOptions) -> Self::CompositionOp { + opts.blend_mode } - fn need_to_draw_shadow(&self, color: &Color) -> bool { - color.as_raqote().a != 0 + fn need_to_draw_shadow(&self, color: &Self::Color) -> bool { + color.a != 0 } - fn set_shadow_color(&mut self, color: AbsoluteColor, state: &mut CanvasPaintState<'_>) { - state.shadow_color = Color::Raqote(color.to_raqote_style()); + fn set_shadow_color(&mut self, color: AbsoluteColor, state: &mut CanvasPaintState<'_, Self>) { + state.shadow_color = color.to_raqote_style(); } fn set_fill_style( &mut self, style: FillOrStrokeStyle, - state: &mut CanvasPaintState<'_>, - _drawtarget: &dyn GenericDrawTarget, + state: &mut CanvasPaintState<'_, Self>, + _drawtarget: &Self::DrawTarget, ) { if let Some(pattern) = style.to_raqote_pattern() { - state.fill_style = canvas_data::Pattern::Raqote(pattern); + state.fill_style = pattern; } } fn set_stroke_style( &mut self, style: FillOrStrokeStyle, - state: &mut CanvasPaintState<'_>, - _drawtarget: &dyn GenericDrawTarget, + state: &mut CanvasPaintState<'_, Self>, + _drawtarget: &Self::DrawTarget, ) { if let Some(pattern) = style.to_raqote_pattern() { - state.stroke_style = canvas_data::Pattern::Raqote(pattern); + state.stroke_style = pattern; } } fn set_global_composition( &mut self, op: CompositionOrBlending, - state: &mut CanvasPaintState<'_>, + state: &mut CanvasPaintState<'_, Self>, ) { - state.draw_options.as_raqote_mut().blend_mode = op.to_raqote_style(); + state.draw_options.blend_mode = op.to_raqote_style(); } - fn create_drawtarget(&self, size: Size2D) -> Box { - Box::new(raqote::DrawTarget::new( - size.width as i32, - size.height as i32, - )) + fn create_drawtarget(&self, size: Size2D) -> Self::DrawTarget { + raqote::DrawTarget::new(size.width as i32, size.height as i32) } - fn recreate_paint_state<'a>(&self, _state: &CanvasPaintState<'a>) -> CanvasPaintState<'a> { - CanvasPaintState::default() - } -} - -impl Default for CanvasPaintState<'_> { - fn default() -> Self { + fn new_paint_state<'a>(&self) -> CanvasPaintState<'a, Self> { let pattern = Pattern::Color(255, 0, 0, 0); CanvasPaintState { - draw_options: DrawOptions::Raqote(raqote::DrawOptions::new()), - fill_style: canvas_data::Pattern::Raqote(pattern.clone()), - stroke_style: canvas_data::Pattern::Raqote(pattern), - stroke_opts: StrokeOptions::Raqote(Default::default()), + draw_options: raqote::DrawOptions::new(), + fill_style: pattern.clone(), + stroke_style: pattern, + stroke_opts: Default::default(), transform: Transform2D::identity(), shadow_offset_x: 0.0, shadow_offset_y: 0.0, shadow_blur: 0.0, - shadow_color: Color::Raqote(raqote::SolidSource::from_unpremultiplied_argb(0, 0, 0, 0)), + shadow_color: raqote::SolidSource::from_unpremultiplied_argb(0, 0, 0, 0), font_style: None, text_align: TextAlign::default(), text_baseline: TextBaseline::default(), + _backend: std::marker::PhantomData, } } } @@ -230,126 +234,122 @@ impl Repetition { } } -impl canvas_data::Pattern<'_> { - pub fn source(&self) -> raqote::Source { +pub fn source<'a>(pattern: &Pattern<'a>) -> raqote::Source<'a> { + match pattern { + Pattern::Color(a, r, g, b) => raqote::Source::Solid( + raqote::SolidSource::from_unpremultiplied_argb(*a, *r, *g, *b), + ), + Pattern::LinearGradient(pattern) => raqote::Source::new_linear_gradient( + pattern.gradient.clone(), + pattern.start, + pattern.end, + raqote::Spread::Pad, + ), + Pattern::RadialGradient(pattern) => raqote::Source::new_two_circle_radial_gradient( + pattern.gradient.clone(), + pattern.center1, + pattern.radius1, + pattern.center2, + pattern.radius2, + raqote::Spread::Pad, + ), + Pattern::Surface(pattern) => raqote::Source::Image( + pattern.image, + pattern.extend, + pattern.filter, + pattern.transform, + ), + } +} + +impl PatternHelpers for Pattern<'_> { + fn is_zero_size_gradient(&self) -> bool { match self { - canvas_data::Pattern::Raqote(pattern) => match pattern { - Pattern::Color(a, r, g, b) => raqote::Source::Solid( - raqote::SolidSource::from_unpremultiplied_argb(*a, *r, *g, *b), - ), - Pattern::LinearGradient(pattern) => raqote::Source::new_linear_gradient( - pattern.gradient.clone(), - pattern.start, - pattern.end, - raqote::Spread::Pad, - ), - Pattern::RadialGradient(pattern) => raqote::Source::new_two_circle_radial_gradient( - pattern.gradient.clone(), - pattern.center1, - pattern.radius1, - pattern.center2, - pattern.radius2, - raqote::Spread::Pad, - ), - Pattern::Surface(pattern) => raqote::Source::Image( - pattern.image, - pattern.extend, - pattern.filter, - pattern.transform, - ), + Pattern::RadialGradient(pattern) => { + let centers_equal = pattern.center1 == pattern.center2; + let radii_equal = pattern.radius1 == pattern.radius2; + (centers_equal && radii_equal) || pattern.gradient.stops.is_empty() }, - } - } - pub fn is_zero_size_gradient(&self) -> bool { - match self { - canvas_data::Pattern::Raqote(pattern) => match pattern { - Pattern::RadialGradient(pattern) => { - let centers_equal = pattern.center1 == pattern.center2; - let radii_equal = pattern.radius1 == pattern.radius2; - (centers_equal && radii_equal) || pattern.gradient.stops.is_empty() - }, - Pattern::LinearGradient(pattern) => { - (pattern.start == pattern.end) || pattern.gradient.stops.is_empty() - }, - Pattern::Color(..) | Pattern::Surface(..) => false, + Pattern::LinearGradient(pattern) => { + (pattern.start == pattern.end) || pattern.gradient.stops.is_empty() }, + Pattern::Color(..) | Pattern::Surface(..) => false, + } + } + + fn draw_rect(&self, rect: &Rect) -> Rect { + match self { + Pattern::Surface(pattern) => { + let pattern_rect = Rect::new(Point2D::origin(), pattern.size()); + let mut draw_rect = rect.intersection(&pattern_rect).unwrap_or(Rect::zero()); + + match pattern.repetition() { + Repetition::NoRepeat => { + draw_rect.size.width = draw_rect.size.width.min(pattern_rect.size.width); + draw_rect.size.height = draw_rect.size.height.min(pattern_rect.size.height); + }, + Repetition::RepeatX => { + draw_rect.size.width = rect.size.width; + draw_rect.size.height = draw_rect.size.height.min(pattern_rect.size.height); + }, + Repetition::RepeatY => { + draw_rect.size.height = rect.size.height; + draw_rect.size.width = draw_rect.size.width.min(pattern_rect.size.width); + }, + Repetition::Repeat => { + draw_rect = *rect; + }, + } + + draw_rect + }, + Pattern::Color(..) | Pattern::LinearGradient(..) | Pattern::RadialGradient(..) => *rect, } } } -impl StrokeOptions { - pub fn set_line_width(&mut self, _val: f32) { - match self { - StrokeOptions::Raqote(options) => options.width = _val, - } +impl StrokeOptionsHelpers for raqote::StrokeStyle { + fn set_line_width(&mut self, _val: f32) { + self.width = _val; } - pub fn set_miter_limit(&mut self, _val: f32) { - match self { - StrokeOptions::Raqote(options) => options.miter_limit = _val, - } + fn set_miter_limit(&mut self, _val: f32) { + self.miter_limit = _val; } - pub fn set_line_join(&mut self, val: LineJoinStyle) { - match self { - StrokeOptions::Raqote(options) => options.join = val.to_raqote_style(), - } + fn set_line_join(&mut self, val: LineJoinStyle) { + self.join = val.to_raqote_style(); } - pub fn set_line_cap(&mut self, val: LineCapStyle) { - match self { - StrokeOptions::Raqote(options) => options.cap = val.to_raqote_style(), - } + fn set_line_cap(&mut self, val: LineCapStyle) { + self.cap = val.to_raqote_style(); } - pub fn as_raqote(&self) -> &raqote::StrokeStyle { - match self { - StrokeOptions::Raqote(options) => options, - } + fn set_line_dash(&mut self, items: Vec) { + self.dash_array = items; + } + fn set_line_dash_offset(&mut self, offset: f32) { + self.dash_offset = offset; } } -impl DrawOptions { - pub fn set_alpha(&mut self, val: f32) { - match self { - DrawOptions::Raqote(draw_options) => draw_options.alpha = val, - } - } - pub fn as_raqote(&self) -> &raqote::DrawOptions { - match self { - DrawOptions::Raqote(options) => options, - } - } - fn as_raqote_mut(&mut self) -> &mut raqote::DrawOptions { - match self { - DrawOptions::Raqote(options) => options, - } +impl DrawOptionsHelpers for raqote::DrawOptions { + fn set_alpha(&mut self, val: f32) { + self.alpha = val; } } -impl Path { - pub fn transformed_copy_to_builder( - &self, - transform: &Transform2D, - ) -> Box { - Box::new(PathBuilder(Some(raqote::PathBuilder::from( - self.as_raqote().clone().transform(transform), - )))) +impl PathHelpers for raqote::Path { + fn transformed_copy_to_builder(&self, transform: &Transform2D) -> PathBuilder { + PathBuilder(Some(raqote::PathBuilder::from( + self.clone().transform(transform), + ))) } - pub fn contains_point(&self, x: f64, y: f64, path_transform: &Transform2D) -> bool { - self.as_raqote() - .clone() + fn contains_point(&self, x: f64, y: f64, path_transform: &Transform2D) -> bool { + self.clone() .transform(path_transform) .contains_point(0.1, x as f32, y as f32) } - pub fn copy_to_builder(&self) -> Box { - Box::new(PathBuilder(Some(raqote::PathBuilder::from( - self.as_raqote().clone(), - )))) - } - - pub fn as_raqote(&self) -> &raqote::Path { - match self { - Path::Raqote(p) => p, - } + fn copy_to_builder(&self) -> PathBuilder { + PathBuilder(Some(raqote::PathBuilder::from(self.clone()))) } } @@ -363,7 +363,7 @@ fn create_gradient_stops(gradient_stops: Vec) -> Vec for raqote::DrawTarget { fn clear_rect(&mut self, rect: &Rect) { let mut pb = raqote::PathBuilder::new(); pb.rect( @@ -375,59 +375,47 @@ impl GenericDrawTarget for raqote::DrawTarget { let mut options = raqote::DrawOptions::new(); options.blend_mode = raqote::BlendMode::Clear; let pattern = Pattern::Color(0, 0, 0, 0); - GenericDrawTarget::fill( - self, - &Path::Raqote(pb.finish()), - canvas_data::Pattern::Raqote(pattern), - &DrawOptions::Raqote(options), - ); + >::fill(self, &pb.finish(), pattern, &options); } #[allow(unsafe_code)] fn copy_surface( &mut self, - surface: SourceSurface, + surface: ::SourceSurface, source: Rect, destination: Point2D, ) { let mut dt = raqote::DrawTarget::new(source.size.width, source.size.height); - let data = surface.as_raqote(); + let data = surface; let s = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u32, data.len() / 4) }; dt.get_data_mut().copy_from_slice(s); raqote::DrawTarget::copy_surface(self, &dt, source.to_box2d(), destination); } - // TODO(pylbrecht) - // Somehow a duplicate of `create_gradient_stops()` with different types. - // It feels cumbersome to convert GradientStop back and forth just to use - // `create_gradient_stops()`, so I'll leave this here for now. - fn create_gradient_stops(&self, gradient_stops: Vec) -> GradientStops { - let mut stops = gradient_stops - .into_iter() - .map(|item| *item.as_raqote()) - .collect::>(); - // https://www.w3.org/html/test/results/2dcontext/annotated-spec/canvas.html#testrefs.2d.gradient.interpolate.overlap - stops.sort_by(|a, b| a.position.partial_cmp(&b.position).unwrap()); - GradientStops::Raqote(stops) - } - fn create_path_builder(&self) -> Box { - Box::new(PathBuilder::new()) + fn create_path_builder(&self) -> ::PathBuilder { + PathBuilder::new() } - fn create_similar_draw_target(&self, size: &Size2D) -> Box { - Box::new(raqote::DrawTarget::new(size.width, size.height)) + fn create_similar_draw_target( + &self, + size: &Size2D, + ) -> ::DrawTarget { + raqote::DrawTarget::new(size.width, size.height) } - fn create_source_surface_from_data(&self, data: &[u8]) -> Option { - Some(SourceSurface::Raqote(data.to_vec())) + fn create_source_surface_from_data( + &self, + data: &[u8], + ) -> Option<::SourceSurface> { + Some(data.to_vec()) } #[allow(unsafe_code)] fn draw_surface( &mut self, - surface: SourceSurface, + surface: ::SourceSurface, dest: Rect, source: Rect, filter: Filter, - draw_options: &DrawOptions, + draw_options: &::DrawOptions, ) { - let surface_data = surface.as_raqote(); + let surface_data = surface; let image = raqote::Image { width: source.size.width as i32, height: source.size.height as i32, @@ -460,33 +448,29 @@ impl GenericDrawTarget for raqote::DrawTarget { dest.size.height as f32, ); - GenericDrawTarget::fill( - self, - &Path::Raqote(pb.finish()), - canvas_data::Pattern::Raqote(pattern), - draw_options, - ); + >::fill(self, &pb.finish(), pattern, draw_options); } fn draw_surface_with_shadow( &self, - _surface: SourceSurface, + _surface: ::SourceSurface, _dest: &Point2D, - _color: &Color, + _color: &::Color, _offset: &Vector2D, _sigma: f32, - _operator: CompositionOp, + _operator: ::CompositionOp, ) { warn!("no support for drawing shadows"); } - fn fill(&mut self, path: &Path, pattern: canvas_data::Pattern, draw_options: &DrawOptions) { - match draw_options.as_raqote().blend_mode { + fn fill( + &mut self, + path: &::Path, + pattern: ::Pattern<'_>, + draw_options: &::DrawOptions, + ) { + match draw_options.blend_mode { raqote::BlendMode::Src => { self.clear(raqote::SolidSource::from_unpremultiplied_argb(0, 0, 0, 0)); - self.fill( - path.as_raqote(), - &pattern.source(), - draw_options.as_raqote(), - ); + self.fill(path, &source(&pattern), draw_options); }, raqote::BlendMode::Clear | raqote::BlendMode::SrcAtop | @@ -495,26 +479,19 @@ impl GenericDrawTarget for raqote::DrawTarget { raqote::BlendMode::Xor | raqote::BlendMode::DstOver | raqote::BlendMode::SrcOver => { - self.fill( - path.as_raqote(), - &pattern.source(), - draw_options.as_raqote(), - ); + self.fill(path, &source(&pattern), draw_options); }, raqote::BlendMode::SrcIn | raqote::BlendMode::SrcOut | raqote::BlendMode::DstIn | raqote::BlendMode::DstAtop => { - let mut options = *draw_options.as_raqote(); + let mut options = *draw_options; self.push_layer_with_blend(1., options.blend_mode); options.blend_mode = raqote::BlendMode::SrcOver; - self.fill(path.as_raqote(), &pattern.source(), &options); + self.fill(path, &source(&pattern), &options); self.pop_layer(); }, - _ => warn!( - "unrecognized blend mode: {:?}", - draw_options.as_raqote().blend_mode - ), + _ => warn!("unrecognized blend mode: {:?}", draw_options.blend_mode), } } @@ -522,8 +499,8 @@ impl GenericDrawTarget for raqote::DrawTarget { &mut self, text_runs: Vec, start: Point2D, - pattern: &canvas_data::Pattern, - draw_options: &DrawOptions, + pattern: &::Pattern<'_>, + draw_options: &::DrawOptions, ) { let mut advance = 0.; for run in text_runs.iter() { @@ -571,8 +548,8 @@ impl GenericDrawTarget for raqote::DrawTarget { run.font.descriptor.pt_size.to_f32_px(), &ids, &positions, - &pattern.source(), - draw_options.as_raqote(), + &source(pattern), + draw_options, ); }) } @@ -581,8 +558,8 @@ impl GenericDrawTarget for raqote::DrawTarget { fn fill_rect( &mut self, rect: &Rect, - pattern: canvas_data::Pattern, - draw_options: Option<&DrawOptions>, + pattern: ::Pattern<'_>, + draw_options: Option<&::DrawOptions>, ) { let mut pb = raqote::PathBuilder::new(); pb.rect( @@ -592,16 +569,16 @@ impl GenericDrawTarget for raqote::DrawTarget { rect.size.height, ); let draw_options = if let Some(options) = draw_options { - *options.as_raqote() + *options } else { raqote::DrawOptions::new() }; - GenericDrawTarget::fill( + >::fill( self, - &Path::Raqote(pb.finish()), + &pb.finish(), pattern, - &DrawOptions::Raqote(draw_options), + &draw_options, ); } fn get_size(&self) -> Size2D { @@ -613,41 +590,36 @@ impl GenericDrawTarget for raqote::DrawTarget { fn pop_clip(&mut self) { self.pop_clip(); } - fn push_clip(&mut self, path: &Path) { - self.push_clip(path.as_raqote()); + fn push_clip(&mut self, path: &::Path) { + self.push_clip(path); } fn set_transform(&mut self, matrix: &Transform2D) { self.set_transform(matrix); } - fn snapshot(&self) -> SourceSurface { - SourceSurface::Raqote(self.snapshot_data_owned()) + fn surface(&self) -> ::SourceSurface { + self.bytes().to_vec() } fn stroke( &mut self, - path: &Path, - pattern: canvas_data::Pattern, - stroke_options: &StrokeOptions, - draw_options: &DrawOptions, + path: &::Path, + pattern: Pattern<'_>, + stroke_options: &::StrokeOptions, + draw_options: &::DrawOptions, ) { - self.stroke( - path.as_raqote(), - &pattern.source(), - stroke_options.as_raqote(), - draw_options.as_raqote(), - ); + self.stroke(path, &source(&pattern), stroke_options, draw_options); } fn stroke_line( &mut self, start: Point2D, end: Point2D, - pattern: canvas_data::Pattern, - stroke_options: &StrokeOptions, - draw_options: &DrawOptions, + pattern: ::Pattern<'_>, + stroke_options: &::StrokeOptions, + draw_options: &::DrawOptions, ) { let mut pb = raqote::PathBuilder::new(); pb.move_to(start.x, start.y); pb.line_to(end.x, end.y); - let mut stroke_options = stroke_options.as_raqote().clone(); + let mut stroke_options = stroke_options.clone(); let cap = match stroke_options.join { raqote::LineJoin::Round => raqote::LineCap::Round, _ => raqote::LineCap::Butt, @@ -656,17 +628,17 @@ impl GenericDrawTarget for raqote::DrawTarget { self.stroke( &pb.finish(), - &pattern.source(), + &source(&pattern), &stroke_options, - draw_options.as_raqote(), + draw_options, ); } fn stroke_rect( &mut self, rect: &Rect, - pattern: canvas_data::Pattern, - stroke_options: &StrokeOptions, - draw_options: &DrawOptions, + pattern: ::Pattern<'_>, + stroke_options: &::StrokeOptions, + draw_options: &::DrawOptions, ) { let mut pb = raqote::PathBuilder::new(); pb.rect( @@ -678,26 +650,15 @@ impl GenericDrawTarget for raqote::DrawTarget { self.stroke( &pb.finish(), - &pattern.source(), - stroke_options.as_raqote(), - draw_options.as_raqote(), + &source(&pattern), + stroke_options, + draw_options, ); } #[allow(unsafe_code)] - fn snapshot_data(&self, f: &dyn Fn(&[u8]) -> Vec) -> Vec { + fn bytes(&self) -> &[u8] { let v = self.get_data(); - f( - unsafe { - std::slice::from_raw_parts(v.as_ptr() as *const u8, std::mem::size_of_val(v)) - }, - ) - } - #[allow(unsafe_code)] - fn snapshot_data_owned(&self) -> Vec { - let v = self.get_data(); - unsafe { - std::slice::from_raw_parts(v.as_ptr() as *const u8, std::mem::size_of_val(v)).into() - } + unsafe { std::slice::from_raw_parts(v.as_ptr() as *const u8, std::mem::size_of_val(v)) } } } @@ -710,7 +671,7 @@ impl Filter { } } -struct PathBuilder(Option); +pub(crate) struct PathBuilder(Option); impl PathBuilder { fn new() -> PathBuilder { @@ -718,25 +679,7 @@ impl PathBuilder { } } -impl GenericPathBuilder for PathBuilder { - fn arc( - &mut self, - origin: Point2D, - radius: f32, - start_angle: f32, - end_angle: f32, - anticlockwise: bool, - ) { - self.ellipse( - origin, - radius, - radius, - 0., - start_angle, - end_angle, - anticlockwise, - ); - } +impl GenericPathBuilder for PathBuilder { fn bezier_curve_to( &mut self, control_point1: &Point2D, @@ -752,98 +695,16 @@ impl GenericPathBuilder for PathBuilder { control_point3.y, ); } + fn close(&mut self) { self.0.as_mut().unwrap().close(); } - fn ellipse( - &mut self, - origin: Point2D, - radius_x: f32, - radius_y: f32, - rotation_angle: f32, - start_angle: f32, - end_angle: f32, - anticlockwise: bool, - ) { - let mut start = Angle::radians(start_angle); - let mut end = Angle::radians(end_angle); - - // Wrap angles mod 2 * PI if necessary - if !anticlockwise && start > end + Angle::two_pi() || - anticlockwise && end > start + Angle::two_pi() - { - start = start.positive(); - end = end.positive(); - } - - // Calculate the total arc we're going to sweep. - let sweep = match anticlockwise { - true => { - if end - start == Angle::two_pi() { - -Angle::two_pi() - } else if end > start { - -(Angle::two_pi() - (end - start)) - } else { - -(start - end) - } - }, - false => { - if start - end == Angle::two_pi() { - Angle::two_pi() - } else if start > end { - Angle::two_pi() - (start - end) - } else { - end - start - } - }, - }; - - let arc: Arc = Arc { - center: origin, - radii: Vector2D::new(radius_x, radius_y), - start_angle: start, - sweep_angle: sweep, - x_rotation: Angle::radians(rotation_angle), - }; - - self.line_to(arc.from()); - - arc.for_each_quadratic_bezier(&mut |q| { - self.quadratic_curve_to(&q.ctrl, &q.to); - }); - } - - fn svg_arc( - &mut self, - radius_x: f32, - radius_y: f32, - rotation_angle: f32, - large_arc: bool, - sweep: bool, - end_point: Point2D, - ) { - let Some(start) = self.get_current_point() else { - return; - }; - - let arc = lyon_geom::SvgArc { - from: start, - to: end_point, - radii: lyon_geom::vector(radius_x, radius_y), - x_rotation: lyon_geom::Angle::degrees(rotation_angle), - flags: lyon_geom::ArcFlags { large_arc, sweep }, - }; - - arc.for_each_quadratic_bezier(&mut |q| { - self.quadratic_curve_to(&q.ctrl, &q.to); - }); - } fn get_current_point(&mut self) -> Option> { let path = self.finish(); - self.0 = Some(path.as_raqote().clone().into()); + self.0 = Some(path.clone().into()); - path.as_raqote().ops.iter().last().and_then(|op| match op { + path.ops.iter().last().and_then(|op| match op { PathOp::MoveTo(point) | PathOp::LineTo(point) => Some(Point2D::new(point.x, point.y)), PathOp::CubicTo(_, _, point) => Some(Point2D::new(point.x, point.y)), PathOp::QuadTo(_, point) => Some(Point2D::new(point.x, point.y)), @@ -865,8 +726,8 @@ impl GenericPathBuilder for PathBuilder { end_point.y, ); } - fn finish(&mut self) -> Path { - Path::Raqote(self.0.take().unwrap().finish()) + fn finish(&mut self) -> raqote::Path { + self.0.take().unwrap().finish() } } @@ -978,14 +839,6 @@ impl ToRaqotePattern<'_> for FillOrStrokeStyle { } } -impl Color { - fn as_raqote(&self) -> &raqote::SolidSource { - match self { - Color::Raqote(s) => s, - } - } -} - impl ToRaqoteStyle for AbsoluteColor { type Target = raqote::SolidSource; @@ -1055,19 +908,3 @@ impl ToRaqoteStyle for CompositionStyle { } } } - -impl SourceSurface { - fn as_raqote(&self) -> &Vec { - match self { - SourceSurface::Raqote(s) => s, - } - } -} - -impl GradientStop { - fn as_raqote(&self) -> &raqote::GradientStop { - match self { - GradientStop::Raqote(s) => s, - } - } -} diff --git a/components/compositing/Cargo.toml b/components/compositing/Cargo.toml index 11d87635f32..8a670a4b4a7 100644 --- a/components/compositing/Cargo.toml +++ b/components/compositing/Cargo.toml @@ -13,12 +13,12 @@ path = "lib.rs" [features] default = [] -multiview = [] tracing = ["dep:tracing"] webxr = ["dep:webxr"] [dependencies] base = { workspace = true } +bincode = { workspace = true } bitflags = { workspace = true } compositing_traits = { workspace = true } constellation_traits = { workspace = true } @@ -35,14 +35,15 @@ net = { path = "../net" } pixels = { path = "../pixels" } profile_traits = { workspace = true } script_traits = { workspace = true } +servo_allocator = { path = "../allocator" } servo_config = { path = "../config" } servo_geometry = { path = "../geometry" } stylo_traits = { workspace = true } tracing = { workspace = true, optional = true } webrender = { workspace = true } webrender_api = { workspace = true } -webrender_traits = { workspace = true } webxr = { path = "../webxr", optional = true } +wr_malloc_size_of = { workspace = true } [dev-dependencies] surfman = { workspace = true } diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index 7f8864120b4..b1669277ba1 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -5,8 +5,7 @@ use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::env; -use std::fs::{File, create_dir_all}; -use std::io::Write; +use std::fs::create_dir_all; use std::iter::once; use std::mem::take; use std::rc::Rc; @@ -17,30 +16,30 @@ use base::cross_process_instant::CrossProcessInstant; use base::id::{PipelineId, WebViewId}; use base::{Epoch, WebRenderEpochToU16}; use bitflags::bitflags; +use compositing_traits::display_list::{CompositorDisplayListInfo, HitTestInfo, ScrollTree}; +use compositing_traits::rendering_context::RenderingContext; use compositing_traits::{ - CompositionPipeline, CompositorMsg, CompositorReceiver, SendableFrameTree, + CompositionPipeline, CompositorMsg, ImageUpdate, SendableFrameTree, WebViewTrait, }; -use constellation_traits::{ - AnimationTickType, CompositorHitTestResult, ConstellationMsg, PaintMetricEvent, - UntrustedNodeAddress, WindowSizeData, WindowSizeType, -}; -use crossbeam_channel::Sender; +use constellation_traits::{EmbedderToConstellationMessage, PaintMetricEvent}; +use crossbeam_channel::{Receiver, Sender}; use dpi::PhysicalSize; use embedder_traits::{ - Cursor, InputEvent, MouseButtonEvent, MouseMoveEvent, ShutdownState, TouchEventType, + CompositorHitTestResult, Cursor, InputEvent, MouseButtonEvent, MouseMoveEvent, ShutdownState, + TouchEventType, UntrustedNodeAddress, ViewportDetails, WheelDelta, WheelEvent, WheelMode, }; -use euclid::{Box2D, Point2D, Rect, Scale, Size2D, Transform3D}; +use euclid::{Point2D, Rect, Scale, Size2D, Transform3D, Vector2D}; use fnv::FnvHashMap; use ipc_channel::ipc::{self, IpcSharedMemory}; use libc::c_void; use log::{debug, info, trace, warn}; use pixels::{CorsStatus, Image, ImageFrame, PixelFormat}; +use profile_traits::mem::{ProcessReports, ProfilerRegistration, Report, ReportKind}; use profile_traits::time::{self as profile_time, ProfilerCategory}; -use profile_traits::time_profile; -use script_traits::AnimationState; +use profile_traits::{path, time_profile}; use servo_config::opts; use servo_geometry::DeviceIndependentPixel; -use style_traits::{CSSPixel, PinchZoomFactor}; +use style_traits::CSSPixel; use webrender::{CaptureBits, RenderApi, Transaction}; use webrender_api::units::{ DeviceIntPoint, DeviceIntRect, DevicePixel, DevicePoint, DeviceRect, LayoutPoint, LayoutRect, @@ -53,13 +52,10 @@ use webrender_api::{ RenderReasons, SampledScrollOffset, ScrollLocation, SpaceAndClipInfo, SpatialId, SpatialTreeItemKey, TransformStyle, }; -use webrender_traits::display_list::{HitTestInfo, ScrollTree}; -use webrender_traits::rendering_context::RenderingContext; -use webrender_traits::{CrossProcessCompositorMessage, ImageUpdate}; use crate::InitialCompositorState; -use crate::webview::{UnknownWebView, WebView, WebViewManager}; -use crate::windowing::{self, EmbedderCoordinates, WebRenderDebugOption, WindowMethods}; +use crate::webview_manager::WebViewManager; +use crate::webview_renderer::{UnknownWebView, WebViewRenderer}; #[derive(Debug, PartialEq)] enum UnableToComposite { @@ -73,10 +69,6 @@ enum NotReadyToPaint { WaitingOnConstellation, } -// Default viewport constraints -const MAX_ZOOM: f32 = 8.0; -const MIN_ZOOM: f32 = 0.1; - /// Holds the state when running reftests that determines when it is /// safe to save the output image. #[derive(Clone, Copy, Debug, PartialEq)] @@ -85,6 +77,14 @@ enum ReadyState { WaitingForConstellationReply, ReadyToSaveImage, } + +/// An option to control what kind of WebRender debugging is enabled while Servo is running. +#[derive(Clone)] +pub enum WebRenderDebugOption { + Profiler, + TextureCacheDebug, + RenderTargetDebug, +} /// Data that is shared by all WebView renderers. pub struct ServoRenderer { /// This is a temporary map between [`PipelineId`]s and their associated [`WebViewId`]. Once @@ -97,10 +97,10 @@ pub struct ServoRenderer { shutdown_state: Rc>, /// The port on which we receive messages. - compositor_receiver: CompositorReceiver, + compositor_receiver: Receiver, /// The channel on which messages can be sent to the constellation. - pub(crate) constellation_sender: Sender, + pub(crate) constellation_sender: Sender, /// The channel on which messages can be sent to the time profiler. time_profiler_chan: profile_time::ProfilerChan, @@ -114,10 +114,6 @@ pub struct ServoRenderer { /// The GL bindings for webrender webrender_gl: Rc, - /// The string representing the version of Servo that is running. This is used to tag - /// WebRender capture output. - version_string: String, - #[cfg(feature = "webxr")] /// Some XR devices want to run on the main thread. webxr_main_thread: webxr::MainThreadRegistry, @@ -137,31 +133,12 @@ pub struct IOCompositor { /// Data that is shared by all WebView renderers. global: Rc>, - /// Our top-level browsing contexts. - webviews: WebViewManager, - - /// The application window. - pub window: Rc, - - /// "Mobile-style" zoom that does not reflow the page. - viewport_zoom: PinchZoomFactor, - - /// Viewport zoom constraints provided by @viewport. - min_viewport_zoom: Option, - max_viewport_zoom: Option, - - /// "Desktop-style" zoom that resizes the viewport to fit the window. - page_zoom: Scale, + /// Our [`WebViewRenderer`]s, one for every `WebView`. + webview_renderers: WebViewManager, /// Tracks whether or not the view needs to be repainted. needs_repaint: Cell, - /// Tracks whether the zoom action has happened recently. - zoom_action: bool, - - /// The time of the last zoom action has started. - zoom_time: f64, - /// Used by the logic that determines when it is safe to output an /// image for the reftest framework. ready_to_save_state: ReadyState, @@ -172,15 +149,16 @@ pub struct IOCompositor { /// The surfman instance that webrender targets rendering_context: Rc, - /// The coordinates of the native window, its view and the screen. - embedder_coordinates: EmbedderCoordinates, - /// The number of frames pending to receive from WebRender. pending_frames: usize, /// The [`Instant`] of the last animation tick, used to avoid flooding the Constellation and /// ScriptThread with a deluge of animation ticks. last_animation_tick: Instant, + + /// A handle to the memory profiler which will automatically unregister + /// when it's dropped. + _mem_profiler_registration: ProfilerRegistration, } /// Why we need to be repainted. This is used for debugging. @@ -219,9 +197,6 @@ pub(crate) struct PipelineDetails { /// The pipeline associated with this PipelineDetails object. pub pipeline: Option, - /// The [`PipelineId`] of this pipeline. - pub id: PipelineId, - /// The id of the parent pipeline, if any. pub parent_pipeline_id: Option, @@ -262,38 +237,15 @@ impl PipelineDetails { self.animation_callbacks_running } - pub(crate) fn tick_animations(&self, compositor: &IOCompositor) -> bool { - let animation_callbacks_running = self.animation_callbacks_running; - let animations_running = self.animations_running; - if !animation_callbacks_running && !animations_running { - return false; - } - - if self.throttled { - return false; - } - - let mut tick_type = AnimationTickType::empty(); - if animations_running { - tick_type.insert(AnimationTickType::CSS_ANIMATIONS_AND_TRANSITIONS); - } - if animation_callbacks_running { - tick_type.insert(AnimationTickType::REQUEST_ANIMATION_FRAME); - } - - let msg = ConstellationMsg::TickAnimation(self.id, tick_type); - if let Err(e) = compositor.global.borrow().constellation_sender.send(msg) { - warn!("Sending tick to constellation failed ({:?}).", e); - } - true + pub(crate) fn animating(&self) -> bool { + !self.throttled && (self.animation_callbacks_running || self.animations_running) } } impl PipelineDetails { - pub(crate) fn new(id: PipelineId) -> PipelineDetails { + pub(crate) fn new() -> PipelineDetails { PipelineDetails { pipeline: None, - id, parent_pipeline_id: None, most_recent_display_list_epoch: None, animations_running: false, @@ -417,7 +369,9 @@ impl ServoRenderer { self.cursor = cursor; if let Err(e) = self .constellation_sender - .send(ConstellationMsg::SetCursor(webview_id, cursor)) + .send(EmbedderToConstellationMessage::SetCursor( + webview_id, cursor, + )) { warn!("Sending event to constellation failed ({:?}).", e); } @@ -425,12 +379,12 @@ impl ServoRenderer { } impl IOCompositor { - pub fn new( - window: Rc, - state: InitialCompositorState, - convert_mouse_to_touch: bool, - version_string: String, - ) -> Self { + pub fn new(state: InitialCompositorState, convert_mouse_to_touch: bool) -> Self { + let registration = state.mem_profiler_chan.prepare_memory_reporting( + "compositor".into(), + state.sender.clone(), + CompositorMsg::CollectMemoryReport, + ); let compositor = IOCompositor { global: Rc::new(RefCell::new(ServoRenderer { shutdown_state: state.shutdown_state, @@ -441,28 +395,20 @@ impl IOCompositor { webrender_api: state.webrender_api, webrender_document: state.webrender_document, webrender_gl: state.webrender_gl, - version_string, #[cfg(feature = "webxr")] webxr_main_thread: state.webxr_main_thread, convert_mouse_to_touch, cursor: Cursor::None, cursor_pos: DevicePoint::new(0.0, 0.0), })), - webviews: WebViewManager::default(), - embedder_coordinates: window.get_coordinates(), - window, + webview_renderers: WebViewManager::default(), needs_repaint: Cell::default(), - page_zoom: Scale::new(1.0), - viewport_zoom: PinchZoomFactor::new(1.0), - min_viewport_zoom: Some(PinchZoomFactor::new(1.0)), - max_viewport_zoom: None, - zoom_action: false, - zoom_time: 0f64, ready_to_save_state: ReadyState::Unknown, webrender: Some(state.webrender), rendering_context: state.rendering_context, pending_frames: 0, last_animation_tick: Instant::now(), + _mem_profiler_registration: registration, }; { @@ -483,6 +429,21 @@ impl IOCompositor { } } + pub fn rendering_context_size(&self) -> Size2D { + self.rendering_context.size2d() + } + + pub fn webxr_running(&self) -> bool { + #[cfg(feature = "webxr")] + { + self.global.borrow().webxr_main_thread.running() + } + #[cfg(not(feature = "webxr"))] + { + false + } + } + fn set_needs_repaint(&self, reason: RepaintReason) { let mut needs_repaint = self.needs_repaint.get(); needs_repaint.insert(reason); @@ -500,8 +461,8 @@ impl IOCompositor { .global .borrow_mut() .compositor_receiver - .try_recv_compositor_msg() - .is_some() + .try_recv() + .is_ok() {} // Tell the profiler, memory profiler, and scrolling timer to shut down. @@ -530,27 +491,43 @@ impl IOCompositor { } match msg { + CompositorMsg::CollectMemoryReport(sender) => { + let ops = + wr_malloc_size_of::MallocSizeOfOps::new(servo_allocator::usable_size, None); + let report = self.global.borrow().webrender_api.report_memory(ops); + let reports = vec![ + Report { + path: path!["webrender", "fonts"], + kind: ReportKind::ExplicitJemallocHeapSize, + size: report.fonts, + }, + Report { + path: path!["webrender", "images"], + kind: ReportKind::ExplicitJemallocHeapSize, + size: report.images, + }, + Report { + path: path!["webrender", "display-list"], + kind: ReportKind::ExplicitJemallocHeapSize, + size: report.display_list, + }, + ]; + sender.send(ProcessReports::new(reports)); + }, + CompositorMsg::ChangeRunningAnimationsState( webview_id, pipeline_id, animation_state, ) => { - let mut throttled = true; - if let Some(webview) = self.webviews.get_mut(webview_id) { - throttled = - webview.change_running_animations_state(pipeline_id, animation_state); - } - - // These operations should eventually happen per-WebView, but they are global now as rendering - // is still global to all WebViews. - if !throttled && animation_state == AnimationState::AnimationsPresent { - self.set_needs_repaint(RepaintReason::ChangedAnimationState); - } - - if !throttled && animation_state == AnimationState::AnimationCallbacksPresent { - // We need to fetch the WebView again in order to avoid a double borrow. - if let Some(webview) = self.webviews.get(webview_id) { - webview.tick_animations_for_pipeline(pipeline_id, self); + if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) { + if webview_renderer + .change_pipeline_running_animations_state(pipeline_id, animation_state) && + webview_renderer.animating() + { + // These operations should eventually happen per-WebView, but they are + // global now as rendering is still global to all WebViews. + self.process_animations(true); } } }, @@ -564,15 +541,15 @@ impl IOCompositor { }, CompositorMsg::TouchEventProcessed(webview_id, result) => { - let Some(webview) = self.webviews.get_mut(webview_id) else { + let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else { warn!("Handling input event for unknown webview: {webview_id}"); return; }; - webview.on_touch_event_processed(result); + webview_renderer.on_touch_event_processed(result); }, - CompositorMsg::CreatePng(page_rect, reply) => { - let res = self.render_to_shared_memory(page_rect); + CompositorMsg::CreatePng(webview_id, page_rect, reply) => { + let res = self.render_to_shared_memory(webview_id, page_rect); if let Err(ref e) = res { info!("Error retrieving PNG: {:?}", e); } @@ -596,9 +573,14 @@ impl IOCompositor { }, CompositorMsg::SetThrottled(webview_id, pipeline_id, throttled) => { - if let Some(webview) = self.webviews.get_mut(webview_id) { - webview.set_throttled(pipeline_id, throttled); - self.process_animations(true); + if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) { + if webview_renderer.set_throttled(pipeline_id, throttled) && + webview_renderer.animating() + { + // These operations should eventually happen per-WebView, but they are + // global now as rendering is still global to all WebViews. + self.process_animations(true); + } } }, @@ -607,8 +589,8 @@ impl IOCompositor { "Compositor got pipeline exited: {:?} {:?}", webview_id, pipeline_id ); - if let Some(webview) = self.webviews.get_mut(webview_id) { - webview.remove_pipeline(pipeline_id); + if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) { + webview_renderer.remove_pipeline(pipeline_id); } let _ = sender.send(()); }, @@ -640,13 +622,13 @@ impl IOCompositor { }, CompositorMsg::WebDriverMouseButtonEvent(webview_id, action, button, x, y) => { - let dppx = self.device_pixels_per_page_pixel(); - let point = dppx.transform_point(Point2D::new(x, y)); - let Some(webview) = self.webviews.get_mut(webview_id) else { + let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else { warn!("Handling input event for unknown webview: {webview_id}"); return; }; - webview.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent { + let dppx = webview_renderer.device_pixels_per_page_pixel(); + let point = dppx.transform_point(Point2D::new(x, y)); + webview_renderer.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent { point, action, button, @@ -654,48 +636,51 @@ impl IOCompositor { }, CompositorMsg::WebDriverMouseMoveEvent(webview_id, x, y) => { - let dppx = self.device_pixels_per_page_pixel(); - let point = dppx.transform_point(Point2D::new(x, y)); - let Some(webview) = self.webviews.get_mut(webview_id) else { + let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else { warn!("Handling input event for unknown webview: {webview_id}"); return; }; - webview.dispatch_input_event(InputEvent::MouseMove(MouseMoveEvent { point })); + let dppx = webview_renderer.device_pixels_per_page_pixel(); + let point = dppx.transform_point(Point2D::new(x, y)); + webview_renderer + .dispatch_input_event(InputEvent::MouseMove(MouseMoveEvent { point })); }, - CompositorMsg::CrossProcess(cross_proces_message) => { - self.handle_cross_process_message(cross_proces_message); + CompositorMsg::WebDriverWheelScrollEvent(webview_id, x, y, delta_x, delta_y) => { + let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else { + warn!("Handling input event for unknown webview: {webview_id}"); + return; + }; + let delta = WheelDelta { + x: delta_x, + y: delta_y, + z: 0.0, + mode: WheelMode::DeltaPixel, + }; + let dppx = webview_renderer.device_pixels_per_page_pixel(); + let point = dppx.transform_point(Point2D::new(x, y)); + let scroll_delta = + dppx.transform_vector(Vector2D::new(delta_x as f32, delta_y as f32)); + webview_renderer + .dispatch_input_event(InputEvent::Wheel(WheelEvent { delta, point })); + webview_renderer.on_webdriver_wheel_action(scroll_delta, point); }, - } - } - /// Accept messages from content processes that need to be relayed to the WebRender - /// instance in the parent process. - #[cfg_attr( - feature = "tracing", - tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") - )] - fn handle_cross_process_message(&mut self, msg: CrossProcessCompositorMessage) { - match msg { - CrossProcessCompositorMessage::SendInitialTransaction(pipeline) => { + CompositorMsg::SendInitialTransaction(pipeline) => { let mut txn = Transaction::new(); txn.set_display_list(WebRenderEpoch(0), (pipeline, Default::default())); self.generate_frame(&mut txn, RenderReasons::SCENE); self.global.borrow_mut().send_transaction(txn); }, - CrossProcessCompositorMessage::SendScrollNode( - webview_id, - pipeline_id, - point, - external_scroll_id, - ) => { - let Some(webview) = self.webviews.get_mut(webview_id) else { + CompositorMsg::SendScrollNode(webview_id, pipeline_id, point, external_scroll_id) => { + let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else { return; }; let pipeline_id = pipeline_id.into(); - let Some(pipeline_details) = webview.pipelines.get_mut(&pipeline_id) else { + let Some(pipeline_details) = webview_renderer.pipelines.get_mut(&pipeline_id) + else { return; }; @@ -723,13 +708,25 @@ impl IOCompositor { self.global.borrow_mut().send_transaction(txn); }, - CrossProcessCompositorMessage::SendDisplayList { + CompositorMsg::SendDisplayList { webview_id, - display_list_info, display_list_descriptor, display_list_receiver, } => { // This must match the order from the sender, currently in `shared/script/lib.rs`. + let display_list_info = match display_list_receiver.recv() { + Ok(display_list_info) => display_list_info, + Err(error) => { + return warn!("Could not receive display list info: {error}"); + }, + }; + let display_list_info: CompositorDisplayListInfo = + match bincode::deserialize(&display_list_info) { + Ok(display_list_info) => display_list_info, + Err(error) => { + return warn!("Could not deserialize display list info: {error}"); + }, + }; let items_data = match display_list_receiver.recv() { Ok(display_list_data) => display_list_data, Err(error) => { @@ -770,12 +767,12 @@ impl IOCompositor { ) .entered(); - let Some(webview) = self.webviews.get_mut(webview_id) else { + let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else { return warn!("Could not find WebView for incoming display list"); }; let pipeline_id = display_list_info.pipeline_id; - let details = webview.ensure_pipeline_details(pipeline_id.into()); + let details = webview_renderer.ensure_pipeline_details(pipeline_id.into()); details.most_recent_display_list_epoch = Some(display_list_info.epoch); details.hit_test_items = display_list_info.hit_test_info; details.install_new_scroll_tree(display_list_info.scroll_tree); @@ -800,7 +797,7 @@ impl IOCompositor { self.global.borrow_mut().send_transaction(transaction); }, - CrossProcessCompositorMessage::HitTest(pipeline, point, flags, sender) => { + CompositorMsg::HitTest(pipeline, point, flags, sender) => { // When a display list is sent to WebRender, it starts scene building in a // separate thread and then that display list is available for hit testing. // Without flushing scene building, any hit test we do might be done against @@ -826,11 +823,11 @@ impl IOCompositor { let _ = sender.send(result); }, - CrossProcessCompositorMessage::GenerateImageKey(sender) => { + CompositorMsg::GenerateImageKey(sender) => { let _ = sender.send(self.global.borrow().webrender_api.generate_image_key()); }, - CrossProcessCompositorMessage::UpdateImages(updates) => { + CompositorMsg::UpdateImages(updates) => { let mut txn = Transaction::new(); for update in updates { match update { @@ -846,26 +843,21 @@ impl IOCompositor { self.global.borrow_mut().send_transaction(txn); }, - CrossProcessCompositorMessage::AddFont(font_key, data, index) => { + CompositorMsg::AddFont(font_key, data, index) => { self.add_font(font_key, index, data); }, - CrossProcessCompositorMessage::AddSystemFont(font_key, native_handle) => { + CompositorMsg::AddSystemFont(font_key, native_handle) => { let mut transaction = Transaction::new(); transaction.add_native_font(font_key, native_handle); self.global.borrow_mut().send_transaction(transaction); }, - CrossProcessCompositorMessage::AddFontInstance( - font_instance_key, - font_key, - size, - flags, - ) => { + CompositorMsg::AddFontInstance(font_instance_key, font_key, size, flags) => { self.add_font_instance(font_instance_key, font_key, size, flags); }, - CrossProcessCompositorMessage::RemoveFonts(keys, instance_keys) => { + CompositorMsg::RemoveFonts(keys, instance_keys) => { let mut transaction = Transaction::new(); for instance in instance_keys.into_iter() { @@ -878,13 +870,13 @@ impl IOCompositor { self.global.borrow_mut().send_transaction(transaction); }, - CrossProcessCompositorMessage::AddImage(key, desc, data) => { + CompositorMsg::AddImage(key, desc, data) => { let mut txn = Transaction::new(); txn.add_image(key, desc, data.into(), None); self.global.borrow_mut().send_transaction(txn); }, - CrossProcessCompositorMessage::GenerateFontKeys( + CompositorMsg::GenerateFontKeys( number_of_font_keys, number_of_font_instance_keys, result_sender, @@ -902,22 +894,36 @@ impl IOCompositor { .collect(); let _ = result_sender.send((font_keys, font_instance_keys)); }, - CrossProcessCompositorMessage::GetClientWindowRect(req) => { - if let Err(e) = req.send(self.embedder_coordinates.window_rect) { - warn!("Sending response to get client window failed ({:?}).", e); + CompositorMsg::GetClientWindowRect(webview_id, response_sender) => { + let client_window_rect = self + .webview_renderers + .get(webview_id) + .map(|webview_renderer| { + webview_renderer.client_window_rect(self.rendering_context.size2d()) + }) + .unwrap_or_default(); + if let Err(error) = response_sender.send(client_window_rect) { + warn!("Sending response to get client window failed ({error:?})."); } }, - CrossProcessCompositorMessage::GetScreenSize(req) => { - if let Err(e) = req.send(self.embedder_coordinates.screen_size) { - warn!("Sending response to get screen size failed ({:?}).", e); + CompositorMsg::GetScreenSize(webview_id, response_sender) => { + let screen_size = self + .webview_renderers + .get(webview_id) + .map(WebViewRenderer::screen_size) + .unwrap_or_default(); + if let Err(error) = response_sender.send(screen_size) { + warn!("Sending response to get screen size failed ({error:?})."); } }, - CrossProcessCompositorMessage::GetAvailableScreenSize(req) => { - if let Err(e) = req.send(self.embedder_coordinates.available_screen_size) { - warn!( - "Sending response to get screen avail size failed ({:?}).", - e - ); + CompositorMsg::GetAvailableScreenSize(webview_id, response_sender) => { + let available_screen_size = self + .webview_renderers + .get(webview_id) + .map(WebViewRenderer::available_screen_size) + .unwrap_or_default(); + if let Err(error) = response_sender.send(available_screen_size) { + warn!("Sending response to get screen size failed ({error:?})."); } }, } @@ -939,21 +945,19 @@ impl IOCompositor { "Compositor got pipeline exited: {:?} {:?}", webview_id, pipeline_id ); - if let Some(webview) = self.webviews.get_mut(webview_id) { - webview.remove_pipeline(pipeline_id); + if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) { + webview_renderer.remove_pipeline(pipeline_id); } let _ = sender.send(()); }, - CompositorMsg::CrossProcess(CrossProcessCompositorMessage::GenerateImageKey( - sender, - )) => { + CompositorMsg::GenerateImageKey(sender) => { let _ = sender.send(self.global.borrow().webrender_api.generate_image_key()); }, - CompositorMsg::CrossProcess(CrossProcessCompositorMessage::GenerateFontKeys( + CompositorMsg::GenerateFontKeys( number_of_font_keys, number_of_font_instance_keys, result_sender, - )) => { + ) => { let font_keys = (0..number_of_font_keys) .map(|_| self.global.borrow().webrender_api.generate_font_key()) .collect(); @@ -967,26 +971,19 @@ impl IOCompositor { .collect(); let _ = result_sender.send((font_keys, font_instance_keys)); }, - CompositorMsg::CrossProcess(CrossProcessCompositorMessage::GetClientWindowRect( - req, - )) => { - if let Err(e) = req.send(self.embedder_coordinates.window_rect) { - warn!("Sending response to get client window failed ({:?}).", e); + CompositorMsg::GetClientWindowRect(_, response_sender) => { + if let Err(error) = response_sender.send(Default::default()) { + warn!("Sending response to get client window failed ({error:?})."); } }, - CompositorMsg::CrossProcess(CrossProcessCompositorMessage::GetScreenSize(req)) => { - if let Err(e) = req.send(self.embedder_coordinates.screen_size) { - warn!("Sending response to get screen size failed ({:?}).", e); + CompositorMsg::GetScreenSize(_, response_sender) => { + if let Err(error) = response_sender.send(Default::default()) { + warn!("Sending response to get client window failed ({error:?})."); } }, - CompositorMsg::CrossProcess(CrossProcessCompositorMessage::GetAvailableScreenSize( - req, - )) => { - if let Err(e) = req.send(self.embedder_coordinates.available_screen_size) { - warn!( - "Sending response to get screen avail size failed ({:?}).", - e - ); + CompositorMsg::GetAvailableScreenSize(_, response_sender) => { + if let Err(error) = response_sender.send(Default::default()) { + warn!("Sending response to get client window failed ({error:?})."); } }, CompositorMsg::NewWebRenderFrameReady(..) => { @@ -1031,43 +1028,50 @@ impl IOCompositor { let mut builder = webrender_api::DisplayListBuilder::new(root_pipeline); builder.begin(); - let zoom_factor = self.device_pixels_per_page_pixel().0; - let zoom_reference_frame = builder.push_reference_frame( + let root_reference_frame = SpatialId::root_reference_frame(root_pipeline); + + let viewport_size = self.rendering_context.size2d().to_f32().to_untyped(); + let viewport_rect = LayoutRect::from_origin_and_size( LayoutPoint::zero(), - SpatialId::root_reference_frame(root_pipeline), - TransformStyle::Flat, - PropertyBinding::Value(Transform3D::scale(zoom_factor, zoom_factor, 1.)), - ReferenceFrameKind::Transform { - is_2d_scale_translation: true, - should_snap: true, - paired_with_perspective: false, - }, - SpatialTreeItemKey::new(0, 0), + LayoutSize::from_untyped(viewport_size), ); - let scaled_viewport_size = - self.rendering_context.size2d().to_f32().to_untyped() / zoom_factor; - let scaled_viewport_rect = LayoutRect::from_origin_and_size( - LayoutPoint::zero(), - LayoutSize::from_untyped(scaled_viewport_size), - ); - - let root_clip_id = builder.define_clip_rect(zoom_reference_frame, scaled_viewport_rect); + let root_clip_id = builder.define_clip_rect(root_reference_frame, viewport_rect); let clip_chain_id = builder.define_clip_chain(None, [root_clip_id]); - for (_, webview) in self.webviews.painting_order() { - if let Some(pipeline_id) = webview.root_pipeline_id { - let scaled_webview_rect = webview.rect / zoom_factor; - builder.push_iframe( - LayoutRect::from_untyped(&scaled_webview_rect.to_untyped()), - LayoutRect::from_untyped(&scaled_webview_rect.to_untyped()), - &SpaceAndClipInfo { - spatial_id: zoom_reference_frame, - clip_chain_id, - }, - pipeline_id.into(), - true, - ); - } + for (_, webview_renderer) in self.webview_renderers.painting_order() { + let Some(pipeline_id) = webview_renderer.root_pipeline_id else { + continue; + }; + + let device_pixels_per_page_pixel = webview_renderer.device_pixels_per_page_pixel().0; + let webview_reference_frame = builder.push_reference_frame( + LayoutPoint::zero(), + root_reference_frame, + TransformStyle::Flat, + PropertyBinding::Value(Transform3D::scale( + device_pixels_per_page_pixel, + device_pixels_per_page_pixel, + 1., + )), + ReferenceFrameKind::Transform { + is_2d_scale_translation: true, + should_snap: true, + paired_with_perspective: false, + }, + SpatialTreeItemKey::new(0, 0), + ); + + let scaled_webview_rect = webview_renderer.rect / device_pixels_per_page_pixel; + builder.push_iframe( + LayoutRect::from_untyped(&scaled_webview_rect.to_untyped()), + LayoutRect::from_untyped(&scaled_webview_rect.to_untyped()), + &SpaceAndClipInfo { + spatial_id: webview_reference_frame, + clip_chain_id, + }, + pipeline_id.into(), + true, + ); } let built_display_list = builder.end(); @@ -1088,8 +1092,8 @@ impl IOCompositor { /// TODO(mrobinson): Could we only send offsets for the branch being modified /// and not the entire scene? fn update_transaction_with_all_scroll_offsets(&self, transaction: &mut Transaction) { - for webview in self.webviews.iter() { - for details in webview.pipelines.values() { + for webview_renderer in self.webview_renderers.iter() { + for details in webview_renderer.pipelines.values() { for node in details.scroll_tree.nodes.iter() { let (Some(offset), Some(external_id)) = (node.offset(), node.external_id()) else { @@ -1109,33 +1113,38 @@ impl IOCompositor { } } - pub fn add_webview(&mut self, webview_id: WebViewId) { - let size = self.rendering_context.size2d().to_f32(); - self.webviews.entry(webview_id).or_insert(WebView::new( - webview_id, - Box2D::from_origin_and_size(Point2D::origin(), size), - self.global.clone(), - )); + pub fn add_webview( + &mut self, + webview: Box, + viewport_details: ViewportDetails, + ) { + self.webview_renderers + .entry(webview.id()) + .or_insert(WebViewRenderer::new( + self.global.clone(), + webview, + viewport_details, + )); } fn set_frame_tree_for_webview(&mut self, frame_tree: &SendableFrameTree) { debug!("{}: Setting frame tree for webview", frame_tree.pipeline.id); let webview_id = frame_tree.pipeline.webview_id; - let Some(webview) = self.webviews.get_mut(webview_id) else { + let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else { warn!( "Attempted to set frame tree on unknown WebView (perhaps closed?): {webview_id:?}" ); return; }; - webview.set_frame_tree(frame_tree); + webview_renderer.set_frame_tree(frame_tree); self.send_root_pipeline_display_list(); } fn remove_webview(&mut self, webview_id: WebViewId) { debug!("{}: Removing", webview_id); - if self.webviews.remove(webview_id).is_err() { + if self.webview_renderers.remove(webview_id).is_err() { warn!("{webview_id}: Removing unknown webview"); return; }; @@ -1143,31 +1152,6 @@ impl IOCompositor { self.send_root_pipeline_display_list(); } - pub fn move_resize_webview(&mut self, webview_id: WebViewId, rect: DeviceRect) { - debug!("{webview_id}: Moving and/or resizing webview; rect={rect:?}"); - let rect_changed; - let size_changed; - match self.webviews.get_mut(webview_id) { - Some(webview) => { - rect_changed = rect != webview.rect; - size_changed = rect.size() != webview.rect.size(); - webview.rect = rect; - }, - None => { - warn!("{webview_id}: MoveResizeWebView on unknown webview id"); - return; - }, - }; - - if rect_changed { - if size_changed { - self.send_window_size_message_for_top_level_browser_context(rect, webview_id); - } - - self.send_root_pipeline_display_list(); - } - } - pub fn show_webview( &mut self, webview_id: WebViewId, @@ -1176,15 +1160,15 @@ impl IOCompositor { debug!("{webview_id}: Showing webview; hide_others={hide_others}"); let painting_order_changed = if hide_others { let result = self - .webviews + .webview_renderers .painting_order() .map(|(&id, _)| id) .ne(once(webview_id)); - self.webviews.hide_all(); - self.webviews.show(webview_id)?; + self.webview_renderers.hide_all(); + self.webview_renderers.show(webview_id)?; result } else { - self.webviews.show(webview_id)? + self.webview_renderers.show(webview_id)? }; if painting_order_changed { self.send_root_pipeline_display_list(); @@ -1194,7 +1178,7 @@ impl IOCompositor { pub fn hide_webview(&mut self, webview_id: WebViewId) -> Result<(), UnknownWebView> { debug!("{webview_id}: Hiding webview"); - if self.webviews.hide(webview_id)? { + if self.webview_renderers.hide(webview_id)? { self.send_root_pipeline_display_list(); } Ok(()) @@ -1208,15 +1192,15 @@ impl IOCompositor { debug!("{webview_id}: Raising webview to top; hide_others={hide_others}"); let painting_order_changed = if hide_others { let result = self - .webviews + .webview_renderers .painting_order() .map(|(&id, _)| id) .ne(once(webview_id)); - self.webviews.hide_all(); - self.webviews.raise_to_top(webview_id)?; + self.webview_renderers.hide_all(); + self.webview_renderers.raise_to_top(webview_id)?; result } else { - self.webviews.raise_to_top(webview_id)? + self.webview_renderers.raise_to_top(webview_id)? }; if painting_order_changed { self.send_root_pipeline_display_list(); @@ -1224,43 +1208,46 @@ impl IOCompositor { Ok(()) } - fn send_window_size_message_for_top_level_browser_context( - &self, - rect: DeviceRect, - webview_id: WebViewId, - ) { - // The device pixel ratio used by the style system should include the scale from page pixels - // to device pixels, but not including any pinch zoom. - let device_pixel_ratio = self.device_pixels_per_page_pixel_not_including_page_zoom(); - let initial_viewport = rect.size().to_f32() / device_pixel_ratio; - let msg = ConstellationMsg::WindowSize( - webview_id, - WindowSizeData { - device_pixel_ratio, - initial_viewport, - }, - WindowSizeType::Resize, - ); - if let Err(e) = self.global.borrow().constellation_sender.send(msg) { - warn!("Sending window resize to constellation failed ({:?}).", e); - } - } - - pub fn on_embedder_window_moved(&mut self) { - self.embedder_coordinates = self.window.get_coordinates(); - } - - pub fn resize_rendering_context(&mut self, new_size: PhysicalSize) -> bool { + pub fn move_resize_webview(&mut self, webview_id: WebViewId, rect: DeviceRect) { if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown { - return false; + return; + } + let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else { + return; + }; + if !webview_renderer.set_rect(rect) { + return; } - let old_hidpi_factor = self.embedder_coordinates.hidpi_factor; - self.embedder_coordinates = self.window.get_coordinates(); - if self.embedder_coordinates.hidpi_factor == old_hidpi_factor && - self.rendering_context.size() == new_size - { - return false; + self.send_root_pipeline_display_list(); + self.set_needs_repaint(RepaintReason::Resize); + } + + pub fn set_hidpi_scale_factor( + &mut self, + webview_id: WebViewId, + new_scale_factor: Scale, + ) { + if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown { + return; + } + let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else { + return; + }; + if !webview_renderer.set_hidpi_scale_factor(new_scale_factor) { + return; + } + + self.send_root_pipeline_display_list(); + self.set_needs_repaint(RepaintReason::Resize); + } + + pub fn resize_rendering_context(&mut self, new_size: PhysicalSize) { + if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown { + return; + } + if self.rendering_context.size() == new_size { + return; } self.rendering_context.resize(new_size); @@ -1273,9 +1260,8 @@ impl IOCompositor { transaction.set_document_view(output_region); self.global.borrow_mut().send_transaction(transaction); - self.update_after_zoom_or_hidpi_change(); + self.send_root_pipeline_display_list(); self.set_needs_repaint(RepaintReason::Resize); - true } /// If there are any animations running, dispatches appropriate messages to the constellation. @@ -1291,64 +1277,45 @@ impl IOCompositor { } self.last_animation_tick = Instant::now(); - #[cfg(feature = "webxr")] - let webxr_running = self.global.borrow().webxr_main_thread.running(); - #[cfg(not(feature = "webxr"))] - let webxr_running = false; - - let any_webviews_animating = !self - .webviews + let animating_webviews: Vec<_> = self + .webview_renderers .iter() - .all(|webview| !webview.tick_all_animations(self)); - - let animation_state = if !any_webviews_animating && !webxr_running { - windowing::AnimationState::Idle - } else { - windowing::AnimationState::Animating - }; - - self.window.set_animation_state(animation_state); + .filter_map(|webview_renderer| { + if webview_renderer.animating() { + Some(webview_renderer.id) + } else { + None + } + }) + .collect(); + if !animating_webviews.is_empty() { + if let Err(error) = self.global.borrow().constellation_sender.send( + EmbedderToConstellationMessage::TickAnimation(animating_webviews), + ) { + warn!("Sending tick to constellation failed ({error:?})."); + } + } } - fn hidpi_factor(&self) -> Scale { - self.embedder_coordinates.hidpi_factor - } - - pub(crate) fn device_pixels_per_page_pixel(&self) -> Scale { - self.device_pixels_per_page_pixel_not_including_page_zoom() * self.pinch_zoom_level() - } - - fn device_pixels_per_page_pixel_not_including_page_zoom( - &self, - ) -> Scale { - self.page_zoom * self.hidpi_factor() - } - - pub fn on_zoom_reset_window_event(&mut self) { + pub fn on_zoom_reset_window_event(&mut self, webview_id: WebViewId) { if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown { return; } - self.page_zoom = Scale::new(1.0); - self.update_after_zoom_or_hidpi_change(); + if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) { + webview_renderer.set_page_zoom(1.0); + } + self.send_root_pipeline_display_list(); } - pub fn on_zoom_window_event(&mut self, magnification: f32) { + pub fn on_zoom_window_event(&mut self, webview_id: WebViewId, magnification: f32) { if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown { return; } - self.page_zoom = - Scale::new((self.page_zoom.get() * magnification).clamp(MIN_ZOOM, MAX_ZOOM)); - self.update_after_zoom_or_hidpi_change(); - } - - fn update_after_zoom_or_hidpi_change(&mut self) { - for (webview_id, webview) in self.webviews.painting_order() { - self.send_window_size_message_for_top_level_browser_context(webview.rect, *webview_id); + if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) { + webview_renderer.set_page_zoom(magnification); } - - // Update the root transform in WebRender to reflect the new zoom. self.send_root_pipeline_display_list(); } @@ -1359,21 +1326,24 @@ impl IOCompositor { .pipeline_to_webview_map .get(&pipeline_id) .cloned()?; - self.webviews.get(webview_id)?.pipelines.get(&pipeline_id) + self.webview_renderers + .get(webview_id)? + .pipelines + .get(&pipeline_id) } // Check if any pipelines currently have active animations or animation callbacks. fn animations_or_animation_callbacks_running(&self) -> bool { - self.webviews + self.webview_renderers .iter() - .any(WebView::animations_or_animation_callbacks_running) + .any(WebViewRenderer::animations_or_animation_callbacks_running) } /// Returns true if any animation callbacks (ie `requestAnimationFrame`) are waiting for a response. fn animation_callbacks_running(&self) -> bool { - self.webviews + self.webview_renderers .iter() - .any(WebView::animation_callbacks_running) + .any(WebViewRenderer::animation_callbacks_running) } /// Query the constellation to see if the current compositor @@ -1389,7 +1359,11 @@ impl IOCompositor { // This gets sent to the constellation for comparison with the current // frame tree. let mut pipeline_epochs = HashMap::new(); - for id in self.webviews.iter().flat_map(WebView::pipeline_ids) { + for id in self + .webview_renderers + .iter() + .flat_map(WebViewRenderer::pipeline_ids) + { if let Some(WebRenderEpoch(epoch)) = self .webrender .as_ref() @@ -1402,7 +1376,7 @@ impl IOCompositor { // Pass the pipeline/epoch states to the constellation and check // if it's safe to output the image. - let msg = ConstellationMsg::IsReadyToSaveImage(pipeline_epochs); + let msg = EmbedderToConstellationMessage::IsReadyToSaveImage(pipeline_epochs); if let Err(e) = self.global.borrow().constellation_sender.send(msg) { warn!("Sending ready to save to constellation failed ({:?}).", e); } @@ -1448,13 +1422,19 @@ impl IOCompositor { /// [`IOCompositor`]. If succesful return the output image in shared memory. fn render_to_shared_memory( &mut self, + webview_id: WebViewId, page_rect: Option>, ) -> Result, UnableToComposite> { self.render_inner()?; let size = self.rendering_context.size2d().to_i32(); let rect = if let Some(rect) = page_rect { - let rect = self.device_pixels_per_page_pixel().transform_rect(&rect); + let scale = self + .webview_renderers + .get(webview_id) + .map(WebViewRenderer::device_pixels_per_page_pixel) + .unwrap_or_else(|| Scale::new(1.0)); + let rect = scale.transform_rect(&rect); let x = rect.origin.x as i32; // We need to convert to the bottom-left origin coordinate @@ -1549,8 +1529,8 @@ impl IOCompositor { fn send_pending_paint_metrics_messages_after_composite(&mut self) { let paint_time = CrossProcessInstant::now(); let document_id = self.webrender_document(); - for webview_details in self.webviews.iter_mut() { - for (pipeline_id, pipeline) in webview_details.pipelines.iter_mut() { + for webview_renderer in self.webview_renderers.iter_mut() { + for (pipeline_id, pipeline) in webview_renderer.pipelines.iter_mut() { let Some(current_epoch) = self .webrender .as_ref() @@ -1567,7 +1547,7 @@ impl IOCompositor { PaintMetricState::Seen(epoch, first_reflow) if epoch <= current_epoch => { assert!(epoch <= current_epoch); if let Err(error) = self.global.borrow().constellation_sender.send( - ConstellationMsg::PaintMetric( + EmbedderToConstellationMessage::PaintMetric( *pipeline_id, PaintMetricEvent::FirstPaint(paint_time, first_reflow), ), @@ -1584,7 +1564,7 @@ impl IOCompositor { match pipeline.first_contentful_paint_metric { PaintMetricState::Seen(epoch, first_reflow) if epoch <= current_epoch => { if let Err(error) = self.global.borrow().constellation_sender.send( - ConstellationMsg::PaintMetric( + EmbedderToConstellationMessage::PaintMetric( *pipeline_id, PaintMetricEvent::FirstContentfulPaint(paint_time, first_reflow), ), @@ -1648,12 +1628,7 @@ impl IOCompositor { // Check for new messages coming from the other threads in the system. let mut compositor_messages = vec![]; let mut found_recomposite_msg = false; - while let Some(msg) = self - .global - .borrow_mut() - .compositor_receiver - .try_recv_compositor_msg() - { + while let Ok(msg) = self.global.borrow_mut().compositor_receiver.try_recv() { match msg { CompositorMsg::NewWebRenderFrameReady(..) if found_recomposite_msg => { // Only take one of duplicate NewWebRendeFrameReady messages, but do subtract @@ -1685,15 +1660,6 @@ impl IOCompositor { return false; } - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_secs() as f64; - // If a pinch-zoom happened recently, ask for tiles at the new resolution - if self.zoom_action && now - self.zoom_time > 0.3 { - self.zoom_action = false; - } - #[cfg(feature = "webxr")] // Run the WebXR main thread self.global.borrow_mut().webxr_main_thread.run_one_frame(); @@ -1702,30 +1668,14 @@ impl IOCompositor { if let Err(err) = self.rendering_context.make_current() { warn!("Failed to make the rendering context current: {:?}", err); } - let mut webviews = take(&mut self.webviews); - for webview in webviews.iter_mut() { - webview.process_pending_scroll_events(self); + let mut webview_renderers = take(&mut self.webview_renderers); + for webview_renderer in webview_renderers.iter_mut() { + webview_renderer.process_pending_scroll_events(self); } - self.webviews = webviews; + self.webview_renderers = webview_renderers; self.global.borrow().shutdown_state() != ShutdownState::FinishedShuttingDown } - pub fn pinch_zoom_level(&self) -> Scale { - Scale::new(self.viewport_zoom.get()) - } - - pub(crate) fn set_pinch_zoom_level(&mut self, mut zoom: f32) -> bool { - if let Some(min) = self.min_viewport_zoom { - zoom = f32::max(min.get(), zoom); - } - if let Some(max) = self.max_viewport_zoom { - zoom = f32::min(max.get(), zoom); - } - - let old_zoom = std::mem::replace(&mut self.viewport_zoom, PinchZoomFactor::new(zoom)); - old_zoom != self.viewport_zoom - } - pub fn toggle_webrender_debug(&mut self, option: WebRenderDebugOption) { let Some(webrender) = self.webrender.as_mut() else { return; @@ -1773,13 +1723,6 @@ impl IOCompositor { .borrow() .webrender_api .save_capture(capture_path.clone(), CaptureBits::all()); - - let version_file_path = capture_path.join("servo-version.txt"); - if let Err(error) = File::create(version_file_path) - .and_then(|mut file| write!(file, "{}", self.global.borrow().version_string)) - { - eprintln!("Unable to write servo version for WebRender Capture: {error:?}"); - } } fn add_font_instance( @@ -1814,8 +1757,8 @@ impl IOCompositor { } pub fn notify_input_event(&mut self, webview_id: WebViewId, event: InputEvent) { - if let Some(webview) = self.webviews.get_mut(webview_id) { - webview.notify_input_event(event); + if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) { + webview_renderer.notify_input_event(event); } } @@ -1826,20 +1769,20 @@ impl IOCompositor { cursor: DeviceIntPoint, event_type: TouchEventType, ) { - if let Some(webview) = self.webviews.get_mut(webview_id) { - webview.notify_scroll_event(scroll_location, cursor, event_type); + if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) { + webview_renderer.notify_scroll_event(scroll_location, cursor, event_type); } } pub fn on_vsync(&mut self, webview_id: WebViewId) { - if let Some(webview) = self.webviews.get_mut(webview_id) { - webview.on_vsync(); + if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) { + webview_renderer.on_vsync(); } } pub fn set_pinch_zoom(&mut self, webview_id: WebViewId, magnification: f32) { - if let Some(webview) = self.webviews.get_mut(webview_id) { - webview.set_pinch_zoom(magnification); + if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) { + webview_renderer.set_pinch_zoom(magnification); } } diff --git a/components/compositing/lib.rs b/components/compositing/lib.rs index 683677d437d..a66c61499e5 100644 --- a/components/compositing/lib.rs +++ b/components/compositing/lib.rs @@ -7,33 +7,33 @@ use std::cell::Cell; use std::rc::Rc; -use compositing_traits::{CompositorProxy, CompositorReceiver}; -use constellation_traits::ConstellationMsg; -use crossbeam_channel::Sender; +use compositing_traits::rendering_context::RenderingContext; +use compositing_traits::{CompositorMsg, CompositorProxy}; +use constellation_traits::EmbedderToConstellationMessage; +use crossbeam_channel::{Receiver, Sender}; use embedder_traits::ShutdownState; use profile_traits::{mem, time}; use webrender::RenderApi; use webrender_api::DocumentId; -use webrender_traits::rendering_context::RenderingContext; -pub use crate::compositor::IOCompositor; +pub use crate::compositor::{IOCompositor, WebRenderDebugOption}; #[macro_use] mod tracing; mod compositor; mod touch; -pub mod webview; -pub mod windowing; +pub mod webview_manager; +pub mod webview_renderer; /// Data used to construct a compositor. pub struct InitialCompositorState { /// A channel to the compositor. pub sender: CompositorProxy, /// A port on which messages inbound to the compositor can be received. - pub receiver: CompositorReceiver, + pub receiver: Receiver, /// A channel to the constellation. - pub constellation_chan: Sender, + pub constellation_chan: Sender, /// A channel to the time profiler thread. pub time_profiler_chan: time::ProfilerChan, /// A channel to the memory profiler thread. diff --git a/components/compositing/touch.rs b/components/compositing/touch.rs index 9975630951b..76d87732b32 100644 --- a/components/compositing/touch.rs +++ b/components/compositing/touch.rs @@ -282,6 +282,10 @@ impl TouchHandler { debug_assert!(old.is_some(), "Sequence already removed?"); } + pub fn try_get_current_touch_sequence(&self) -> Option<&TouchSequenceInfo> { + self.touch_sequence_map.get(&self.current_sequence_id) + } + pub fn get_current_touch_sequence_mut(&mut self) -> &mut TouchSequenceInfo { self.touch_sequence_map .get_mut(&self.current_sequence_id) @@ -329,10 +333,7 @@ impl TouchHandler { .active_touch_points .push(TouchPoint::new(id, point)); match touch_sequence.active_touch_points.len() { - 2 => { - touch_sequence.state = Pinching; - }, - 3.. => { + 2.. => { touch_sequence.state = MultiTouch; }, 0..2 => { @@ -381,7 +382,8 @@ impl TouchHandler { { Some(i) => i, None => { - unreachable!("Got a touchmove event for a non-active touch point"); + error!("Got a touchmove event for a non-active touch point"); + return TouchMoveAction::NoAction; }, }; let old_point = touch_sequence.active_touch_points[idx].point; diff --git a/components/compositing/tracing.rs b/components/compositing/tracing.rs index 907c931aebe..a8bb8b42bb8 100644 --- a/components/compositing/tracing.rs +++ b/components/compositing/tracing.rs @@ -42,7 +42,23 @@ mod from_constellation { Self::LoadComplete(..) => target!("LoadComplete"), Self::WebDriverMouseButtonEvent(..) => target!("WebDriverMouseButtonEvent"), Self::WebDriverMouseMoveEvent(..) => target!("WebDriverMouseMoveEvent"), - Self::CrossProcess(_) => target!("CrossProcess"), + Self::WebDriverWheelScrollEvent(..) => target!("WebDriverWheelScrollEvent"), + Self::SendInitialTransaction(..) => target!("SendInitialTransaction"), + Self::SendScrollNode(..) => target!("SendScrollNode"), + Self::SendDisplayList { .. } => target!("SendDisplayList"), + Self::HitTest(..) => target!("HitTest"), + Self::GenerateImageKey(..) => target!("GenerateImageKey"), + Self::AddImage(..) => target!("AddImage"), + Self::UpdateImages(..) => target!("UpdateImages"), + Self::GenerateFontKeys(..) => target!("GenerateFontKeys"), + Self::AddFont(..) => target!("AddFont"), + Self::AddSystemFont(..) => target!("AddSystemFont"), + Self::AddFontInstance(..) => target!("AddFontInstance"), + Self::RemoveFonts(..) => target!("RemoveFonts"), + Self::GetClientWindowRect(..) => target!("GetClientWindowRect"), + Self::GetScreenSize(..) => target!("GetScreenSize"), + Self::GetAvailableScreenSize(..) => target!("GetAvailableScreenSize"), + Self::CollectMemoryReport(..) => target!("CollectMemoryReport"), } } } diff --git a/components/compositing/webview.rs b/components/compositing/webview.rs deleted file mode 100644 index 17402f7f988..00000000000 --- a/components/compositing/webview.rs +++ /dev/null @@ -1,1071 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -use std::cell::RefCell; -use std::collections::HashMap; -use std::collections::hash_map::{Entry, Keys, Values, ValuesMut}; -use std::rc::Rc; - -use base::id::{PipelineId, WebViewId}; -use compositing_traits::SendableFrameTree; -use constellation_traits::{CompositorHitTestResult, ConstellationMsg, ScrollState}; -use embedder_traits::{ - InputEvent, MouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent, ShutdownState, - TouchEvent, TouchEventType, TouchId, -}; -use euclid::{Point2D, Scale, Vector2D}; -use fnv::FnvHashSet; -use log::{debug, warn}; -use script_traits::{AnimationState, TouchEventResult}; -use webrender::Transaction; -use webrender_api::units::{DeviceIntPoint, DevicePoint, DeviceRect, LayoutVector2D}; -use webrender_api::{ - ExternalScrollId, HitTestFlags, RenderReasons, SampledScrollOffset, ScrollLocation, -}; - -use crate::IOCompositor; -use crate::compositor::{PipelineDetails, ServoRenderer}; -use crate::touch::{TouchHandler, TouchMoveAction, TouchMoveAllowed, TouchSequenceState}; - -#[derive(Clone, Copy)] -struct ScrollEvent { - /// Scroll by this offset, or to Start or End - scroll_location: ScrollLocation, - /// Apply changes to the frame at this location - cursor: DeviceIntPoint, - /// The number of OS events that have been coalesced together into this one event. - event_count: u32, -} - -#[derive(Clone, Copy)] -enum ScrollZoomEvent { - /// An pinch zoom event that magnifies the view by the given factor. - PinchZoom(f32), - /// A scroll event that scrolls the scroll node at the given location by the - /// given amount. - Scroll(ScrollEvent), -} - -pub(crate) struct WebView { - /// The [`WebViewId`] of the `WebView` associated with this [`WebViewDetails`]. - pub id: WebViewId, - /// The root [`PipelineId`] of the currently displayed page in this WebView. - pub root_pipeline_id: Option, - pub rect: DeviceRect, - /// Tracks details about each active pipeline that the compositor knows about. - pub pipelines: HashMap, - /// Data that is shared by all WebView renderers. - pub(crate) global: Rc>, - /// Pending scroll/zoom events. - pending_scroll_zoom_events: Vec, - /// Touch input state machine - touch_handler: TouchHandler, -} - -impl Drop for WebView { - fn drop(&mut self) { - self.global - .borrow_mut() - .pipeline_to_webview_map - .retain(|_, webview_id| self.id != *webview_id); - } -} - -impl WebView { - pub(crate) fn new(id: WebViewId, rect: DeviceRect, global: Rc>) -> Self { - Self { - id, - root_pipeline_id: None, - rect, - pipelines: Default::default(), - touch_handler: TouchHandler::new(), - global, - pending_scroll_zoom_events: Default::default(), - } - } - - pub(crate) fn animations_or_animation_callbacks_running(&self) -> bool { - self.pipelines - .values() - .any(PipelineDetails::animations_or_animation_callbacks_running) - } - - pub(crate) fn animation_callbacks_running(&self) -> bool { - self.pipelines - .values() - .any(PipelineDetails::animation_callbacks_running) - } - - pub(crate) fn pipeline_ids(&self) -> Keys<'_, PipelineId, PipelineDetails> { - self.pipelines.keys() - } - - /// Returns the [`PipelineDetails`] for the given [`PipelineId`], creating it if needed. - pub(crate) fn ensure_pipeline_details( - &mut self, - pipeline_id: PipelineId, - ) -> &mut PipelineDetails { - self.pipelines.entry(pipeline_id).or_insert_with(|| { - self.global - .borrow_mut() - .pipeline_to_webview_map - .insert(pipeline_id, self.id); - PipelineDetails::new(pipeline_id) - }) - } - - pub(crate) fn set_throttled(&mut self, pipeline_id: PipelineId, throttled: bool) { - self.ensure_pipeline_details(pipeline_id).throttled = throttled; - } - - pub(crate) fn remove_pipeline(&mut self, pipeline_id: PipelineId) { - self.global - .borrow_mut() - .pipeline_to_webview_map - .remove(&pipeline_id); - self.pipelines.remove(&pipeline_id); - } - - pub(crate) fn set_frame_tree(&mut self, frame_tree: &SendableFrameTree) { - let pipeline_id = frame_tree.pipeline.id; - let old_pipeline_id = std::mem::replace(&mut self.root_pipeline_id, Some(pipeline_id)); - - if old_pipeline_id != self.root_pipeline_id { - debug!( - "Updating webview ({:?}) from pipeline {:?} to {:?}", - 3, old_pipeline_id, self.root_pipeline_id - ); - } - - self.set_frame_tree_on_pipeline_details(frame_tree, None); - self.reset_scroll_tree_for_unattached_pipelines(frame_tree); - self.send_scroll_positions_to_layout_for_pipeline(pipeline_id); - } - - pub(crate) fn send_scroll_positions_to_layout_for_pipeline(&self, pipeline_id: PipelineId) { - let Some(details) = self.pipelines.get(&pipeline_id) else { - return; - }; - - let mut scroll_states = Vec::new(); - details.scroll_tree.nodes.iter().for_each(|node| { - if let (Some(scroll_id), Some(scroll_offset)) = (node.external_id(), node.offset()) { - scroll_states.push(ScrollState { - scroll_id, - scroll_offset, - }); - } - }); - - let _ = self - .global - .borrow() - .constellation_sender - .send(ConstellationMsg::SetScrollStates( - pipeline_id, - scroll_states, - )); - } - - pub(crate) fn set_frame_tree_on_pipeline_details( - &mut self, - frame_tree: &SendableFrameTree, - parent_pipeline_id: Option, - ) { - let pipeline_id = frame_tree.pipeline.id; - let pipeline_details = self.ensure_pipeline_details(pipeline_id); - pipeline_details.pipeline = Some(frame_tree.pipeline.clone()); - pipeline_details.parent_pipeline_id = parent_pipeline_id; - - for kid in &frame_tree.children { - self.set_frame_tree_on_pipeline_details(kid, Some(pipeline_id)); - } - } - - pub(crate) fn reset_scroll_tree_for_unattached_pipelines( - &mut self, - frame_tree: &SendableFrameTree, - ) { - // TODO(mrobinson): Eventually this can selectively preserve the scroll trees - // state for some unattached pipelines in order to preserve scroll position when - // navigating backward and forward. - fn collect_pipelines( - pipelines: &mut FnvHashSet, - frame_tree: &SendableFrameTree, - ) { - pipelines.insert(frame_tree.pipeline.id); - for kid in &frame_tree.children { - collect_pipelines(pipelines, kid); - } - } - - let mut attached_pipelines: FnvHashSet = FnvHashSet::default(); - collect_pipelines(&mut attached_pipelines, frame_tree); - - self.pipelines - .iter_mut() - .filter(|(id, _)| !attached_pipelines.contains(id)) - .for_each(|(_, details)| { - details.scroll_tree.nodes.iter_mut().for_each(|node| { - node.set_offset(LayoutVector2D::zero()); - }) - }) - } - - /// Sets or unsets the animations-running flag for the given pipeline, and schedules a - /// recomposite if necessary. Returns true if the pipeline is throttled. - pub(crate) fn change_running_animations_state( - &mut self, - pipeline_id: PipelineId, - animation_state: AnimationState, - ) -> bool { - let pipeline_details = self.ensure_pipeline_details(pipeline_id); - match animation_state { - AnimationState::AnimationsPresent => { - pipeline_details.animations_running = true; - }, - AnimationState::AnimationCallbacksPresent => { - pipeline_details.animation_callbacks_running = true; - }, - AnimationState::NoAnimationsPresent => { - pipeline_details.animations_running = false; - }, - AnimationState::NoAnimationCallbacksPresent => { - pipeline_details.animation_callbacks_running = false; - }, - } - pipeline_details.throttled - } - - pub(crate) fn tick_all_animations(&self, compositor: &IOCompositor) -> bool { - let mut ticked_any = false; - for pipeline_details in self.pipelines.values() { - ticked_any = pipeline_details.tick_animations(compositor) || ticked_any; - } - ticked_any - } - - pub(crate) fn tick_animations_for_pipeline( - &self, - pipeline_id: PipelineId, - compositor: &IOCompositor, - ) { - if let Some(pipeline_details) = self.pipelines.get(&pipeline_id) { - pipeline_details.tick_animations(compositor); - } - } - - /// On a Window refresh tick (e.g. vsync) - pub fn on_vsync(&mut self) { - if let Some(fling_action) = self.touch_handler.on_vsync() { - self.on_scroll_window_event( - ScrollLocation::Delta(fling_action.delta), - fling_action.cursor, - ); - } - } - - pub(crate) fn dispatch_input_event(&mut self, event: InputEvent) { - // Events that do not need to do hit testing are sent directly to the - // constellation to filter down. - let Some(point) = event.point() else { - return; - }; - - // If we can't find a pipeline to send this event to, we cannot continue. - let get_pipeline_details = |pipeline_id| self.pipelines.get(&pipeline_id); - let Some(result) = self - .global - .borrow() - .hit_test_at_point(point, get_pipeline_details) - else { - return; - }; - - self.global.borrow_mut().update_cursor(point, &result); - - if let Err(error) = - self.global - .borrow() - .constellation_sender - .send(ConstellationMsg::ForwardInputEvent( - self.id, - event, - Some(result), - )) - { - warn!("Sending event to constellation failed ({error:?})."); - } - } - - pub fn notify_input_event(&mut self, event: InputEvent) { - if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown { - return; - } - - if let InputEvent::Touch(event) = event { - self.on_touch_event(event); - return; - } - - if self.global.borrow().convert_mouse_to_touch { - match event { - InputEvent::MouseButton(event) => { - match event.action { - MouseButtonAction::Click => {}, - MouseButtonAction::Down => self.on_touch_down(TouchEvent::new( - TouchEventType::Down, - TouchId(0), - event.point, - )), - MouseButtonAction::Up => self.on_touch_up(TouchEvent::new( - TouchEventType::Up, - TouchId(0), - event.point, - )), - } - return; - }, - InputEvent::MouseMove(event) => { - self.on_touch_move(TouchEvent::new( - TouchEventType::Move, - TouchId(0), - event.point, - )); - return; - }, - _ => {}, - } - } - - self.dispatch_input_event(event); - } - - fn send_touch_event(&self, mut event: TouchEvent) -> bool { - let get_pipeline_details = |pipeline_id| self.pipelines.get(&pipeline_id); - let Some(result) = self - .global - .borrow() - .hit_test_at_point(event.point, get_pipeline_details) - else { - return false; - }; - - event.init_sequence_id(self.touch_handler.current_sequence_id); - let event = InputEvent::Touch(event); - if let Err(e) = - self.global - .borrow() - .constellation_sender - .send(ConstellationMsg::ForwardInputEvent( - self.id, - event, - Some(result), - )) - { - warn!("Sending event to constellation failed ({:?}).", e); - false - } else { - true - } - } - - pub fn on_touch_event(&mut self, event: TouchEvent) { - if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown { - return; - } - - match event.event_type { - TouchEventType::Down => self.on_touch_down(event), - TouchEventType::Move => self.on_touch_move(event), - TouchEventType::Up => self.on_touch_up(event), - TouchEventType::Cancel => self.on_touch_cancel(event), - } - } - - fn on_touch_down(&mut self, event: TouchEvent) { - self.touch_handler.on_touch_down(event.id, event.point); - self.send_touch_event(event); - } - - fn on_touch_move(&mut self, mut event: TouchEvent) { - let action: TouchMoveAction = self.touch_handler.on_touch_move(event.id, event.point); - if TouchMoveAction::NoAction != action { - // if first move processed and allowed, we directly process the move event, - // without waiting for the script handler. - if self - .touch_handler - .move_allowed(self.touch_handler.current_sequence_id) - { - // https://w3c.github.io/touch-events/#cancelability - event.disable_cancelable(); - match action { - TouchMoveAction::Scroll(delta, point) => self.on_scroll_window_event( - ScrollLocation::Delta(LayoutVector2D::from_untyped(delta.to_untyped())), - point.cast(), - ), - TouchMoveAction::Zoom(magnification, scroll_delta) => { - let cursor = Point2D::new(-1, -1); // Make sure this hits the base layer. - - // The order of these events doesn't matter, because zoom is handled by - // a root display list and the scroll event here is handled by the scroll - // applied to the content display list. - self.pending_scroll_zoom_events - .push(ScrollZoomEvent::PinchZoom(magnification)); - self.pending_scroll_zoom_events - .push(ScrollZoomEvent::Scroll(ScrollEvent { - scroll_location: ScrollLocation::Delta( - LayoutVector2D::from_untyped(scroll_delta.to_untyped()), - ), - cursor, - event_count: 1, - })); - }, - _ => {}, - } - } - // When the event is touchmove, if the script thread is processing the touch - // move event, we skip sending the event to the script thread. - // This prevents the script thread from stacking up for a large amount of time. - if !self - .touch_handler - .is_handling_touch_move(self.touch_handler.current_sequence_id) && - self.send_touch_event(event) && - event.is_cancelable() - { - self.touch_handler - .set_handling_touch_move(self.touch_handler.current_sequence_id, true); - } - } - } - - fn on_touch_up(&mut self, event: TouchEvent) { - self.touch_handler.on_touch_up(event.id, event.point); - self.send_touch_event(event); - } - - fn on_touch_cancel(&mut self, event: TouchEvent) { - // Send the event to script. - self.touch_handler.on_touch_cancel(event.id, event.point); - self.send_touch_event(event); - } - - pub(crate) fn on_touch_event_processed(&mut self, result: TouchEventResult) { - match result { - TouchEventResult::DefaultPrevented(sequence_id, event_type) => { - debug!( - "Touch event {:?} in sequence {:?} prevented!", - event_type, sequence_id - ); - match event_type { - TouchEventType::Down => { - // prevents both click and move - self.touch_handler.prevent_click(sequence_id); - self.touch_handler.prevent_move(sequence_id); - self.touch_handler - .remove_pending_touch_move_action(sequence_id); - }, - TouchEventType::Move => { - // script thread processed the touch move event, mark this false. - if let Some(info) = self.touch_handler.get_touch_sequence_mut(sequence_id) { - info.prevent_move = TouchMoveAllowed::Prevented; - if let TouchSequenceState::PendingFling { .. } = info.state { - info.state = TouchSequenceState::Finished; - } - self.touch_handler.set_handling_touch_move( - self.touch_handler.current_sequence_id, - false, - ); - self.touch_handler - .remove_pending_touch_move_action(sequence_id); - } - }, - TouchEventType::Up => { - // Note: We don't have to consider PendingFling here, since we handle that - // in the DefaultAllowed case of the touch_move event. - // Note: Removing can and should fail, if we still have an active Fling, - let Some(info) = - &mut self.touch_handler.get_touch_sequence_mut(sequence_id) - else { - // The sequence ID could already be removed, e.g. if Fling finished, - // before the touch_up event was handled (since fling can start - // immediately if move was previously allowed, and clicks are anyway not - // happening from fling). - return; - }; - match info.state { - TouchSequenceState::PendingClick(_) => { - info.state = TouchSequenceState::Finished; - self.touch_handler.remove_touch_sequence(sequence_id); - }, - TouchSequenceState::Flinging { .. } => { - // We can't remove the touch sequence yet - }, - TouchSequenceState::Finished => { - self.touch_handler.remove_touch_sequence(sequence_id); - }, - TouchSequenceState::Touching | - TouchSequenceState::Panning { .. } | - TouchSequenceState::Pinching | - TouchSequenceState::MultiTouch | - TouchSequenceState::PendingFling { .. } => { - // It's possible to transition from Pinch to pan, Which means that - // a touch_up event for a pinch might have arrived here, but we - // already transitioned to pan or even PendingFling. - // We don't need to do anything in these cases though. - }, - } - }, - TouchEventType::Cancel => { - // We could still have pending event handlers, so we remove the pending - // actions, and try to remove the touch sequence. - self.touch_handler - .remove_pending_touch_move_action(sequence_id); - self.touch_handler.try_remove_touch_sequence(sequence_id); - }, - } - }, - TouchEventResult::DefaultAllowed(sequence_id, event_type) => { - debug!( - "Touch event {:?} in sequence {:?} allowed", - event_type, sequence_id - ); - match event_type { - TouchEventType::Down => {}, - TouchEventType::Move => { - if let Some(action) = - self.touch_handler.pending_touch_move_action(sequence_id) - { - match action { - TouchMoveAction::Scroll(delta, point) => self - .on_scroll_window_event( - ScrollLocation::Delta(LayoutVector2D::from_untyped( - delta.to_untyped(), - )), - point.cast(), - ), - TouchMoveAction::Zoom(magnification, scroll_delta) => { - let cursor = Point2D::new(-1, -1); - // Make sure this hits the base layer. - // The order of these events doesn't matter, because zoom is handled by - // a root display list and the scroll event here is handled by the scroll - // applied to the content display list. - self.pending_scroll_zoom_events - .push(ScrollZoomEvent::PinchZoom(magnification)); - self.pending_scroll_zoom_events - .push(ScrollZoomEvent::Scroll(ScrollEvent { - scroll_location: ScrollLocation::Delta( - LayoutVector2D::from_untyped( - scroll_delta.to_untyped(), - ), - ), - cursor, - event_count: 1, - })); - }, - TouchMoveAction::NoAction => { - // This shouldn't happen, but we can also just ignore it. - }, - } - self.touch_handler - .remove_pending_touch_move_action(sequence_id); - } - self.touch_handler - .set_handling_touch_move(self.touch_handler.current_sequence_id, false); - if let Some(info) = self.touch_handler.get_touch_sequence_mut(sequence_id) { - info.prevent_move = TouchMoveAllowed::Allowed; - if let TouchSequenceState::PendingFling { velocity, cursor } = - info.state - { - info.state = TouchSequenceState::Flinging { velocity, cursor } - } - } - }, - TouchEventType::Up => { - let Some(info) = self.touch_handler.get_touch_sequence_mut(sequence_id) - else { - // The sequence was already removed because there is no default action. - return; - }; - match info.state { - TouchSequenceState::PendingClick(point) => { - info.state = TouchSequenceState::Finished; - // PreventDefault from touch_down may have been processed after - // touch_up already occurred. - if !info.prevent_click { - self.simulate_mouse_click(point); - } - self.touch_handler.remove_touch_sequence(sequence_id); - }, - TouchSequenceState::Flinging { .. } => { - // We can't remove the touch sequence yet - }, - TouchSequenceState::Finished => { - self.touch_handler.remove_touch_sequence(sequence_id); - }, - TouchSequenceState::Panning { .. } | - TouchSequenceState::Pinching | - TouchSequenceState::PendingFling { .. } => { - // It's possible to transition from Pinch to pan, Which means that - // a touch_up event for a pinch might have arrived here, but we - // already transitioned to pan or even PendingFling. - // We don't need to do anything in these cases though. - }, - TouchSequenceState::MultiTouch | TouchSequenceState::Touching => { - // We transitioned to touching from multi-touch or pinching. - }, - } - }, - TouchEventType::Cancel => { - self.touch_handler - .remove_pending_touch_move_action(sequence_id); - self.touch_handler.try_remove_touch_sequence(sequence_id); - }, - } - }, - } - } - - /// - fn simulate_mouse_click(&mut self, point: DevicePoint) { - let button = MouseButton::Left; - self.dispatch_input_event(InputEvent::MouseMove(MouseMoveEvent { point })); - self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent { - button, - action: MouseButtonAction::Down, - point, - })); - self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent { - button, - action: MouseButtonAction::Up, - point, - })); - self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent { - button, - action: MouseButtonAction::Click, - point, - })); - } - - pub fn notify_scroll_event( - &mut self, - scroll_location: ScrollLocation, - cursor: DeviceIntPoint, - event_type: TouchEventType, - ) { - if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown { - return; - } - - match event_type { - TouchEventType::Move => self.on_scroll_window_event(scroll_location, cursor), - TouchEventType::Up | TouchEventType::Cancel => { - self.on_scroll_window_event(scroll_location, cursor); - }, - TouchEventType::Down => { - self.on_scroll_window_event(scroll_location, cursor); - }, - } - } - - fn on_scroll_window_event(&mut self, scroll_location: ScrollLocation, cursor: DeviceIntPoint) { - self.pending_scroll_zoom_events - .push(ScrollZoomEvent::Scroll(ScrollEvent { - scroll_location, - cursor, - event_count: 1, - })); - } - - pub(crate) fn process_pending_scroll_events(&mut self, compositor: &mut IOCompositor) { - if self.pending_scroll_zoom_events.is_empty() { - return; - } - - // Batch up all scroll events into one, or else we'll do way too much painting. - let mut combined_scroll_event: Option = None; - let mut combined_magnification = 1.0; - for scroll_event in self.pending_scroll_zoom_events.drain(..) { - match scroll_event { - ScrollZoomEvent::PinchZoom(magnification) => { - combined_magnification *= magnification - }, - ScrollZoomEvent::Scroll(scroll_event_info) => { - let combined_event = match combined_scroll_event.as_mut() { - None => { - combined_scroll_event = Some(scroll_event_info); - continue; - }, - Some(combined_event) => combined_event, - }; - - match ( - combined_event.scroll_location, - scroll_event_info.scroll_location, - ) { - (ScrollLocation::Delta(old_delta), ScrollLocation::Delta(new_delta)) => { - // Mac OS X sometimes delivers scroll events out of vsync during a - // fling. This causes events to get bunched up occasionally, causing - // nasty-looking "pops". To mitigate this, during a fling we average - // deltas instead of summing them. - let old_event_count = Scale::new(combined_event.event_count as f32); - combined_event.event_count += 1; - let new_event_count = Scale::new(combined_event.event_count as f32); - combined_event.scroll_location = ScrollLocation::Delta( - (old_delta * old_event_count + new_delta) / new_event_count, - ); - }, - (ScrollLocation::Start, _) | (ScrollLocation::End, _) => { - // Once we see Start or End, we shouldn't process any more events. - break; - }, - (_, ScrollLocation::Start) | (_, ScrollLocation::End) => { - // If this is an event which is scrolling to the start or end of the page, - // disregard other pending events and exit the loop. - *combined_event = scroll_event_info; - break; - }, - } - }, - } - } - - let zoom_changed = compositor - .set_pinch_zoom_level(compositor.pinch_zoom_level().get() * combined_magnification); - let scroll_result = combined_scroll_event.and_then(|combined_event| { - self.scroll_node_at_device_point( - combined_event.cursor.to_f32(), - combined_event.scroll_location, - compositor, - ) - }); - if !zoom_changed && scroll_result.is_none() { - return; - } - - let mut transaction = Transaction::new(); - if zoom_changed { - compositor.send_root_pipeline_display_list_in_transaction(&mut transaction); - } - - if let Some((pipeline_id, external_id, offset)) = scroll_result { - let offset = LayoutVector2D::new(-offset.x, -offset.y); - transaction.set_scroll_offsets( - external_id, - vec![SampledScrollOffset { - offset, - generation: 0, - }], - ); - self.send_scroll_positions_to_layout_for_pipeline(pipeline_id); - } - - compositor.generate_frame(&mut transaction, RenderReasons::APZ); - self.global.borrow_mut().send_transaction(transaction); - } - - /// Perform a hit test at the given [`DevicePoint`] and apply the [`ScrollLocation`] - /// scrolling to the applicable scroll node under that point. If a scroll was - /// performed, returns the [`PipelineId`] of the node scrolled, the id, and the final - /// scroll delta. - fn scroll_node_at_device_point( - &mut self, - cursor: DevicePoint, - scroll_location: ScrollLocation, - compositor: &mut IOCompositor, - ) -> Option<(PipelineId, ExternalScrollId, LayoutVector2D)> { - let scroll_location = match scroll_location { - ScrollLocation::Delta(delta) => { - let device_pixels_per_page = compositor.device_pixels_per_page_pixel(); - let scaled_delta = (Vector2D::from_untyped(delta.to_untyped()) / - device_pixels_per_page) - .to_untyped(); - let calculated_delta = LayoutVector2D::from_untyped(scaled_delta); - ScrollLocation::Delta(calculated_delta) - }, - // Leave ScrollLocation unchanged if it is Start or End location. - ScrollLocation::Start | ScrollLocation::End => scroll_location, - }; - - let get_pipeline_details = |pipeline_id| self.pipelines.get(&pipeline_id); - let hit_test_results = self - .global - .borrow() - .hit_test_at_point_with_flags_and_pipeline( - cursor, - HitTestFlags::FIND_ALL, - None, - get_pipeline_details, - ); - - // Iterate through all hit test results, processing only the first node of each pipeline. - // This is needed to propagate the scroll events from a pipeline representing an iframe to - // its ancestor pipelines. - let mut previous_pipeline_id = None; - for CompositorHitTestResult { - pipeline_id, - scroll_tree_node, - .. - } in hit_test_results.iter() - { - let pipeline_details = self.pipelines.get_mut(pipeline_id)?; - if previous_pipeline_id.replace(pipeline_id) != Some(pipeline_id) { - let scroll_result = pipeline_details - .scroll_tree - .scroll_node_or_ancestor(scroll_tree_node, scroll_location); - if let Some((external_id, offset)) = scroll_result { - return Some((*pipeline_id, external_id, offset)); - } - } - } - None - } - - /// Simulate a pinch zoom - pub fn set_pinch_zoom(&mut self, magnification: f32) { - if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown { - return; - } - - // TODO: Scroll to keep the center in view? - self.pending_scroll_zoom_events - .push(ScrollZoomEvent::PinchZoom(magnification)); - } -} -#[derive(Debug)] -pub struct WebViewManager { - /// Our top-level browsing contexts. In the WebRender scene, their pipelines are the children of - /// a single root pipeline that also applies any pinch zoom transformation. - webviews: HashMap, - - /// The order to paint them in, topmost last. - pub(crate) painting_order: Vec, -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct UnknownWebView(pub WebViewId); - -impl Default for WebViewManager { - fn default() -> Self { - Self { - webviews: Default::default(), - painting_order: Default::default(), - } - } -} - -impl WebViewManager { - pub fn remove(&mut self, webview_id: WebViewId) -> Result { - self.painting_order.retain(|b| *b != webview_id); - self.webviews - .remove(&webview_id) - .ok_or(UnknownWebView(webview_id)) - } - - pub fn get(&self, webview_id: WebViewId) -> Option<&WebView> { - self.webviews.get(&webview_id) - } - - pub fn get_mut(&mut self, webview_id: WebViewId) -> Option<&mut WebView> { - self.webviews.get_mut(&webview_id) - } - - /// Returns true iff the painting order actually changed. - pub fn show(&mut self, webview_id: WebViewId) -> Result { - if !self.webviews.contains_key(&webview_id) { - return Err(UnknownWebView(webview_id)); - } - if !self.painting_order.contains(&webview_id) { - self.painting_order.push(webview_id); - return Ok(true); - } - Ok(false) - } - - /// Returns true iff the painting order actually changed. - pub fn hide(&mut self, webview_id: WebViewId) -> Result { - if !self.webviews.contains_key(&webview_id) { - return Err(UnknownWebView(webview_id)); - } - if self.painting_order.contains(&webview_id) { - self.painting_order.retain(|b| *b != webview_id); - return Ok(true); - } - Ok(false) - } - - /// Returns true iff the painting order actually changed. - pub fn hide_all(&mut self) -> bool { - if !self.painting_order.is_empty() { - self.painting_order.clear(); - return true; - } - false - } - - /// Returns true iff the painting order actually changed. - pub fn raise_to_top(&mut self, webview_id: WebViewId) -> Result { - if !self.webviews.contains_key(&webview_id) { - return Err(UnknownWebView(webview_id)); - } - if self.painting_order.last() != Some(&webview_id) { - self.hide(webview_id)?; - self.show(webview_id)?; - return Ok(true); - } - Ok(false) - } - - pub fn painting_order(&self) -> impl Iterator { - self.painting_order - .iter() - .flat_map(move |webview_id| self.get(*webview_id).map(|b| (webview_id, b))) - } - - pub fn entry(&mut self, webview_id: WebViewId) -> Entry<'_, WebViewId, WebView> { - self.webviews.entry(webview_id) - } - - pub fn iter(&self) -> Values<'_, WebViewId, WebView> { - self.webviews.values() - } - - pub fn iter_mut(&mut self) -> ValuesMut<'_, WebViewId, WebView> { - self.webviews.values_mut() - } -} - -#[cfg(test)] -mod test { - use std::num::NonZeroU32; - - use base::id::{ - BrowsingContextId, BrowsingContextIndex, PipelineNamespace, PipelineNamespaceId, WebViewId, - }; - - use crate::webview::{UnknownWebView, WebViewAlreadyExists, WebViewManager}; - - fn top_level_id(namespace_id: u32, index: u32) -> WebViewId { - WebViewId(BrowsingContextId { - namespace_id: PipelineNamespaceId(namespace_id), - index: BrowsingContextIndex(NonZeroU32::new(index).unwrap()), - }) - } - - fn webviews_sorted( - webviews: &WebViewManager, - ) -> Vec<(WebViewId, WebView)> { - let mut keys = webviews.webviews.keys().collect::>(); - keys.sort(); - keys.iter() - .map(|&id| (*id, webviews.webviews.get(id).cloned().unwrap())) - .collect() - } - - #[test] - fn test() { - PipelineNamespace::install(PipelineNamespaceId(0)); - let mut webviews = WebViewManager::default(); - - // add() adds the webview to the map, but not the painting order. - assert!(webviews.add(WebViewId::new(), 'a').is_ok()); - assert!(webviews.add(WebViewId::new(), 'b').is_ok()); - assert!(webviews.add(WebViewId::new(), 'c').is_ok()); - assert_eq!( - webviews_sorted(&webviews), - vec![ - (top_level_id(0, 1), 'a'), - (top_level_id(0, 2), 'b'), - (top_level_id(0, 3), 'c'), - ] - ); - assert!(webviews.painting_order.is_empty()); - - // add() returns WebViewAlreadyExists if the webview id already exists. - assert_eq!( - webviews.add(top_level_id(0, 3), 'd'), - Err(WebViewAlreadyExists(top_level_id(0, 3))) - ); - - // Other methods return UnknownWebView or None if the webview id doesn’t exist. - assert_eq!( - webviews.remove(top_level_id(1, 1)), - Err(UnknownWebView(top_level_id(1, 1))) - ); - assert_eq!(webviews.get(top_level_id(1, 1)), None); - assert_eq!(webviews.get_mut(top_level_id(1, 1)), None); - assert_eq!( - webviews.show(top_level_id(1, 1)), - Err(UnknownWebView(top_level_id(1, 1))) - ); - assert_eq!( - webviews.hide(top_level_id(1, 1)), - Err(UnknownWebView(top_level_id(1, 1))) - ); - assert_eq!( - webviews.raise_to_top(top_level_id(1, 1)), - Err(UnknownWebView(top_level_id(1, 1))) - ); - - // For webviews not yet visible, both show() and raise_to_top() add the given webview on top. - assert_eq!(webviews.show(top_level_id(0, 2)), Ok(true)); - assert_eq!(webviews.show(top_level_id(0, 2)), Ok(false)); - assert_eq!(webviews.painting_order, vec![top_level_id(0, 2)]); - assert_eq!(webviews.raise_to_top(top_level_id(0, 1)), Ok(true)); - assert_eq!(webviews.raise_to_top(top_level_id(0, 1)), Ok(false)); - assert_eq!( - webviews.painting_order, - vec![top_level_id(0, 2), top_level_id(0, 1)] - ); - assert_eq!(webviews.show(top_level_id(0, 3)), Ok(true)); - assert_eq!(webviews.show(top_level_id(0, 3)), Ok(false)); - assert_eq!( - webviews.painting_order, - vec![top_level_id(0, 2), top_level_id(0, 1), top_level_id(0, 3)] - ); - - // For webviews already visible, show() does nothing, while raise_to_top() makes it on top. - assert_eq!(webviews.show(top_level_id(0, 1)), Ok(false)); - assert_eq!( - webviews.painting_order, - vec![top_level_id(0, 2), top_level_id(0, 1), top_level_id(0, 3)] - ); - assert_eq!(webviews.raise_to_top(top_level_id(0, 1)), Ok(true)); - assert_eq!(webviews.raise_to_top(top_level_id(0, 1)), Ok(false)); - assert_eq!( - webviews.painting_order, - vec![top_level_id(0, 2), top_level_id(0, 3), top_level_id(0, 1)] - ); - - // hide() removes the webview from the painting order, but not the map. - assert_eq!(webviews.hide(top_level_id(0, 3)), Ok(true)); - assert_eq!(webviews.hide(top_level_id(0, 3)), Ok(false)); - assert_eq!( - webviews.painting_order, - vec![top_level_id(0, 2), top_level_id(0, 1)] - ); - assert_eq!( - webviews_sorted(&webviews), - vec![ - (top_level_id(0, 1), 'a'), - (top_level_id(0, 2), 'b'), - (top_level_id(0, 3), 'c'), - ] - ); - - // painting_order() returns only the visible webviews, in painting order. - let mut painting_order = webviews.painting_order(); - assert_eq!(painting_order.next(), Some((&top_level_id(0, 2), &'b'))); - assert_eq!(painting_order.next(), Some((&top_level_id(0, 1), &'a'))); - assert_eq!(painting_order.next(), None); - drop(painting_order); - - // remove() removes the given webview from both the map and the painting order. - assert!(webviews.remove(top_level_id(0, 1)).is_ok()); - assert!(webviews.remove(top_level_id(0, 2)).is_ok()); - assert!(webviews.remove(top_level_id(0, 3)).is_ok()); - assert!(webviews_sorted(&webviews).is_empty()); - assert!(webviews.painting_order.is_empty()); - } -} diff --git a/components/compositing/webview_manager.rs b/components/compositing/webview_manager.rs new file mode 100644 index 00000000000..201f9d1c5c5 --- /dev/null +++ b/components/compositing/webview_manager.rs @@ -0,0 +1,242 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::collections::HashMap; +use std::collections::hash_map::{Entry, Values, ValuesMut}; + +use base::id::WebViewId; + +use crate::webview_renderer::UnknownWebView; + +#[derive(Debug)] +pub struct WebViewManager { + /// Our top-level browsing contexts. In the WebRender scene, their pipelines are the children of + /// a single root pipeline that also applies any pinch zoom transformation. + webviews: HashMap, + + /// The order to paint them in, topmost last. + pub(crate) painting_order: Vec, +} + +impl Default for WebViewManager { + fn default() -> Self { + Self { + webviews: Default::default(), + painting_order: Default::default(), + } + } +} + +impl WebViewManager { + pub fn remove(&mut self, webview_id: WebViewId) -> Result { + self.painting_order.retain(|b| *b != webview_id); + self.webviews + .remove(&webview_id) + .ok_or(UnknownWebView(webview_id)) + } + + pub fn get(&self, webview_id: WebViewId) -> Option<&WebView> { + self.webviews.get(&webview_id) + } + + pub fn get_mut(&mut self, webview_id: WebViewId) -> Option<&mut WebView> { + self.webviews.get_mut(&webview_id) + } + + /// Returns true iff the painting order actually changed. + pub fn show(&mut self, webview_id: WebViewId) -> Result { + if !self.webviews.contains_key(&webview_id) { + return Err(UnknownWebView(webview_id)); + } + if !self.painting_order.contains(&webview_id) { + self.painting_order.push(webview_id); + return Ok(true); + } + Ok(false) + } + + /// Returns true iff the painting order actually changed. + pub fn hide(&mut self, webview_id: WebViewId) -> Result { + if !self.webviews.contains_key(&webview_id) { + return Err(UnknownWebView(webview_id)); + } + if self.painting_order.contains(&webview_id) { + self.painting_order.retain(|b| *b != webview_id); + return Ok(true); + } + Ok(false) + } + + /// Returns true iff the painting order actually changed. + pub fn hide_all(&mut self) -> bool { + if !self.painting_order.is_empty() { + self.painting_order.clear(); + return true; + } + false + } + + /// Returns true iff the painting order actually changed. + pub fn raise_to_top(&mut self, webview_id: WebViewId) -> Result { + if !self.webviews.contains_key(&webview_id) { + return Err(UnknownWebView(webview_id)); + } + if self.painting_order.last() != Some(&webview_id) { + self.hide(webview_id)?; + self.show(webview_id)?; + return Ok(true); + } + Ok(false) + } + + pub fn painting_order(&self) -> impl Iterator { + self.painting_order + .iter() + .flat_map(move |webview_id| self.get(*webview_id).map(|b| (webview_id, b))) + } + + pub fn entry(&mut self, webview_id: WebViewId) -> Entry<'_, WebViewId, WebView> { + self.webviews.entry(webview_id) + } + + pub fn iter(&self) -> Values<'_, WebViewId, WebView> { + self.webviews.values() + } + + pub fn iter_mut(&mut self) -> ValuesMut<'_, WebViewId, WebView> { + self.webviews.values_mut() + } +} + +#[cfg(test)] +mod test { + use base::id::{BrowsingContextId, Index, PipelineNamespace, PipelineNamespaceId, WebViewId}; + + use crate::webview_manager::WebViewManager; + use crate::webview_renderer::UnknownWebView; + + fn top_level_id(namespace_id: u32, index: u32) -> WebViewId { + WebViewId(BrowsingContextId { + namespace_id: PipelineNamespaceId(namespace_id), + index: Index::new(index).unwrap(), + }) + } + + fn webviews_sorted( + webviews: &WebViewManager, + ) -> Vec<(WebViewId, WebView)> { + let mut keys = webviews.webviews.keys().collect::>(); + keys.sort(); + keys.iter() + .map(|&id| (*id, webviews.webviews.get(id).cloned().unwrap())) + .collect() + } + + #[test] + fn test() { + PipelineNamespace::install(PipelineNamespaceId(0)); + let mut webviews = WebViewManager::default(); + + // entry() adds the webview to the map, but not the painting order. + webviews.entry(WebViewId::new()).or_insert('a'); + webviews.entry(WebViewId::new()).or_insert('b'); + webviews.entry(WebViewId::new()).or_insert('c'); + assert!(webviews.get(top_level_id(0, 1)).is_some()); + assert!(webviews.get(top_level_id(0, 2)).is_some()); + assert!(webviews.get(top_level_id(0, 3)).is_some()); + assert_eq!( + webviews_sorted(&webviews), + vec![ + (top_level_id(0, 1), 'a'), + (top_level_id(0, 2), 'b'), + (top_level_id(0, 3), 'c'), + ] + ); + assert!(webviews.painting_order.is_empty()); + + // add() returns WebViewAlreadyExists if the webview id already exists. + webviews.entry(top_level_id(0, 3)).or_insert('d'); + assert!(webviews.get(top_level_id(0, 3)).is_some()); + + // Other methods return UnknownWebView or None if the webview id doesn’t exist. + assert_eq!( + webviews.remove(top_level_id(1, 1)), + Err(UnknownWebView(top_level_id(1, 1))) + ); + assert_eq!(webviews.get(top_level_id(1, 1)), None); + assert_eq!(webviews.get_mut(top_level_id(1, 1)), None); + assert_eq!( + webviews.show(top_level_id(1, 1)), + Err(UnknownWebView(top_level_id(1, 1))) + ); + assert_eq!( + webviews.hide(top_level_id(1, 1)), + Err(UnknownWebView(top_level_id(1, 1))) + ); + assert_eq!( + webviews.raise_to_top(top_level_id(1, 1)), + Err(UnknownWebView(top_level_id(1, 1))) + ); + + // For webviews not yet visible, both show() and raise_to_top() add the given webview on top. + assert_eq!(webviews.show(top_level_id(0, 2)), Ok(true)); + assert_eq!(webviews.show(top_level_id(0, 2)), Ok(false)); + assert_eq!(webviews.painting_order, vec![top_level_id(0, 2)]); + assert_eq!(webviews.raise_to_top(top_level_id(0, 1)), Ok(true)); + assert_eq!(webviews.raise_to_top(top_level_id(0, 1)), Ok(false)); + assert_eq!( + webviews.painting_order, + vec![top_level_id(0, 2), top_level_id(0, 1)] + ); + assert_eq!(webviews.show(top_level_id(0, 3)), Ok(true)); + assert_eq!(webviews.show(top_level_id(0, 3)), Ok(false)); + assert_eq!( + webviews.painting_order, + vec![top_level_id(0, 2), top_level_id(0, 1), top_level_id(0, 3)] + ); + + // For webviews already visible, show() does nothing, while raise_to_top() makes it on top. + assert_eq!(webviews.show(top_level_id(0, 1)), Ok(false)); + assert_eq!( + webviews.painting_order, + vec![top_level_id(0, 2), top_level_id(0, 1), top_level_id(0, 3)] + ); + assert_eq!(webviews.raise_to_top(top_level_id(0, 1)), Ok(true)); + assert_eq!(webviews.raise_to_top(top_level_id(0, 1)), Ok(false)); + assert_eq!( + webviews.painting_order, + vec![top_level_id(0, 2), top_level_id(0, 3), top_level_id(0, 1)] + ); + + // hide() removes the webview from the painting order, but not the map. + assert_eq!(webviews.hide(top_level_id(0, 3)), Ok(true)); + assert_eq!(webviews.hide(top_level_id(0, 3)), Ok(false)); + assert_eq!( + webviews.painting_order, + vec![top_level_id(0, 2), top_level_id(0, 1)] + ); + assert_eq!( + webviews_sorted(&webviews), + vec![ + (top_level_id(0, 1), 'a'), + (top_level_id(0, 2), 'b'), + (top_level_id(0, 3), 'c'), + ] + ); + + // painting_order() returns only the visible webviews, in painting order. + let mut painting_order = webviews.painting_order(); + assert_eq!(painting_order.next(), Some((&top_level_id(0, 2), &'b'))); + assert_eq!(painting_order.next(), Some((&top_level_id(0, 1), &'a'))); + assert_eq!(painting_order.next(), None); + drop(painting_order); + + // remove() removes the given webview from both the map and the painting order. + assert!(webviews.remove(top_level_id(0, 1)).is_ok()); + assert!(webviews.remove(top_level_id(0, 2)).is_ok()); + assert!(webviews.remove(top_level_id(0, 3)).is_ok()); + assert!(webviews_sorted(&webviews).is_empty()); + assert!(webviews.painting_order.is_empty()); + } +} diff --git a/components/compositing/webview_renderer.rs b/components/compositing/webview_renderer.rs new file mode 100644 index 00000000000..a51dd5f8cda --- /dev/null +++ b/components/compositing/webview_renderer.rs @@ -0,0 +1,991 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::cell::RefCell; +use std::collections::HashMap; +use std::collections::hash_map::Keys; +use std::rc::Rc; + +use base::id::{PipelineId, WebViewId}; +use compositing_traits::{SendableFrameTree, WebViewTrait}; +use constellation_traits::{EmbedderToConstellationMessage, ScrollState, WindowSizeType}; +use embedder_traits::{ + AnimationState, CompositorHitTestResult, InputEvent, MouseButton, MouseButtonAction, + MouseButtonEvent, MouseMoveEvent, ShutdownState, TouchEvent, TouchEventResult, TouchEventType, + TouchId, ViewportDetails, +}; +use euclid::{Box2D, Point2D, Scale, Size2D, Vector2D}; +use fnv::FnvHashSet; +use log::{debug, warn}; +use servo_geometry::DeviceIndependentPixel; +use style_traits::{CSSPixel, PinchZoomFactor}; +use webrender::Transaction; +use webrender_api::units::{ + DeviceIntPoint, DeviceIntRect, DevicePixel, DevicePoint, DeviceRect, LayoutVector2D, +}; +use webrender_api::{ + ExternalScrollId, HitTestFlags, RenderReasons, SampledScrollOffset, ScrollLocation, +}; + +use crate::IOCompositor; +use crate::compositor::{PipelineDetails, ServoRenderer}; +use crate::touch::{TouchHandler, TouchMoveAction, TouchMoveAllowed, TouchSequenceState}; + +// Default viewport constraints +const MAX_ZOOM: f32 = 8.0; +const MIN_ZOOM: f32 = 0.1; + +#[derive(Clone, Copy)] +struct ScrollEvent { + /// Scroll by this offset, or to Start or End + scroll_location: ScrollLocation, + /// Apply changes to the frame at this location + cursor: DeviceIntPoint, + /// The number of OS events that have been coalesced together into this one event. + event_count: u32, +} + +#[derive(Clone, Copy)] +enum ScrollZoomEvent { + /// An pinch zoom event that magnifies the view by the given factor. + PinchZoom(f32), + /// A scroll event that scrolls the scroll node at the given location by the + /// given amount. + Scroll(ScrollEvent), +} + +/// A renderer for a libservo `WebView`. This is essentially the [`ServoRenderer`]'s interface to a +/// libservo `WebView`, but the code here cannot depend on libservo in order to prevent circular +/// dependencies, which is why we store a `dyn WebViewTrait` here instead of the `WebView` itself. +pub(crate) struct WebViewRenderer { + /// The [`WebViewId`] of the `WebView` associated with this [`WebViewDetails`]. + pub id: WebViewId, + /// The renderer's view of the embedding layer `WebView` as a trait implementation, + /// so that the renderer doesn't need to depend on the embedding layer. This avoids + /// a dependency cycle. + pub webview: Box, + /// The root [`PipelineId`] of the currently displayed page in this WebView. + pub root_pipeline_id: Option, + pub rect: DeviceRect, + /// Tracks details about each active pipeline that the compositor knows about. + pub pipelines: HashMap, + /// Data that is shared by all WebView renderers. + pub(crate) global: Rc>, + /// Pending scroll/zoom events. + pending_scroll_zoom_events: Vec, + /// Touch input state machine + touch_handler: TouchHandler, + /// "Desktop-style" zoom that resizes the viewport to fit the window. + pub page_zoom: Scale, + /// "Mobile-style" zoom that does not reflow the page. + viewport_zoom: PinchZoomFactor, + /// Viewport zoom constraints provided by @viewport. + min_viewport_zoom: Option, + max_viewport_zoom: Option, + /// The HiDPI scale factor for the `WebView` associated with this renderer. This is controlled + /// by the embedding layer. + hidpi_scale_factor: Scale, + /// Whether or not this [`WebViewRenderer`] isn't throttled and has a pipeline with + /// active animations or animation frame callbacks. + animating: bool, +} + +impl Drop for WebViewRenderer { + fn drop(&mut self) { + self.global + .borrow_mut() + .pipeline_to_webview_map + .retain(|_, webview_id| self.id != *webview_id); + } +} + +impl WebViewRenderer { + pub(crate) fn new( + global: Rc>, + renderer_webview: Box, + viewport_details: ViewportDetails, + ) -> Self { + let hidpi_scale_factor = viewport_details.hidpi_scale_factor; + let size = viewport_details.size * viewport_details.hidpi_scale_factor; + Self { + id: renderer_webview.id(), + webview: renderer_webview, + root_pipeline_id: None, + rect: DeviceRect::from_origin_and_size(DevicePoint::origin(), size), + pipelines: Default::default(), + touch_handler: TouchHandler::new(), + global, + pending_scroll_zoom_events: Default::default(), + page_zoom: Scale::new(1.0), + viewport_zoom: PinchZoomFactor::new(1.0), + min_viewport_zoom: Some(PinchZoomFactor::new(1.0)), + max_viewport_zoom: None, + hidpi_scale_factor: Scale::new(hidpi_scale_factor.0), + animating: false, + } + } + + pub(crate) fn animations_or_animation_callbacks_running(&self) -> bool { + self.pipelines + .values() + .any(PipelineDetails::animations_or_animation_callbacks_running) + } + + pub(crate) fn animation_callbacks_running(&self) -> bool { + self.pipelines + .values() + .any(PipelineDetails::animation_callbacks_running) + } + + pub(crate) fn pipeline_ids(&self) -> Keys<'_, PipelineId, PipelineDetails> { + self.pipelines.keys() + } + + pub(crate) fn animating(&self) -> bool { + self.animating + } + + /// Returns the [`PipelineDetails`] for the given [`PipelineId`], creating it if needed. + pub(crate) fn ensure_pipeline_details( + &mut self, + pipeline_id: PipelineId, + ) -> &mut PipelineDetails { + self.pipelines.entry(pipeline_id).or_insert_with(|| { + self.global + .borrow_mut() + .pipeline_to_webview_map + .insert(pipeline_id, self.id); + PipelineDetails::new() + }) + } + + pub(crate) fn remove_pipeline(&mut self, pipeline_id: PipelineId) { + self.global + .borrow_mut() + .pipeline_to_webview_map + .remove(&pipeline_id); + self.pipelines.remove(&pipeline_id); + } + + pub(crate) fn set_frame_tree(&mut self, frame_tree: &SendableFrameTree) { + let pipeline_id = frame_tree.pipeline.id; + let old_pipeline_id = std::mem::replace(&mut self.root_pipeline_id, Some(pipeline_id)); + + if old_pipeline_id != self.root_pipeline_id { + debug!( + "Updating webview ({:?}) from pipeline {:?} to {:?}", + 3, old_pipeline_id, self.root_pipeline_id + ); + } + + self.set_frame_tree_on_pipeline_details(frame_tree, None); + self.reset_scroll_tree_for_unattached_pipelines(frame_tree); + self.send_scroll_positions_to_layout_for_pipeline(pipeline_id); + } + + pub(crate) fn send_scroll_positions_to_layout_for_pipeline(&self, pipeline_id: PipelineId) { + let Some(details) = self.pipelines.get(&pipeline_id) else { + return; + }; + + let mut scroll_states = Vec::new(); + details.scroll_tree.nodes.iter().for_each(|node| { + if let (Some(scroll_id), Some(scroll_offset)) = (node.external_id(), node.offset()) { + scroll_states.push(ScrollState { + scroll_id, + scroll_offset, + }); + } + }); + + let _ = self.global.borrow().constellation_sender.send( + EmbedderToConstellationMessage::SetScrollStates(pipeline_id, scroll_states), + ); + } + + pub(crate) fn set_frame_tree_on_pipeline_details( + &mut self, + frame_tree: &SendableFrameTree, + parent_pipeline_id: Option, + ) { + let pipeline_id = frame_tree.pipeline.id; + let pipeline_details = self.ensure_pipeline_details(pipeline_id); + pipeline_details.pipeline = Some(frame_tree.pipeline.clone()); + pipeline_details.parent_pipeline_id = parent_pipeline_id; + + for kid in &frame_tree.children { + self.set_frame_tree_on_pipeline_details(kid, Some(pipeline_id)); + } + } + + pub(crate) fn reset_scroll_tree_for_unattached_pipelines( + &mut self, + frame_tree: &SendableFrameTree, + ) { + // TODO(mrobinson): Eventually this can selectively preserve the scroll trees + // state for some unattached pipelines in order to preserve scroll position when + // navigating backward and forward. + fn collect_pipelines( + pipelines: &mut FnvHashSet, + frame_tree: &SendableFrameTree, + ) { + pipelines.insert(frame_tree.pipeline.id); + for kid in &frame_tree.children { + collect_pipelines(pipelines, kid); + } + } + + let mut attached_pipelines: FnvHashSet = FnvHashSet::default(); + collect_pipelines(&mut attached_pipelines, frame_tree); + + self.pipelines + .iter_mut() + .filter(|(id, _)| !attached_pipelines.contains(id)) + .for_each(|(_, details)| { + details.scroll_tree.nodes.iter_mut().for_each(|node| { + node.set_offset(LayoutVector2D::zero()); + }) + }) + } + + /// Sets or unsets the animations-running flag for the given pipeline. Returns + /// true if the [`WebViewRenderer`]'s overall animating state changed. + pub(crate) fn change_pipeline_running_animations_state( + &mut self, + pipeline_id: PipelineId, + animation_state: AnimationState, + ) -> bool { + let pipeline_details = self.ensure_pipeline_details(pipeline_id); + match animation_state { + AnimationState::AnimationsPresent => { + pipeline_details.animations_running = true; + }, + AnimationState::AnimationCallbacksPresent => { + pipeline_details.animation_callbacks_running = true; + }, + AnimationState::NoAnimationsPresent => { + pipeline_details.animations_running = false; + }, + AnimationState::NoAnimationCallbacksPresent => { + pipeline_details.animation_callbacks_running = false; + }, + } + self.update_animation_state() + } + + /// Sets or unsets the throttled flag for the given pipeline. Returns + /// true if the [`WebViewRenderer`]'s overall animating state changed. + pub(crate) fn set_throttled(&mut self, pipeline_id: PipelineId, throttled: bool) -> bool { + self.ensure_pipeline_details(pipeline_id).throttled = throttled; + + // Throttling a pipeline can cause it to be taken into the "not-animating" state. + self.update_animation_state() + } + + pub(crate) fn update_animation_state(&mut self) -> bool { + let animating = self.pipelines.values().any(PipelineDetails::animating); + let old_animating = std::mem::replace(&mut self.animating, animating); + self.webview.set_animating(self.animating); + old_animating != self.animating + } + + /// On a Window refresh tick (e.g. vsync) + pub(crate) fn on_vsync(&mut self) { + if let Some(fling_action) = self.touch_handler.on_vsync() { + self.on_scroll_window_event( + ScrollLocation::Delta(fling_action.delta), + fling_action.cursor, + ); + } + } + + pub(crate) fn dispatch_input_event(&mut self, event: InputEvent) { + // Events that do not need to do hit testing are sent directly to the + // constellation to filter down. + let Some(point) = event.point() else { + return; + }; + + // If we can't find a pipeline to send this event to, we cannot continue. + let get_pipeline_details = |pipeline_id| self.pipelines.get(&pipeline_id); + let Some(result) = self + .global + .borrow() + .hit_test_at_point(point, get_pipeline_details) + else { + return; + }; + + self.global.borrow_mut().update_cursor(point, &result); + + if let Err(error) = self.global.borrow().constellation_sender.send( + EmbedderToConstellationMessage::ForwardInputEvent(self.id, event, Some(result)), + ) { + warn!("Sending event to constellation failed ({error:?})."); + } + } + + pub(crate) fn notify_input_event(&mut self, event: InputEvent) { + if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown { + return; + } + + if let InputEvent::Touch(event) = event { + self.on_touch_event(event); + return; + } + + if self.global.borrow().convert_mouse_to_touch { + match event { + InputEvent::MouseButton(event) => { + match (event.button, event.action) { + (MouseButton::Left, MouseButtonAction::Down) => self.on_touch_down( + TouchEvent::new(TouchEventType::Down, TouchId(0), event.point), + ), + (MouseButton::Left, MouseButtonAction::Up) => self.on_touch_up( + TouchEvent::new(TouchEventType::Up, TouchId(0), event.point), + ), + _ => {}, + } + return; + }, + InputEvent::MouseMove(event) => { + if let Some(state) = self.touch_handler.try_get_current_touch_sequence() { + // We assume that the debug option `-Z convert-mouse-to-touch` will only + // be used on devices without native touch input, so we can directly + // reuse the touch handler for tracking the state of pressed buttons. + match state.state { + TouchSequenceState::Touching | TouchSequenceState::Panning { .. } => { + self.on_touch_move(TouchEvent::new( + TouchEventType::Move, + TouchId(0), + event.point, + )); + }, + TouchSequenceState::MultiTouch => { + // Multitouch simulation currently is not implemented. + // Since we only get one mouse move event, we would need to + // dispatch one mouse move event per currently pressed mouse button. + }, + TouchSequenceState::Pinching => { + // We only have one mouse button, so Pinching should be impossible. + #[cfg(debug_assertions)] + log::error!( + "Touch handler is in Pinching state, which should be unreachable with \ + -Z convert-mouse-to-touch debug option." + ); + }, + TouchSequenceState::PendingFling { .. } | + TouchSequenceState::Flinging { .. } | + TouchSequenceState::PendingClick(_) | + TouchSequenceState::Finished => { + // Mouse movement without a button being pressed is not + // translated to touch events. + }, + } + } + // We don't want to (directly) dispatch mouse events when simulating touch input. + return; + }, + _ => {}, + } + } + + self.dispatch_input_event(event); + } + + fn send_touch_event(&self, mut event: TouchEvent) -> bool { + let get_pipeline_details = |pipeline_id| self.pipelines.get(&pipeline_id); + let Some(result) = self + .global + .borrow() + .hit_test_at_point(event.point, get_pipeline_details) + else { + return false; + }; + + event.init_sequence_id(self.touch_handler.current_sequence_id); + let event = InputEvent::Touch(event); + if let Err(e) = self.global.borrow().constellation_sender.send( + EmbedderToConstellationMessage::ForwardInputEvent(self.id, event, Some(result)), + ) { + warn!("Sending event to constellation failed ({:?}).", e); + false + } else { + true + } + } + + pub(crate) fn on_touch_event(&mut self, event: TouchEvent) { + if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown { + return; + } + + match event.event_type { + TouchEventType::Down => self.on_touch_down(event), + TouchEventType::Move => self.on_touch_move(event), + TouchEventType::Up => self.on_touch_up(event), + TouchEventType::Cancel => self.on_touch_cancel(event), + } + } + + fn on_touch_down(&mut self, event: TouchEvent) { + self.touch_handler.on_touch_down(event.id, event.point); + self.send_touch_event(event); + } + + fn on_touch_move(&mut self, mut event: TouchEvent) { + let action: TouchMoveAction = self.touch_handler.on_touch_move(event.id, event.point); + if TouchMoveAction::NoAction != action { + // if first move processed and allowed, we directly process the move event, + // without waiting for the script handler. + if self + .touch_handler + .move_allowed(self.touch_handler.current_sequence_id) + { + // https://w3c.github.io/touch-events/#cancelability + event.disable_cancelable(); + match action { + TouchMoveAction::Scroll(delta, point) => self.on_scroll_window_event( + ScrollLocation::Delta(LayoutVector2D::from_untyped(delta.to_untyped())), + point.cast(), + ), + TouchMoveAction::Zoom(magnification, scroll_delta) => { + let cursor = Point2D::new(-1, -1); // Make sure this hits the base layer. + + // The order of these events doesn't matter, because zoom is handled by + // a root display list and the scroll event here is handled by the scroll + // applied to the content display list. + self.pending_scroll_zoom_events + .push(ScrollZoomEvent::PinchZoom(magnification)); + self.pending_scroll_zoom_events + .push(ScrollZoomEvent::Scroll(ScrollEvent { + scroll_location: ScrollLocation::Delta( + LayoutVector2D::from_untyped(scroll_delta.to_untyped()), + ), + cursor, + event_count: 1, + })); + }, + _ => {}, + } + } + // When the event is touchmove, if the script thread is processing the touch + // move event, we skip sending the event to the script thread. + // This prevents the script thread from stacking up for a large amount of time. + if !self + .touch_handler + .is_handling_touch_move(self.touch_handler.current_sequence_id) && + self.send_touch_event(event) && + event.is_cancelable() + { + self.touch_handler + .set_handling_touch_move(self.touch_handler.current_sequence_id, true); + } + } + } + + fn on_touch_up(&mut self, event: TouchEvent) { + self.touch_handler.on_touch_up(event.id, event.point); + self.send_touch_event(event); + } + + fn on_touch_cancel(&mut self, event: TouchEvent) { + // Send the event to script. + self.touch_handler.on_touch_cancel(event.id, event.point); + self.send_touch_event(event); + } + + pub(crate) fn on_touch_event_processed(&mut self, result: TouchEventResult) { + match result { + TouchEventResult::DefaultPrevented(sequence_id, event_type) => { + debug!( + "Touch event {:?} in sequence {:?} prevented!", + event_type, sequence_id + ); + match event_type { + TouchEventType::Down => { + // prevents both click and move + self.touch_handler.prevent_click(sequence_id); + self.touch_handler.prevent_move(sequence_id); + self.touch_handler + .remove_pending_touch_move_action(sequence_id); + }, + TouchEventType::Move => { + // script thread processed the touch move event, mark this false. + if let Some(info) = self.touch_handler.get_touch_sequence_mut(sequence_id) { + info.prevent_move = TouchMoveAllowed::Prevented; + if let TouchSequenceState::PendingFling { .. } = info.state { + info.state = TouchSequenceState::Finished; + } + self.touch_handler.set_handling_touch_move( + self.touch_handler.current_sequence_id, + false, + ); + self.touch_handler + .remove_pending_touch_move_action(sequence_id); + } + }, + TouchEventType::Up => { + // Note: We don't have to consider PendingFling here, since we handle that + // in the DefaultAllowed case of the touch_move event. + // Note: Removing can and should fail, if we still have an active Fling, + let Some(info) = + &mut self.touch_handler.get_touch_sequence_mut(sequence_id) + else { + // The sequence ID could already be removed, e.g. if Fling finished, + // before the touch_up event was handled (since fling can start + // immediately if move was previously allowed, and clicks are anyway not + // happening from fling). + return; + }; + match info.state { + TouchSequenceState::PendingClick(_) => { + info.state = TouchSequenceState::Finished; + self.touch_handler.remove_touch_sequence(sequence_id); + }, + TouchSequenceState::Flinging { .. } => { + // We can't remove the touch sequence yet + }, + TouchSequenceState::Finished => { + self.touch_handler.remove_touch_sequence(sequence_id); + }, + TouchSequenceState::Touching | + TouchSequenceState::Panning { .. } | + TouchSequenceState::Pinching | + TouchSequenceState::MultiTouch | + TouchSequenceState::PendingFling { .. } => { + // It's possible to transition from Pinch to pan, Which means that + // a touch_up event for a pinch might have arrived here, but we + // already transitioned to pan or even PendingFling. + // We don't need to do anything in these cases though. + }, + } + }, + TouchEventType::Cancel => { + // We could still have pending event handlers, so we remove the pending + // actions, and try to remove the touch sequence. + self.touch_handler + .remove_pending_touch_move_action(sequence_id); + self.touch_handler.try_remove_touch_sequence(sequence_id); + }, + } + }, + TouchEventResult::DefaultAllowed(sequence_id, event_type) => { + debug!( + "Touch event {:?} in sequence {:?} allowed", + event_type, sequence_id + ); + match event_type { + TouchEventType::Down => {}, + TouchEventType::Move => { + if let Some(action) = + self.touch_handler.pending_touch_move_action(sequence_id) + { + match action { + TouchMoveAction::Scroll(delta, point) => self + .on_scroll_window_event( + ScrollLocation::Delta(LayoutVector2D::from_untyped( + delta.to_untyped(), + )), + point.cast(), + ), + TouchMoveAction::Zoom(magnification, scroll_delta) => { + let cursor = Point2D::new(-1, -1); + // Make sure this hits the base layer. + // The order of these events doesn't matter, because zoom is handled by + // a root display list and the scroll event here is handled by the scroll + // applied to the content display list. + self.pending_scroll_zoom_events + .push(ScrollZoomEvent::PinchZoom(magnification)); + self.pending_scroll_zoom_events + .push(ScrollZoomEvent::Scroll(ScrollEvent { + scroll_location: ScrollLocation::Delta( + LayoutVector2D::from_untyped( + scroll_delta.to_untyped(), + ), + ), + cursor, + event_count: 1, + })); + }, + TouchMoveAction::NoAction => { + // This shouldn't happen, but we can also just ignore it. + }, + } + self.touch_handler + .remove_pending_touch_move_action(sequence_id); + } + self.touch_handler + .set_handling_touch_move(self.touch_handler.current_sequence_id, false); + if let Some(info) = self.touch_handler.get_touch_sequence_mut(sequence_id) { + if info.prevent_move == TouchMoveAllowed::Pending { + info.prevent_move = TouchMoveAllowed::Allowed; + if let TouchSequenceState::PendingFling { velocity, cursor } = + info.state + { + info.state = TouchSequenceState::Flinging { velocity, cursor } + } + } + } + }, + TouchEventType::Up => { + let Some(info) = self.touch_handler.get_touch_sequence_mut(sequence_id) + else { + // The sequence was already removed because there is no default action. + return; + }; + match info.state { + TouchSequenceState::PendingClick(point) => { + info.state = TouchSequenceState::Finished; + // PreventDefault from touch_down may have been processed after + // touch_up already occurred. + if !info.prevent_click { + self.simulate_mouse_click(point); + } + self.touch_handler.remove_touch_sequence(sequence_id); + }, + TouchSequenceState::Flinging { .. } => { + // We can't remove the touch sequence yet + }, + TouchSequenceState::Finished => { + self.touch_handler.remove_touch_sequence(sequence_id); + }, + TouchSequenceState::Panning { .. } | + TouchSequenceState::Pinching | + TouchSequenceState::PendingFling { .. } => { + // It's possible to transition from Pinch to pan, Which means that + // a touch_up event for a pinch might have arrived here, but we + // already transitioned to pan or even PendingFling. + // We don't need to do anything in these cases though. + }, + TouchSequenceState::MultiTouch | TouchSequenceState::Touching => { + // We transitioned to touching from multi-touch or pinching. + }, + } + }, + TouchEventType::Cancel => { + self.touch_handler + .remove_pending_touch_move_action(sequence_id); + self.touch_handler.try_remove_touch_sequence(sequence_id); + }, + } + }, + } + } + + /// + fn simulate_mouse_click(&mut self, point: DevicePoint) { + let button = MouseButton::Left; + self.dispatch_input_event(InputEvent::MouseMove(MouseMoveEvent { point })); + self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent { + button, + action: MouseButtonAction::Down, + point, + })); + self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent { + button, + action: MouseButtonAction::Up, + point, + })); + } + + pub(crate) fn notify_scroll_event( + &mut self, + scroll_location: ScrollLocation, + cursor: DeviceIntPoint, + event_type: TouchEventType, + ) { + if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown { + return; + } + + match event_type { + TouchEventType::Move => self.on_scroll_window_event(scroll_location, cursor), + TouchEventType::Up | TouchEventType::Cancel => { + self.on_scroll_window_event(scroll_location, cursor); + }, + TouchEventType::Down => { + self.on_scroll_window_event(scroll_location, cursor); + }, + } + } + + fn on_scroll_window_event(&mut self, scroll_location: ScrollLocation, cursor: DeviceIntPoint) { + self.pending_scroll_zoom_events + .push(ScrollZoomEvent::Scroll(ScrollEvent { + scroll_location, + cursor, + event_count: 1, + })); + } + + /// Push scroll pending event when receiving wheel action from webdriver + pub(crate) fn on_webdriver_wheel_action( + &mut self, + scroll_delta: Vector2D, + point: Point2D, + ) { + if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown { + return; + } + + let scroll_location = + ScrollLocation::Delta(LayoutVector2D::from_untyped(scroll_delta.to_untyped())); + let cursor = DeviceIntPoint::new(point.x as i32, point.y as i32); + self.on_scroll_window_event(scroll_location, cursor) + } + + pub(crate) fn process_pending_scroll_events(&mut self, compositor: &mut IOCompositor) { + if self.pending_scroll_zoom_events.is_empty() { + return; + } + + // Batch up all scroll events into one, or else we'll do way too much painting. + let mut combined_scroll_event: Option = None; + let mut combined_magnification = 1.0; + for scroll_event in self.pending_scroll_zoom_events.drain(..) { + match scroll_event { + ScrollZoomEvent::PinchZoom(magnification) => { + combined_magnification *= magnification + }, + ScrollZoomEvent::Scroll(scroll_event_info) => { + let combined_event = match combined_scroll_event.as_mut() { + None => { + combined_scroll_event = Some(scroll_event_info); + continue; + }, + Some(combined_event) => combined_event, + }; + + match ( + combined_event.scroll_location, + scroll_event_info.scroll_location, + ) { + (ScrollLocation::Delta(old_delta), ScrollLocation::Delta(new_delta)) => { + // Mac OS X sometimes delivers scroll events out of vsync during a + // fling. This causes events to get bunched up occasionally, causing + // nasty-looking "pops". To mitigate this, during a fling we average + // deltas instead of summing them. + let old_event_count = Scale::new(combined_event.event_count as f32); + combined_event.event_count += 1; + let new_event_count = Scale::new(combined_event.event_count as f32); + combined_event.scroll_location = ScrollLocation::Delta( + (old_delta * old_event_count + new_delta) / new_event_count, + ); + }, + (ScrollLocation::Start, _) | (ScrollLocation::End, _) => { + // Once we see Start or End, we shouldn't process any more events. + break; + }, + (_, ScrollLocation::Start) | (_, ScrollLocation::End) => { + // If this is an event which is scrolling to the start or end of the page, + // disregard other pending events and exit the loop. + *combined_event = scroll_event_info; + break; + }, + } + }, + } + } + + let zoom_changed = + self.set_pinch_zoom_level(self.pinch_zoom_level().get() * combined_magnification); + let scroll_result = combined_scroll_event.and_then(|combined_event| { + self.scroll_node_at_device_point( + combined_event.cursor.to_f32(), + combined_event.scroll_location, + ) + }); + if !zoom_changed && scroll_result.is_none() { + return; + } + + let mut transaction = Transaction::new(); + if zoom_changed { + compositor.send_root_pipeline_display_list_in_transaction(&mut transaction); + } + + if let Some((pipeline_id, external_id, offset)) = scroll_result { + let offset = LayoutVector2D::new(-offset.x, -offset.y); + transaction.set_scroll_offsets( + external_id, + vec![SampledScrollOffset { + offset, + generation: 0, + }], + ); + self.send_scroll_positions_to_layout_for_pipeline(pipeline_id); + } + + compositor.generate_frame(&mut transaction, RenderReasons::APZ); + self.global.borrow_mut().send_transaction(transaction); + } + + /// Perform a hit test at the given [`DevicePoint`] and apply the [`ScrollLocation`] + /// scrolling to the applicable scroll node under that point. If a scroll was + /// performed, returns the [`PipelineId`] of the node scrolled, the id, and the final + /// scroll delta. + fn scroll_node_at_device_point( + &mut self, + cursor: DevicePoint, + scroll_location: ScrollLocation, + ) -> Option<(PipelineId, ExternalScrollId, LayoutVector2D)> { + let scroll_location = match scroll_location { + ScrollLocation::Delta(delta) => { + let device_pixels_per_page = self.device_pixels_per_page_pixel(); + let scaled_delta = (Vector2D::from_untyped(delta.to_untyped()) / + device_pixels_per_page) + .to_untyped(); + let calculated_delta = LayoutVector2D::from_untyped(scaled_delta); + ScrollLocation::Delta(calculated_delta) + }, + // Leave ScrollLocation unchanged if it is Start or End location. + ScrollLocation::Start | ScrollLocation::End => scroll_location, + }; + + let get_pipeline_details = |pipeline_id| self.pipelines.get(&pipeline_id); + let hit_test_results = self + .global + .borrow() + .hit_test_at_point_with_flags_and_pipeline( + cursor, + HitTestFlags::FIND_ALL, + None, + get_pipeline_details, + ); + + // Iterate through all hit test results, processing only the first node of each pipeline. + // This is needed to propagate the scroll events from a pipeline representing an iframe to + // its ancestor pipelines. + let mut previous_pipeline_id = None; + for CompositorHitTestResult { + pipeline_id, + scroll_tree_node, + .. + } in hit_test_results.iter() + { + let pipeline_details = self.pipelines.get_mut(pipeline_id)?; + if previous_pipeline_id.replace(pipeline_id) != Some(pipeline_id) { + let scroll_result = pipeline_details + .scroll_tree + .scroll_node_or_ancestor(scroll_tree_node, scroll_location); + if let Some((external_id, offset)) = scroll_result { + return Some((*pipeline_id, external_id, offset)); + } + } + } + None + } + + pub(crate) fn pinch_zoom_level(&self) -> Scale { + Scale::new(self.viewport_zoom.get()) + } + + fn set_pinch_zoom_level(&mut self, mut zoom: f32) -> bool { + if let Some(min) = self.min_viewport_zoom { + zoom = f32::max(min.get(), zoom); + } + if let Some(max) = self.max_viewport_zoom { + zoom = f32::min(max.get(), zoom); + } + + let old_zoom = std::mem::replace(&mut self.viewport_zoom, PinchZoomFactor::new(zoom)); + old_zoom != self.viewport_zoom + } + + pub(crate) fn set_page_zoom(&mut self, magnification: f32) { + self.page_zoom = + Scale::new((self.page_zoom.get() * magnification).clamp(MIN_ZOOM, MAX_ZOOM)); + } + + pub(crate) fn device_pixels_per_page_pixel(&self) -> Scale { + self.page_zoom * self.hidpi_scale_factor * self.pinch_zoom_level() + } + + pub(crate) fn device_pixels_per_page_pixel_not_including_pinch_zoom( + &self, + ) -> Scale { + self.page_zoom * self.hidpi_scale_factor + } + + /// Simulate a pinch zoom + pub(crate) fn set_pinch_zoom(&mut self, magnification: f32) { + if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown { + return; + } + + // TODO: Scroll to keep the center in view? + self.pending_scroll_zoom_events + .push(ScrollZoomEvent::PinchZoom(magnification)); + } + + fn send_window_size_message(&self) { + // The device pixel ratio used by the style system should include the scale from page pixels + // to device pixels, but not including any pinch zoom. + let device_pixel_ratio = self.device_pixels_per_page_pixel_not_including_pinch_zoom(); + let initial_viewport = self.rect.size().to_f32() / device_pixel_ratio; + let msg = EmbedderToConstellationMessage::ChangeViewportDetails( + self.id, + ViewportDetails { + hidpi_scale_factor: device_pixel_ratio, + size: initial_viewport, + }, + WindowSizeType::Resize, + ); + if let Err(e) = self.global.borrow().constellation_sender.send(msg) { + warn!("Sending window resize to constellation failed ({:?}).", e); + } + } + + /// Set the `hidpi_scale_factor` for this renderer, returning `true` if the value actually changed. + pub(crate) fn set_hidpi_scale_factor( + &mut self, + new_scale: Scale, + ) -> bool { + let old_scale_factor = std::mem::replace(&mut self.hidpi_scale_factor, new_scale); + if self.hidpi_scale_factor == old_scale_factor { + return false; + } + + self.send_window_size_message(); + true + } + + /// Set the `rect` for this renderer, returning `true` if the value actually changed. + pub(crate) fn set_rect(&mut self, new_rect: DeviceRect) -> bool { + let old_rect = std::mem::replace(&mut self.rect, new_rect); + if old_rect.size() != self.rect.size() { + self.send_window_size_message(); + } + old_rect != self.rect + } + + pub(crate) fn client_window_rect( + &self, + rendering_context_size: Size2D, + ) -> Box2D { + let screen_geometry = self.webview.screen_geometry().unwrap_or_default(); + let rect = DeviceIntRect::from_origin_and_size( + screen_geometry.offset, + rendering_context_size.to_i32(), + ) + .to_f32() / + self.hidpi_scale_factor; + rect.to_i32() + } + + pub(crate) fn screen_size(&self) -> Size2D { + let screen_geometry = self.webview.screen_geometry().unwrap_or_default(); + (screen_geometry.size.to_f32() / self.hidpi_scale_factor).to_i32() + } + + pub(crate) fn available_screen_size(&self) -> Size2D { + let screen_geometry = self.webview.screen_geometry().unwrap_or_default(); + (screen_geometry.available_size.to_f32() / self.hidpi_scale_factor).to_i32() + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct UnknownWebView(pub WebViewId); diff --git a/components/compositing/windowing.rs b/components/compositing/windowing.rs deleted file mode 100644 index 2ac4e82b81d..00000000000 --- a/components/compositing/windowing.rs +++ /dev/null @@ -1,89 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! Abstract windowing methods. The concrete implementations of these can be found in `platform/`. - -use std::fmt::Debug; - -use embedder_traits::{EventLoopWaker, MouseButton}; -use euclid::Scale; -use net::protocols::ProtocolRegistry; -use servo_geometry::{DeviceIndependentIntRect, DeviceIndependentIntSize, DeviceIndependentPixel}; -use webrender_api::units::{DevicePixel, DevicePoint}; - -#[derive(Clone)] -pub enum MouseWindowEvent { - Click(MouseButton, DevicePoint), - MouseDown(MouseButton, DevicePoint), - MouseUp(MouseButton, DevicePoint), -} - -/// Various debug and profiling flags that WebRender supports. -#[derive(Clone)] -pub enum WebRenderDebugOption { - Profiler, - TextureCacheDebug, - RenderTargetDebug, -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum AnimationState { - Idle, - Animating, -} - -// TODO: this trait assumes that the window is responsible -// for creating the GL context, making it current, buffer -// swapping, etc. Really that should all be done by surfman. -pub trait WindowMethods { - /// Get the coordinates of the native window, the screen and the framebuffer. - fn get_coordinates(&self) -> EmbedderCoordinates; - /// Set whether the application is currently animating. - /// Typically, when animations are active, the window - /// will want to avoid blocking on UI events, and just - /// run the event loop at the vsync interval. - fn set_animation_state(&self, _state: AnimationState); -} - -pub trait EmbedderMethods { - /// Returns a thread-safe object to wake up the window's event loop. - fn create_event_loop_waker(&mut self) -> Box; - - #[cfg(feature = "webxr")] - /// Register services with a WebXR Registry. - fn register_webxr( - &mut self, - _: &mut webxr::MainThreadRegistry, - _: embedder_traits::EmbedderProxy, - ) { - } - - /// Returns the user agent string to report in network requests. - fn get_user_agent_string(&self) -> Option { - None - } - - /// Returns the version string of this embedder. - fn get_version_string(&self) -> Option { - None - } - - /// Returns the protocol handlers implemented by that embedder. - /// They will be merged with the default internal ones. - fn get_protocol_handlers(&self) -> ProtocolRegistry { - ProtocolRegistry::default() - } -} - -#[derive(Clone, Copy, Debug)] -pub struct EmbedderCoordinates { - /// The pixel density of the display. - pub hidpi_factor: Scale, - /// Size of the screen. - pub screen_size: DeviceIndependentIntSize, - /// Size of the available screen space (screen without toolbars and docks). - pub available_screen_size: DeviceIndependentIntSize, - /// Position and size of the native window. - pub window_rect: DeviceIndependentIntRect, -} diff --git a/components/config/prefs.rs b/components/config/prefs.rs index f8dfb63a7ac..a9ec112e3eb 100644 --- a/components/config/prefs.rs +++ b/components/config/prefs.rs @@ -9,7 +9,7 @@ use servo_config_macro::ServoPreferences; pub use crate::pref_util::PrefValue; -static PREFERENCES: RwLock = RwLock::new(Preferences::new()); +static PREFERENCES: RwLock = RwLock::new(Preferences::const_default()); #[inline] /// Get the current set of global preferences for Servo. @@ -69,6 +69,7 @@ pub struct Preferences { /// List of comma-separated backends to be used by wgpu. pub dom_webgpu_wgpu_backend: String, pub dom_abort_controller_enabled: bool, + pub dom_async_clipboard_enabled: bool, pub dom_bluetooth_enabled: bool, pub dom_bluetooth_testing_enabled: bool, pub dom_allow_scripts_to_close_windows: bool, @@ -81,7 +82,6 @@ pub struct Preferences { pub dom_document_dblclick_timeout: i64, pub dom_document_dblclick_dist: i64, pub dom_fontface_enabled: bool, - pub dom_forcetouch_enabled: bool, pub dom_fullscreen_test: bool, pub dom_gamepad_enabled: bool, pub dom_imagebitmap_enabled: bool, @@ -114,6 +114,13 @@ pub struct Preferences { pub dom_testing_element_activation_enabled: bool, pub dom_testing_html_input_element_select_files_enabled: bool, pub dom_testperf_enabled: bool, + // https://testutils.spec.whatwg.org#availability + pub dom_testutils_enabled: bool, + pub dom_trusted_types_enabled: bool, + /// Enable the [URLPattern] API. + /// + /// [URLPattern]: https://developer.mozilla.org/en-US/docs/Web/API/URLPattern + pub dom_urlpattern_enabled: bool, pub dom_xpath_enabled: bool, /// Enable WebGL2 APIs. pub dom_webgl2_enabled: bool, @@ -226,16 +233,22 @@ pub struct Preferences { pub threadpools_resource_workers_max: i64, /// Maximum number of workers for webrender pub threadpools_webrender_workers_max: i64, + /// The user-agent to use for Servo. This can also be set via [`UserAgentPlatform`] in + /// order to set the value to the default value for the given platform. + pub user_agent: String, + + pub log_filter: String, } impl Preferences { - const fn new() -> Self { + const fn const_default() -> Self { Self { css_animations_testing_enabled: false, devtools_server_enabled: false, devtools_server_port: 0, dom_abort_controller_enabled: false, dom_allow_scripts_to_close_windows: false, + dom_async_clipboard_enabled: false, dom_bluetooth_enabled: false, dom_bluetooth_testing_enabled: false, dom_canvas_capture_enabled: false, @@ -247,7 +260,6 @@ impl Preferences { dom_document_dblclick_dist: 1, dom_document_dblclick_timeout: 300, dom_fontface_enabled: false, - dom_forcetouch_enabled: false, dom_fullscreen_test: false, dom_gamepad_enabled: true, dom_imagebitmap_enabled: false, @@ -280,6 +292,9 @@ impl Preferences { dom_testing_element_activation_enabled: false, dom_testing_html_input_element_select_files_enabled: false, dom_testperf_enabled: false, + dom_testutils_enabled: false, + dom_trusted_types_enabled: false, + dom_urlpattern_enabled: false, dom_webgl2_enabled: false, dom_webgpu_enabled: false, dom_webgpu_wgpu_backend: String::new(), @@ -384,12 +399,88 @@ impl Preferences { threadpools_resource_workers_max: 4, threadpools_webrender_workers_max: 4, webgl_testing_context_creation_error: false, + user_agent: String::new(), + log_filter: String::new(), } } } impl Default for Preferences { fn default() -> Self { - Self::new() + let mut preferences = Self::const_default(); + preferences.user_agent = UserAgentPlatform::default().to_user_agent_string(); + preferences + } +} + +pub enum UserAgentPlatform { + Desktop, + Android, + OpenHarmony, + Ios, +} + +impl UserAgentPlatform { + /// Return the default `UserAgentPlatform` for this platform. This is + /// not an implementation of `Default` so that it can be `const`. + const fn default() -> Self { + if cfg!(target_os = "android") { + Self::Android + } else if cfg!(target_env = "ohos") { + Self::OpenHarmony + } else if cfg!(target_os = "ios") { + Self::Ios + } else { + Self::Desktop + } + } +} + +impl UserAgentPlatform { + /// Convert this [`UserAgentPlatform`] into its corresponding `String` value, ie the + /// default user-agent to use for this platform. + pub fn to_user_agent_string(&self) -> String { + const SERVO_VERSION: &str = env!("CARGO_PKG_VERSION"); + match self { + UserAgentPlatform::Desktop + if cfg!(all(target_os = "windows", target_arch = "x86_64")) => + { + #[cfg(target_arch = "x86_64")] + const ARCHITECTURE: &str = "x86; "; + #[cfg(not(target_arch = "x86_64"))] + const ARCHITECTURE: &str = ""; + + format!( + "Mozilla/5.0 (Windows NT 10.0; Win64; {ARCHITECTURE}rv:128.0) Servo/{SERVO_VERSION} Firefox/128.0" + ) + }, + UserAgentPlatform::Desktop if cfg!(target_os = "macos") => { + format!( + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:128.0) Servo/{SERVO_VERSION} Firefox/128.0" + ) + }, + UserAgentPlatform::Desktop => { + #[cfg(target_arch = "x86_64")] + const ARCHITECTURE: &str = "x86_64"; + // TODO: This is clearly wrong for other platforms. + #[cfg(not(target_arch = "x86_64"))] + const ARCHITECTURE: &str = "i686"; + + format!( + "Mozilla/5.0 (X11; Linux {ARCHITECTURE}; rv:128.0) Servo/{SERVO_VERSION} Firefox/128.0" + ) + }, + UserAgentPlatform::Android => { + format!( + "Mozilla/5.0 (Android; Mobile; rv:128.0) Servo/{SERVO_VERSION} Firefox/128.0" + ) + }, + UserAgentPlatform::OpenHarmony => format!( + "Mozilla/5.0 (OpenHarmony; Mobile; rv:128.0) Servo/{SERVO_VERSION} Firefox/128.0" + ), + UserAgentPlatform::Ios => format!( + "Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X; rv:128.0) Servo/{SERVO_VERSION} Firefox/128.0" + ), + } } } diff --git a/components/constellation/Cargo.toml b/components/constellation/Cargo.toml index 10623c540d6..1053a23948a 100644 --- a/components/constellation/Cargo.toml +++ b/components/constellation/Cargo.toml @@ -14,13 +14,12 @@ path = "lib.rs" [features] bluetooth = ["bluetooth_traits"] default = [] -multiview = [] tracing = ["dep:tracing"] webgpu = ["script_traits/webgpu"] [dependencies] -background_hang_monitor_api = { workspace = true } background_hang_monitor = { path = "../background_hang_monitor" } +background_hang_monitor_api = { workspace = true } backtrace = { workspace = true } base = { workspace = true } bluetooth_traits = { workspace = true, optional = true } @@ -39,6 +38,7 @@ media = { path = "../media" } net = { path = "../net" } net_traits = { workspace = true } parking_lot = { workspace = true } +profile = { path = "../profile" } profile_traits = { workspace = true } script_layout_interface = { workspace = true } script_traits = { workspace = true } @@ -49,9 +49,9 @@ servo_url = { path = "../url" } stylo_traits = { workspace = true } tracing = { workspace = true, optional = true } webgpu = { path = "../webgpu" } +webgpu_traits = { workspace = true } webrender = { workspace = true } webrender_api = { workspace = true } -webrender_traits = { workspace = true } webxr-api = { workspace = true, features = ["ipc"] } [target.'cfg(any(target_os="macos", all(not(target_os = "windows"), not(target_os = "ios"), not(target_os="android"), not(target_env="ohos"), not(target_arch="arm"), not(target_arch="aarch64"))))'.dependencies] diff --git a/components/constellation/browsingcontext.rs b/components/constellation/browsingcontext.rs index 6f8b24dabcb..0a6974a1f2a 100644 --- a/components/constellation/browsingcontext.rs +++ b/components/constellation/browsingcontext.rs @@ -5,9 +5,8 @@ use std::collections::{HashMap, HashSet}; use base::id::{BrowsingContextGroupId, BrowsingContextId, PipelineId, WebViewId}; -use euclid::Size2D; +use embedder_traits::ViewportDetails; use log::warn; -use style_traits::CSSPixel; use crate::pipeline::Pipeline; @@ -50,8 +49,8 @@ pub struct BrowsingContext { /// The top-level browsing context ancestor pub top_level_id: WebViewId, - /// The size of the frame. - pub size: Size2D, + /// The [`ViewportDetails`] of the frame that this [`BrowsingContext`] represents. + pub viewport_details: ViewportDetails, /// Whether this browsing context is in private browsing mode. pub is_private: bool, @@ -85,7 +84,7 @@ impl BrowsingContext { top_level_id: WebViewId, pipeline_id: PipelineId, parent_pipeline_id: Option, - size: Size2D, + viewport_details: ViewportDetails, is_private: bool, inherited_secure_context: Option, throttled: bool, @@ -96,7 +95,7 @@ impl BrowsingContext { bc_group_id, id, top_level_id, - size, + viewport_details, is_private, inherited_secure_context, throttled, diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index b8fd7a465aa..5db37800c42 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -84,7 +84,7 @@ //! //! See -use std::borrow::{Cow, ToOwned}; +use std::borrow::ToOwned; use std::collections::hash_map::Entry; use std::collections::{HashMap, HashSet, VecDeque}; use std::marker::PhantomData; @@ -108,12 +108,19 @@ use bluetooth_traits::BluetoothRequest; use canvas_traits::ConstellationCanvasMsg; use canvas_traits::canvas::{CanvasId, CanvasMsg}; use canvas_traits::webgl::WebGLThreads; -use compositing_traits::{CompositorMsg, CompositorProxy, SendableFrameTree}; -use constellation_traits::{ - AnimationTickType, CompositorHitTestResult, ConstellationMsg as FromCompositorMsg, LogEntry, - PaintMetricEvent, ScrollState, TraversalDirection, WindowSizeData, WindowSizeType, +use compositing_traits::{ + CompositorMsg, CompositorProxy, SendableFrameTree, WebrenderExternalImageRegistry, }; -use crossbeam_channel::{Receiver, Sender, select, unbounded}; +use constellation_traits::{ + AuxiliaryWebViewCreationRequest, AuxiliaryWebViewCreationResponse, BroadcastMsg, DocumentState, + EmbedderToConstellationMessage, IFrameLoadInfo, IFrameLoadInfoWithData, IFrameSandboxState, + IFrameSizeMsg, Job, LoadData, LoadOrigin, LogEntry, MessagePortMsg, NavigationHistoryBehavior, + PaintMetricEvent, PortMessageTask, PortTransferInfo, SWManagerMsg, SWManagerSenders, + ScriptToConstellationChan, ScriptToConstellationMessage, ScrollState, + ServiceWorkerManagerFactory, ServiceWorkerMsg, StructuredSerializedData, TraversalDirection, + WindowSizeType, +}; +use crossbeam_channel::{Receiver, Select, Sender, unbounded}; use devtools_traits::{ ChromeToDevtoolsControlMsg, DevtoolsControlMsg, DevtoolsPageInfo, NavigationState, ScriptToDevtoolsControlMsg, @@ -121,9 +128,11 @@ use devtools_traits::{ use embedder_traits::resources::{self, Resource}; use embedder_traits::user_content_manager::UserContentManager; use embedder_traits::{ - Cursor, EmbedderMsg, EmbedderProxy, ImeEvent, InputEvent, MediaSessionActionType, - MediaSessionEvent, MediaSessionPlaybackState, MouseButton, MouseButtonAction, MouseButtonEvent, - Theme, WebDriverCommandMsg, WebDriverLoadStatus, + AnimationState, CompositorHitTestResult, Cursor, EmbedderMsg, EmbedderProxy, + FocusSequenceNumber, ImeEvent, InputEvent, JSValue, JavaScriptEvaluationError, + JavaScriptEvaluationId, MediaSessionActionType, MediaSessionEvent, MediaSessionPlaybackState, + MouseButton, MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, WebDriverCommandMsg, + WebDriverLoadStatus, }; use euclid::Size2D; use euclid::default::Size2D as UntypedSize2D; @@ -142,13 +151,8 @@ use net_traits::{self, IpcSend, ReferrerPolicy, ResourceThreads}; use profile_traits::{mem, time}; use script_layout_interface::{LayoutFactory, ScriptThreadFactory}; use script_traits::{ - AnimationState, AuxiliaryWebViewCreationRequest, AuxiliaryWebViewCreationResponse, - BroadcastMsg, ConstellationInputEvent, DiscardBrowsingContext, DocumentActivity, DocumentState, - IFrameLoadInfo, IFrameLoadInfoWithData, IFrameSandboxState, IFrameSizeMsg, Job, LoadData, - LoadOrigin, MessagePortMsg, NavigationHistoryBehavior, PortMessageTask, - ProgressiveWebMetricType, SWManagerMsg, SWManagerSenders, ScriptMsg as FromScriptMsg, - ScriptThreadMessage, ScriptToConstellationChan, ServiceWorkerManagerFactory, ServiceWorkerMsg, - StructuredSerializedData, UpdatePipelineIdReason, + ConstellationInputEvent, DiscardBrowsingContext, DocumentActivity, ProgressiveWebMetricType, + ScriptThreadMessage, UpdatePipelineIdReason, }; use serde::{Deserialize, Serialize}; use servo_config::{opts, pref}; @@ -158,12 +162,11 @@ use style_traits::CSSPixel; #[cfg(feature = "webgpu")] use webgpu::swapchain::WGPUImageMap; #[cfg(feature = "webgpu")] -use webgpu::{self, WebGPU, WebGPURequest}; +use webgpu_traits::{WebGPU, WebGPURequest}; #[cfg(feature = "webgpu")] use webrender::RenderApi; use webrender::RenderApiSender; use webrender_api::{DocumentId, ImageKey}; -use webrender_traits::WebrenderExternalImageRegistry; use crate::browsingcontext::{ AllBrowsingContextsIterator, BrowsingContext, FullyActiveBrowsingContextsIterator, @@ -171,11 +174,12 @@ use crate::browsingcontext::{ }; use crate::event_loop::EventLoop; use crate::pipeline::{InitialPipelineState, Pipeline}; +use crate::process_manager::ProcessManager; use crate::serviceworker::ServiceWorkerUnprivilegedContent; use crate::session_history::{ JointSessionHistory, NeedsToReload, SessionHistoryChange, SessionHistoryDiff, }; -use crate::webview::WebViewManager; +use crate::webview_manager::WebViewManager; type PendingApprovalNavigations = HashMap; @@ -200,9 +204,6 @@ enum TransferState { /// While a completion failed, another global requested to complete the transfer. /// We are still buffering messages, and awaiting the return of the buffer from the global who failed. CompletionRequested(MessagePortRouterId, VecDeque), - /// The entangled port has been removed while the port was in-transfer, - /// the current port should be removed as well once it is managed again. - EntangledRemoved, } #[derive(Debug)] @@ -284,11 +285,11 @@ pub struct Constellation { /// An IPC channel for script threads to send messages to the constellation. /// This is the script threads' view of `script_receiver`. - script_sender: IpcSender<(PipelineId, FromScriptMsg)>, + script_sender: IpcSender<(PipelineId, ScriptToConstellationMessage)>, /// A channel for the constellation to receive messages from script threads. /// This is the constellation's view of `script_sender`. - script_receiver: Receiver>, + script_receiver: Receiver>, /// A handle to register components for hang monitoring. /// None when in multiprocess mode. @@ -313,7 +314,7 @@ pub struct Constellation { layout_factory: Arc, /// A channel for the constellation to receive messages from the compositor thread. - compositor_receiver: Receiver, + compositor_receiver: Receiver, /// A channel through which messages can be sent to the embedder. embedder_proxy: EmbedderProxy, @@ -371,6 +372,7 @@ pub struct Constellation { mem_profiler_chan: mem::ProfilerChan, /// A single WebRender document the constellation operates on. + #[cfg(feature = "webgpu")] webrender_document: DocumentId, /// Webrender related objects required by WebGPU threads @@ -415,9 +417,6 @@ pub struct Constellation { /// and the namespaces are allocated by the constellation. next_pipeline_namespace_id: PipelineNamespaceId, - /// The size of the top-level window. - window_size: WindowSizeData, - /// Bits of state used to interact with the webdriver implementation webdriver: WebDriverData, @@ -465,9 +464,6 @@ pub struct Constellation { /// Pipeline ID of the active media session. active_media_session: Option, - /// User agent string to report in network requests. - user_agent: Cow<'static, str>, - /// The image bytes associated with the RippyPNG embedder resource. /// Read during startup and provided to image caches that are created /// on an as-needed basis, rather than retrieving it every time. @@ -475,6 +471,9 @@ pub struct Constellation { /// User content manager user_content_manager: UserContentManager, + + /// The process manager. + process_manager: ProcessManager, } /// State needed to construct a constellation. @@ -522,9 +521,6 @@ pub struct InitialConstellationState { /// The XR device registry pub webxr_registry: Option, - /// User agent string to report in network requests. - pub user_agent: Cow<'static, str>, - #[cfg(feature = "webgpu")] pub wgpu_image_map: WGPUImageMap, @@ -607,13 +603,12 @@ where pub fn start( state: InitialConstellationState, layout_factory: Arc, - initial_window_size: WindowSizeData, random_pipeline_closure_probability: Option, random_pipeline_closure_seed: Option, hard_fail: bool, canvas_create_sender: Sender, canvas_ipc_sender: IpcSender, - ) -> Sender { + ) -> Sender { let (compositor_sender, compositor_receiver) = unbounded(); // service worker manager to communicate with constellation @@ -718,11 +713,11 @@ where // namespace 0 for the embedder, and 0 for the constellation next_pipeline_namespace_id: PipelineNamespaceId(2), time_profiler_chan: state.time_profiler_chan, - mem_profiler_chan: state.mem_profiler_chan, - window_size: initial_window_size, + mem_profiler_chan: state.mem_profiler_chan.clone(), phantom: PhantomData, webdriver: WebDriverData::new(), document_states: HashMap::new(), + #[cfg(feature = "webgpu")] webrender_document: state.webrender_document, #[cfg(feature = "webgpu")] webrender_wgpu, @@ -744,9 +739,9 @@ where active_keyboard_modifiers: Modifiers::empty(), hard_fail, active_media_session: None, - user_agent: state.user_agent, rippy_data, user_content_manager: state.user_content_manager, + process_manager: ProcessManager::new(state.mem_profiler_chan), }; constellation.run(); @@ -884,7 +879,7 @@ where webview_id: WebViewId, parent_pipeline_id: Option, opener: Option, - initial_window_size: Size2D, + initial_viewport_details: ViewportDetails, // TODO: we have to provide ownership of the LoadData // here, because it will be send on an ipc channel, // and ipc channels take onership of their data. @@ -974,21 +969,16 @@ where resource_threads, time_profiler_chan: self.time_profiler_chan.clone(), mem_profiler_chan: self.mem_profiler_chan.clone(), - window_size: WindowSizeData { - initial_viewport: initial_window_size, - device_pixel_ratio: self.window_size.device_pixel_ratio, - }, + viewport_details: initial_viewport_details, event_loop, load_data, prev_throttled: throttled, - webrender_document: self.webrender_document, webgl_chan: self .webgl_threads .as_ref() .map(|threads| threads.pipeline()), webxr_registry: self.webxr_registry.clone(), player_context: WindowGLContext::get(), - user_agent: self.user_agent.clone(), rippy_data: self.rippy_data.clone(), user_content_manager: self.user_content_manager.clone(), }); @@ -1012,6 +1002,12 @@ where ); } + if let Some((lifeline_receiver, process)) = pipeline.lifeline { + let crossbeam_receiver = + route_ipc_receiver_to_new_crossbeam_receiver_preserving_errors(lifeline_receiver); + self.process_manager.add(crossbeam_receiver, process); + } + assert!(!self.pipelines.contains_key(&pipeline_id)); self.pipelines.insert(pipeline_id, pipeline.pipeline); } @@ -1048,6 +1044,44 @@ where } } + /// Enumerate the specified browsing context's ancestor pipelines up to + /// the top-level pipeline. + fn ancestor_pipelines_of_browsing_context_iter( + &self, + browsing_context_id: BrowsingContextId, + ) -> impl Iterator + '_ { + let mut state: Option = self + .browsing_contexts + .get(&browsing_context_id) + .and_then(|browsing_context| browsing_context.parent_pipeline_id); + std::iter::from_fn(move || { + if let Some(pipeline_id) = state { + let pipeline = self.pipelines.get(&pipeline_id)?; + let browsing_context = self.browsing_contexts.get(&pipeline.browsing_context_id)?; + state = browsing_context.parent_pipeline_id; + Some(pipeline) + } else { + None + } + }) + } + + /// Enumerate the specified browsing context's ancestor-or-self pipelines up + /// to the top-level pipeline. + fn ancestor_or_self_pipelines_of_browsing_context_iter( + &self, + browsing_context_id: BrowsingContextId, + ) -> impl Iterator + '_ { + let this_pipeline = self + .browsing_contexts + .get(&browsing_context_id) + .map(|browsing_context| browsing_context.pipeline_id) + .and_then(|pipeline_id| self.pipelines.get(&pipeline_id)); + this_pipeline + .into_iter() + .chain(self.ancestor_pipelines_of_browsing_context_iter(browsing_context_id)) + } + /// Create a new browsing context and update the internal bookkeeping. #[allow(clippy::too_many_arguments)] fn new_browsing_context( @@ -1056,7 +1090,7 @@ where top_level_id: WebViewId, pipeline_id: PipelineId, parent_pipeline_id: Option, - size: Size2D, + viewport_details: ViewportDetails, is_private: bool, inherited_secure_context: Option, throttled: bool, @@ -1089,7 +1123,7 @@ where top_level_id, pipeline_id, parent_pipeline_id, - size, + viewport_details, is_private, inherited_secure_context, throttled, @@ -1126,10 +1160,11 @@ where #[derive(Debug)] enum Request { PipelineNamespace(PipelineNamespaceRequest), - Script((PipelineId, FromScriptMsg)), + Script((PipelineId, ScriptToConstellationMessage)), BackgroundHangMonitor(HangMonitorAlert), - Compositor(FromCompositorMsg), + Compositor(EmbedderToConstellationMessage), FromSWManager(SWManagerMsg), + RemoveProcess(usize), } // Get one incoming request. // This is one of the few places where the compositor is @@ -1142,26 +1177,49 @@ where // produces undefined behaviour, resulting in the destructor // being called. If this happens, there's not much we can do // other than panic. + let mut sel = Select::new(); + sel.recv(&self.namespace_receiver); + sel.recv(&self.script_receiver); + sel.recv(&self.background_hang_monitor_receiver); + sel.recv(&self.compositor_receiver); + sel.recv(&self.swmanager_receiver); + + self.process_manager.register(&mut sel); + let request = { + let oper = sel.select(); + let index = oper.index(); + #[cfg(feature = "tracing")] let _span = tracing::trace_span!("handle_request::select", servo_profiling = true).entered(); - select! { - recv(self.namespace_receiver) -> msg => { - msg.expect("Unexpected script channel panic in constellation").map(Request::PipelineNamespace) - } - recv(self.script_receiver) -> msg => { - msg.expect("Unexpected script channel panic in constellation").map(Request::Script) - } - recv(self.background_hang_monitor_receiver) -> msg => { - msg.expect("Unexpected BHM channel panic in constellation").map(Request::BackgroundHangMonitor) - } - recv(self.compositor_receiver) -> msg => { - Ok(Request::Compositor(msg.expect("Unexpected compositor channel panic in constellation"))) - } - recv(self.swmanager_receiver) -> msg => { - msg.expect("Unexpected SW channel panic in constellation").map(Request::FromSWManager) - } + match index { + 0 => oper + .recv(&self.namespace_receiver) + .expect("Unexpected script channel panic in constellation") + .map(Request::PipelineNamespace), + 1 => oper + .recv(&self.script_receiver) + .expect("Unexpected script channel panic in constellation") + .map(Request::Script), + 2 => oper + .recv(&self.background_hang_monitor_receiver) + .expect("Unexpected BHM channel panic in constellation") + .map(Request::BackgroundHangMonitor), + 3 => Ok(Request::Compositor( + oper.recv(&self.compositor_receiver) + .expect("Unexpected compositor channel panic in constellation"), + )), + 4 => oper + .recv(&self.swmanager_receiver) + .expect("Unexpected SW channel panic in constellation") + .map(Request::FromSWManager), + _ => { + // This can only be a error reading on a closed lifeline receiver. + let process_index = index - 5; + let _ = oper.recv(self.process_manager.receiver_at(process_index)); + Ok(Request::RemoveProcess(process_index)) + }, } }; @@ -1184,6 +1242,7 @@ where Request::FromSWManager(message) => { self.handle_request_from_swmanager(message); }, + Request::RemoveProcess(index) => self.process_manager.remove(index), } } @@ -1226,19 +1285,27 @@ where feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn handle_request_from_compositor(&mut self, message: FromCompositorMsg) { + fn handle_request_from_compositor(&mut self, message: EmbedderToConstellationMessage) { trace_msg_from_compositor!(message, "{message:?}"); match message { - FromCompositorMsg::Exit => { + EmbedderToConstellationMessage::Exit => { self.handle_exit(); }, - FromCompositorMsg::GetFocusTopLevelBrowsingContext(resp_chan) => { - let _ = resp_chan.send(self.webviews.focused_webview().map(|(id, _)| id)); + EmbedderToConstellationMessage::GetFocusTopLevelBrowsingContext(resp_chan) => { + let focused_context = self + .webviews + .focused_webview() + .filter(|(_, webview)| { + self.browsing_contexts + .contains_key(&webview.focused_browsing_context_id) + }) + .map(|(id, _)| id); + let _ = resp_chan.send(focused_context); }, // Perform a navigation previously requested by script, if approved by the embedder. // If there is already a pending page (self.pending_changes), it will not be overridden; // However, if the id is not encompassed by another change, it will be. - FromCompositorMsg::AllowNavigationResponse(pipeline_id, allowed) => { + EmbedderToConstellationMessage::AllowNavigationResponse(pipeline_id, allowed) => { let pending = self.pending_approval_navigations.remove(&pipeline_id); let webview_id = match self.pipelines.get(&pipeline_id) { @@ -1285,14 +1352,14 @@ where }, } }, - FromCompositorMsg::ClearCache => { + EmbedderToConstellationMessage::ClearCache => { self.public_resource_threads.clear_cache(); self.private_resource_threads.clear_cache(); }, // Load a new page from a typed url // If there is already a pending page (self.pending_changes), it will not be overridden; // However, if the id is not encompassed by another change, it will be. - FromCompositorMsg::LoadUrl(webview_id, url) => { + EmbedderToConstellationMessage::LoadUrl(webview_id, url) => { let load_data = LoadData::new( LoadOrigin::Constellation, url, @@ -1301,6 +1368,7 @@ where ReferrerPolicy::EmptyString, None, None, + false, ); let ctx_id = BrowsingContextId::from(webview_id); let pipeline_id = match self.browsing_contexts.get(&ctx_id) { @@ -1318,7 +1386,7 @@ where NavigationHistoryBehavior::Push, ); }, - FromCompositorMsg::IsReadyToSaveImage(pipeline_states) => { + EmbedderToConstellationMessage::IsReadyToSaveImage(pipeline_states) => { let is_ready = self.handle_is_ready_to_save_image(pipeline_states); debug!("Ready to save image {:?}.", is_ready); self.compositor_proxy @@ -1328,57 +1396,65 @@ where }, // Create a new top level browsing context. Will use response_chan to return // the browsing context id. - FromCompositorMsg::NewWebView(url, webview_id) => { - self.handle_new_top_level_browsing_context(url, webview_id, None); + EmbedderToConstellationMessage::NewWebView(url, webview_id, viewport_details) => { + self.handle_new_top_level_browsing_context(url, webview_id, viewport_details, None); }, // Close a top level browsing context. - FromCompositorMsg::CloseWebView(webview_id) => { + EmbedderToConstellationMessage::CloseWebView(webview_id) => { self.handle_close_top_level_browsing_context(webview_id); }, // Panic a top level browsing context. - FromCompositorMsg::SendError(webview_id, error) => { + EmbedderToConstellationMessage::SendError(webview_id, error) => { debug!("constellation got SendError message"); if webview_id.is_none() { warn!("constellation got a SendError message without top level id"); } self.handle_panic(webview_id, error, None); }, - FromCompositorMsg::FocusWebView(webview_id) => { + EmbedderToConstellationMessage::FocusWebView(webview_id) => { self.handle_focus_web_view(webview_id); }, - FromCompositorMsg::BlurWebView => { + EmbedderToConstellationMessage::BlurWebView => { self.webviews.unfocus(); self.embedder_proxy.send(EmbedderMsg::WebViewBlurred); }, // Handle a forward or back request - FromCompositorMsg::TraverseHistory(webview_id, direction) => { + EmbedderToConstellationMessage::TraverseHistory(webview_id, direction) => { self.handle_traverse_history_msg(webview_id, direction); }, - FromCompositorMsg::WindowSize(webview_id, new_size, size_type) => { - self.handle_window_size_msg(webview_id, new_size, size_type); + EmbedderToConstellationMessage::ChangeViewportDetails( + webview_id, + new_viewport_details, + size_type, + ) => { + self.handle_change_viewport_details_msg( + webview_id, + new_viewport_details, + size_type, + ); }, - FromCompositorMsg::ThemeChange(theme) => { + EmbedderToConstellationMessage::ThemeChange(theme) => { self.handle_theme_change(theme); }, - FromCompositorMsg::TickAnimation(pipeline_id, tick_type) => { - self.handle_tick_animation(pipeline_id, tick_type) + EmbedderToConstellationMessage::TickAnimation(webview_ids) => { + self.handle_tick_animation(webview_ids) }, - FromCompositorMsg::WebDriverCommand(command) => { + EmbedderToConstellationMessage::WebDriverCommand(command) => { self.handle_webdriver_msg(command); }, - FromCompositorMsg::Reload(webview_id) => { + EmbedderToConstellationMessage::Reload(webview_id) => { self.handle_reload_msg(webview_id); }, - FromCompositorMsg::LogEntry(webview_id, thread_name, entry) => { + EmbedderToConstellationMessage::LogEntry(webview_id, thread_name, entry) => { self.handle_log_entry(webview_id, thread_name, entry); }, - FromCompositorMsg::ForwardInputEvent(webview_id, event, hit_test) => { + EmbedderToConstellationMessage::ForwardInputEvent(webview_id, event, hit_test) => { self.forward_input_event(webview_id, event, hit_test); }, - FromCompositorMsg::SetCursor(webview_id, cursor) => { + EmbedderToConstellationMessage::SetCursor(webview_id, cursor) => { self.handle_set_cursor_msg(webview_id, cursor) }, - FromCompositorMsg::ToggleProfiler(rate, max_duration) => { + EmbedderToConstellationMessage::ToggleProfiler(rate, max_duration) => { for background_monitor_control_sender in &self.background_monitor_control_senders { if let Err(e) = background_monitor_control_sender.send( BackgroundHangMonitorControlMsg::ToggleSampler(rate, max_duration), @@ -1387,21 +1463,28 @@ where } } }, - FromCompositorMsg::ExitFullScreen(webview_id) => { + EmbedderToConstellationMessage::ExitFullScreen(webview_id) => { self.handle_exit_fullscreen_msg(webview_id); }, - FromCompositorMsg::MediaSessionAction(action) => { + EmbedderToConstellationMessage::MediaSessionAction(action) => { self.handle_media_session_action_msg(action); }, - FromCompositorMsg::SetWebViewThrottled(webview_id, throttled) => { + EmbedderToConstellationMessage::SetWebViewThrottled(webview_id, throttled) => { self.set_webview_throttled(webview_id, throttled); }, - FromCompositorMsg::SetScrollStates(pipeline_id, scroll_states) => { + EmbedderToConstellationMessage::SetScrollStates(pipeline_id, scroll_states) => { self.handle_set_scroll_states(pipeline_id, scroll_states) }, - FromCompositorMsg::PaintMetric(pipeline_id, paint_metric_event) => { + EmbedderToConstellationMessage::PaintMetric(pipeline_id, paint_metric_event) => { self.handle_paint_metric(pipeline_id, paint_metric_event); }, + EmbedderToConstellationMessage::EvaluateJavaScript( + webview_id, + evaluation_id, + script, + ) => { + self.handle_evaluate_javascript(webview_id, evaluation_id, script); + }, } } @@ -1409,7 +1492,46 @@ where feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn handle_request_from_script(&mut self, message: (PipelineId, FromScriptMsg)) { + fn handle_evaluate_javascript( + &mut self, + webview_id: WebViewId, + evaluation_id: JavaScriptEvaluationId, + script: String, + ) { + let browsing_context_id = BrowsingContextId::from(webview_id); + let Some(pipeline) = self + .browsing_contexts + .get(&browsing_context_id) + .and_then(|browsing_context| self.pipelines.get(&browsing_context.pipeline_id)) + else { + self.handle_finish_javascript_evaluation( + evaluation_id, + Err(JavaScriptEvaluationError::InternalError), + ); + return; + }; + + if pipeline + .event_loop + .send(ScriptThreadMessage::EvaluateJavaScript( + pipeline.id, + evaluation_id, + script, + )) + .is_err() + { + self.handle_finish_javascript_evaluation( + evaluation_id, + Err(JavaScriptEvaluationError::InternalError), + ); + } + } + + #[cfg_attr( + feature = "tracing", + tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") + )] + fn handle_request_from_script(&mut self, message: (PipelineId, ScriptToConstellationMessage)) { let (source_pipeline_id, content) = message; trace_script_msg!(content, "{source_pipeline_id}: {content:?}"); @@ -1423,35 +1545,43 @@ where }; match content { - FromScriptMsg::CompleteMessagePortTransfer(router_id, ports) => { + ScriptToConstellationMessage::CompleteMessagePortTransfer(router_id, ports) => { self.handle_complete_message_port_transfer(router_id, ports); }, - FromScriptMsg::MessagePortTransferResult(router_id, succeeded, failed) => { + ScriptToConstellationMessage::MessagePortTransferResult( + router_id, + succeeded, + failed, + ) => { self.handle_message_port_transfer_completed(router_id, succeeded); self.handle_message_port_transfer_failed(failed); }, - FromScriptMsg::RerouteMessagePort(port_id, task) => { + ScriptToConstellationMessage::RerouteMessagePort(port_id, task) => { self.handle_reroute_messageport(port_id, task); }, - FromScriptMsg::MessagePortShipped(port_id) => { + ScriptToConstellationMessage::MessagePortShipped(port_id) => { self.handle_messageport_shipped(port_id); }, - FromScriptMsg::NewMessagePortRouter(router_id, ipc_sender) => { + ScriptToConstellationMessage::NewMessagePortRouter(router_id, ipc_sender) => { self.handle_new_messageport_router(router_id, ipc_sender); }, - FromScriptMsg::RemoveMessagePortRouter(router_id) => { + ScriptToConstellationMessage::RemoveMessagePortRouter(router_id) => { self.handle_remove_messageport_router(router_id); }, - FromScriptMsg::NewMessagePort(router_id, port_id) => { + ScriptToConstellationMessage::NewMessagePort(router_id, port_id) => { self.handle_new_messageport(router_id, port_id); }, - FromScriptMsg::RemoveMessagePort(port_id) => { - self.handle_remove_messageport(port_id); - }, - FromScriptMsg::EntanglePorts(port1, port2) => { + ScriptToConstellationMessage::EntanglePorts(port1, port2) => { self.handle_entangle_messageports(port1, port2); }, - FromScriptMsg::NewBroadcastChannelRouter(router_id, response_sender, origin) => { + ScriptToConstellationMessage::DisentanglePorts(port1, port2) => { + self.handle_disentangle_messageports(port1, port2); + }, + ScriptToConstellationMessage::NewBroadcastChannelRouter( + router_id, + response_sender, + origin, + ) => { self.handle_new_broadcast_channel_router( source_pipeline_id, router_id, @@ -1459,7 +1589,11 @@ where origin, ); }, - FromScriptMsg::NewBroadcastChannelNameInRouter(router_id, channel_name, origin) => { + ScriptToConstellationMessage::NewBroadcastChannelNameInRouter( + router_id, + channel_name, + origin, + ) => { self.handle_new_broadcast_channel_name_in_router( source_pipeline_id, router_id, @@ -1467,7 +1601,11 @@ where origin, ); }, - FromScriptMsg::RemoveBroadcastChannelNameInRouter(router_id, channel_name, origin) => { + ScriptToConstellationMessage::RemoveBroadcastChannelNameInRouter( + router_id, + channel_name, + origin, + ) => { self.handle_remove_broadcast_channel_name_in_router( source_pipeline_id, router_id, @@ -1475,38 +1613,38 @@ where origin, ); }, - FromScriptMsg::RemoveBroadcastChannelRouter(router_id, origin) => { + ScriptToConstellationMessage::RemoveBroadcastChannelRouter(router_id, origin) => { self.handle_remove_broadcast_channel_router(source_pipeline_id, router_id, origin); }, - FromScriptMsg::ScheduleBroadcast(router_id, message) => { + ScriptToConstellationMessage::ScheduleBroadcast(router_id, message) => { self.handle_schedule_broadcast(source_pipeline_id, router_id, message); }, - FromScriptMsg::ForwardToEmbedder(embedder_msg) => { + ScriptToConstellationMessage::ForwardToEmbedder(embedder_msg) => { self.embedder_proxy.send(embedder_msg); }, - FromScriptMsg::PipelineExited => { + ScriptToConstellationMessage::PipelineExited => { self.handle_pipeline_exited(source_pipeline_id); }, - FromScriptMsg::DiscardDocument => { + ScriptToConstellationMessage::DiscardDocument => { self.handle_discard_document(webview_id, source_pipeline_id); }, - FromScriptMsg::DiscardTopLevelBrowsingContext => { + ScriptToConstellationMessage::DiscardTopLevelBrowsingContext => { self.handle_close_top_level_browsing_context(webview_id); }, - FromScriptMsg::ScriptLoadedURLInIFrame(load_info) => { + ScriptToConstellationMessage::ScriptLoadedURLInIFrame(load_info) => { self.handle_script_loaded_url_in_iframe_msg(load_info); }, - FromScriptMsg::ScriptNewIFrame(load_info) => { + ScriptToConstellationMessage::ScriptNewIFrame(load_info) => { self.handle_script_new_iframe(load_info); }, - FromScriptMsg::CreateAuxiliaryWebView(load_info) => { + ScriptToConstellationMessage::CreateAuxiliaryWebView(load_info) => { self.handle_script_new_auxiliary(load_info); }, - FromScriptMsg::ChangeRunningAnimationsState(animation_state) => { + ScriptToConstellationMessage::ChangeRunningAnimationsState(animation_state) => { self.handle_change_running_animations_state(source_pipeline_id, animation_state) }, // Ask the embedder for permission to load a new page. - FromScriptMsg::LoadUrl(load_data, history_handling) => { + ScriptToConstellationMessage::LoadUrl(load_data, history_handling) => { self.schedule_navigation( webview_id, source_pipeline_id, @@ -1514,38 +1652,38 @@ where history_handling, ); }, - FromScriptMsg::AbortLoadUrl => { + ScriptToConstellationMessage::AbortLoadUrl => { self.handle_abort_load_url_msg(source_pipeline_id); }, // A page loaded has completed all parsing, script, and reflow messages have been sent. - FromScriptMsg::LoadComplete => { + ScriptToConstellationMessage::LoadComplete => { self.handle_load_complete_msg(webview_id, source_pipeline_id) }, // Handle navigating to a fragment - FromScriptMsg::NavigatedToFragment(new_url, replacement_enabled) => { + ScriptToConstellationMessage::NavigatedToFragment(new_url, replacement_enabled) => { self.handle_navigated_to_fragment(source_pipeline_id, new_url, replacement_enabled); }, // Handle a forward or back request - FromScriptMsg::TraverseHistory(direction) => { + ScriptToConstellationMessage::TraverseHistory(direction) => { self.handle_traverse_history_msg(webview_id, direction); }, // Handle a push history state request. - FromScriptMsg::PushHistoryState(history_state_id, url) => { + ScriptToConstellationMessage::PushHistoryState(history_state_id, url) => { self.handle_push_history_state_msg(source_pipeline_id, history_state_id, url); }, - FromScriptMsg::ReplaceHistoryState(history_state_id, url) => { + ScriptToConstellationMessage::ReplaceHistoryState(history_state_id, url) => { self.handle_replace_history_state_msg(source_pipeline_id, history_state_id, url); }, // Handle a joint session history length request. - FromScriptMsg::JointSessionHistoryLength(response_sender) => { + ScriptToConstellationMessage::JointSessionHistoryLength(response_sender) => { self.handle_joint_session_history_length(webview_id, response_sender); }, // Notification that the new document is ready to become active - FromScriptMsg::ActivateDocument => { + ScriptToConstellationMessage::ActivateDocument => { self.handle_activate_document_msg(source_pipeline_id); }, // Update pipeline url after redirections - FromScriptMsg::SetFinalUrl(final_url) => { + ScriptToConstellationMessage::SetFinalUrl(final_url) => { // The script may have finished loading after we already started shutting down. if let Some(ref mut pipeline) = self.pipelines.get_mut(&source_pipeline_id) { pipeline.url = final_url; @@ -1553,7 +1691,7 @@ where warn!("constellation got set final url message for dead pipeline"); } }, - FromScriptMsg::PostMessage { + ScriptToConstellationMessage::PostMessage { target: browsing_context_id, source: source_pipeline_id, target_origin: origin, @@ -1568,38 +1706,45 @@ where data, ); }, - FromScriptMsg::Focus => { - self.handle_focus_msg(source_pipeline_id); + ScriptToConstellationMessage::Focus(focused_child_browsing_context_id, sequence) => { + self.handle_focus_msg( + source_pipeline_id, + focused_child_browsing_context_id, + sequence, + ); }, - FromScriptMsg::SetThrottledComplete(throttled) => { + ScriptToConstellationMessage::FocusRemoteDocument(focused_browsing_context_id) => { + self.handle_focus_remote_document_msg(focused_browsing_context_id); + }, + ScriptToConstellationMessage::SetThrottledComplete(throttled) => { self.handle_set_throttled_complete(source_pipeline_id, throttled); }, - FromScriptMsg::RemoveIFrame(browsing_context_id, response_sender) => { + ScriptToConstellationMessage::RemoveIFrame(browsing_context_id, response_sender) => { let removed_pipeline_ids = self.handle_remove_iframe_msg(browsing_context_id); if let Err(e) = response_sender.send(removed_pipeline_ids) { warn!("Error replying to remove iframe ({})", e); } }, - FromScriptMsg::CreateCanvasPaintThread(size, response_sender) => { + ScriptToConstellationMessage::CreateCanvasPaintThread(size, response_sender) => { self.handle_create_canvas_paint_thread_msg(size, response_sender) }, - FromScriptMsg::SetDocumentState(state) => { + ScriptToConstellationMessage::SetDocumentState(state) => { self.document_states.insert(source_pipeline_id, state); }, - FromScriptMsg::SetLayoutEpoch(epoch, response_sender) => { + ScriptToConstellationMessage::SetLayoutEpoch(epoch, response_sender) => { if let Some(pipeline) = self.pipelines.get_mut(&source_pipeline_id) { pipeline.layout_epoch = epoch; } response_sender.send(true).unwrap_or_default(); }, - FromScriptMsg::LogEntry(thread_name, entry) => { + ScriptToConstellationMessage::LogEntry(thread_name, entry) => { self.handle_log_entry(Some(webview_id), thread_name, entry); }, - FromScriptMsg::TouchEventProcessed(result) => self + ScriptToConstellationMessage::TouchEventProcessed(result) => self .compositor_proxy .send(CompositorMsg::TouchEventProcessed(webview_id, result)), - FromScriptMsg::GetBrowsingContextInfo(pipeline_id, response_sender) => { + ScriptToConstellationMessage::GetBrowsingContextInfo(pipeline_id, response_sender) => { let result = self .pipelines .get(&pipeline_id) @@ -1612,7 +1757,10 @@ where ); } }, - FromScriptMsg::GetTopForBrowsingContext(browsing_context_id, response_sender) => { + ScriptToConstellationMessage::GetTopForBrowsingContext( + browsing_context_id, + response_sender, + ) => { let result = self .browsing_contexts .get(&browsing_context_id) @@ -1624,7 +1772,7 @@ where ); } }, - FromScriptMsg::GetChildBrowsingContextId( + ScriptToConstellationMessage::GetChildBrowsingContextId( browsing_context_id, index, response_sender, @@ -1642,17 +1790,23 @@ where ); } }, - FromScriptMsg::ScheduleJob(job) => { + ScriptToConstellationMessage::ScheduleJob(job) => { self.handle_schedule_serviceworker_job(source_pipeline_id, job); }, - FromScriptMsg::ForwardDOMMessage(msg_vec, scope_url) => { + ScriptToConstellationMessage::ForwardDOMMessage(msg_vec, scope_url) => { if let Some(mgr) = self.sw_managers.get(&scope_url.origin()) { let _ = mgr.send(ServiceWorkerMsg::ForwardDOMMessage(msg_vec, scope_url)); } else { warn!("Unable to forward DOMMessage for postMessage call"); } }, - FromScriptMsg::BroadcastStorageEvent(storage, url, key, old_value, new_value) => { + ScriptToConstellationMessage::BroadcastStorageEvent( + storage, + url, + key, + old_value, + new_value, + ) => { self.handle_broadcast_storage_event( source_pipeline_id, storage, @@ -1662,7 +1816,7 @@ where new_value, ); }, - FromScriptMsg::MediaSessionEvent(pipeline_id, event) => { + ScriptToConstellationMessage::MediaSessionEvent(pipeline_id, event) => { // Unlikely at this point, but we may receive events coming from // different media sessions, so we set the active media session based // on Playing events. @@ -1684,29 +1838,35 @@ where .send(EmbedderMsg::MediaSessionEvent(webview_id, event)); }, #[cfg(feature = "webgpu")] - FromScriptMsg::RequestAdapter(response_sender, options, ids) => self + ScriptToConstellationMessage::RequestAdapter(response_sender, options, ids) => self .handle_wgpu_request( source_pipeline_id, BrowsingContextId::from(webview_id), - FromScriptMsg::RequestAdapter(response_sender, options, ids), + ScriptToConstellationMessage::RequestAdapter(response_sender, options, ids), ), #[cfg(feature = "webgpu")] - FromScriptMsg::GetWebGPUChan(response_sender) => self.handle_wgpu_request( - source_pipeline_id, - BrowsingContextId::from(webview_id), - FromScriptMsg::GetWebGPUChan(response_sender), - ), - FromScriptMsg::TitleChanged(pipeline, title) => { + ScriptToConstellationMessage::GetWebGPUChan(response_sender) => self + .handle_wgpu_request( + source_pipeline_id, + BrowsingContextId::from(webview_id), + ScriptToConstellationMessage::GetWebGPUChan(response_sender), + ), + ScriptToConstellationMessage::TitleChanged(pipeline, title) => { if let Some(pipeline) = self.pipelines.get_mut(&pipeline) { pipeline.title = title; } }, - FromScriptMsg::IFrameSizes(iframe_sizes) => self.handle_iframe_size_msg(iframe_sizes), - FromScriptMsg::ReportMemory(sender) => { + ScriptToConstellationMessage::IFrameSizes(iframe_sizes) => { + self.handle_iframe_size_msg(iframe_sizes) + }, + ScriptToConstellationMessage::ReportMemory(sender) => { // get memory report and send it back. self.mem_profiler_chan .send(mem::ProfilerMsg::Report(sender)); }, + ScriptToConstellationMessage::FinishJavaScriptEvaluation(evaluation_id, result) => { + self.handle_finish_javascript_evaluation(evaluation_id, result) + }, } } @@ -1900,8 +2060,10 @@ where &mut self, source_pipeline_id: PipelineId, browsing_context_id: BrowsingContextId, - request: FromScriptMsg, + request: ScriptToConstellationMessage, ) { + use webgpu::start_webgpu_thread; + let browsing_context_group_id = match self.browsing_contexts.get(&browsing_context_id) { Some(bc) => &bc.bc_group_id, None => return warn!("Browsing context not found"), @@ -1923,7 +2085,7 @@ where return warn!("Browsing context group not found"); }; let webgpu_chan = match browsing_context_group.webgpus.entry(host) { - Entry::Vacant(v) => WebGPU::new( + Entry::Vacant(v) => start_webgpu_thread( self.webrender_wgpu.webrender_api.create_sender(), self.webrender_document, self.webrender_wgpu.webrender_external_images.clone(), @@ -1942,7 +2104,7 @@ where Entry::Occupied(o) => Some(o.get().clone()), }; match request { - FromScriptMsg::RequestAdapter(response_sender, options, adapter_id) => { + ScriptToConstellationMessage::RequestAdapter(response_sender, options, adapter_id) => { match webgpu_chan { None => { if let Err(e) = response_sender.send(None) { @@ -1961,7 +2123,7 @@ where }, } }, - FromScriptMsg::GetWebGPUChan(response_sender) => { + ScriptToConstellationMessage::GetWebGPUChan(response_sender) => { if response_sender.send(webgpu_chan).is_err() { warn!( "{}: Failed to send WebGPU channel to pipeline", @@ -2005,17 +2167,6 @@ where Entry::Occupied(entry) => entry, }; match entry.get().state { - TransferState::EntangledRemoved => { - // If the entangled port has been removed while this one was in-transfer, - // remove it now. - if let Some(ipc_sender) = self.message_port_routers.get(&router_id) { - let _ = ipc_sender.send(MessagePortMsg::RemoveMessagePort(port_id)); - } else { - warn!("No message-port sender for {:?}", router_id); - } - entry.remove_entry(); - continue; - }, TransferState::CompletionInProgress(expected_router_id) => { // Here, the transfer was normally completed. @@ -2039,9 +2190,9 @@ where fn handle_message_port_transfer_failed( &mut self, - ports: HashMap>, + ports: HashMap, ) { - for (port_id, mut previous_buffer) in ports.into_iter() { + for (port_id, mut transfer_info) in ports.into_iter() { let entry = match self.message_ports.remove(&port_id) { None => { warn!( @@ -2053,11 +2204,6 @@ where Some(entry) => entry, }; let new_info = match entry.state { - TransferState::EntangledRemoved => { - // If the entangled port has been removed while this one was in-transfer, - // just drop it. - continue; - }, TransferState::CompletionFailed(mut current_buffer) => { // The transfer failed, // and now the global has returned us the buffer we previously sent. @@ -2065,7 +2211,7 @@ where // Tasks in the previous buffer are older, // hence need to be added to the front of the current one. - while let Some(task) = previous_buffer.pop_back() { + while let Some(task) = transfer_info.port_message_queue.pop_back() { current_buffer.push_front(task); } // Update the state to transfer-in-progress. @@ -2084,7 +2230,7 @@ where // Tasks in the previous buffer are older, // hence need to be added to the front of the current one. - while let Some(task) = previous_buffer.pop_back() { + while let Some(task) = transfer_info.port_message_queue.pop_back() { current_buffer.push_front(task); } // Forward the buffered message-queue to complete the current transfer. @@ -2092,7 +2238,10 @@ where if ipc_sender .send(MessagePortMsg::CompletePendingTransfer( port_id, - current_buffer, + PortTransferInfo { + port_message_queue: current_buffer, + disentangled: entry.entangled_with.is_none(), + }, )) .is_err() { @@ -2139,18 +2288,14 @@ where Some(entry) => entry, }; let new_info = match entry.state { - TransferState::EntangledRemoved => { - // If the entangled port has been removed while this one was in-transfer, - // remove it now. - if let Some(ipc_sender) = self.message_port_routers.get(&router_id) { - let _ = ipc_sender.send(MessagePortMsg::RemoveMessagePort(port_id)); - } else { - warn!("No message-port sender for {:?}", router_id); - } - continue; - }, TransferState::TransferInProgress(buffer) => { - response.insert(port_id, buffer); + response.insert( + port_id, + PortTransferInfo { + port_message_queue: buffer, + disentangled: entry.entangled_with.is_none(), + }, + ); // If the port was in transfer, and a global is requesting completion, // we note the start of the completion. @@ -2229,10 +2374,6 @@ where TransferState::TransferInProgress(queue) => queue.push_back(task), TransferState::CompletionFailed(queue) => queue.push_back(task), TransferState::CompletionRequested(_, queue) => queue.push_back(task), - TransferState::EntangledRemoved => warn!( - "Messageport received a message, but entangled has alread been removed {:?}", - port_id - ), } } @@ -2294,59 +2435,6 @@ where } } - #[cfg_attr( - feature = "tracing", - tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") - )] - fn handle_remove_messageport(&mut self, port_id: MessagePortId) { - let entangled = match self.message_ports.remove(&port_id) { - Some(info) => info.entangled_with, - None => { - return warn!( - "Constellation asked to remove unknown messageport {:?}", - port_id - ); - }, - }; - let entangled_id = match entangled { - Some(id) => id, - None => return, - }; - let info = match self.message_ports.get_mut(&entangled_id) { - Some(info) => info, - None => { - return warn!( - "Constellation asked to remove unknown entangled messageport {:?}", - entangled_id - ); - }, - }; - let router_id = match info.state { - TransferState::EntangledRemoved => { - return warn!( - "Constellation asked to remove entangled messageport by a port that was already removed {:?}", - port_id - ); - }, - TransferState::TransferInProgress(_) | - TransferState::CompletionInProgress(_) | - TransferState::CompletionFailed(_) | - TransferState::CompletionRequested(_, _) => { - // Note: since the port is in-transer, we don't have a router to send it a message - // to let it know that its entangled port has been removed. - // Hence we mark it so that it will be messaged and removed once the transfer completes. - info.state = TransferState::EntangledRemoved; - return; - }, - TransferState::Managed(router_id) => router_id, - }; - if let Some(sender) = self.message_port_routers.get(&router_id) { - let _ = sender.send(MessagePortMsg::RemoveMessagePort(entangled_id)); - } else { - warn!("No message-port sender for {:?}", router_id); - } - } - #[cfg_attr( feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") @@ -2370,6 +2458,57 @@ where } } + #[cfg_attr( + feature = "tracing", + tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") + )] + /// + fn handle_disentangle_messageports( + &mut self, + port1: MessagePortId, + port2: Option, + ) { + // Disentangle initiatorPort and otherPort, + // so that they are no longer entangled or associated with each other. + // Note: If `port2` is some, then this is the first message + // and `port1` is the initiatorPort, `port2` is the otherPort. + // We can immediately remove the initiator. + let _ = self.message_ports.remove(&port1); + + // Note: the none case is when otherPort sent this message + // in response to completing its own local disentanglement. + let Some(port2) = port2 else { + return; + }; + + // Start disentanglement of the other port. + if let Some(info) = self.message_ports.get_mut(&port2) { + info.entangled_with = None; + match &mut info.state { + TransferState::Managed(router_id) | + TransferState::CompletionInProgress(router_id) => { + // We try to disentangle the other port now, + // and if it has been transfered out by the time the message is received, + // it will be ignored, + // and disentanglement will be completed as part of the transfer. + if let Some(ipc_sender) = self.message_port_routers.get(router_id) { + let _ = ipc_sender.send(MessagePortMsg::CompleteDisentanglement(port2)); + } else { + warn!("No message-port sender for {:?}", router_id); + } + }, + _ => { + // Note: the port is in transfer, disentanglement will complete along with it. + }, + } + } else { + warn!( + "Constellation asked to disentangle unknown messageport: {:?}", + port2 + ); + } + } + /// /// and /// @@ -2404,13 +2543,24 @@ where own_sender: own_sender.clone(), receiver, }; - let content = ServiceWorkerUnprivilegedContent::new(sw_senders, origin); if opts::get().multiprocess { - if content.spawn_multiprocess().is_err() { + let (sender, receiver) = + ipc::channel().expect("Failed to create lifeline channel for sw"); + let content = + ServiceWorkerUnprivilegedContent::new(sw_senders, origin, Some(sender)); + + if let Ok(process) = content.spawn_multiprocess() { + let crossbeam_receiver = + route_ipc_receiver_to_new_crossbeam_receiver_preserving_errors( + receiver, + ); + self.process_manager.add(crossbeam_receiver, process); + } else { return warn!("Failed to spawn process for SW manager."); } } else { + let content = ServiceWorkerUnprivilegedContent::new(sw_senders, origin, None); content.start::(); } entry.insert(own_sender) @@ -2720,7 +2870,7 @@ where Some(context) => context, None => return warn!("failed browsing context is missing"), }; - let window_size = browsing_context.size; + let viewport_details = browsing_context.viewport_details; let pipeline_id = browsing_context.pipeline_id; let throttled = browsing_context.throttled; @@ -2765,7 +2915,7 @@ where webview_id, None, opener, - window_size, + viewport_details, new_load_data, sandbox, is_private, @@ -2779,7 +2929,7 @@ where // to avoid closing again in handle_activate_document_msg (though it would be harmless) replace: Some(NeedsToReload::Yes(old_pipeline_id, old_load_data)), new_browsing_context_info: None, - window_size, + viewport_details, }); } @@ -2956,9 +3106,9 @@ where &mut self, url: ServoUrl, webview_id: WebViewId, + viewport_details: ViewportDetails, response_sender: Option>, ) { - let window_size = self.window_size.initial_viewport; let pipeline_id = PipelineId::new(); let browsing_context_id = BrowsingContextId::from(webview_id); let load_data = LoadData::new( @@ -2969,6 +3119,7 @@ where ReferrerPolicy::EmptyString, None, None, + false, ); let sandbox = IFrameSandboxState::IFrameUnsandboxed; let is_private = false; @@ -2999,7 +3150,7 @@ where webview_id, None, None, - window_size, + viewport_details, load_data, sandbox, is_private, @@ -3016,7 +3167,7 @@ where inherited_secure_context: None, throttled, }), - window_size, + viewport_details, }); if let Some(response_sender) = response_sender { @@ -3073,15 +3224,26 @@ where type_, } in iframe_sizes { - let window_size = WindowSizeData { - initial_viewport: size, - device_pixel_ratio: self.window_size.device_pixel_ratio, - }; - - self.resize_browsing_context(window_size, type_, browsing_context_id); + self.resize_browsing_context(size, type_, browsing_context_id); } } + #[cfg_attr( + feature = "tracing", + tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") + )] + fn handle_finish_javascript_evaluation( + &mut self, + evaluation_id: JavaScriptEvaluationId, + result: Result, + ) { + self.embedder_proxy + .send(EmbedderMsg::FinishJavaScriptEvaluation( + evaluation_id, + result, + )); + } + #[cfg_attr( feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") @@ -3206,14 +3368,13 @@ where None }; - // https://github.com/rust-lang/rust/issues/59159 - let browsing_context_size = browsing_context.size; + let browsing_context_size = browsing_context.viewport_details; let browsing_context_throttled = browsing_context.throttled; // TODO(servo#30571) revert to debug_assert_eq!() once underlying bug is fixed #[cfg(debug_assertions)] - if !(browsing_context_size == load_info.window_size.initial_viewport) { + if !(browsing_context_size == load_info.viewport_details) { log::warn!( - "debug assertion failed! browsing_context_size == load_info.window_size.initial_viewport" + "debug assertion failed! browsing_context_size == load_info.viewport_details.initial_viewport" ); } @@ -3237,7 +3398,7 @@ where replace, // Browsing context for iframe already exists. new_browsing_context_info: None, - window_size: load_info.window_size.initial_viewport, + viewport_details: load_info.viewport_details, }); } @@ -3301,7 +3462,7 @@ where inherited_secure_context: is_parent_secure, throttled: is_parent_throttled, }), - window_size: load_info.window_size.initial_viewport, + viewport_details: load_info.viewport_details, }); } @@ -3329,8 +3490,8 @@ where opener_webview_id, webview_id_sender, )); - let new_webview_id = match webview_id_receiver.recv() { - Ok(Some(webview_id)) => webview_id, + let (new_webview_id, viewport_details) = match webview_id_receiver.recv() { + Ok(Some((webview_id, viewport_details))) => (webview_id, viewport_details), Ok(None) | Err(_) => { let _ = response_sender.send(None); return; @@ -3415,7 +3576,7 @@ where inherited_secure_context: is_opener_secure, throttled: is_opener_throttled, }), - window_size: self.window_size.initial_viewport, + viewport_details, }); } @@ -3454,15 +3615,24 @@ where feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn handle_tick_animation(&mut self, pipeline_id: PipelineId, tick_type: AnimationTickType) { - let pipeline = match self.pipelines.get(&pipeline_id) { - Some(pipeline) => pipeline, - None => return warn!("{}: Got script tick after closure", pipeline_id), - }; + fn handle_tick_animation(&mut self, webview_ids: Vec) { + let mut animating_event_loops = HashSet::new(); - let message = ScriptThreadMessage::TickAllAnimations(pipeline_id, tick_type); - if let Err(e) = pipeline.event_loop.send(message) { - self.handle_send_error(pipeline_id, e); + for webview_id in webview_ids.iter() { + for browsing_context in self.fully_active_browsing_contexts_iter(*webview_id) { + let Some(pipeline) = self.pipelines.get(&browsing_context.pipeline_id) else { + continue; + }; + animating_event_loops.insert(pipeline.event_loop.clone()); + } + } + + for event_loop in animating_event_loops { + // No error handling here. It's unclear what to do when this fails as the error isn't associated + // with a particular pipeline. In addition, the danger of not progressing animations is pretty + // low, so it's probably safe to ignore this error and handle the crashed ScriptThread on + // some other message. + let _ = event_loop.send(ScriptThreadMessage::TickAllAnimations(webview_ids.clone())); } } @@ -3534,10 +3704,10 @@ where return None; }, }; - let (window_size, pipeline_id, parent_pipeline_id, is_private, is_throttled) = + let (viewport_details, pipeline_id, parent_pipeline_id, is_private, is_throttled) = match self.browsing_contexts.get(&browsing_context_id) { Some(ctx) => ( - ctx.size, + ctx.viewport_details, ctx.pipeline_id, ctx.parent_pipeline_id, ctx.is_private, @@ -3614,7 +3784,7 @@ where webview_id, None, opener, - window_size, + viewport_details, load_data, sandbox, is_private, @@ -3627,7 +3797,7 @@ where replace, // `load_url` is always invoked on an existing browsing context. new_browsing_context_info: None, - window_size, + viewport_details, }); Some(new_pipeline_id) }, @@ -3662,8 +3832,8 @@ where fn handle_load_complete_msg(&mut self, webview_id: WebViewId, pipeline_id: PipelineId) { let mut webdriver_reset = false; if let Some((expected_pipeline_id, ref reply_chan)) = self.webdriver.load_channel { - debug!("Sending load for {:?} to WebDriver", expected_pipeline_id); if expected_pipeline_id == pipeline_id { + debug!("Sending load for {:?} to WebDriver", expected_pipeline_id); let _ = reply_chan.send(WebDriverLoadStatus::Complete); webdriver_reset = true; } @@ -3907,7 +4077,7 @@ where top_level_id, old_pipeline_id, parent_pipeline_id, - window_size, + viewport_details, is_private, throttled, ) = match self.browsing_contexts.get(&browsing_context_id) { @@ -3915,7 +4085,7 @@ where ctx.top_level_id, ctx.pipeline_id, ctx.parent_pipeline_id, - ctx.size, + ctx.viewport_details, ctx.is_private, ctx.throttled, ), @@ -3932,7 +4102,7 @@ where top_level_id, parent_pipeline_id, opener, - window_size, + viewport_details, load_data.clone(), sandbox, is_private, @@ -3945,7 +4115,7 @@ where replace: Some(NeedsToReload::Yes(pipeline_id, load_data)), // Browsing context must exist at this point. new_browsing_context_info: None, - window_size, + viewport_details, }); return; }, @@ -3988,6 +4158,7 @@ where } new_pipeline.set_throttled(false); + self.notify_focus_state(new_pipeline_id); } self.update_activity(old_pipeline_id); @@ -4193,22 +4364,96 @@ where feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn handle_focus_msg(&mut self, pipeline_id: PipelineId) { - let (browsing_context_id, webview_id) = match self.pipelines.get(&pipeline_id) { - Some(pipeline) => (pipeline.browsing_context_id, pipeline.webview_id), + fn handle_focus_msg( + &mut self, + pipeline_id: PipelineId, + focused_child_browsing_context_id: Option, + sequence: FocusSequenceNumber, + ) { + let (browsing_context_id, webview_id) = match self.pipelines.get_mut(&pipeline_id) { + Some(pipeline) => { + pipeline.focus_sequence = sequence; + (pipeline.browsing_context_id, pipeline.webview_id) + }, None => return warn!("{}: Focus parent after closure", pipeline_id), }; + // Ignore if the pipeline isn't fully active. + if self.get_activity(pipeline_id) != DocumentActivity::FullyActive { + debug!( + "Ignoring the focus request because pipeline {} is not \ + fully active", + pipeline_id + ); + return; + } + // Focus the top-level browsing context. self.webviews.focus(webview_id); self.embedder_proxy .send(EmbedderMsg::WebViewFocused(webview_id)); + // If a container with a non-null nested browsing context is focused, + // the nested browsing context's active document becomes the focused + // area of the top-level browsing context instead. + let focused_browsing_context_id = + focused_child_browsing_context_id.unwrap_or(browsing_context_id); + + // Send focus messages to the affected pipelines, except + // `pipeline_id`, which has already its local focus state + // updated. + self.focus_browsing_context(Some(pipeline_id), focused_browsing_context_id); + } + + fn handle_focus_remote_document_msg(&mut self, focused_browsing_context_id: BrowsingContextId) { + let pipeline_id = match self.browsing_contexts.get(&focused_browsing_context_id) { + Some(browsing_context) => browsing_context.pipeline_id, + None => return warn!("Browsing context {} not found", focused_browsing_context_id), + }; + + // Ignore if its active document isn't fully active. + if self.get_activity(pipeline_id) != DocumentActivity::FullyActive { + debug!( + "Ignoring the remote focus request because pipeline {} of \ + browsing context {} is not fully active", + pipeline_id, focused_browsing_context_id, + ); + return; + } + + self.focus_browsing_context(None, focused_browsing_context_id); + } + + /// Perform [the focusing steps][1] for the active document of + /// `focused_browsing_context_id`. + /// + /// If `initiator_pipeline_id` is specified, this method avoids sending + /// a message to `initiator_pipeline_id`, assuming its local focus state has + /// already been updated. This is necessary for performing the focusing + /// steps for an object that is not the document itself but something that + /// belongs to the document. + /// + /// [1]: https://html.spec.whatwg.org/multipage/#focusing-steps + #[cfg_attr( + feature = "tracing", + tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") + )] + fn focus_browsing_context( + &mut self, + initiator_pipeline_id: Option, + focused_browsing_context_id: BrowsingContextId, + ) { + let webview_id = match self.browsing_contexts.get(&focused_browsing_context_id) { + Some(browsing_context) => browsing_context.top_level_id, + None => return warn!("Browsing context {} not found", focused_browsing_context_id), + }; + // Update the webview’s focused browsing context. - match self.webviews.get_mut(webview_id) { - Some(webview) => { - webview.focused_browsing_context_id = browsing_context_id; - }, + let old_focused_browsing_context_id = match self.webviews.get_mut(webview_id) { + Some(browser) => replace( + &mut browser.focused_browsing_context_id, + focused_browsing_context_id, + ), None => { return warn!( "{}: Browsing context for focus msg does not exist", @@ -4217,42 +4462,133 @@ where }, }; - // Focus parent iframes recursively - self.focus_parent_pipeline(browsing_context_id); - } + // The following part is similar to [the focus update steps][1] except + // that only `Document`s in the given focus chains are considered. It's + // ultimately up to the script threads to fire focus events at the + // affected objects. + // + // [1]: https://html.spec.whatwg.org/multipage/#focus-update-steps + let mut old_focus_chain_pipelines: Vec<&Pipeline> = self + .ancestor_or_self_pipelines_of_browsing_context_iter(old_focused_browsing_context_id) + .collect(); + let mut new_focus_chain_pipelines: Vec<&Pipeline> = self + .ancestor_or_self_pipelines_of_browsing_context_iter(focused_browsing_context_id) + .collect(); - #[cfg_attr( - feature = "tracing", - tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") - )] - fn focus_parent_pipeline(&mut self, browsing_context_id: BrowsingContextId) { - let parent_pipeline_id = match self.browsing_contexts.get(&browsing_context_id) { - Some(ctx) => ctx.parent_pipeline_id, - None => { - return warn!("{}: Focus parent after closure", browsing_context_id); - }, - }; - let parent_pipeline_id = match parent_pipeline_id { - Some(parent_id) => parent_id, - None => { - return debug!("{}: Focus has no parent", browsing_context_id); - }, - }; + debug!( + "old_focus_chain_pipelines = {:?}", + old_focus_chain_pipelines + .iter() + .map(|p| p.id.to_string()) + .collect::>() + ); + debug!( + "new_focus_chain_pipelines = {:?}", + new_focus_chain_pipelines + .iter() + .map(|p| p.id.to_string()) + .collect::>() + ); - // Send a message to the parent of the provided browsing context (if it - // exists) telling it to mark the iframe element as focused. - let msg = ScriptThreadMessage::FocusIFrame(parent_pipeline_id, browsing_context_id); - let (result, parent_browsing_context_id) = match self.pipelines.get(&parent_pipeline_id) { - Some(pipeline) => { - let result = pipeline.event_loop.send(msg); - (result, pipeline.browsing_context_id) + // At least the last entries should match. Otherwise something is wrong, + // and we don't want to proceed and crash the top-level pipeline by + // sending an impossible `Unfocus` message to it. + match ( + &old_focus_chain_pipelines[..], + &new_focus_chain_pipelines[..], + ) { + ([.., p1], [.., p2]) if p1.id == p2.id => {}, + _ => { + warn!("Aborting the focus operation - focus chain sanity check failed"); + return; }, - None => return warn!("{}: Focus after closure", parent_pipeline_id), - }; - if let Err(e) = result { - self.handle_send_error(parent_pipeline_id, e); } - self.focus_parent_pipeline(parent_browsing_context_id); + + // > If the last entry in `old chain` and the last entry in `new chain` + // > are the same, pop the last entry from `old chain` and the last + // > entry from `new chain` and redo this step. + let mut first_common_pipeline_in_chain = None; + while let ([.., p1], [.., p2]) = ( + &old_focus_chain_pipelines[..], + &new_focus_chain_pipelines[..], + ) { + if p1.id != p2.id { + break; + } + old_focus_chain_pipelines.pop(); + first_common_pipeline_in_chain = new_focus_chain_pipelines.pop(); + } + + let mut send_errors = Vec::new(); + + // > For each entry `entry` in `old chain`, in order, run these + // > substeps: [...] + for &pipeline in old_focus_chain_pipelines.iter() { + if Some(pipeline.id) != initiator_pipeline_id { + let msg = ScriptThreadMessage::Unfocus(pipeline.id, pipeline.focus_sequence); + trace!("Sending {:?} to {}", msg, pipeline.id); + if let Err(e) = pipeline.event_loop.send(msg) { + send_errors.push((pipeline.id, e)); + } + } else { + trace!( + "Not notifying {} - it's the initiator of this focus operation", + pipeline.id + ); + } + } + + // > For each entry entry in `new chain`, in reverse order, run these + // > substeps: [...] + let mut child_browsing_context_id = None; + for &pipeline in new_focus_chain_pipelines.iter().rev() { + // Don't send a message to the browsing context that initiated this + // focus operation. It already knows that it has gotten focus. + if Some(pipeline.id) != initiator_pipeline_id { + let msg = if let Some(child_browsing_context_id) = child_browsing_context_id { + // Focus the container element of `child_browsing_context_id`. + ScriptThreadMessage::FocusIFrame( + pipeline.id, + child_browsing_context_id, + pipeline.focus_sequence, + ) + } else { + // Focus the document. + ScriptThreadMessage::FocusDocument(pipeline.id, pipeline.focus_sequence) + }; + trace!("Sending {:?} to {}", msg, pipeline.id); + if let Err(e) = pipeline.event_loop.send(msg) { + send_errors.push((pipeline.id, e)); + } + } else { + trace!( + "Not notifying {} - it's the initiator of this focus operation", + pipeline.id + ); + } + child_browsing_context_id = Some(pipeline.browsing_context_id); + } + + if let (Some(pipeline), Some(child_browsing_context_id)) = + (first_common_pipeline_in_chain, child_browsing_context_id) + { + if Some(pipeline.id) != initiator_pipeline_id { + // Focus the container element of `child_browsing_context_id`. + let msg = ScriptThreadMessage::FocusIFrame( + pipeline.id, + child_browsing_context_id, + pipeline.focus_sequence, + ); + trace!("Sending {:?} to {}", msg, pipeline.id); + if let Err(e) = pipeline.event_loop.send(msg) { + send_errors.push((pipeline.id, e)); + } + } + } + + for (pipeline_id, e) in send_errors { + self.handle_send_error(pipeline_id, e); + } } #[cfg_attr( @@ -4354,14 +4690,15 @@ where }; self.embedder_proxy .send(EmbedderMsg::AllowOpeningWebView(webview_id, chan)); - let webview_id = match port.recv() { - Ok(Some(webview_id)) => webview_id, + let (webview_id, viewport_details) = match port.recv() { + Ok(Some((webview_id, viewport_details))) => (webview_id, viewport_details), Ok(None) => return warn!("Embedder refused to allow opening webview"), Err(error) => return warn!("Failed to receive webview id: {error:?}"), }; self.handle_new_top_level_browsing_context( ServoUrl::parse_with_base(None, "about:blank").expect("Infallible parse"), webview_id, + viewport_details, Some(load_sender), ); let _ = sender.send(webview_id); @@ -4369,8 +4706,14 @@ where WebDriverCommandMsg::FocusWebView(webview_id) => { self.handle_focus_web_view(webview_id); }, - WebDriverCommandMsg::GetWindowSize(_, response_sender) => { - let _ = response_sender.send(self.window_size.initial_viewport); + WebDriverCommandMsg::GetWindowSize(webview_id, response_sender) => { + let browsing_context_id = BrowsingContextId::from(webview_id); + let size = self + .browsing_contexts + .get(&browsing_context_id) + .map(|browsing_context| browsing_context.viewport_details.size) + .unwrap_or_default(); + let _ = response_sender.send(size); }, WebDriverCommandMsg::SetWindowSize(webview_id, size, response_sender) => { self.webdriver.resize_channel = Some(response_sender); @@ -4386,6 +4729,7 @@ where ReferrerPolicy::EmptyString, None, None, + false, ); self.load_url_for_webdriver( webview_id, @@ -4413,6 +4757,7 @@ where NavigationHistoryBehavior::Replace, ); }, + // TODO: This should use the ScriptThreadMessage::EvaluateJavaScript command WebDriverCommandMsg::ScriptCommand(browsing_context_id, cmd) => { let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) { Some(browsing_context) => browsing_context.pipeline_id, @@ -4505,9 +4850,18 @@ where self.compositor_proxy .send(CompositorMsg::WebDriverMouseMoveEvent(webview_id, x, y)); }, - WebDriverCommandMsg::TakeScreenshot(_, rect, response_sender) => { + WebDriverCommandMsg::WheelScrollAction(webview, x, y, delta_x, delta_y) => { self.compositor_proxy - .send(CompositorMsg::CreatePng(rect, response_sender)); + .send(CompositorMsg::WebDriverWheelScrollEvent( + webview, x, y, delta_x, delta_y, + )); + }, + WebDriverCommandMsg::TakeScreenshot(webview_id, rect, response_sender) => { + self.compositor_proxy.send(CompositorMsg::CreatePng( + webview_id, + rect, + response_sender, + )); }, } } @@ -4732,7 +5086,7 @@ where change.webview_id, change.new_pipeline_id, new_context_info.parent_pipeline_id, - change.window_size, + change.viewport_details, new_context_info.is_private, new_context_info.inherited_secure_context, new_context_info.throttled, @@ -4836,10 +5190,42 @@ where self.trim_history(top_level_id); } + self.notify_focus_state(change.new_pipeline_id); + self.notify_history_changed(change.webview_id); self.update_webview_in_compositor(change.webview_id); } + /// Update the focus state of the specified pipeline that recently became + /// active (thus doesn't have a focused container element) and may have + /// out-dated information. + fn notify_focus_state(&mut self, pipeline_id: PipelineId) { + let pipeline = match self.pipelines.get(&pipeline_id) { + Some(pipeline) => pipeline, + None => return warn!("Pipeline {} is closed", pipeline_id), + }; + + let is_focused = match self.webviews.get(pipeline.webview_id) { + Some(webview) => webview.focused_browsing_context_id == pipeline.browsing_context_id, + None => { + return warn!( + "Pipeline {}'s top-level browsing context {} is closed", + pipeline_id, pipeline.webview_id + ); + }, + }; + + // If the browsing context is focused, focus the document + let msg = if is_focused { + ScriptThreadMessage::FocusDocument(pipeline_id, pipeline.focus_sequence) + } else { + ScriptThreadMessage::Unfocus(pipeline_id, pipeline.focus_sequence) + }; + if let Err(e) = pipeline.event_loop.send(msg) { + self.handle_send_error(pipeline_id, e); + } + } + #[cfg_attr( feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") @@ -4975,25 +5361,23 @@ where feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn handle_window_size_msg( + fn handle_change_viewport_details_msg( &mut self, webview_id: WebViewId, - new_size: WindowSizeData, + new_viewport_details: ViewportDetails, size_type: WindowSizeType, ) { debug!( - "handle_window_size_msg: {:?}", - new_size.initial_viewport.to_untyped() + "handle_change_viewport_details_msg: {:?}", + new_viewport_details.size.to_untyped() ); let browsing_context_id = BrowsingContextId::from(webview_id); - self.resize_browsing_context(new_size, size_type, browsing_context_id); + self.resize_browsing_context(new_viewport_details, size_type, browsing_context_id); if let Some(response_sender) = self.webdriver.resize_channel.take() { - let _ = response_sender.send(new_size.initial_viewport); + let _ = response_sender.send(new_viewport_details.size); } - - self.window_size = new_size; } /// Called when the window exits from fullscreen mode @@ -5067,7 +5451,7 @@ where // If the rectangle for this pipeline is zero sized, it will // never be painted. In this case, don't query the layout // thread as it won't contribute to the final output image. - if browsing_context.size == Size2D::zero() { + if browsing_context.viewport_details.size == Size2D::zero() { continue; } @@ -5162,12 +5546,12 @@ where )] fn resize_browsing_context( &mut self, - new_size: WindowSizeData, + new_viewport_details: ViewportDetails, size_type: WindowSizeType, browsing_context_id: BrowsingContextId, ) { if let Some(browsing_context) = self.browsing_contexts.get_mut(&browsing_context_id) { - browsing_context.size = new_size.initial_viewport; + browsing_context.viewport_details = new_viewport_details; // Send Resize (or ResizeInactive) messages to each pipeline in the frame tree. let pipeline_id = browsing_context.pipeline_id; let pipeline = match self.pipelines.get(&pipeline_id) { @@ -5176,7 +5560,7 @@ where }; let _ = pipeline.event_loop.send(ScriptThreadMessage::Resize( pipeline.id, - new_size, + new_viewport_details, size_type, )); let pipeline_ids = browsing_context @@ -5187,7 +5571,10 @@ where if let Some(pipeline) = self.pipelines.get(id) { let _ = pipeline .event_loop - .send(ScriptThreadMessage::ResizeInactive(pipeline.id, new_size)); + .send(ScriptThreadMessage::ResizeInactive( + pipeline.id, + new_viewport_details, + )); } } } @@ -5205,7 +5592,7 @@ where if pipeline.browsing_context_id == browsing_context_id { let _ = pipeline.event_loop.send(ScriptThreadMessage::Resize( pipeline.id, - new_size, + new_viewport_details, size_type, )); } @@ -5288,7 +5675,29 @@ where None => { warn!("{parent_pipeline_id}: Child closed after parent"); }, - Some(parent_pipeline) => parent_pipeline.remove_child(browsing_context_id), + Some(parent_pipeline) => { + parent_pipeline.remove_child(browsing_context_id); + + // If `browsing_context_id` has focus, focus the parent + // browsing context + if let Some(webview) = self.webviews.get_mut(browsing_context.top_level_id) { + if webview.focused_browsing_context_id == browsing_context_id { + trace!( + "About-to-be-closed browsing context {} is currently focused, so \ + focusing its parent {}", + browsing_context_id, parent_pipeline.browsing_context_id + ); + webview.focused_browsing_context_id = + parent_pipeline.browsing_context_id; + } + } else { + warn!( + "Browsing context {} contains a reference to \ + a non-existent top-level browsing context {}", + browsing_context_id, browsing_context.top_level_id + ); + } + }, }; } debug!("{}: Closed", browsing_context_id); diff --git a/components/constellation/event_loop.rs b/components/constellation/event_loop.rs index 362960eba64..46542e7212f 100644 --- a/components/constellation/event_loop.rs +++ b/components/constellation/event_loop.rs @@ -6,17 +6,36 @@ //! view of a script thread. When an `EventLoop` is dropped, an `ExitScriptThread` //! message is sent to the script thread, asking it to shut down. +use std::hash::Hash; use std::marker::PhantomData; use std::rc::Rc; +use std::sync::atomic::{AtomicUsize, Ordering}; use ipc_channel::Error; use ipc_channel::ipc::IpcSender; use script_traits::ScriptThreadMessage; +static CURRENT_EVENT_LOOP_ID: AtomicUsize = AtomicUsize::new(0); + /// pub struct EventLoop { script_chan: IpcSender, dont_send_or_sync: PhantomData>, + id: usize, +} + +impl PartialEq for EventLoop { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Eq for EventLoop {} + +impl Hash for EventLoop { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } } impl Drop for EventLoop { @@ -28,9 +47,11 @@ impl Drop for EventLoop { impl EventLoop { /// Create a new event loop from the channel to its script thread. pub fn new(script_chan: IpcSender) -> Rc { + let id = CURRENT_EVENT_LOOP_ID.fetch_add(1, Ordering::Relaxed); Rc::new(EventLoop { script_chan, dont_send_or_sync: PhantomData, + id, }) } diff --git a/components/constellation/lib.rs b/components/constellation/lib.rs index e00bc060cd5..c24543743b7 100644 --- a/components/constellation/lib.rs +++ b/components/constellation/lib.rs @@ -12,12 +12,13 @@ mod constellation; mod event_loop; mod logging; mod pipeline; +mod process_manager; mod sandboxing; mod serviceworker; mod session_history; -mod webview; +mod webview_manager; pub use crate::constellation::{Constellation, InitialConstellationState}; -pub use crate::logging::{FromCompositorLogger, FromScriptLogger}; +pub use crate::logging::{FromEmbedderLogger, FromScriptLogger}; pub use crate::pipeline::UnprivilegedPipelineContent; pub use crate::sandboxing::{UnprivilegedContent, content_process_sandbox_profile}; diff --git a/components/constellation/logging.rs b/components/constellation/logging.rs index fd17494b18a..97ff0202bd8 100644 --- a/components/constellation/logging.rs +++ b/components/constellation/logging.rs @@ -12,11 +12,13 @@ use std::thread; use backtrace::Backtrace; use base::id::WebViewId; -use constellation_traits::{ConstellationMsg as FromCompositorMsg, LogEntry}; +use constellation_traits::{ + EmbedderToConstellationMessage, LogEntry, ScriptToConstellationChan, + ScriptToConstellationMessage, +}; use crossbeam_channel::Sender; use log::{Level, LevelFilter, Log, Metadata, Record}; use parking_lot::ReentrantMutex; -use script_traits::{ScriptMsg as FromScriptMsg, ScriptToConstellationChan}; /// A logger directed at the constellation from content processes /// #[derive(Clone)] @@ -50,7 +52,7 @@ impl Log for FromScriptLogger { fn log(&self, record: &Record) { if let Some(entry) = log_entry(record) { let thread_name = thread::current().name().map(ToOwned::to_owned); - let msg = FromScriptMsg::LogEntry(thread_name, entry); + let msg = ScriptToConstellationMessage::LogEntry(thread_name, entry); let chan = self.script_to_constellation_chan.lock(); let _ = chan.send(msg); } @@ -61,15 +63,15 @@ impl Log for FromScriptLogger { /// A logger directed at the constellation from the compositor #[derive(Clone)] -pub struct FromCompositorLogger { +pub struct FromEmbedderLogger { /// A channel to the constellation - pub constellation_chan: Arc>>, + pub constellation_chan: Arc>>, } -impl FromCompositorLogger { +impl FromEmbedderLogger { /// Create a new constellation logger. - pub fn new(constellation_chan: Sender) -> FromCompositorLogger { - FromCompositorLogger { + pub fn new(constellation_chan: Sender) -> FromEmbedderLogger { + FromEmbedderLogger { constellation_chan: Arc::new(ReentrantMutex::new(constellation_chan)), } } @@ -80,7 +82,7 @@ impl FromCompositorLogger { } } -impl Log for FromCompositorLogger { +impl Log for FromEmbedderLogger { fn enabled(&self, metadata: &Metadata) -> bool { metadata.level() <= Level::Warn } @@ -89,7 +91,7 @@ impl Log for FromCompositorLogger { if let Some(entry) = log_entry(record) { let top_level_id = WebViewId::installed(); let thread_name = thread::current().name().map(ToOwned::to_owned); - let msg = FromCompositorMsg::LogEntry(top_level_id, thread_name, entry); + let msg = EmbedderToConstellationMessage::LogEntry(top_level_id, thread_name, entry); let chan = self.constellation_chan.lock(); let _ = chan.send(msg); } diff --git a/components/constellation/pipeline.rs b/components/constellation/pipeline.rs index 65f0c9dbfbd..556ef9bd60f 100644 --- a/components/constellation/pipeline.rs +++ b/components/constellation/pipeline.rs @@ -2,7 +2,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use std::borrow::Cow; use std::collections::HashSet; use std::rc::Rc; use std::sync::Arc; @@ -19,11 +18,14 @@ use base::id::{ #[cfg(feature = "bluetooth")] use bluetooth_traits::BluetoothRequest; use canvas_traits::webgl::WebGLPipeline; -use compositing_traits::{CompositionPipeline, CompositorMsg, CompositorProxy}; -use constellation_traits::WindowSizeData; +use compositing_traits::{ + CompositionPipeline, CompositorMsg, CompositorProxy, CrossProcessCompositorApi, +}; +use constellation_traits::{LoadData, SWManagerMsg, ScriptToConstellationChan}; use crossbeam_channel::{Sender, unbounded}; use devtools_traits::{DevtoolsControlMsg, ScriptToDevtoolsControlMsg}; use embedder_traits::user_content_manager::UserContentManager; +use embedder_traits::{AnimationState, FocusSequenceNumber, ViewportDetails}; use fonts::{SystemFontServiceProxy, SystemFontServiceProxySender}; use ipc_channel::Error; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; @@ -33,20 +35,21 @@ use media::WindowGLContext; use net::image_cache::ImageCacheImpl; use net_traits::ResourceThreads; use net_traits::image_cache::ImageCache; +use profile::system_reporter; +use profile_traits::mem::{ProfilerMsg, Reporter}; use profile_traits::{mem as profile_mem, time}; use script_layout_interface::{LayoutFactory, ScriptThreadFactory}; use script_traits::{ - AnimationState, DiscardBrowsingContext, DocumentActivity, InitialScriptState, LoadData, - NewLayoutInfo, SWManagerMsg, ScriptThreadMessage, ScriptToConstellationChan, + DiscardBrowsingContext, DocumentActivity, InitialScriptState, NewLayoutInfo, + ScriptThreadMessage, }; use serde::{Deserialize, Serialize}; use servo_config::opts::{self, Opts}; use servo_config::prefs::{self, Preferences}; use servo_url::ServoUrl; -use webrender_api::DocumentId; -use webrender_traits::CrossProcessCompositorApi; use crate::event_loop::EventLoop; +use crate::process_manager::Process; use crate::sandboxing::{UnprivilegedContent, spawn_multiprocess}; /// A `Pipeline` is the constellation's view of a `Window`. Each pipeline has an event loop @@ -99,6 +102,8 @@ pub struct Pipeline { /// The last compositor [`Epoch`] that was laid out in this pipeline if "exit after load" is /// enabled. pub layout_epoch: Epoch, + + pub focus_sequence: FocusSequenceNumber, } /// Initial setup data needed to construct a pipeline. @@ -162,8 +167,8 @@ pub struct InitialPipelineState { /// A channel to the memory profiler thread. pub mem_profiler_chan: profile_mem::ProfilerChan, - /// Information about the initial window size. - pub window_size: WindowSizeData, + /// The initial [`ViewportDetails`] to use when starting this new [`Pipeline`]. + pub viewport_details: ViewportDetails, /// The ID of the pipeline namespace for this script thread. pub pipeline_namespace_id: PipelineNamespaceId, @@ -180,9 +185,6 @@ pub struct InitialPipelineState { /// compositor threads after spawning a pipeline. pub prev_throttled: bool, - /// The ID of the document processed by this script thread. - pub webrender_document: DocumentId, - /// A channel to the WebGL thread. pub webgl_chan: Option, @@ -192,9 +194,6 @@ pub struct InitialPipelineState { /// Application window's GL Context for Media player pub player_context: WindowGLContext, - /// User agent string to report in network requests. - pub user_agent: Cow<'static, str>, - /// The image bytes associated with the RippyPNG embedder resource. pub rippy_data: Vec, @@ -205,6 +204,7 @@ pub struct InitialPipelineState { pub struct NewPipeline { pub pipeline: Pipeline, pub bhm_control_chan: Option>, + pub lifeline: Option<(IpcReceiver<()>, Process)>, } impl Pipeline { @@ -214,7 +214,7 @@ impl Pipeline { ) -> Result { // Note: we allow channel creation to panic, since recovering from this // probably requires a general low-memory strategy. - let (script_chan, bhm_control_chan) = match state.event_loop { + let (script_chan, (bhm_control_chan, lifeline)) = match state.event_loop { Some(script_chan) => { let new_layout_info = NewLayoutInfo { parent_info: state.parent_pipeline_id, @@ -223,14 +223,14 @@ impl Pipeline { webview_id: state.webview_id, opener: state.opener, load_data: state.load_data.clone(), - window_size: state.window_size, + viewport_details: state.viewport_details, }; if let Err(e) = script_chan.send(ScriptThreadMessage::AttachLayout(new_layout_info)) { warn!("Sending to script during pipeline creation failed ({})", e); } - (script_chan, None) + (script_chan, (None, None)) }, None => { let (script_chan, script_port) = ipc::channel().expect("Pipeline script chan"); @@ -279,14 +279,13 @@ impl Pipeline { resource_threads: state.resource_threads, time_profiler_chan: state.time_profiler_chan, mem_profiler_chan: state.mem_profiler_chan, - window_size: state.window_size, + viewport_details: state.viewport_details, script_chan: script_chan.clone(), load_data: state.load_data.clone(), script_port, opts: (*opts::get()).clone(), prefs: Box::new(prefs::get().clone()), pipeline_namespace_id: state.pipeline_namespace_id, - webrender_document: state.webrender_document, cross_process_compositor_api: state .compositor_proxy .cross_process_compositor_api @@ -294,20 +293,23 @@ impl Pipeline { webgl_chan: state.webgl_chan, webxr_registry: state.webxr_registry, player_context: state.player_context, - user_agent: state.user_agent, rippy_data: state.rippy_data, user_content_manager: state.user_content_manager, + lifeline_sender: None, }; // Spawn the child process. // // Yes, that's all there is to it! - let bhm_control_chan = if opts::get().multiprocess { + let multiprocess_data = if opts::get().multiprocess { let (bhm_control_chan, bhm_control_port) = ipc::channel().expect("Sampler chan"); unprivileged_pipeline_content.bhm_control_port = Some(bhm_control_port); - unprivileged_pipeline_content.spawn_multiprocess()?; - Some(bhm_control_chan) + let (sender, receiver) = + ipc::channel().expect("Failed to create lifeline channel"); + unprivileged_pipeline_content.lifeline_sender = Some(sender); + let process = unprivileged_pipeline_content.spawn_multiprocess()?; + (Some(bhm_control_chan), Some((receiver, process))) } else { // Should not be None in single-process mode. let register = state @@ -318,10 +320,10 @@ impl Pipeline { state.layout_factory, register, ); - None + (None, None) }; - (EventLoop::new(script_chan), bhm_control_chan) + (EventLoop::new(script_chan), multiprocess_data) }, }; @@ -338,6 +340,7 @@ impl Pipeline { Ok(NewPipeline { pipeline, bhm_control_chan, + lifeline, }) } @@ -369,6 +372,7 @@ impl Pipeline { completely_loaded: false, title: String::new(), layout_epoch: Epoch(0), + focus_sequence: FocusSequenceNumber::default(), }; pipeline.set_throttled(throttled); @@ -489,7 +493,7 @@ pub struct UnprivilegedPipelineContent { resource_threads: ResourceThreads, time_profiler_chan: time::ProfilerChan, mem_profiler_chan: profile_mem::ProfilerChan, - window_size: WindowSizeData, + viewport_details: ViewportDetails, script_chan: IpcSender, load_data: LoadData, script_port: IpcReceiver, @@ -497,13 +501,12 @@ pub struct UnprivilegedPipelineContent { prefs: Box, pipeline_namespace_id: PipelineNamespaceId, cross_process_compositor_api: CrossProcessCompositorApi, - webrender_document: DocumentId, webgl_chan: Option, webxr_registry: Option, player_context: WindowGLContext, - user_agent: Cow<'static, str>, rippy_data: Vec, user_content_manager: UserContentManager, + lifeline_sender: Option>, } impl UnprivilegedPipelineContent { @@ -540,12 +543,11 @@ impl UnprivilegedPipelineContent { time_profiler_sender: self.time_profiler_chan.clone(), memory_profiler_sender: self.mem_profiler_chan.clone(), devtools_server_sender: self.devtools_ipc_sender, - window_size: self.window_size, + viewport_details: self.viewport_details, pipeline_namespace_id: self.pipeline_namespace_id, content_process_shutdown_sender: content_process_shutdown_chan, webgl_chan: self.webgl_chan, webxr_registry: self.webxr_registry, - webrender_document: self.webrender_document, compositor_api: self.cross_process_compositor_api.clone(), player_context: self.player_context.clone(), inherited_secure_context: self.load_data.inherited_secure_context, @@ -554,7 +556,6 @@ impl UnprivilegedPipelineContent { layout_factory, Arc::new(self.system_font_service.to_proxy()), self.load_data.clone(), - self.user_agent, ); if wait_for_completion { @@ -565,7 +566,7 @@ impl UnprivilegedPipelineContent { } } - pub fn spawn_multiprocess(self) -> Result<(), Error> { + pub fn spawn_multiprocess(self) -> Result { spawn_multiprocess(UnprivilegedContent::Pipeline(self)) } @@ -590,4 +591,24 @@ impl UnprivilegedPipelineContent { pub fn prefs(&self) -> &Preferences { &self.prefs } + + pub fn register_system_memory_reporter(&self) { + // Register the system memory reporter, which will run on its own thread. It never needs to + // be unregistered, because as long as the memory profiler is running the system memory + // reporter can make measurements. + let (system_reporter_sender, system_reporter_receiver) = + ipc::channel().expect("failed to create ipc channel"); + ROUTER.add_typed_route( + system_reporter_receiver, + Box::new(|message| { + if let Ok(request) = message { + system_reporter::collect_reports(request); + } + }), + ); + self.mem_profiler_chan.send(ProfilerMsg::RegisterReporter( + format!("system-content-{}", std::process::id()), + Reporter(system_reporter_sender), + )); + } } diff --git a/components/constellation/process_manager.rs b/components/constellation/process_manager.rs new file mode 100644 index 00000000000..7da2e9c63df --- /dev/null +++ b/components/constellation/process_manager.rs @@ -0,0 +1,79 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::process::Child; + +use crossbeam_channel::{Receiver, Select}; +use log::{debug, warn}; +use profile_traits::mem::{ProfilerChan, ProfilerMsg}; + +pub enum Process { + Unsandboxed(Child), + Sandboxed(u32), +} + +impl Process { + fn pid(&self) -> u32 { + match self { + Self::Unsandboxed(child) => child.id(), + Self::Sandboxed(pid) => *pid, + } + } + + fn wait(&mut self) { + match self { + Self::Unsandboxed(child) => { + let _ = child.wait(); + }, + Self::Sandboxed(_pid) => { + // TODO: use nix::waitpid() on supported platforms. + warn!("wait() is not yet implemented for sandboxed processes."); + }, + } + } +} + +type ProcessReceiver = Receiver>; + +pub(crate) struct ProcessManager { + processes: Vec<(Process, ProcessReceiver)>, + mem_profiler_chan: ProfilerChan, +} + +impl ProcessManager { + pub fn new(mem_profiler_chan: ProfilerChan) -> Self { + Self { + processes: vec![], + mem_profiler_chan, + } + } + + pub fn add(&mut self, receiver: ProcessReceiver, process: Process) { + debug!("Adding process pid={}", process.pid()); + self.processes.push((process, receiver)); + } + + pub fn register<'a>(&'a self, select: &mut Select<'a>) { + for (_, receiver) in &self.processes { + select.recv(receiver); + } + } + + pub fn receiver_at(&self, index: usize) -> &ProcessReceiver { + let (_, receiver) = &self.processes[index]; + receiver + } + + pub fn remove(&mut self, index: usize) { + let (mut process, _) = self.processes.swap_remove(index); + debug!("Removing process pid={}", process.pid()); + // Unregister this process system memory profiler + self.mem_profiler_chan + .send(ProfilerMsg::UnregisterReporter(format!( + "system-content-{}", + process.pid() + ))); + process.wait(); + } +} diff --git a/components/constellation/sandboxing.rs b/components/constellation/sandboxing.rs index 0f883b4743a..b4c6e7a9a39 100644 --- a/components/constellation/sandboxing.rs +++ b/components/constellation/sandboxing.rs @@ -25,6 +25,7 @@ use servo_config::opts::Opts; use servo_config::prefs::Preferences; use crate::pipeline::UnprivilegedPipelineContent; +use crate::process_manager::Process; use crate::serviceworker::ServiceWorkerUnprivilegedContent; #[derive(Deserialize, Serialize)] @@ -146,7 +147,7 @@ pub fn content_process_sandbox_profile() { target_arch = "arm", all(target_arch = "aarch64", not(target_os = "windows")) ))] -pub fn spawn_multiprocess(content: UnprivilegedContent) -> Result<(), Error> { +pub fn spawn_multiprocess(content: UnprivilegedContent) -> Result { use ipc_channel::ipc::{IpcOneShotServer, IpcSender}; // Note that this function can panic, due to process creation, // avoiding this panic would require a mechanism for dealing @@ -159,14 +160,14 @@ pub fn spawn_multiprocess(content: UnprivilegedContent) -> Result<(), Error> { setup_common(&mut child_process, token); #[allow(clippy::zombie_processes)] - let _ = child_process + let child = child_process .spawn() .expect("Failed to start unsandboxed child process!"); let (_receiver, sender) = server.accept().expect("Server failed to accept."); sender.send(content)?; - Ok(()) + Ok(Process::Unsandboxed(child)) } #[cfg(all( @@ -177,7 +178,7 @@ pub fn spawn_multiprocess(content: UnprivilegedContent) -> Result<(), Error> { not(target_arch = "arm"), not(target_arch = "aarch64") ))] -pub fn spawn_multiprocess(content: UnprivilegedContent) -> Result<(), Error> { +pub fn spawn_multiprocess(content: UnprivilegedContent) -> Result { use gaol::sandbox::{self, Sandbox, SandboxMethods}; use ipc_channel::ipc::{IpcOneShotServer, IpcSender}; @@ -208,33 +209,37 @@ pub fn spawn_multiprocess(content: UnprivilegedContent) -> Result<(), Error> { .expect("Failed to create IPC one-shot server."); // If there is a sandbox, use the `gaol` API to create the child process. - if content.opts().sandbox { + let process = if content.opts().sandbox { let mut command = sandbox::Command::me().expect("Failed to get current sandbox."); setup_common(&mut command, token); let profile = content_process_sandbox_profile(); - let _ = Sandbox::new(profile) - .start(&mut command) - .expect("Failed to start sandboxed child process!"); + Process::Sandboxed( + Sandbox::new(profile) + .start(&mut command) + .expect("Failed to start sandboxed child process!") + .pid as u32, + ) } else { let path_to_self = env::current_exe().expect("Failed to get current executor."); let mut child_process = process::Command::new(path_to_self); setup_common(&mut child_process, token); - #[allow(clippy::zombie_processes)] - let _ = child_process - .spawn() - .expect("Failed to start unsandboxed child process!"); - } + Process::Unsandboxed( + child_process + .spawn() + .expect("Failed to start unsandboxed child process!"), + ) + }; let (_receiver, sender) = server.accept().expect("Server failed to accept."); sender.send(content)?; - Ok(()) + Ok(process) } #[cfg(any(target_os = "windows", target_os = "ios"))] -pub fn spawn_multiprocess(_content: UnprivilegedContent) -> Result<(), Error> { +pub fn spawn_multiprocess(_content: UnprivilegedContent) -> Result { log::error!("Multiprocess is not supported on Windows or iOS."); process::exit(1); } diff --git a/components/constellation/serviceworker.rs b/components/constellation/serviceworker.rs index e3de4355fc9..e566b8b01a0 100644 --- a/components/constellation/serviceworker.rs +++ b/components/constellation/serviceworker.rs @@ -2,14 +2,16 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use constellation_traits::{SWManagerSenders, ServiceWorkerManagerFactory}; use ipc_channel::Error; -use script_traits::{SWManagerSenders, ServiceWorkerManagerFactory}; +use ipc_channel::ipc::IpcSender; use serde::{Deserialize, Serialize}; use servo_config::opts::{self, Opts}; use servo_config::prefs; use servo_config::prefs::Preferences; use servo_url::ImmutableOrigin; +use crate::process_manager::Process; use crate::sandboxing::{UnprivilegedContent, spawn_multiprocess}; /// Conceptually, this is glue to start an agent-cluster for a service worker agent. @@ -20,18 +22,21 @@ pub struct ServiceWorkerUnprivilegedContent { prefs: Box, senders: SWManagerSenders, origin: ImmutableOrigin, + lifeline_sender: Option>, } impl ServiceWorkerUnprivilegedContent { pub fn new( senders: SWManagerSenders, origin: ImmutableOrigin, + lifeline_sender: Option>, ) -> ServiceWorkerUnprivilegedContent { ServiceWorkerUnprivilegedContent { opts: (*opts::get()).clone(), prefs: Box::new(prefs::get().clone()), senders, origin, + lifeline_sender, } } @@ -44,7 +49,7 @@ impl ServiceWorkerUnprivilegedContent { } /// Start the agent-cluster in it's own process. - pub fn spawn_multiprocess(self) -> Result<(), Error> { + pub fn spawn_multiprocess(self) -> Result { spawn_multiprocess(UnprivilegedContent::ServiceWorker(self)) } diff --git a/components/constellation/session_history.rs b/components/constellation/session_history.rs index 9155ffcfca8..4d7c9979497 100644 --- a/components/constellation/session_history.rs +++ b/components/constellation/session_history.rs @@ -6,11 +6,10 @@ use std::cmp::PartialEq; use std::fmt; use base::id::{BrowsingContextId, HistoryStateId, PipelineId, WebViewId}; -use euclid::Size2D; +use constellation_traits::LoadData; +use embedder_traits::ViewportDetails; use log::debug; -use script_traits::LoadData; use servo_url::ServoUrl; -use style_traits::CSSPixel; use crate::browsingcontext::NewBrowsingContextInfo; @@ -127,8 +126,8 @@ pub struct SessionHistoryChange { /// easily available when they need to be constructed. pub new_browsing_context_info: Option, - /// The size of the viewport for the browsing context. - pub window_size: Size2D, + /// The size and hidpi scale factor of the viewport for the browsing context. + pub viewport_details: ViewportDetails, } /// Represents a pipeline or discarded pipeline in a history entry. diff --git a/components/constellation/tracing.rs b/components/constellation/tracing.rs index 08e6fecc099..fd7fe7dd251 100644 --- a/components/constellation/tracing.rs +++ b/components/constellation/tracing.rs @@ -46,7 +46,7 @@ mod from_compositor { }; } - impl LogTarget for constellation_traits::ConstellationMsg { + impl LogTarget for constellation_traits::EmbedderToConstellationMessage { fn log_target(&self) -> &'static str { match self { Self::Exit => target!("Exit"), @@ -58,7 +58,7 @@ mod from_compositor { Self::LoadUrl(..) => target!("LoadUrl"), Self::ClearCache => target!("ClearCache"), Self::TraverseHistory(..) => target!("TraverseHistory"), - Self::WindowSize(..) => target!("WindowSize"), + Self::ChangeViewportDetails(..) => target!("ChangeViewportDetails"), Self::ThemeChange(..) => target!("ThemeChange"), Self::TickAnimation(..) => target!("TickAnimation"), Self::WebDriverCommand(..) => target!("WebDriverCommand"), @@ -77,6 +77,7 @@ mod from_compositor { Self::SetWebViewThrottled(_, _) => target!("SetWebViewThrottled"), Self::SetScrollStates(..) => target!("SetScrollStates"), Self::PaintMetric(..) => target!("PaintMetric"), + Self::EvaluateJavaScript(..) => target!("EvaluateJavaScript"), } } } @@ -113,7 +114,7 @@ mod from_script { }; } - impl LogTarget for script_traits::ScriptMsg { + impl LogTarget for constellation_traits::ScriptToConstellationMessage { fn log_target(&self) -> &'static str { match self { Self::CompleteMessagePortTransfer(..) => target!("CompleteMessagePortTransfer"), @@ -123,8 +124,8 @@ mod from_script { Self::RemoveMessagePortRouter(..) => target!("RemoveMessagePortRouter"), Self::RerouteMessagePort(..) => target!("RerouteMessagePort"), Self::MessagePortShipped(..) => target!("MessagePortShipped"), - Self::RemoveMessagePort(..) => target!("RemoveMessagePort"), Self::EntanglePorts(..) => target!("EntanglePorts"), + Self::DisentanglePorts(..) => target!("DisentanglePorts"), Self::NewBroadcastChannelRouter(..) => target!("NewBroadcastChannelRouter"), Self::RemoveBroadcastChannelRouter(..) => target!("RemoveBroadcastChannelRouter"), Self::NewBroadcastChannelNameInRouter(..) => { @@ -138,7 +139,8 @@ mod from_script { Self::BroadcastStorageEvent(..) => target!("BroadcastStorageEvent"), Self::ChangeRunningAnimationsState(..) => target!("ChangeRunningAnimationsState"), Self::CreateCanvasPaintThread(..) => target!("CreateCanvasPaintThread"), - Self::Focus => target!("Focus"), + Self::Focus(..) => target!("Focus"), + Self::FocusRemoteDocument(..) => target!("FocusRemoteDocument"), Self::GetTopForBrowsingContext(..) => target!("GetTopForBrowsingContext"), Self::GetBrowsingContextInfo(..) => target!("GetBrowsingContextInfo"), Self::GetChildBrowsingContextId(..) => target!("GetChildBrowsingContextId"), @@ -175,6 +177,7 @@ mod from_script { Self::TitleChanged(..) => target!("TitleChanged"), Self::IFrameSizes(..) => target!("IFrameSizes"), Self::ReportMemory(..) => target!("ReportMemory"), + Self::FinishJavaScriptEvaluation(..) => target!("FinishJavaScriptEvaluation"), } } } @@ -236,6 +239,10 @@ mod from_script { Self::StopGamepadHapticEffect(..) => target_variant!("StopGamepadHapticEffect"), Self::ShutdownComplete => target_variant!("ShutdownComplete"), Self::ShowNotification(..) => target_variant!("ShowNotification"), + Self::ShowSelectElementMenu(..) => target_variant!("ShowSelectElementMenu"), + Self::FinishJavaScriptEvaluation(..) => { + target_variant!("FinishJavaScriptEvaluation") + }, } } } diff --git a/components/constellation/webview.rs b/components/constellation/webview.rs deleted file mode 100644 index 8b134b62f2e..00000000000 --- a/components/constellation/webview.rs +++ /dev/null @@ -1,183 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -use std::collections::HashMap; - -use base::id::WebViewId; - -#[derive(Debug)] -pub struct WebViewManager { - /// Our top-level browsing contexts. In the WebRender scene, their pipelines are the children of - /// a single root pipeline that also applies any pinch zoom transformation. - webviews: HashMap, - - /// The order in which they were focused, latest last. - focus_order: Vec, - - /// Whether the latest webview in focus order is currently focused. - is_focused: bool, -} - -impl Default for WebViewManager { - fn default() -> Self { - Self { - webviews: HashMap::default(), - focus_order: Vec::default(), - is_focused: false, - } - } -} - -impl WebViewManager { - pub fn add(&mut self, webview_id: WebViewId, webview: WebView) { - self.webviews.insert(webview_id, webview); - } - - pub fn remove(&mut self, webview_id: WebViewId) -> Option { - if self.focus_order.last() == Some(&webview_id) { - self.is_focused = false; - } - self.focus_order.retain(|b| *b != webview_id); - self.webviews.remove(&webview_id) - } - - pub fn get(&self, webview_id: WebViewId) -> Option<&WebView> { - self.webviews.get(&webview_id) - } - - pub fn get_mut(&mut self, webview_id: WebViewId) -> Option<&mut WebView> { - self.webviews.get_mut(&webview_id) - } - - pub fn focused_webview(&self) -> Option<(WebViewId, &WebView)> { - if !self.is_focused { - return None; - } - - if let Some(webview_id) = self.focus_order.last().cloned() { - debug_assert!( - self.webviews.contains_key(&webview_id), - "BUG: webview in .focus_order not in .webviews!", - ); - self.get(webview_id).map(|webview| (webview_id, webview)) - } else { - debug_assert!(false, "BUG: .is_focused but no webviews in .focus_order!"); - None - } - } - - pub fn focus(&mut self, webview_id: WebViewId) { - debug_assert!(self.webviews.contains_key(&webview_id)); - self.focus_order.retain(|b| *b != webview_id); - self.focus_order.push(webview_id); - self.is_focused = true; - } - - pub fn unfocus(&mut self) { - self.is_focused = false; - } -} - -#[cfg(test)] -mod test { - use std::num::NonZeroU32; - - use base::id::{ - BrowsingContextId, BrowsingContextIndex, PipelineNamespace, PipelineNamespaceId, WebViewId, - }; - - use crate::webview::WebViewManager; - - fn id(namespace_id: u32, index: u32) -> WebViewId { - WebViewId(BrowsingContextId { - namespace_id: PipelineNamespaceId(namespace_id), - index: BrowsingContextIndex(NonZeroU32::new(index).expect("Incorrect test case")), - }) - } - - fn webviews_sorted( - webviews: &WebViewManager, - ) -> Vec<(WebViewId, WebView)> { - let mut keys = webviews.webviews.keys().collect::>(); - keys.sort(); - keys.iter() - .map(|&id| { - ( - *id, - webviews - .webviews - .get(id) - .cloned() - .expect("Incorrect test case"), - ) - }) - .collect() - } - - #[test] - fn test() { - PipelineNamespace::install(PipelineNamespaceId(0)); - let mut webviews = WebViewManager::default(); - - // add() adds the webview to the map, but does not focus it. - webviews.add(WebViewId::new(), 'a'); - webviews.add(WebViewId::new(), 'b'); - webviews.add(WebViewId::new(), 'c'); - assert_eq!( - webviews_sorted(&webviews), - vec![(id(0, 1), 'a'), (id(0, 2), 'b'), (id(0, 3), 'c'),] - ); - assert!(webviews.focus_order.is_empty()); - assert_eq!(webviews.is_focused, false); - - // focus() makes the given webview the latest in focus order. - webviews.focus(id(0, 2)); - assert_eq!(webviews.focus_order, vec![id(0, 2)]); - assert_eq!(webviews.is_focused, true); - webviews.focus(id(0, 1)); - assert_eq!(webviews.focus_order, vec![id(0, 2), id(0, 1)]); - assert_eq!(webviews.is_focused, true); - webviews.focus(id(0, 3)); - assert_eq!(webviews.focus_order, vec![id(0, 2), id(0, 1), id(0, 3)]); - assert_eq!(webviews.is_focused, true); - - // unfocus() clears the “is focused” flag, but does not touch the focus order. - webviews.unfocus(); - assert_eq!(webviews.focus_order, vec![id(0, 2), id(0, 1), id(0, 3)]); - assert_eq!(webviews.is_focused, false); - - // focus() avoids duplicates in focus order, when the given webview has been focused before. - webviews.focus(id(0, 1)); - assert_eq!(webviews.focus_order, vec![id(0, 2), id(0, 3), id(0, 1)]); - assert_eq!(webviews.is_focused, true); - - // remove() clears the “is focused” flag iff the given webview was focused. - webviews.remove(id(1, 1)); - assert_eq!(webviews.is_focused, true); - webviews.remove(id(1, 2)); - assert_eq!(webviews.is_focused, true); - webviews.remove(id(2, 1)); - assert_eq!(webviews.is_focused, true); - webviews.remove(id(2, 2)); - assert_eq!(webviews.is_focused, true); - webviews.remove(id(2, 3)); - assert_eq!(webviews.is_focused, true); - webviews.remove(id(2, 4)); - assert_eq!(webviews.is_focused, true); - webviews.remove(id(3, 1)); - assert_eq!(webviews.is_focused, true); - webviews.remove(id(4, 1)); - assert_eq!(webviews.is_focused, true); - webviews.remove(id(0, 2)); - assert_eq!(webviews.is_focused, true); - webviews.remove(id(0, 1)); - assert_eq!(webviews.is_focused, false); - webviews.remove(id(0, 3)); - assert_eq!(webviews.is_focused, false); - - // remove() removes the given webview from both the map and the focus order. - assert!(webviews_sorted(&webviews).is_empty()); - assert!(webviews.focus_order.is_empty()); - } -} diff --git a/components/constellation/webview_manager.rs b/components/constellation/webview_manager.rs new file mode 100644 index 00000000000..7d6e98a54ea --- /dev/null +++ b/components/constellation/webview_manager.rs @@ -0,0 +1,179 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::collections::HashMap; + +use base::id::WebViewId; + +#[derive(Debug)] +pub struct WebViewManager { + /// Our top-level browsing contexts. In the WebRender scene, their pipelines are the children of + /// a single root pipeline that also applies any pinch zoom transformation. + webviews: HashMap, + + /// The order in which they were focused, latest last. + focus_order: Vec, + + /// Whether the latest webview in focus order is currently focused. + is_focused: bool, +} + +impl Default for WebViewManager { + fn default() -> Self { + Self { + webviews: HashMap::default(), + focus_order: Vec::default(), + is_focused: false, + } + } +} + +impl WebViewManager { + pub fn add(&mut self, webview_id: WebViewId, webview: WebView) { + self.webviews.insert(webview_id, webview); + } + + pub fn remove(&mut self, webview_id: WebViewId) -> Option { + if self.focus_order.last() == Some(&webview_id) { + self.is_focused = false; + } + self.focus_order.retain(|b| *b != webview_id); + self.webviews.remove(&webview_id) + } + + pub fn get(&self, webview_id: WebViewId) -> Option<&WebView> { + self.webviews.get(&webview_id) + } + + pub fn get_mut(&mut self, webview_id: WebViewId) -> Option<&mut WebView> { + self.webviews.get_mut(&webview_id) + } + + pub fn focused_webview(&self) -> Option<(WebViewId, &WebView)> { + if !self.is_focused { + return None; + } + + if let Some(webview_id) = self.focus_order.last().cloned() { + debug_assert!( + self.webviews.contains_key(&webview_id), + "BUG: webview in .focus_order not in .webviews!", + ); + self.get(webview_id).map(|webview| (webview_id, webview)) + } else { + debug_assert!(false, "BUG: .is_focused but no webviews in .focus_order!"); + None + } + } + + pub fn focus(&mut self, webview_id: WebViewId) { + debug_assert!(self.webviews.contains_key(&webview_id)); + self.focus_order.retain(|b| *b != webview_id); + self.focus_order.push(webview_id); + self.is_focused = true; + } + + pub fn unfocus(&mut self) { + self.is_focused = false; + } +} + +#[cfg(test)] +mod test { + use base::id::{BrowsingContextId, Index, PipelineNamespace, PipelineNamespaceId, WebViewId}; + + use crate::webview_manager::WebViewManager; + + fn id(namespace_id: u32, index: u32) -> WebViewId { + WebViewId(BrowsingContextId { + namespace_id: PipelineNamespaceId(namespace_id), + index: Index::new(index).expect("Incorrect test case"), + }) + } + + fn webviews_sorted( + webviews: &WebViewManager, + ) -> Vec<(WebViewId, WebView)> { + let mut keys = webviews.webviews.keys().collect::>(); + keys.sort(); + keys.iter() + .map(|&id| { + ( + *id, + webviews + .webviews + .get(id) + .cloned() + .expect("Incorrect test case"), + ) + }) + .collect() + } + + #[test] + fn test() { + PipelineNamespace::install(PipelineNamespaceId(0)); + let mut webviews = WebViewManager::default(); + + // add() adds the webview to the map, but does not focus it. + webviews.add(WebViewId::new(), 'a'); + webviews.add(WebViewId::new(), 'b'); + webviews.add(WebViewId::new(), 'c'); + assert_eq!( + webviews_sorted(&webviews), + vec![(id(0, 1), 'a'), (id(0, 2), 'b'), (id(0, 3), 'c'),] + ); + assert!(webviews.focus_order.is_empty()); + assert_eq!(webviews.is_focused, false); + + // focus() makes the given webview the latest in focus order. + webviews.focus(id(0, 2)); + assert_eq!(webviews.focus_order, vec![id(0, 2)]); + assert_eq!(webviews.is_focused, true); + webviews.focus(id(0, 1)); + assert_eq!(webviews.focus_order, vec![id(0, 2), id(0, 1)]); + assert_eq!(webviews.is_focused, true); + webviews.focus(id(0, 3)); + assert_eq!(webviews.focus_order, vec![id(0, 2), id(0, 1), id(0, 3)]); + assert_eq!(webviews.is_focused, true); + + // unfocus() clears the “is focused” flag, but does not touch the focus order. + webviews.unfocus(); + assert_eq!(webviews.focus_order, vec![id(0, 2), id(0, 1), id(0, 3)]); + assert_eq!(webviews.is_focused, false); + + // focus() avoids duplicates in focus order, when the given webview has been focused before. + webviews.focus(id(0, 1)); + assert_eq!(webviews.focus_order, vec![id(0, 2), id(0, 3), id(0, 1)]); + assert_eq!(webviews.is_focused, true); + + // remove() clears the “is focused” flag iff the given webview was focused. + webviews.remove(id(1, 1)); + assert_eq!(webviews.is_focused, true); + webviews.remove(id(1, 2)); + assert_eq!(webviews.is_focused, true); + webviews.remove(id(2, 1)); + assert_eq!(webviews.is_focused, true); + webviews.remove(id(2, 2)); + assert_eq!(webviews.is_focused, true); + webviews.remove(id(2, 3)); + assert_eq!(webviews.is_focused, true); + webviews.remove(id(2, 4)); + assert_eq!(webviews.is_focused, true); + webviews.remove(id(3, 1)); + assert_eq!(webviews.is_focused, true); + webviews.remove(id(4, 1)); + assert_eq!(webviews.is_focused, true); + webviews.remove(id(0, 2)); + assert_eq!(webviews.is_focused, true); + webviews.remove(id(0, 1)); + assert_eq!(webviews.is_focused, false); + webviews.remove(id(0, 3)); + assert_eq!(webviews.is_focused, false); + + // remove() removes the given webview from both the map and the focus order. + assert!(webviews_sorted(&webviews).is_empty()); + assert!(webviews.focus_order.is_empty()); + } +} diff --git a/components/devtools/Cargo.toml b/components/devtools/Cargo.toml index a74cbb88b9b..bc7322d9ebc 100644 --- a/components/devtools/Cargo.toml +++ b/components/devtools/Cargo.toml @@ -11,9 +11,6 @@ rust-version.workspace = true name = "devtools" path = "lib.rs" -[build-dependencies] -chrono = { workspace = true } - [dependencies] base = { workspace = true } chrono = { workspace = true } @@ -31,3 +28,6 @@ servo_config = { path = "../config" } servo_rand = { path = "../rand" } servo_url = { path = "../url" } uuid = { workspace = true } + +[build-dependencies] +chrono = { workspace = true } diff --git a/components/devtools/actors/browsing_context.rs b/components/devtools/actors/browsing_context.rs index 5875f039c66..81a00e82d47 100644 --- a/components/devtools/actors/browsing_context.rs +++ b/components/devtools/actors/browsing_context.rs @@ -10,9 +10,12 @@ use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::net::TcpStream; -use base::id::{BrowsingContextId, PipelineId, WebViewId}; -use devtools_traits::DevtoolScriptControlMsg::{self, GetCssDatabase, WantsLiveNotifications}; +use base::id::PipelineId; +use devtools_traits::DevtoolScriptControlMsg::{ + self, GetCssDatabase, SimulateColorScheme, WantsLiveNotifications, +}; use devtools_traits::{DevtoolsPageInfo, NavigationState}; +use embedder_traits::Theme; use ipc_channel::ipc::{self, IpcSender}; use serde::Serialize; use serde_json::{Map, Value}; @@ -26,9 +29,17 @@ use crate::actors::stylesheets::StyleSheetsActor; use crate::actors::tab::TabDescriptorActor; use crate::actors::thread::ThreadActor; use crate::actors::watcher::{SessionContext, SessionContextType, WatcherActor}; +use crate::id::{DevtoolsBrowserId, DevtoolsBrowsingContextId, DevtoolsOuterWindowId, IdMap}; use crate::protocol::JsonPacketStream; +use crate::resource::ResourceAvailable; use crate::{EmptyReplyMsg, StreamId}; +#[derive(Serialize)] +struct ListWorkersReply { + from: String, + workers: Vec<()>, +} + #[derive(Serialize)] struct FrameUpdateReply { from: String, @@ -46,14 +57,6 @@ struct FrameUpdateMsg { title: String, } -#[derive(Serialize)] -struct ResourceAvailableReply { - from: String, - #[serde(rename = "type")] - type_: String, - array: Vec<(String, Vec)>, -} - #[derive(Serialize)] struct TabNavigated { from: String, @@ -124,10 +127,11 @@ pub(crate) struct BrowsingContextActor { pub name: String, pub title: RefCell, pub url: RefCell, - /// This correspond to webview_id - pub browser_id: WebViewId, - pub active_pipeline: Cell, - pub browsing_context_id: BrowsingContextId, + /// This corresponds to webview_id + pub browser_id: DevtoolsBrowserId, + pub active_pipeline_id: Cell, + pub active_outer_window_id: Cell, + pub browsing_context_id: DevtoolsBrowsingContextId, pub accessibility: String, pub console: String, pub css_properties: String, @@ -141,6 +145,12 @@ pub(crate) struct BrowsingContextActor { pub watcher: String, } +impl ResourceAvailable for BrowsingContextActor { + fn actor_name(&self) -> String { + self.name.clone() + } +} + impl Actor for BrowsingContextActor { fn name(&self) -> String { self.name.clone() @@ -161,6 +171,14 @@ impl Actor for BrowsingContextActor { let _ = stream.write_json_packet(&msg); ActorMessageStatus::Processed }, + "listWorkers" => { + let _ = stream.write_json_packet(&ListWorkersReply { + from: self.name(), + // TODO: Find out what needs to be listed here + workers: vec![], + }); + ActorMessageStatus::Processed + }, _ => ActorMessageStatus::Ignored, }) } @@ -169,19 +187,21 @@ impl Actor for BrowsingContextActor { self.streams.borrow_mut().remove(&id); if self.streams.borrow().is_empty() { self.script_chan - .send(WantsLiveNotifications(self.active_pipeline.get(), false)) + .send(WantsLiveNotifications(self.active_pipeline_id.get(), false)) .unwrap(); } } } impl BrowsingContextActor { + #[allow(clippy::too_many_arguments)] pub(crate) fn new( console: String, - browser_id: WebViewId, - browsing_context_id: BrowsingContextId, + browser_id: DevtoolsBrowserId, + browsing_context_id: DevtoolsBrowsingContextId, page_info: DevtoolsPageInfo, pipeline_id: PipelineId, + outer_window_id: DevtoolsOuterWindowId, script_sender: IpcSender, actors: &mut ActorRegistry, ) -> BrowsingContextActor { @@ -230,7 +250,8 @@ impl BrowsingContextActor { script_chan: script_sender, title: RefCell::new(title), url: RefCell::new(url.into_string()), - active_pipeline: Cell::new(pipeline_id), + active_pipeline_id: Cell::new(pipeline_id), + active_outer_window_id: Cell::new(outer_window_id), browser_id, browsing_context_id, accessibility: accessibility.name(), @@ -270,11 +291,9 @@ impl BrowsingContextActor { }, title: self.title.borrow().clone(), url: self.url.borrow().clone(), - browser_id: self.browser_id.0.index.0.get(), - //FIXME: shouldn't ignore pipeline namespace field - browsing_context_id: self.browsing_context_id.index.0.get(), - //FIXME: shouldn't ignore pipeline namespace field - outer_window_id: self.active_pipeline.get().index.0.get(), + browser_id: self.browser_id.value(), + browsing_context_id: self.browsing_context_id.value(), + outer_window_id: self.active_outer_window_id.get().value(), is_top_level_target: true, accessibility_actor: self.accessibility.clone(), console_actor: self.console.clone(), @@ -286,15 +305,17 @@ impl BrowsingContextActor { } } - pub(crate) fn navigate(&self, state: NavigationState) { - let (pipeline, title, url, state) = match state { + pub(crate) fn navigate(&self, state: NavigationState, id_map: &mut IdMap) { + let (pipeline_id, title, url, state) = match state { NavigationState::Start(url) => (None, None, url, "start"), NavigationState::Stop(pipeline, info) => { (Some(pipeline), Some(info.title), info.url, "stop") }, }; - if let Some(p) = pipeline { - self.active_pipeline.set(p); + if let Some(pipeline_id) = pipeline_id { + let outer_window_id = id_map.outer_window_id(pipeline_id); + self.active_outer_window_id.set(outer_window_id); + self.active_pipeline_id.set(pipeline_id); } url.as_str().clone_into(&mut self.url.borrow_mut()); if let Some(ref t) = title { @@ -317,7 +338,7 @@ impl BrowsingContextActor { } pub(crate) fn title_changed(&self, pipeline_id: PipelineId, title: String) { - if pipeline_id != self.active_pipeline.get() { + if pipeline_id != self.active_pipeline_id.get() { return; } *self.title.borrow_mut() = title; @@ -328,7 +349,7 @@ impl BrowsingContextActor { from: self.name(), type_: "frameUpdate".into(), frames: vec![FrameUpdateMsg { - id: self.browsing_context_id.index.0.get(), + id: self.browsing_context_id.value(), is_top_level: true, title: self.title.borrow().clone(), url: self.url.borrow().clone(), @@ -336,15 +357,9 @@ impl BrowsingContextActor { }); } - pub(crate) fn resource_available(&self, message: T, resource_type: String) { - let msg = ResourceAvailableReply:: { - from: self.name(), - type_: "resources-available-array".into(), - array: vec![(resource_type, vec![message])], - }; - - for stream in self.streams.borrow_mut().values_mut() { - let _ = stream.write_json_packet(&msg); - } + pub fn simulate_color_scheme(&self, theme: Theme) -> Result<(), ()> { + self.script_chan + .send(SimulateColorScheme(self.active_pipeline_id.get(), theme)) + .map_err(|_| ()) } } diff --git a/components/devtools/actors/console.rs b/components/devtools/actors/console.rs index bd22ef8e7b6..fd0bd184bfa 100644 --- a/components/devtools/actors/console.rs +++ b/components/devtools/actors/console.rs @@ -30,6 +30,7 @@ use crate::actors::browsing_context::BrowsingContextActor; use crate::actors::object::ObjectActor; use crate::actors::worker::WorkerActor; use crate::protocol::JsonPacketStream; +use crate::resource::ResourceAvailable; use crate::{StreamId, UniqueId}; trait EncodableConsoleMessage { @@ -152,7 +153,7 @@ impl ConsoleActor { Root::BrowsingContext(bc) => UniqueId::Pipeline( registry .find::(bc) - .active_pipeline + .active_pipeline_id .get(), ), Root::DedicatedWorker(w) => UniqueId::Worker(registry.find::(w).worker_id), @@ -251,6 +252,7 @@ impl ConsoleActor { page_error: PageError, id: UniqueId, registry: &ActorRegistry, + stream: &mut TcpStream, ) { self.cached_events .borrow_mut() @@ -261,7 +263,11 @@ impl ConsoleActor { if let Root::BrowsingContext(bc) = &self.root { registry .find::(bc) - .resource_available(PageErrorWrapper { page_error }, "error-message".into()) + .resource_available( + PageErrorWrapper { page_error }, + "error-message".into(), + stream, + ) }; } } @@ -271,6 +277,7 @@ impl ConsoleActor { console_message: ConsoleMessage, id: UniqueId, registry: &ActorRegistry, + stream: &mut TcpStream, ) { let log_message: ConsoleLog = console_message.into(); self.cached_events @@ -282,7 +289,7 @@ impl ConsoleActor { if let Root::BrowsingContext(bc) = &self.root { registry .find::(bc) - .resource_available(log_message, "console-message".into()) + .resource_available(log_message, "console-message".into(), stream) }; } } diff --git a/components/devtools/actors/inspector.rs b/components/devtools/actors/inspector.rs index 0dd886ada9f..28a4d7ccf94 100644 --- a/components/devtools/actors/inspector.rs +++ b/components/devtools/actors/inspector.rs @@ -80,7 +80,7 @@ impl Actor for InspectorActor { _id: StreamId, ) -> Result { let browsing_context = registry.find::(&self.browsing_context); - let pipeline = browsing_context.active_pipeline.get(); + let pipeline = browsing_context.active_pipeline_id.get(); Ok(match msg_type { "getWalker" => { let (tx, rx) = ipc::channel().unwrap(); @@ -166,6 +166,8 @@ impl Actor for InspectorActor { if self.highlighter.borrow().is_none() { let highlighter_actor = HighlighterActor { name: registry.new_name("highlighter"), + pipeline, + script_sender: self.script_chan.clone(), }; let mut highlighter = self.highlighter.borrow_mut(); *highlighter = Some(highlighter_actor.name()); diff --git a/components/devtools/actors/inspector/highlighter.rs b/components/devtools/actors/inspector/highlighter.rs index f75d25f2175..0dbf2b1e52f 100644 --- a/components/devtools/actors/inspector/highlighter.rs +++ b/components/devtools/actors/inspector/highlighter.rs @@ -7,6 +7,9 @@ use std::net::TcpStream; +use base::id::PipelineId; +use devtools_traits::DevtoolScriptControlMsg; +use ipc_channel::ipc::IpcSender; use serde::Serialize; use serde_json::{self, Map, Value}; @@ -21,6 +24,8 @@ pub struct HighlighterMsg { pub struct HighlighterActor { pub name: String, + pub script_sender: IpcSender, + pub pipeline: PipelineId, } #[derive(Serialize)] @@ -41,14 +46,39 @@ impl Actor for HighlighterActor { /// - `hide`: Disables highlighting for the selected node fn handle_message( &self, - _registry: &ActorRegistry, + registry: &ActorRegistry, msg_type: &str, - _msg: &Map, + msg: &Map, stream: &mut TcpStream, _id: StreamId, ) -> Result { Ok(match msg_type { "show" => { + let Some(node_actor) = msg.get("node") else { + // TODO: send missing parameter error + return Ok(ActorMessageStatus::Ignored); + }; + + let Some(node_actor_name) = node_actor.as_str() else { + // TODO: send invalid parameter error + return Ok(ActorMessageStatus::Ignored); + }; + + if node_actor_name.starts_with("inspector") { + // TODO: For some reason, the client initially asks us to highlight + // the inspector? Investigate what this is supposed to mean. + let msg = ShowReply { + from: self.name(), + value: false, + }; + let _ = stream.write_json_packet(&msg); + return Ok(ActorMessageStatus::Processed); + } + + self.instruct_script_thread_to_highlight_node( + Some(node_actor_name.to_owned()), + registry, + ); let msg = ShowReply { from: self.name(), value: true, @@ -58,6 +88,8 @@ impl Actor for HighlighterActor { }, "hide" => { + self.instruct_script_thread_to_highlight_node(None, registry); + let msg = EmptyReplyMsg { from: self.name() }; let _ = stream.write_json_packet(&msg); ActorMessageStatus::Processed @@ -67,3 +99,19 @@ impl Actor for HighlighterActor { }) } } + +impl HighlighterActor { + fn instruct_script_thread_to_highlight_node( + &self, + node_actor: Option, + registry: &ActorRegistry, + ) { + let node_id = node_actor.map(|node_actor| registry.actor_to_script(node_actor)); + self.script_sender + .send(DevtoolScriptControlMsg::HighlightDomNode( + self.pipeline, + node_id, + )) + .unwrap(); + } +} diff --git a/components/devtools/actors/inspector/node.rs b/components/devtools/actors/inspector/node.rs index 10ff9801844..a731f15b2d8 100644 --- a/components/devtools/actors/inspector/node.rs +++ b/components/devtools/actors/inspector/node.rs @@ -78,6 +78,18 @@ pub struct NodeActorMsg { shadow_root_mode: Option, traits: HashMap, attrs: Vec, + + /// The `DOCTYPE` name if this is a `DocumentType` node, `None` otherwise + #[serde(skip_serializing_if = "Option::is_none")] + name: Option, + + /// The `DOCTYPE` public identifier if this is a `DocumentType` node, `None` otherwise + #[serde(skip_serializing_if = "Option::is_none")] + public_id: Option, + + /// The `DOCTYPE` system identifier if this is a `DocumentType` node, `None` otherwise + #[serde(skip_serializing_if = "Option::is_none")] + system_id: Option, } pub struct NodeActor { @@ -276,6 +288,9 @@ impl NodeInfoToProtocol for NodeInfo { value: attr.value, }) .collect(), + name: self.doctype_name, + public_id: self.doctype_public_identifier, + system_id: self.doctype_system_identifier, } } } diff --git a/components/devtools/actors/root.rs b/components/devtools/actors/root.rs index 2fb3c4de722..8ad21fc4bda 100644 --- a/components/devtools/actors/root.rs +++ b/components/devtools/actors/root.rs @@ -9,6 +9,7 @@ //! //! [Firefox JS implementation]: https://searchfox.org/mozilla-central/source/devtools/server/actors/root.js +use std::cell::RefCell; use std::net::TcpStream; use serde::Serialize; @@ -129,6 +130,7 @@ pub struct RootActor { pub device: String, pub preference: String, pub process: String, + pub active_tab: RefCell>, } impl Actor for RootActor { @@ -303,13 +305,24 @@ impl RootActor { registry: &ActorRegistry, browser_id: u32, ) -> Option { - self.tabs + let tab_msg = self + .tabs .iter() .map(|target| { registry .find::(target) .encodable(registry, true) }) - .find(|tab| tab.browser_id() == browser_id) + .find(|tab| tab.browser_id() == browser_id); + + if let Some(ref msg) = tab_msg { + *self.active_tab.borrow_mut() = Some(msg.actor()); + } + tab_msg + } + + #[allow(dead_code)] + pub fn active_tab(&self) -> Option { + self.active_tab.borrow().clone() } } diff --git a/components/devtools/actors/source.rs b/components/devtools/actors/source.rs new file mode 100644 index 00000000000..9d29bc1d3ef --- /dev/null +++ b/components/devtools/actors/source.rs @@ -0,0 +1,50 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::cell::{Ref, RefCell}; +use std::collections::BTreeSet; + +use serde::Serialize; +use servo_url::ServoUrl; + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct SourceData { + pub actor: String, + /// URL of the script, or URL of the page for inline scripts. + pub url: String, + pub is_black_boxed: bool, +} + +#[derive(Serialize)] +pub(crate) struct SourcesReply { + pub from: String, + pub sources: Vec, +} + +pub(crate) struct Source { + actor_name: String, + source_urls: RefCell>, +} + +impl Source { + pub fn new(actor_name: String) -> Self { + Self { + actor_name, + source_urls: RefCell::new(BTreeSet::default()), + } + } + + pub fn add_source(&self, url: ServoUrl) { + self.source_urls.borrow_mut().insert(SourceData { + actor: self.actor_name.clone(), + url: url.to_string(), + is_black_boxed: false, + }); + } + + pub fn sources(&self) -> Ref> { + self.source_urls.borrow() + } +} diff --git a/components/devtools/actors/tab.rs b/components/devtools/actors/tab.rs index 702e765535e..4a1c60b06c3 100644 --- a/components/devtools/actors/tab.rs +++ b/components/devtools/actors/tab.rs @@ -43,6 +43,10 @@ impl TabDescriptorActorMsg { pub fn browser_id(&self) -> u32 { self.browser_id } + + pub fn actor(&self) -> String { + self.actor.clone() + } } #[derive(Serialize)] @@ -142,18 +146,15 @@ impl TabDescriptorActor { pub fn encodable(&self, registry: &ActorRegistry, selected: bool) -> TabDescriptorActorMsg { let ctx_actor = registry.find::(&self.browsing_context_actor); - let browser_id = ctx_actor.browser_id.0.index.0.get(); - let browsing_context_id = ctx_actor.browsing_context_id.index.0.get(); - let outer_window_id = ctx_actor.active_pipeline.get().index.0.get(); let title = ctx_actor.title.borrow().clone(); let url = ctx_actor.url.borrow().clone(); TabDescriptorActorMsg { actor: self.name(), - browser_id, - browsing_context_id, + browser_id: ctx_actor.browser_id.value(), + browsing_context_id: ctx_actor.browsing_context_id.value(), is_zombie_tab: false, - outer_window_id, + outer_window_id: ctx_actor.active_outer_window_id.get().value(), selected, title, traits: DescriptorTraits { @@ -167,4 +168,9 @@ impl TabDescriptorActor { pub(crate) fn is_top_level_global(&self) -> bool { self.is_top_level_global } + + #[allow(dead_code)] + pub fn browsing_context(&self) -> String { + self.browsing_context_actor.clone() + } } diff --git a/components/devtools/actors/thread.rs b/components/devtools/actors/thread.rs index 6e272cd3d28..7ff11dff675 100644 --- a/components/devtools/actors/thread.rs +++ b/components/devtools/actors/thread.rs @@ -7,6 +7,7 @@ use std::net::TcpStream; use serde::Serialize; use serde_json::{Map, Value}; +use super::source::{Source, SourcesReply}; use crate::actor::{Actor, ActorMessageStatus, ActorRegistry}; use crate::protocol::JsonPacketStream; use crate::{EmptyReplyMsg, StreamId}; @@ -49,22 +50,17 @@ struct ThreadInterruptedReply { type_: String, } -#[derive(Serialize)] -struct SourcesReply { - from: String, - sources: Vec, -} - -#[derive(Serialize)] -enum Source {} - pub struct ThreadActor { - name: String, + pub name: String, + pub source_manager: Source, } impl ThreadActor { pub fn new(name: String) -> ThreadActor { - ThreadActor { name } + ThreadActor { + name: name.clone(), + source_manager: Source::new(name), + } } } @@ -125,6 +121,8 @@ impl Actor for ThreadActor { ActorMessageStatus::Processed }, + // Client has attached to the thread and wants to load script sources. + // "sources" => { let msg = SourcesReply { from: self.name(), diff --git a/components/devtools/actors/watcher.rs b/components/devtools/actors/watcher.rs index 94a15b36e7a..061ffc92336 100644 --- a/components/devtools/actors/watcher.rs +++ b/components/devtools/actors/watcher.rs @@ -19,8 +19,11 @@ use serde::Serialize; use serde_json::{Map, Value}; use self::network_parent::{NetworkParentActor, NetworkParentActorMsg}; +use super::thread::ThreadActor; +use super::worker::WorkerMsg; use crate::actor::{Actor, ActorMessageStatus, ActorRegistry}; use crate::actors::browsing_context::{BrowsingContextActor, BrowsingContextActorMsg}; +use crate::actors::root::RootActor; use crate::actors::watcher::target_configuration::{ TargetConfigurationActor, TargetConfigurationActorMsg, }; @@ -28,7 +31,8 @@ use crate::actors::watcher::thread_configuration::{ ThreadConfigurationActor, ThreadConfigurationActorMsg, }; use crate::protocol::JsonPacketStream; -use crate::{EmptyReplyMsg, StreamId}; +use crate::resource::ResourceAvailable; +use crate::{EmptyReplyMsg, StreamId, WorkerActor}; pub mod network_parent; pub mod target_configuration; @@ -53,7 +57,7 @@ impl SessionContext { supported_targets: HashMap::from([ ("frame", true), ("process", false), - ("worker", false), + ("worker", true), ("service_worker", false), ("shared_worker", false), ]), @@ -78,7 +82,7 @@ impl SessionContext { ("network-event-stacktrace", false), ("reflow", false), ("stylesheet", false), - ("source", false), + ("source", true), ("thread-state", false), ("server-sent-event", false), ("websocket", false), @@ -100,12 +104,19 @@ pub enum SessionContextType { _All, } +#[derive(Serialize)] +#[serde(untagged)] +enum TargetActorMsg { + BrowsingContext(BrowsingContextActorMsg), + Worker(WorkerMsg), +} + #[derive(Serialize)] struct WatchTargetsReply { from: String, #[serde(rename = "type")] type_: String, - target: BrowsingContextActorMsg, + target: TargetActorMsg, } #[derive(Serialize)] @@ -133,6 +144,18 @@ struct GetThreadConfigurationActorReply { configuration: ThreadConfigurationActorMsg, } +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct GetBreakpointListActorReply { + from: String, + breakpoint_list: GetBreakpointListActorReplyInner, +} + +#[derive(Serialize)] +struct GetBreakpointListActorReplyInner { + actor: String, +} + #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct DocumentEvent { @@ -198,16 +221,38 @@ impl Actor for WatcherActor { _id: StreamId, ) -> Result { let target = registry.find::(&self.browsing_context_actor); + let root = registry.find::("root"); Ok(match msg_type { "watchTargets" => { - let msg = WatchTargetsReply { - from: self.name(), - type_: "target-available-form".into(), - target: target.encodable(), - }; - let _ = stream.write_json_packet(&msg); + // As per logs we either get targetType as "frame" or "worker" + let target_type = msg + .get("targetType") + .and_then(Value::as_str) + .unwrap_or("frame"); // default to "frame" - target.frame_update(stream); + if target_type == "frame" { + let msg = WatchTargetsReply { + from: self.name(), + type_: "target-available-form".into(), + target: TargetActorMsg::BrowsingContext(target.encodable()), + }; + let _ = stream.write_json_packet(&msg); + + target.frame_update(stream); + } else if target_type == "worker" { + for worker_name in &root.workers { + let worker = registry.find::(worker_name); + let worker_msg = WatchTargetsReply { + from: self.name(), + type_: "target-available-form".into(), + target: TargetActorMsg::Worker(worker.encodable()), + }; + let _ = stream.write_json_packet(&worker_msg); + } + } else { + warn!("Unexpected target_type: {}", target_type); + return Ok(ActorMessageStatus::Ignored); + } // Messages that contain a `type` field are used to send event callbacks, but they // don't count as a reply. Since every message needs to be responded, we send an @@ -246,7 +291,28 @@ impl Actor for WatcherActor { title: Some(target.title.borrow().clone()), url: Some(target.url.borrow().clone()), }; - target.resource_available(event, "document-event".into()); + target.resource_available(event, "document-event".into(), stream); + } + }, + "source" => { + let thread_actor = registry.find::(&target.thread); + let sources = thread_actor.source_manager.sources(); + target.resources_available( + sources.iter().collect(), + "source".into(), + stream, + ); + + for worker_name in &root.workers { + let worker = registry.find::(worker_name); + let thread = registry.find::(&worker.thread); + let worker_sources = thread.source_manager.sources(); + + worker.resources_available( + worker_sources.iter().collect(), + "source".into(), + stream, + ); } }, "console-message" | "error-message" => {}, @@ -259,10 +325,9 @@ impl Actor for WatcherActor { ActorMessageStatus::Processed }, "getParentBrowsingContextID" => { - let browsing_context_id = target.browsing_context_id.index.0.get(); let msg = GetParentBrowsingContextIDReply { from: self.name(), - browsing_context_id, + browsing_context_id: target.browsing_context_id.value(), }; let _ = stream.write_json_packet(&msg); ActorMessageStatus::Processed @@ -296,6 +361,15 @@ impl Actor for WatcherActor { let _ = stream.write_json_packet(&msg); ActorMessageStatus::Processed }, + "getBreakpointListActor" => { + let _ = stream.write_json_packet(&GetBreakpointListActorReply { + from: self.name(), + breakpoint_list: GetBreakpointListActorReplyInner { + actor: registry.new_name("breakpoint-list"), + }, + }); + ActorMessageStatus::Processed + }, _ => ActorMessageStatus::Ignored, }) } diff --git a/components/devtools/actors/watcher/target_configuration.rs b/components/devtools/actors/watcher/target_configuration.rs index 7b83cdde698..0d366e81475 100644 --- a/components/devtools/actors/watcher/target_configuration.rs +++ b/components/devtools/actors/watcher/target_configuration.rs @@ -8,12 +8,16 @@ use std::collections::HashMap; use std::net::TcpStream; +use embedder_traits::Theme; +use log::warn; use serde::Serialize; use serde_json::{Map, Value}; use crate::actor::{Actor, ActorMessageStatus, ActorRegistry}; +use crate::actors::browsing_context::BrowsingContextActor; +use crate::actors::tab::TabDescriptorActor; use crate::protocol::JsonPacketStream; -use crate::{EmptyReplyMsg, StreamId}; +use crate::{EmptyReplyMsg, RootActor, StreamId}; #[derive(Serialize)] #[serde(rename_all = "camelCase")] @@ -44,15 +48,39 @@ impl Actor for TargetConfigurationActor { /// - `updateConfiguration`: Receives new configuration flags from the devtools host. fn handle_message( &self, - _registry: &ActorRegistry, + registry: &ActorRegistry, msg_type: &str, - _msg: &Map, + msg: &Map, stream: &mut TcpStream, _id: StreamId, ) -> Result { Ok(match msg_type { "updateConfiguration" => { - // TODO: Actually update configuration + let config = match msg.get("configuration").and_then(|v| v.as_object()) { + Some(config) => config, + None => { + let msg = EmptyReplyMsg { from: self.name() }; + let _ = stream.write_json_packet(&msg); + return Ok(ActorMessageStatus::Processed); + }, + }; + if let Some(scheme) = config.get("colorSchemeSimulation").and_then(|v| v.as_str()) { + let theme = match scheme { + "dark" => Theme::Dark, + "light" => Theme::Light, + _ => Theme::Light, + }; + let root_actor = registry.find::("root"); + if let Some(tab_name) = root_actor.active_tab() { + let tab_actor = registry.find::(&tab_name); + let browsing_context_name = tab_actor.browsing_context(); + let browsing_context_actor = + registry.find::(&browsing_context_name); + browsing_context_actor.simulate_color_scheme(theme)?; + } else { + warn!("No active tab for updateConfiguration"); + } + } let msg = EmptyReplyMsg { from: self.name() }; let _ = stream.write_json_packet(&msg); ActorMessageStatus::Processed @@ -69,7 +97,7 @@ impl TargetConfigurationActor { configuration: HashMap::new(), supported_options: HashMap::from([ ("cacheDisabled", false), - ("colorSchemeSimulation", false), + ("colorSchemeSimulation", true), ("customFormatters", false), ("customUserAgent", false), ("javascriptEnabled", false), diff --git a/components/devtools/actors/worker.rs b/components/devtools/actors/worker.rs index b5d1370c320..f3ca4f2aed7 100644 --- a/components/devtools/actors/worker.rs +++ b/components/devtools/actors/worker.rs @@ -17,6 +17,7 @@ use servo_url::ServoUrl; use crate::StreamId; use crate::actor::{Actor, ActorMessageStatus, ActorRegistry}; use crate::protocol::JsonPacketStream; +use crate::resource::ResourceAvailable; #[derive(Clone, Copy)] #[allow(dead_code)] @@ -43,16 +44,24 @@ impl WorkerActor { actor: self.name.clone(), console_actor: self.console.clone(), thread_actor: self.thread.clone(), - worker_id: self.worker_id.0.to_string(), + id: self.worker_id.0.to_string(), url: self.url.to_string(), traits: WorkerTraits { is_parent_intercept_enabled: false, + supports_top_level_target_flag: false, }, type_: self.type_ as u32, + target_type: "worker".to_string(), } } } +impl ResourceAvailable for WorkerActor { + fn actor_name(&self) -> String { + self.name.clone() + } +} + impl Actor for WorkerActor { fn name(&self) -> String { self.name.clone() @@ -149,6 +158,7 @@ struct ConnectReply { #[serde(rename_all = "camelCase")] struct WorkerTraits { is_parent_intercept_enabled: bool, + supports_top_level_target_flag: bool, } #[derive(Serialize)] @@ -157,9 +167,11 @@ pub(crate) struct WorkerMsg { actor: String, console_actor: String, thread_actor: String, - worker_id: String, + id: String, url: String, traits: WorkerTraits, #[serde(rename = "type")] type_: u32, + #[serde(rename = "targetType")] + target_type: String, } diff --git a/components/devtools/id.rs b/components/devtools/id.rs new file mode 100644 index 00000000000..40408d91f95 --- /dev/null +++ b/components/devtools/id.rs @@ -0,0 +1,175 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::collections::HashMap; + +use base::id::{BrowsingContextId, PipelineId, WebViewId}; + +#[derive(Debug, Default)] +pub(crate) struct IdMap { + pub(crate) browser_ids: HashMap, + pub(crate) browsing_context_ids: HashMap, + pub(crate) outer_window_ids: HashMap, +} + +impl IdMap { + pub(crate) fn browser_id(&mut self, webview_id: WebViewId) -> DevtoolsBrowserId { + let len = self + .browser_ids + .len() + .checked_add(1) + .expect("WebViewId count overflow") + .try_into() + .expect("DevtoolsBrowserId overflow"); + DevtoolsBrowserId(*self.browser_ids.entry(webview_id).or_insert(len)) + } + pub(crate) fn browsing_context_id( + &mut self, + browsing_context_id: BrowsingContextId, + ) -> DevtoolsBrowsingContextId { + let len = self + .browsing_context_ids + .len() + .checked_add(1) + .expect("BrowsingContextId count overflow") + .try_into() + .expect("DevtoolsBrowsingContextId overflow"); + DevtoolsBrowsingContextId( + *self + .browsing_context_ids + .entry(browsing_context_id) + .or_insert(len), + ) + } + pub(crate) fn outer_window_id(&mut self, pipeline_id: PipelineId) -> DevtoolsOuterWindowId { + let len = self + .outer_window_ids + .len() + .checked_add(1) + .expect("PipelineId count overflow") + .try_into() + .expect("DevtoolsOuterWindowId overflow"); + DevtoolsOuterWindowId(*self.outer_window_ids.entry(pipeline_id).or_insert(len)) + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) struct DevtoolsBrowserId(u32); + +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) struct DevtoolsBrowsingContextId(u32); + +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) struct DevtoolsOuterWindowId(u32); + +impl DevtoolsBrowserId { + pub(crate) fn value(&self) -> u32 { + self.0 + } +} + +impl DevtoolsBrowsingContextId { + pub(crate) fn value(&self) -> u32 { + self.0 + } +} + +impl DevtoolsOuterWindowId { + pub(crate) fn value(&self) -> u32 { + self.0 + } +} + +#[test] +pub(crate) fn test_id_map() { + use std::thread; + + use base::id::{PipelineNamespace, PipelineNamespaceId}; + use crossbeam_channel::unbounded; + + macro_rules! test_sequential_id_assignment { + ($id_type:ident, $new_id_function:expr, $map_id_function:expr) => { + let (sender, receiver) = unbounded(); + let sender1 = sender.clone(); + let sender2 = sender.clone(); + let sender3 = sender.clone(); + let threads = [ + thread::spawn(move || { + PipelineNamespace::install(PipelineNamespaceId(1)); + sender1.send($new_id_function()).expect("Send failed"); + sender1.send($new_id_function()).expect("Send failed"); + sender1.send($new_id_function()).expect("Send failed"); + }), + thread::spawn(move || { + PipelineNamespace::install(PipelineNamespaceId(2)); + sender2.send($new_id_function()).expect("Send failed"); + sender2.send($new_id_function()).expect("Send failed"); + sender2.send($new_id_function()).expect("Send failed"); + }), + thread::spawn(move || { + PipelineNamespace::install(PipelineNamespaceId(3)); + sender3.send($new_id_function()).expect("Send failed"); + sender3.send($new_id_function()).expect("Send failed"); + sender3.send($new_id_function()).expect("Send failed"); + }), + ]; + for thread in threads { + thread.join().expect("Thread join failed"); + } + let mut id_map = IdMap::default(); + assert_eq!( + $map_id_function(&mut id_map, receiver.recv().expect("Recv failed")), + $id_type(1) + ); + assert_eq!( + $map_id_function(&mut id_map, receiver.recv().expect("Recv failed")), + $id_type(2) + ); + assert_eq!( + $map_id_function(&mut id_map, receiver.recv().expect("Recv failed")), + $id_type(3) + ); + assert_eq!( + $map_id_function(&mut id_map, receiver.recv().expect("Recv failed")), + $id_type(4) + ); + assert_eq!( + $map_id_function(&mut id_map, receiver.recv().expect("Recv failed")), + $id_type(5) + ); + assert_eq!( + $map_id_function(&mut id_map, receiver.recv().expect("Recv failed")), + $id_type(6) + ); + assert_eq!( + $map_id_function(&mut id_map, receiver.recv().expect("Recv failed")), + $id_type(7) + ); + assert_eq!( + $map_id_function(&mut id_map, receiver.recv().expect("Recv failed")), + $id_type(8) + ); + assert_eq!( + $map_id_function(&mut id_map, receiver.recv().expect("Recv failed")), + $id_type(9) + ); + }; + } + + test_sequential_id_assignment!( + DevtoolsBrowserId, + || WebViewId::new(), + |id_map: &mut IdMap, id| id_map.browser_id(id) + ); + test_sequential_id_assignment!( + DevtoolsBrowsingContextId, + || BrowsingContextId::new(), + |id_map: &mut IdMap, id| id_map.browsing_context_id(id) + ); + test_sequential_id_assignment!( + DevtoolsOuterWindowId, + || PipelineId::new(), + |id_map: &mut IdMap, id| id_map.outer_window_id(id) + ); +} diff --git a/components/devtools/lib.rs b/components/devtools/lib.rs index eb3aa80dbf9..d097cb25e9d 100644 --- a/components/devtools/lib.rs +++ b/components/devtools/lib.rs @@ -19,16 +19,18 @@ use std::net::{Shutdown, TcpListener, TcpStream}; use std::sync::{Arc, Mutex}; use std::thread; +use actors::source::SourceData; use base::id::{BrowsingContextId, PipelineId, WebViewId}; use crossbeam_channel::{Receiver, Sender, unbounded}; use devtools_traits::{ ChromeToDevtoolsControlMsg, ConsoleMessage, ConsoleMessageBuilder, DevtoolScriptControlMsg, DevtoolsControlMsg, DevtoolsPageInfo, LogLevel, NavigationState, NetworkEvent, PageError, - ScriptToDevtoolsControlMsg, WorkerId, + ScriptToDevtoolsControlMsg, SourceInfo, WorkerId, }; use embedder_traits::{AllowOrDeny, EmbedderMsg, EmbedderProxy}; use ipc_channel::ipc::{self, IpcSender}; use log::trace; +use resource::ResourceAvailable; use serde::Serialize; use servo_rand::RngCore; @@ -44,6 +46,7 @@ use crate::actors::process::ProcessActor; use crate::actors::root::RootActor; use crate::actors::thread::ThreadActor; use crate::actors::worker::{WorkerActor, WorkerType}; +use crate::id::IdMap; use crate::network_handler::handle_network_event; use crate::protocol::JsonPacketStream; @@ -63,6 +66,7 @@ mod actors { pub mod process; pub mod reflow; pub mod root; + pub mod source; pub mod stylesheets; pub mod tab; pub mod thread; @@ -70,8 +74,10 @@ mod actors { pub mod watcher; pub mod worker; } +mod id; mod network_handler; mod protocol; +mod resource; #[derive(Clone, Debug, Eq, Hash, PartialEq)] enum UniqueId { @@ -106,6 +112,7 @@ pub(crate) struct StreamId(u32); struct DevtoolsInstance { actors: Arc>, + id_map: Arc>, browsing_contexts: HashMap, receiver: Receiver, pipelines: HashMap, @@ -153,6 +160,7 @@ impl DevtoolsInstance { performance: performance.name(), preference: preference.name(), process: process.name(), + active_tab: None.into(), }); registry.register(root); @@ -166,6 +174,7 @@ impl DevtoolsInstance { let instance = Self { actors, + id_map: Arc::new(Mutex::new(IdMap::default())), browsing_contexts: HashMap::new(), pipelines: HashMap::new(), receiver, @@ -242,6 +251,10 @@ impl DevtoolsInstance { console_message, worker_id, )) => self.handle_console_message(pipeline_id, worker_id, console_message), + DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::ScriptSourceLoaded( + pipeline_id, + source_info, + )) => self.handle_script_source_info(pipeline_id, source_info), DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::ReportPageError( pipeline_id, page_error, @@ -298,7 +311,7 @@ impl DevtoolsInstance { .lock() .unwrap() .find::(actor_name) - .navigate(state); + .navigate(state, &mut self.id_map.lock().expect("Mutex poisoned")); } // We need separate actor representations for each script global that exists; @@ -313,6 +326,10 @@ impl DevtoolsInstance { let mut actors = self.actors.lock().unwrap(); let (browsing_context_id, pipeline_id, worker_id, webview_id) = ids; + let id_map = &mut self.id_map.lock().expect("Mutex poisoned"); + let devtools_browser_id = id_map.browser_id(webview_id); + let devtools_browsing_context_id = id_map.browsing_context_id(browsing_context_id); + let devtools_outer_window_id = id_map.outer_window_id(pipeline_id); let console_name = actors.new_name("console"); @@ -320,7 +337,7 @@ impl DevtoolsInstance { assert!(self.pipelines.contains_key(&pipeline_id)); assert!(self.browsing_contexts.contains_key(&browsing_context_id)); - let thread = ThreadActor::new(actors.new_name("context")); + let thread = ThreadActor::new(actors.new_name("thread")); let thread_name = thread.name(); actors.register(Box::new(thread)); @@ -350,10 +367,11 @@ impl DevtoolsInstance { .or_insert_with(|| { let browsing_context_actor = BrowsingContextActor::new( console_name.clone(), - webview_id, - browsing_context_id, + devtools_browser_id, + devtools_browsing_context_id, page_info, pipeline_id, + devtools_outer_window_id, script_sender, &mut actors, ); @@ -396,7 +414,7 @@ impl DevtoolsInstance { } fn handle_page_error( - &self, + &mut self, pipeline_id: PipelineId, worker_id: Option, page_error: PageError, @@ -408,11 +426,13 @@ impl DevtoolsInstance { let actors = self.actors.lock().unwrap(); let console_actor = actors.find::(&console_actor_name); let id = worker_id.map_or(UniqueId::Pipeline(pipeline_id), UniqueId::Worker); - console_actor.handle_page_error(page_error, id, &actors); + for stream in self.connections.values_mut() { + console_actor.handle_page_error(page_error.clone(), id.clone(), &actors, stream); + } } fn handle_console_message( - &self, + &mut self, pipeline_id: PipelineId, worker_id: Option, console_message: ConsoleMessage, @@ -424,7 +444,9 @@ impl DevtoolsInstance { let actors = self.actors.lock().unwrap(); let console_actor = actors.find::(&console_actor_name); let id = worker_id.map_or(UniqueId::Pipeline(pipeline_id), UniqueId::Worker); - console_actor.handle_console_api(console_message, id, &actors); + for stream in self.connections.values_mut() { + console_actor.handle_console_api(console_message.clone(), id.clone(), &actors, stream); + } } fn find_console_actor( @@ -488,6 +510,65 @@ impl DevtoolsInstance { }, } } + + fn handle_script_source_info(&mut self, pipeline_id: PipelineId, source_info: SourceInfo) { + let mut actors = self.actors.lock().unwrap(); + + if let Some(worker_id) = source_info.worker_id { + let Some(worker_actor_name) = self.actor_workers.get(&worker_id) else { + return; + }; + + let thread_actor_name = actors.find::(worker_actor_name).thread.clone(); + + let thread_actor = actors.find_mut::(&thread_actor_name); + thread_actor + .source_manager + .add_source(source_info.url.clone()); + + let source = SourceData { + actor: thread_actor_name.clone(), + url: source_info.url.to_string(), + is_black_boxed: false, + }; + + let worker_actor = actors.find::(worker_actor_name); + + for stream in self.connections.values_mut() { + worker_actor.resource_available(&source, "source".into(), stream); + } + } else { + let Some(browsing_context_id) = self.pipelines.get(&pipeline_id) else { + return; + }; + let Some(actor_name) = self.browsing_contexts.get(browsing_context_id) else { + return; + }; + + let thread_actor_name = actors + .find::(actor_name) + .thread + .clone(); + + let thread_actor = actors.find_mut::(&thread_actor_name); + thread_actor + .source_manager + .add_source(source_info.url.clone()); + + let source = SourceData { + actor: thread_actor_name.clone(), + url: source_info.url.to_string(), + is_black_boxed: false, + }; + + // Notify browsing context about the new source + let browsing_context = actors.find::(actor_name); + + for stream in self.connections.values_mut() { + browsing_context.resource_available(&source, "source".into(), stream); + } + } + } } fn allow_devtools_client(stream: &mut TcpStream, embedder: &EmbedderProxy, token: &str) -> bool { diff --git a/components/devtools/resource.rs b/components/devtools/resource.rs new file mode 100644 index 00000000000..4e6aa4042b8 --- /dev/null +++ b/components/devtools/resource.rs @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::net::TcpStream; + +use serde::Serialize; + +use crate::protocol::JsonPacketStream; + +#[derive(Serialize)] +pub(crate) struct ResourceAvailableReply { + pub from: String, + #[serde(rename = "type")] + pub type_: String, + pub array: Vec<(String, Vec)>, +} + +pub(crate) trait ResourceAvailable { + fn actor_name(&self) -> String; + + fn resource_available( + &self, + resource: T, + resource_type: String, + stream: &mut TcpStream, + ) { + self.resources_available(vec![resource], resource_type, stream); + } + + fn resources_available( + &self, + resources: Vec, + resource_type: String, + stream: &mut TcpStream, + ) { + let msg = ResourceAvailableReply:: { + from: self.actor_name(), + type_: "resources-available-array".into(), + array: vec![(resource_type, resources)], + }; + + let _ = stream.write_json_packet(&msg); + } +} diff --git a/components/fonts/Cargo.toml b/components/fonts/Cargo.toml index 15f5d6e81e2..ce51a9f9112 100644 --- a/components/fonts/Cargo.toml +++ b/components/fonts/Cargo.toml @@ -21,10 +21,11 @@ app_units = { workspace = true } atomic_refcell = { workspace = true } base = { workspace = true } bitflags = { workspace = true } +compositing_traits = { workspace = true } euclid = { workspace = true } fnv = { workspace = true } -fontsan = { git = "https://github.com/servo/fontsan" } fonts_traits = { workspace = true } +fontsan = { git = "https://github.com/servo/fontsan" } # FIXME (#34517): macOS only needs this when building libservo without `--features media-gstreamer` harfbuzz-sys = { workspace = true, features = ["bundled"] } ipc-channel = { workspace = true } @@ -37,23 +38,20 @@ memmap2 = { workspace = true } net_traits = { workspace = true } num-traits = { workspace = true } parking_lot = { workspace = true } +profile_traits = { workspace = true } range = { path = "../range" } serde = { workspace = true } servo_arc = { workspace = true } -stylo_atoms = { workspace = true } servo_config = { path = "../config" } servo_url = { path = "../url" } smallvec = { workspace = true, features = ["union"] } stylo = { workspace = true } +stylo_atoms = { workspace = true } tracing = { workspace = true, optional = true } unicode-properties = { workspace = true } unicode-script = { workspace = true } url = { workspace = true } webrender_api = { workspace = true } -webrender_traits = { workspace = true } - -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(ohos_mock)'] } [target.'cfg(target_os = "macos")'.dependencies] byteorder = { workspace = true } @@ -74,3 +72,6 @@ xml-rs = "0.8" [target.'cfg(target_os = "windows")'.dependencies] dwrote = "0.11.2" truetype = { version = "0.47.3", features = ["ignore-invalid-language-ids"] } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(ohos_mock)'] } diff --git a/components/fonts/font_context.rs b/components/fonts/font_context.rs index b11a922abdf..58c08b29d3e 100644 --- a/components/fonts/font_context.rs +++ b/components/fonts/font_context.rs @@ -10,6 +10,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use app_units::Au; use base::id::WebViewId; +use compositing_traits::CrossProcessCompositorApi; use fnv::FnvHasher; use fonts_traits::StylesheetWebFontLoadFinishedCallback; use log::{debug, trace}; @@ -32,7 +33,6 @@ use style::stylesheets::{CssRule, DocumentStyleSheet, FontFaceRule, StylesheetIn use style::values::computed::font::{FamilyName, FontFamilyNameSyntax, SingleFontFamily}; use url::Url; use webrender_api::{FontInstanceFlags, FontInstanceKey, FontKey}; -use webrender_traits::CrossProcessCompositorApi; use crate::font::{ Font, FontDescriptor, FontFamilyDescriptor, FontGroup, FontRef, FontSearchScope, @@ -900,9 +900,9 @@ impl RemoteWebFontDownloader { response_message: FetchResponseMsg, ) -> DownloaderResponseResult { match response_message { - FetchResponseMsg::ProcessRequestBody(..) | FetchResponseMsg::ProcessRequestEOF(..) => { - DownloaderResponseResult::InProcess - }, + FetchResponseMsg::ProcessRequestBody(..) | + FetchResponseMsg::ProcessRequestEOF(..) | + FetchResponseMsg::ProcessCspViolations(..) => DownloaderResponseResult::InProcess, FetchResponseMsg::ProcessResponse(_, meta_result) => { trace!( "@font-face {} metadata ok={:?}", diff --git a/components/fonts/font_store.rs b/components/fonts/font_store.rs index 826be947672..0099c56c266 100644 --- a/components/fonts/font_store.rs +++ b/components/fonts/font_store.rs @@ -5,8 +5,8 @@ use std::collections::HashMap; use std::sync::Arc; -use atomic_refcell::AtomicRefCell; use log::warn; +use malloc_size_of_derive::MallocSizeOf; use parking_lot::RwLock; use style::stylesheets::DocumentStyleSheet; use style::values::computed::{FontStyle, FontWeight}; @@ -15,7 +15,7 @@ use crate::font::FontDescriptor; use crate::font_template::{FontTemplate, FontTemplateRef, FontTemplateRefMethods, IsOblique}; use crate::system_font_service::{FontIdentifier, LowercaseFontFamilyName}; -#[derive(Default)] +#[derive(Default, MallocSizeOf)] pub struct FontStore { pub(crate) families: HashMap, web_fonts_loading_for_stylesheets: Vec<(DocumentStyleSheet, usize)>, @@ -134,7 +134,7 @@ impl FontStore { /// /// This optimization is taken from: /// . -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, MallocSizeOf)] struct SimpleFamily { regular: Option, bold: Option, @@ -190,7 +190,7 @@ impl SimpleFamily { } } /// A list of font templates that make up a given font family. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, MallocSizeOf)] pub struct FontTemplates { pub(crate) templates: Vec, simple_family: Option, @@ -263,7 +263,7 @@ impl FontTemplates { } } - let new_template = Arc::new(AtomicRefCell::new(new_template)); + let new_template = FontTemplateRef::new(new_template); self.templates.push(new_template.clone()); self.update_simple_family(new_template); } diff --git a/components/fonts/font_template.rs b/components/fonts/font_template.rs index eca1017d14e..b8173ee0317 100644 --- a/components/fonts/font_template.rs +++ b/components/fonts/font_template.rs @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::fmt::{Debug, Error, Formatter}; -use std::ops::RangeInclusive; +use std::ops::{Deref, RangeInclusive}; use std::sync::Arc; use atomic_refcell::AtomicRefCell; @@ -20,7 +20,21 @@ use crate::system_font_service::{ }; /// A reference to a [`FontTemplate`] with shared ownership and mutability. -pub type FontTemplateRef = Arc>; +#[derive(Clone, Debug, MallocSizeOf)] +pub struct FontTemplateRef(#[conditional_malloc_size_of] Arc>); + +impl FontTemplateRef { + pub fn new(template: FontTemplate) -> Self { + Self(Arc::new(AtomicRefCell::new(template))) + } +} + +impl Deref for FontTemplateRef { + type Target = Arc>; + fn deref(&self) -> &Self::Target { + &self.0 + } +} /// Describes how to select a font from a given family. This is very basic at the moment and needs /// to be expanded or refactored when we support more of the font styling parameters. diff --git a/components/fonts/glyph.rs b/components/fonts/glyph.rs index e0e85ed7f74..db97ee0ebee 100644 --- a/components/fonts/glyph.rs +++ b/components/fonts/glyph.rs @@ -741,9 +741,10 @@ impl fmt::Debug for GlyphStore { } /// A single series of glyphs within a text run. -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] pub struct GlyphRun { /// The glyphs. + #[conditional_malloc_size_of] pub glyph_store: Arc, /// The byte range of characters in the containing run. pub range: Range, diff --git a/components/fonts/system_font_service.rs b/components/fonts/system_font_service.rs index 61b36e699df..f799affa7c8 100644 --- a/components/fonts/system_font_service.rs +++ b/components/fonts/system_font_service.rs @@ -6,15 +6,19 @@ use std::borrow::ToOwned; use std::cell::OnceCell; use std::collections::HashMap; use std::ops::{Deref, RangeInclusive}; -use std::sync::Arc; use std::{fmt, thread}; use app_units::Au; -use atomic_refcell::AtomicRefCell; +use compositing_traits::CrossProcessCompositorApi; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use log::debug; +use malloc_size_of::MallocSizeOf as MallocSizeOfTrait; use malloc_size_of_derive::MallocSizeOf; use parking_lot::{Mutex, RwLock}; +use profile_traits::mem::{ + ProcessReports, ProfilerChan, Report, ReportKind, ReportsChan, perform_memory_report, +}; +use profile_traits::path; use serde::{Deserialize, Serialize}; use servo_config::pref; use servo_url::ServoUrl; @@ -25,7 +29,6 @@ use style::values::computed::font::{ use style::values::computed::{FontStretch, FontWeight}; use style::values::specified::FontStretch as SpecifiedFontStretch; use webrender_api::{FontInstanceFlags, FontInstanceKey, FontKey}; -use webrender_traits::CrossProcessCompositorApi; use crate::font::FontDescriptor; use crate::font_store::FontStore; @@ -66,11 +69,12 @@ pub enum SystemFontServiceMessage { ), GetFontKey(IpcSender), GetFontInstanceKey(IpcSender), + CollectMemoryReport(ReportsChan), Exit(IpcSender<()>), Ping, } -#[derive(Default)] +#[derive(Default, MallocSizeOf)] struct ResolvedGenericFontFamilies { default: OnceCell, serif: OnceCell, @@ -84,6 +88,7 @@ struct ResolvedGenericFontFamilies { /// The system font service. There is one of these for every Servo instance. This is a thread, /// responsible for reading the list of system fonts, handling requests to match against /// them, and ensuring that only one copy of system font data is loaded at a time. +#[derive(MallocSizeOf)] pub struct SystemFontService { port: IpcReceiver, local_families: FontStore, @@ -118,8 +123,12 @@ impl SystemFontServiceProxySender { } impl SystemFontService { - pub fn spawn(compositor_api: CrossProcessCompositorApi) -> SystemFontServiceProxySender { + pub fn spawn( + compositor_api: CrossProcessCompositorApi, + memory_profiler_sender: ProfilerChan, + ) -> SystemFontServiceProxySender { let (sender, receiver) = ipc::channel().unwrap(); + let memory_reporter_sender = sender.clone(); thread::Builder::new() .name("SystemFontService".to_owned()) @@ -138,7 +147,13 @@ impl SystemFontService { cache.fetch_new_keys(); cache.refresh_local_families(); - cache.run(); + + memory_profiler_sender.run_with_memory_reporting( + || cache.run(), + "system-fonts".to_owned(), + memory_reporter_sender, + SystemFontServiceMessage::CollectMemoryReport, + ); }) .expect("Thread spawning failed"); @@ -172,6 +187,9 @@ impl SystemFontService { self.fetch_new_keys(); let _ = result_sender.send(self.free_font_instance_keys.pop().unwrap()); }, + SystemFontServiceMessage::CollectMemoryReport(report_sender) => { + self.collect_memory_report(report_sender); + }, SystemFontServiceMessage::Ping => (), SystemFontServiceMessage::Exit(result) => { let _ = result.send(()); @@ -181,6 +199,17 @@ impl SystemFontService { } } + fn collect_memory_report(&self, report_sender: ReportsChan) { + perform_memory_report(|ops| { + let reports = vec![Report { + path: path!["system-fonts"], + kind: ReportKind::ExplicitSystemHeapSize, + size: self.size_of(ops), + }]; + report_sender.send(ProcessReports::new(reports)); + }); + } + #[cfg_attr( feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") @@ -528,11 +557,7 @@ impl SystemFontServiceProxy { panic!("SystemFontService has already exited."); }; - let templates: Vec<_> = templates - .into_iter() - .map(AtomicRefCell::new) - .map(Arc::new) - .collect(); + let templates: Vec<_> = templates.into_iter().map(FontTemplateRef::new).collect(); self.templates.write().insert(cache_key, templates.clone()); templates diff --git a/components/fonts/tests/font.rs b/components/fonts/tests/font.rs index 78c507e7b93..a473be9222b 100644 --- a/components/fonts/tests/font.rs +++ b/components/fonts/tests/font.rs @@ -5,14 +5,13 @@ use std::fs::File; use std::io::Read; use std::path::PathBuf; -use std::sync::Arc; use app_units::Au; use euclid::num::Zero; use fonts::platform::font::PlatformFont; use fonts::{ - Font, FontData, FontDescriptor, FontIdentifier, FontTemplate, PlatformFontMethods, - ShapingFlags, ShapingOptions, + Font, FontData, FontDescriptor, FontIdentifier, FontTemplate, FontTemplateRef, + PlatformFontMethods, ShapingFlags, ShapingOptions, }; use servo_url::ServoUrl; use style::properties::longhands::font_variant_caps::computed_value::T as FontVariantCaps; @@ -42,13 +41,7 @@ fn make_font(path: PathBuf) -> Font { variant: FontVariantCaps::Normal, pt_size: Au::from_px(24), }; - Font::new( - Arc::new(atomic_refcell::AtomicRefCell::new(template)), - descriptor, - Some(data), - None, - ) - .unwrap() + Font::new(FontTemplateRef::new(template), descriptor, Some(data), None).unwrap() } #[test] diff --git a/components/fonts/tests/font_context.rs b/components/fonts/tests/font_context.rs index ed102069eee..0793c1e4ce1 100644 --- a/components/fonts/tests/font_context.rs +++ b/components/fonts/tests/font_context.rs @@ -14,6 +14,7 @@ mod font_context { use std::thread; use app_units::Au; + use compositing_traits::CrossProcessCompositorApi; use fonts::platform::font::PlatformFont; use fonts::{ FallbackFontSelectionOptions, FontContext, FontDescriptor, FontFamilyDescriptor, @@ -34,7 +35,6 @@ mod font_context { }; use stylo_atoms::Atom; use webrender_api::{FontInstanceKey, FontKey, IdNamespace}; - use webrender_traits::CrossProcessCompositorApi; struct TestContext { context: FontContext, @@ -137,6 +137,7 @@ mod font_context { break; }, SystemFontServiceMessage::Ping => {}, + SystemFontServiceMessage::CollectMemoryReport(..) => {}, } } } diff --git a/components/layout/Cargo.toml b/components/layout/Cargo.toml new file mode 100644 index 00000000000..0505581fba7 --- /dev/null +++ b/components/layout/Cargo.toml @@ -0,0 +1,69 @@ +[package] +name = "layout" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +publish.workspace = true +rust-version.workspace = true + +[lib] +name = "layout" +path = "lib.rs" +test = true +doctest = false + +[features] +tracing = ["dep:tracing"] + +[dependencies] +app_units = { workspace = true } +atomic_refcell = { workspace = true } +base = { workspace = true } +bitflags = { workspace = true } +canvas_traits = { workspace = true } +compositing_traits = { workspace = true } +constellation_traits = { workspace = true } +data-url = { workspace = true } +embedder_traits = { workspace = true } +euclid = { workspace = true } +fnv = { workspace = true } +fonts = { path = "../fonts" } +fonts_traits = { workspace = true } +fxhash = { workspace = true } +html5ever = { workspace = true } +icu_locid = { workspace = true } +icu_segmenter = { workspace = true } +ipc-channel = { workspace = true } +itertools = { workspace = true } +log = { workspace = true } +malloc_size_of = { workspace = true } +malloc_size_of_derive = { workspace = true } +net_traits = { workspace = true } +parking_lot = { workspace = true } +pixels = { path = "../pixels" } +profile_traits = { workspace = true } +range = { path = "../range" } +rayon = { workspace = true } +script = { path = "../script" } +script_layout_interface = { workspace = true } +script_traits = { workspace = true } +selectors = { workspace = true } +servo_arc = { workspace = true } +servo_config = { path = "../config" } +servo_geometry = { path = "../geometry" } +servo_url = { path = "../url" } +stylo = { workspace = true } +stylo_atoms = { workspace = true } +stylo_traits = { workspace = true } +taffy = { workspace = true } +tracing = { workspace = true, optional = true } +unicode-bidi = { workspace = true } +unicode-script = { workspace = true } +url = { workspace = true } +webrender_api = { workspace = true } +xi-unicode = { workspace = true } + +[dev-dependencies] +num-traits = { workspace = true } +quickcheck = "1" diff --git a/components/layout_2020/cell.rs b/components/layout/cell.rs similarity index 89% rename from components/layout_2020/cell.rs rename to components/layout/cell.rs index 910a6ab2104..8b35fdf7943 100644 --- a/components/layout_2020/cell.rs +++ b/components/layout/cell.rs @@ -6,9 +6,12 @@ use std::fmt; use std::ops::Deref; use atomic_refcell::AtomicRefCell; +use malloc_size_of_derive::MallocSizeOf; use servo_arc::Arc; -pub(crate) struct ArcRefCell { +#[derive(MallocSizeOf)] +pub struct ArcRefCell { + #[conditional_malloc_size_of] value: Arc>, } diff --git a/components/layout_2020/construct_modern.rs b/components/layout/construct_modern.rs similarity index 82% rename from components/layout_2020/construct_modern.rs rename to components/layout/construct_modern.rs index e66082234ee..8f1282ec9f6 100644 --- a/components/layout_2020/construct_modern.rs +++ b/components/layout/construct_modern.rs @@ -5,12 +5,14 @@ //! Layout construction code that is shared between modern layout modes (Flexbox and CSS Grid) use std::borrow::Cow; +use std::sync::LazyLock; use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use style::selector_parser::PseudoElement; use crate::PropagatedBoxTreeData; use crate::context::LayoutContext; -use crate::dom::{BoxSlot, NodeExt}; +use crate::dom::BoxSlot; use crate::dom_traversal::{Contents, NodeAndStyleInfo, TraversalHandler}; use crate::flow::inline::construct::InlineFormattingContextBuilder; use crate::flow::{BlockContainer, BlockFormattingContext}; @@ -22,32 +24,32 @@ use crate::layout_box_base::LayoutBoxBase; use crate::style_ext::DisplayGeneratingBox; /// A builder used for both flex and grid containers. -pub(crate) struct ModernContainerBuilder<'a, 'dom, Node> { +pub(crate) struct ModernContainerBuilder<'a, 'dom> { context: &'a LayoutContext<'a>, - info: &'a NodeAndStyleInfo, + info: &'a NodeAndStyleInfo<'dom>, propagated_data: PropagatedBoxTreeData, - contiguous_text_runs: Vec>, + contiguous_text_runs: Vec>, /// To be run in parallel with rayon in `finish` - jobs: Vec>, + jobs: Vec>, has_text_runs: bool, } -enum ModernContainerJob<'dom, Node> { +enum ModernContainerJob<'dom> { ElementOrPseudoElement { - info: NodeAndStyleInfo, + info: NodeAndStyleInfo<'dom>, display: DisplayGeneratingBox, contents: Contents, box_slot: BoxSlot<'dom>, }, - TextRuns(Vec>), + TextRuns(Vec>), } -struct ModernContainerTextRun<'dom, Node> { - info: NodeAndStyleInfo, +struct ModernContainerTextRun<'dom> { + info: NodeAndStyleInfo<'dom>, text: Cow<'dom, str>, } -impl ModernContainerTextRun<'_, Node> { +impl ModernContainerTextRun<'_> { /// fn is_only_document_white_space(&self) -> bool { // FIXME: is this the right definition? See @@ -71,11 +73,8 @@ pub(crate) struct ModernItem<'dom> { pub formatting_context: IndependentFormattingContext, } -impl<'dom, Node: 'dom> TraversalHandler<'dom, Node> for ModernContainerBuilder<'_, 'dom, Node> -where - Node: NodeExt<'dom>, -{ - fn handle_text(&mut self, info: &NodeAndStyleInfo, text: Cow<'dom, str>) { +impl<'dom> TraversalHandler<'dom> for ModernContainerBuilder<'_, 'dom> { + fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: Cow<'dom, str>) { self.contiguous_text_runs.push(ModernContainerTextRun { info: info.clone(), text, @@ -85,7 +84,7 @@ where /// Or pseudo-element fn handle_element( &mut self, - info: &NodeAndStyleInfo, + info: &NodeAndStyleInfo<'dom>, display: DisplayGeneratingBox, contents: Contents, box_slot: BoxSlot<'dom>, @@ -101,13 +100,10 @@ where } } -impl<'a, 'dom, Node: 'dom> ModernContainerBuilder<'a, 'dom, Node> -where - Node: NodeExt<'dom>, -{ +impl<'a, 'dom> ModernContainerBuilder<'a, 'dom> { pub fn new( context: &'a LayoutContext<'a>, - info: &'a NodeAndStyleInfo, + info: &'a NodeAndStyleInfo<'dom>, propagated_data: PropagatedBoxTreeData, ) -> Self { ModernContainerBuilder { @@ -136,27 +132,17 @@ where pub(crate) fn finish(mut self) -> Vec> { self.wrap_any_text_in_anonymous_block_container(); - let anonymous_style = if self.has_text_runs { - Some( - self.context - .shared_context() - .stylist - .style_for_anonymous::( - &self.context.shared_context().guards, - &style::selector_parser::PseudoElement::ServoAnonymousBox, - &self.info.style, - ), - ) - } else { - None - }; - + let anonymous_info = LazyLock::new(|| { + self.info + .pseudo(self.context, PseudoElement::ServoAnonymousBox) + .expect("Should always be able to construct info for anonymous boxes.") + }); let mut children: Vec = std::mem::take(&mut self.jobs) .into_par_iter() .filter_map(|job| match job { ModernContainerJob::TextRuns(runs) => { let mut inline_formatting_context_builder = - InlineFormattingContextBuilder::new(); + InlineFormattingContextBuilder::new(self.info); for flex_text_run in runs.into_iter() { inline_formatting_context_builder .push_text(flex_text_run.text, &flex_text_run.info); @@ -173,7 +159,7 @@ where let block_formatting_context = BlockFormattingContext::from_block_container( BlockContainer::InlineFormattingContext(inline_formatting_context), ); - let info = &self.info.new_anonymous(anonymous_style.clone().unwrap()); + let info: &NodeAndStyleInfo = &anonymous_info; let formatting_context = IndependentFormattingContext { base: LayoutBoxBase::new(info.into(), info.style.clone()), contents: IndependentFormattingContextContents::NonReplaced( diff --git a/components/layout/context.rs b/components/layout/context.rs new file mode 100644 index 00000000000..3411eed486c --- /dev/null +++ b/components/layout/context.rs @@ -0,0 +1,242 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::sync::Arc; + +use base::id::PipelineId; +use euclid::Size2D; +use fnv::FnvHashMap; +use fonts::FontContext; +use fxhash::FxHashMap; +use net_traits::image_cache::{ + ImageCache, ImageCacheResult, ImageOrMetadataAvailable, UsePlaceholder, +}; +use parking_lot::{Mutex, RwLock}; +use pixels::Image as PixelImage; +use script_layout_interface::{IFrameSizes, ImageAnimationState, PendingImage, PendingImageState}; +use servo_url::{ImmutableOrigin, ServoUrl}; +use style::context::SharedStyleContext; +use style::dom::OpaqueNode; +use style::values::computed::image::{Gradient, Image}; + +use crate::display_list::WebRenderImageInfo; + +pub struct LayoutContext<'a> { + pub id: PipelineId, + pub use_rayon: bool, + pub origin: ImmutableOrigin, + + /// Bits shared by the layout and style system. + pub style_context: SharedStyleContext<'a>, + + /// A FontContext to be used during layout. + pub font_context: Arc, + + /// Reference to the script thread image cache. + pub image_cache: Arc, + + /// A list of in-progress image loads to be shared with the script thread. + pub pending_images: Mutex>, + + /// A collection of ` as second child under