diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3668ac41f48..0bac7800467 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -11,7 +11,6 @@ /components/compositing @mrobinson # Reviewers for layout-related code -/components/layout_2020 @mrobinson @Loirooriol @nicoburns /components/layout @mrobinson @Loirooriol @nicoburns # Reviewers for Minibrowser related code diff --git a/.github/workflows/dispatch-workflow.yml b/.github/workflows/dispatch-workflow.yml index 472f4813cd7..2e4b8ef7076 100644 --- a/.github/workflows/dispatch-workflow.yml +++ b/.github/workflows/dispatch-workflow.yml @@ -8,6 +8,9 @@ on: profile: required: true type: string + build-args: + required: true + type: string wpt-args: required: true type: string @@ -35,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 }} @@ -46,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 }} @@ -59,6 +64,7 @@ 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 }} diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 033f9795a23..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 @@ -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 }} 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 1545dfa2450..1d11033a326 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -56,6 +56,7 @@ 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 }} diff --git a/.github/workflows/ohos.yml b/.github/workflows/ohos.yml index 8728d31865c..2cda0acbfc9 100644 --- a/.github/workflows/ohos.yml +++ b/.github/workflows/ohos.yml @@ -36,6 +36,7 @@ env: RUST_BACKTRACE: 1 SHELL: /bin/bash CARGO_INCREMENTAL: 0 + BENCHER_PROJECT: ${{ vars.BENCHER_PROJECT || 'servo' }} jobs: build: @@ -168,11 +169,17 @@ jobs: - name: Build for aarch64 HarmonyOS run: | ./mach build --locked --target aarch64-unknown-linux-ohos --profile=${{ inputs.profile }} --flavor=harmonyos --no-default-features --features tracing,tracing-hitrace + - name: Upload supprt/hitrace-bencher/runs.json + uses: actions/upload-artifact@v4 + with: + name: runs.json + path: support/hitrace-bencher/runs.json + overwrite: true - uses: actions/upload-artifact@v4 with: # Upload the **unsigned** artifact - We don't have the signing materials in pull request workflows - path: target/openharmony/aarch64-unknown-linux-ohos/${{ inputs.profile }}/entry/build/harmonyos/outputs/default/servoshell-default-unsigned.hap name: servoshell-hos-${{ inputs.profile }}.hap + path: target/openharmony/aarch64-unknown-linux-ohos/${{ inputs.profile }}/entry/build/harmonyos/outputs/default/servoshell-default-unsigned.hap test-harmonyos-aarch64: @@ -223,10 +230,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 @@ -240,3 +245,19 @@ jobs: [[ $servo_pid =~ ^[0-9]+$ ]] || { echo "It looks like servo crashed!" ; exit 1; } # If the grep fails, then the trace output for the "page loaded" prompt is missing grep 'org\.servo\.servo-.* tracing_mark_write.*PageLoadEndedPrompt' test_output/servo.ftrace + - name: Getting runs file + uses: actions/download-artifact@v4 + with: + # Name of the artifact to download. + # If unspecified, all artifacts for the run are downloaded. + name: runs.json + - name: "Run benchmark" + run: hitrace-bench -r runs.json + - name: Getting bencher + uses: bencherdev/bencher@main + - name: Getting model name + run: | + echo "MODEL_NAME=$(hdc bugreport | head -n 20 | grep MarketName | awk '{for (i=2; i> $GITHUB_ENV + - name: Uploading to bencher.dev + run: | + bencher run --adapter json --file bench.json --project '${{ env.BENCHER_PROJECT }}' --token '${{ secrets.BENCHER_API_TOKEN }}' --github-actions '${{ secrets.GITHUB_TOKEN }}' --testbed="$MODEL_NAME" diff --git a/.github/workflows/try-label.yml b/.github/workflows/try-label.yml index b1ca27a6f58..d4ce3f944f6 100644 --- a/.github/workflows/try-label.yml +++ b/.github/workflows/try-label.yml @@ -128,6 +128,7 @@ 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 }} diff --git a/.github/workflows/try.yml b/.github/workflows/try.yml index cba4fc03e06..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,6 +112,7 @@ 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 }} 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 e4ebac08f89..bf2c9c74e51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,9 +20,9 @@ checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" [[package]] name = "accountable-refcell" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e2bba6f21fcf0ae382750eb6d9387c42807761fa7329d3a05fcd1334e8c3f2" +checksum = "6afea9052e0b2d90e38572691d87194b2e5d8d6899b9b850b22aaab17e486252" dependencies = [ "backtrace", ] @@ -139,7 +139,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 2.9.0", + "bitflags 2.9.1", "cc", "cesu8", "jni", @@ -238,12 +238,12 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" dependencies = [ "anstyle", - "once_cell", + "once_cell_polyfill", "windows-sys 0.59.0", ] @@ -330,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", @@ -393,9 +393,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-lc-rs" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b756939cb2f8dc900aa6dcd505e6e2428e9cae7ff7b028c49e3946efa70878" +checksum = "93fcc8f365936c834db5514fc45aee5b1202d677e6b40e48468aaaa8183ca8c7" dependencies = [ "aws-lc-sys", "zeroize", @@ -403,9 +403,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.28.2" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa9b6986f250236c27e5a204062434a773a13243d2ffc2955f37bdba4c5c6a1" +checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079" dependencies = [ "bindgen 0.69.5", "cc", @@ -442,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", @@ -500,7 +500,7 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cexpr", "clang-sys", "itertools 0.10.5", @@ -523,10 +523,10 @@ version = "0.71.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cexpr", "clang-sys", - "itertools 0.13.0", + "itertools 0.10.5", "proc-macro2", "quote", "regex", @@ -564,9 +564,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ "serde", ] @@ -601,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]] @@ -609,7 +609,7 @@ name = "bluetooth" version = "0.0.1" dependencies = [ "base", - "bitflags 2.9.0", + "bitflags 2.9.1", "bluetooth_traits", "blurdroid", "blurmac", @@ -669,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", @@ -680,9 +680,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.3" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -713,9 +713,9 @@ 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", ] @@ -759,7 +759,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "log", "polling", "rustix", @@ -784,34 +784,25 @@ 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", "net_traits", - "num-traits", "pixels", "range", "raqote", "servo_arc", + "snapshot", "stylo", - "surfman", "unicode-script", - "webrender", "webrender_api", - "webxr", - "webxr-api", ] [[package]] @@ -829,6 +820,7 @@ dependencies = [ "serde_bytes", "servo_config", "servo_malloc_size_of", + "snapshot", "stylo", "webrender_api", "webxr-api", @@ -851,9 +843,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.19" +version = "1.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" +checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" dependencies = [ "jobserver", "libc", @@ -877,9 +869,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.17.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d4ba6e40bd1184518716a6e1a781bf9160e286d219ccdb8ab2612e74cfe4789" +checksum = "e34e221e91c7eb5e8315b5c9cf1a61670938c0626451f954a51693ed44b37f45" dependencies = [ "smallvec", "target-lexicon", @@ -908,9 +900,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", @@ -971,18 +963,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.37" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" +checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.37" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" +checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" dependencies = [ "anstyle", "clap_lex", @@ -1091,7 +1083,7 @@ version = "0.0.1" dependencies = [ "base", "bincode", - "bitflags 2.9.0", + "bitflags 2.9.1", "compositing_traits", "constellation_traits", "crossbeam-channel", @@ -1106,7 +1098,6 @@ dependencies = [ "net", "pixels", "profile_traits", - "script_traits", "servo_allocator", "servo_config", "servo_geometry", @@ -1204,7 +1195,6 @@ name = "constellation_traits" version = "0.0.1" dependencies = [ "base", - "bitflags 2.9.0", "canvas_traits", "devtools_traits", "embedder_traits", @@ -1230,10 +1220,10 @@ dependencies = [ [[package]] name = "content-security-policy" version = "0.5.4" -source = "git+https://github.com/servo/rust-content-security-policy/?branch=servo-csp#827eea44ec0f3d91457d1c0467881cb4f9752520" +source = "git+https://github.com/servo/rust-content-security-policy/?branch=servo-csp#58a09ee320fd6fbb828748ae04255e4c8d3f9c9e" dependencies = [ "base64 0.22.1", - "bitflags 2.9.0", + "bitflags 2.9.1", "once_cell", "percent-encoding", "regex", @@ -1579,9 +1569,9 @@ dependencies = [ [[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", @@ -1615,7 +1605,7 @@ name = "devtools_traits" version = "0.0.1" dependencies = [ "base", - "bitflags 2.9.0", + "bitflags 2.9.1", "embedder_traits", "http 1.3.1", "ipc-channel", @@ -1721,6 +1711,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.1", + "objc2 0.6.1", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -1775,9 +1775,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" @@ -1831,7 +1831,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25dd34cec49ab55d85ebf70139cb1ccd29c977ef6b6ba4fe85489d6877ee9ef3" dependencies = [ "ahash", - "bitflags 2.9.0", + "bitflags 2.9.1", "emath", "epaint", "log", @@ -1909,7 +1909,6 @@ name = "embedder_traits" version = "0.0.1" dependencies = [ "base", - "cfg-if", "cookie 0.18.1", "crossbeam-channel", "euclid", @@ -2024,9 +2023,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2034,9 +2033,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" @@ -2153,7 +2152,7 @@ version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b64b34f4efd515f905952d91bc185039863705592c0c53ae6d979805dd154520" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "byteorder", "core-foundation 0.9.4", "core-graphics", @@ -2179,7 +2178,7 @@ dependencies = [ "app_units", "atomic_refcell", "base", - "bitflags 2.9.0", + "bitflags 2.9.1", "byteorder", "compositing_traits", "core-foundation 0.9.4", @@ -2193,7 +2192,7 @@ dependencies = [ "freetype-sys", "harfbuzz-sys", "ipc-channel", - "itertools 0.13.0", + "itertools 0.14.0", "libc", "log", "malloc_size_of_derive", @@ -2201,6 +2200,7 @@ dependencies = [ "net_traits", "num-traits", "parking_lot", + "profile_traits", "range", "serde", "servo_allocator", @@ -2234,7 +2234,7 @@ dependencies = [ [[package]] name = "fontsan" version = "0.5.2" -source = "git+https://github.com/servo/fontsan#138bdb0451c4ea02a303caddc1a6c1fd654ae927" +source = "git+https://github.com/servo/fontsan#c0d0b5333117901e1c31bc3c502c384115b93e6f" dependencies = [ "cc", "glob", @@ -2320,6 +2320,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "fst" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab85b9b05e3978cc9a9cf8fea7f01b494e1a09ed3037e16ba39edc7a29eb61a" + [[package]] name = "futf" version = "0.1.5" @@ -2476,9 +2482,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", @@ -2547,9 +2553,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", @@ -2580,11 +2586,11 @@ 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", + "bitflags 2.9.1", "futures-channel", "futures-core", "futures-executor", @@ -2601,9 +2607,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", @@ -2614,9 +2620,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", @@ -2660,9 +2666,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", @@ -2675,7 +2681,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "gpu-alloc-types", ] @@ -2685,7 +2691,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -2706,7 +2712,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "gpu-descriptor-types", "hashbrown", ] @@ -2717,7 +2723,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -2728,9 +2734,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", @@ -2738,7 +2744,7 @@ dependencies = [ "futures-util", "glib", "gstreamer-sys", - "itertools 0.13.0", + "itertools 0.14.0", "libc", "muldiv", "num-integer", @@ -2781,9 +2787,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", @@ -2797,9 +2803,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", @@ -2811,9 +2817,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", @@ -2825,9 +2831,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", @@ -2838,9 +2844,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", @@ -2853,9 +2859,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", @@ -2866,9 +2872,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", @@ -2878,9 +2884,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", @@ -2968,9 +2974,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", @@ -2980,9 +2986,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", @@ -2997,9 +3003,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", @@ -3101,9 +3107,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" dependencies = [ "foldhash", "serde", @@ -3384,9 +3390,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710" dependencies = [ "bytes", "futures-channel", @@ -3924,7 +3930,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "inotify-sys", "libc", ] @@ -4017,9 +4023,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", ] @@ -4032,9 +4038,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.10" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a064218214dc6a10fbae5ec5fa888d80c45d611aba169222fc272072bf7aef6" +checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93" dependencies = [ "jiff-static", "log", @@ -4045,9 +4051,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.10" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "199b7932d97e325aff3a7030e141eafe7f2c6268e1d1b24859b753a627f45254" +checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442" dependencies = [ "proc-macro2", "quote", @@ -4119,7 +4125,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "serde", "unicode-segmentation", ] @@ -4148,8 +4154,7 @@ dependencies = [ "app_units", "atomic_refcell", "base", - "bitflags 2.9.0", - "canvas_traits", + "bitflags 2.9.1", "compositing_traits", "constellation_traits", "data-url", @@ -4163,7 +4168,7 @@ dependencies = [ "icu_locid", "icu_segmenter", "ipc-channel", - "itertools 0.13.0", + "itertools 0.14.0", "log", "malloc_size_of_derive", "net_traits", @@ -4245,19 +4250,19 @@ dependencies = [ [[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.52.6", + "windows-targets 0.48.5", ] [[package]] name = "libm" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d154aedcb0b7a1e91a3fddbe2a8350d3da76ac9d0220ae20da5c7aa8269612" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" @@ -4265,7 +4270,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "libc", "redox_syscall 0.5.7", ] @@ -4283,7 +4288,6 @@ dependencies = [ "bluetooth_traits", "canvas", "canvas_traits", - "cfg-if", "compositing", "compositing_traits", "constellation", @@ -4321,6 +4325,7 @@ dependencies = [ "servo-media", "servo-media-dummy", "servo-media-gstreamer", + "servo-tracing", "servo_allocator", "servo_config", "servo_geometry", @@ -4331,6 +4336,7 @@ dependencies = [ "tracing", "url", "webdriver_server", + "webgl", "webgpu", "webrender", "webrender_api", @@ -4545,7 +4551,7 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block", "core-graphics-types", "foreign-types 0.5.0", @@ -4559,7 +4565,6 @@ name = "metrics" version = "0.0.1" dependencies = [ "base", - "constellation_traits", "ipc-channel", "log", "malloc_size_of_derive", @@ -4640,7 +4645,7 @@ dependencies = [ [[package]] name = "mozjs" version = "0.14.1" -source = "git+https://github.com/servo/mozjs#d1525dfaee22cc1ea9ee16c552cdeedaa9f20741" +source = "git+https://github.com/servo/mozjs#728acdf3d4ce0604e9f75dd1d539dc6f291ccec7" dependencies = [ "bindgen 0.71.1", "cc", @@ -4651,8 +4656,8 @@ dependencies = [ [[package]] name = "mozjs_sys" -version = "0.128.9-1" -source = "git+https://github.com/servo/mozjs#d1525dfaee22cc1ea9ee16c552cdeedaa9f20741" +version = "0.128.9-2" +source = "git+https://github.com/servo/mozjs#728acdf3d4ce0604e9f75dd1d539dc6f291ccec7" dependencies = [ "bindgen 0.71.1", "cc", @@ -4680,7 +4685,7 @@ checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632" dependencies = [ "arrayvec", "bit-set", - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg_aliases", "codespan-reporting", "half", @@ -4730,7 +4735,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32036ede4ef064610304337831e9d49dac23e7edc4e9efd076c8259eab6d19a9" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "ctor", "napi-sys-ohos", ] @@ -4750,7 +4755,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "jni-sys", "log", "ndk-sys 0.6.0+11769913", @@ -4802,6 +4807,7 @@ dependencies = [ "devtools_traits", "embedder_traits", "flate2", + "fst", "futures 0.3.31", "futures-core", "futures-util", @@ -4815,7 +4821,7 @@ dependencies = [ "hyper_serde", "imsz", "ipc-channel", - "itertools 0.13.0", + "itertools 0.14.0", "log", "malloc_size_of_derive", "mime", @@ -4829,7 +4835,6 @@ dependencies = [ "rustls-pki-types", "serde", "serde_json", - "servo_allocator", "servo_arc", "servo_config", "servo_malloc_size_of", @@ -4857,6 +4862,7 @@ dependencies = [ "content-security-policy", "cookie 0.18.1", "crossbeam-channel", + "data-url", "embedder_traits", "headers 0.4.0", "http 1.3.1", @@ -4892,7 +4898,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg-if", "cfg_aliases", "libc", @@ -5068,33 +5074,53 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "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.1", + "objc2 0.6.1", + "objc2-foundation 0.3.1", +] + [[package]] name = "objc2-cloud-kit" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2", - "objc2", + "objc2 0.5.2", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -5104,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]] @@ -5114,10 +5140,21 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "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.1", + "dispatch2", + "objc2 0.6.1", ] [[package]] @@ -5127,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", ] @@ -5139,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]] @@ -5156,11 +5193,22 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "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.1", + "objc2 0.6.1", + "objc2-core-foundation", ] [[package]] @@ -5170,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]] @@ -5181,10 +5229,10 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -5193,10 +5241,10 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", "objc2-metal", ] @@ -5206,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]] @@ -5216,14 +5264,14 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "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", @@ -5238,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]] @@ -5248,11 +5296,11 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2", - "objc2", + "objc2 0.5.2", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -5334,6 +5382,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + [[package]] name = "oorandom" version = "11.1.5" @@ -5472,7 +5526,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", @@ -5481,13 +5535,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]] @@ -5768,7 +5821,6 @@ dependencies = [ "ipc-channel", "libc", "log", - "parking_lot", "profile_traits", "regex", "serde", @@ -5804,7 +5856,6 @@ dependencies = [ "servo_config", "servo_malloc_size_of", "signpost", - "strum_macros", "time", "tracing", ] @@ -5849,9 +5900,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.37.4" +version = "0.37.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4ce8c88de324ff838700f36fb6ab86c96df0e3c4ab6ef3a9b2044465cce1369" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" dependencies = [ "memchr", ] @@ -5987,7 +6038,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -6012,12 +6063,6 @@ dependencies = [ "thiserror 2.0.9", ] -[[package]] -name = "ref_filter_map" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5ceb840e4009da4841ed22a15eb49f64fdd00a2138945c5beacf506b2fb5ed" - [[package]] name = "regex" version = "1.11.1" @@ -6087,14 +6132,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", - "bitflags 2.9.0", + "base64 0.22.1", + "bitflags 2.9.1", "serde", "serde_derive", + "unicode-ident", ] [[package]] @@ -6130,7 +6176,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys", @@ -6139,9 +6185,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.26" +version = "0.23.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" +checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" dependencies = [ "aws-lc-rs", "log", @@ -6163,15 +6209,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", @@ -6181,9 +6230,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" @@ -6229,7 +6278,7 @@ dependencies = [ "base", "base64 0.22.1", "bincode", - "bitflags 2.9.0", + "bitflags 2.9.1", "bluetooth_traits", "canvas_traits", "cbc", @@ -6262,7 +6311,7 @@ dependencies = [ "image", "indexmap", "ipc-channel", - "itertools 0.13.0", + "itertools 0.14.0", "jstraceable_derive", "keyboard-types", "libc", @@ -6279,13 +6328,11 @@ dependencies = [ "nom", "num-traits", "num_cpus", - "parking_lot", "percent-encoding", "phf", "pixels", "profile_traits", "range", - "ref_filter_map", "regex", "script_bindings", "script_layout_interface", @@ -6302,6 +6349,7 @@ dependencies = [ "servo_rand", "servo_url", "smallvec", + "snapshot", "strum", "strum_macros", "stylo", @@ -6318,6 +6366,7 @@ dependencies = [ "unicode-bidi", "unicode-segmentation", "url", + "urlpattern", "utf-8", "uuid", "webdriver", @@ -6333,7 +6382,7 @@ dependencies = [ name = "script_bindings" version = "0.0.1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "crossbeam-channel", "cssparser", "deny_public_fields", @@ -6373,7 +6422,6 @@ dependencies = [ "app_units", "atomic_refcell", "base", - "canvas_traits", "compositing_traits", "constellation_traits", "embedder_traits", @@ -6386,7 +6434,6 @@ dependencies = [ "ipc-channel", "libc", "malloc_size_of_derive", - "metrics", "net_traits", "pixels", "profile_traits", @@ -6398,7 +6445,6 @@ dependencies = [ "servo_malloc_size_of", "servo_url", "stylo", - "stylo_traits", "webrender_api", ] @@ -6426,7 +6472,6 @@ dependencies = [ "devtools_traits", "embedder_traits", "euclid", - "http 1.3.1", "ipc-channel", "keyboard-types", "malloc_size_of_derive", @@ -6461,10 +6506,10 @@ dependencies = [ [[package]] name = "selectors" -version = "0.27.0" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#0eaeea3dfd4aa0415529700353075ad1e1e47e5b" +version = "0.28.0" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cssparser", "derive_more", "fxhash", @@ -6572,7 +6617,7 @@ dependencies = [ [[package]] name = "servo-media" version = "0.1.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" dependencies = [ "once_cell", "servo-media-audio", @@ -6585,7 +6630,7 @@ dependencies = [ [[package]] name = "servo-media-audio" version = "0.2.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" dependencies = [ "byte-slice-cast", "euclid", @@ -6606,7 +6651,7 @@ dependencies = [ [[package]] name = "servo-media-derive" version = "0.1.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" dependencies = [ "proc-macro2", "quote", @@ -6616,7 +6661,7 @@ dependencies = [ [[package]] name = "servo-media-dummy" version = "0.1.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" dependencies = [ "ipc-channel", "servo-media", @@ -6630,7 +6675,7 @@ dependencies = [ [[package]] name = "servo-media-gstreamer" version = "0.1.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" dependencies = [ "byte-slice-cast", "glib", @@ -6663,7 +6708,7 @@ dependencies = [ [[package]] name = "servo-media-gstreamer-render" version = "0.1.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" dependencies = [ "gstreamer", "gstreamer-video", @@ -6673,7 +6718,7 @@ dependencies = [ [[package]] name = "servo-media-gstreamer-render-android" version = "0.1.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" dependencies = [ "glib", "gstreamer", @@ -6687,7 +6732,7 @@ dependencies = [ [[package]] name = "servo-media-gstreamer-render-unix" version = "0.1.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" dependencies = [ "glib", "gstreamer", @@ -6702,7 +6747,7 @@ dependencies = [ [[package]] name = "servo-media-player" version = "0.1.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" dependencies = [ "ipc-channel", "serde", @@ -6714,7 +6759,7 @@ dependencies = [ [[package]] name = "servo-media-streams" version = "0.1.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" dependencies = [ "uuid", ] @@ -6722,18 +6767,28 @@ dependencies = [ [[package]] name = "servo-media-traits" version = "0.1.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" [[package]] name = "servo-media-webrtc" version = "0.1.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" 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" @@ -6746,8 +6801,8 @@ dependencies = [ [[package]] name = "servo_arc" -version = "0.4.0" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#0eaeea3dfd4aa0415529700353075ad1e1e47e5b" +version = "0.4.1" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb" dependencies = [ "serde", "stable_deref_trait", @@ -6813,6 +6868,7 @@ dependencies = [ "unicode-bidi", "unicode-script", "url", + "urlpattern", "uuid", "webrender_api", "wr_malloc_size_of", @@ -6862,19 +6918,15 @@ dependencies = [ "euclid", "getopts", "gilrs", - "gleam", "glow", "headers 0.4.0", "hilog", "hitrace", - "http 1.3.1", - "icu_locid", "image", "ipc-channel", "jni", "keyboard-types", "libc", - "libloading", "libservo", "log", "mime_guess", @@ -6883,8 +6935,8 @@ 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", @@ -6892,7 +6944,6 @@ dependencies = [ "rustls", "serde_json", "servo_allocator", - "shellwords", "sig", "surfman", "tokio", @@ -6919,9 +6970,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", @@ -6937,16 +6988,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shellwords" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e515aa4699a88148ed5ef96413ceef0048ce95b43fbc955a33bde0a70fcae6" -dependencies = [ - "lazy_static", - "regex", -] - [[package]] name = "shlex" version = "1.3.0" @@ -7030,7 +7071,7 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "calloop", "calloop-wayland-source", "cursor-icon", @@ -7069,6 +7110,16 @@ dependencies = [ "serde", ] +[[package]] +name = "snapshot" +version = "0.0.1" +dependencies = [ + "euclid", + "ipc-channel", + "pixels", + "serde", +] + [[package]] name = "socket2" version = "0.5.9" @@ -7091,7 +7142,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -7195,13 +7246,13 @@ dependencies = [ [[package]] name = "stylo" -version = "0.2.1" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#0eaeea3dfd4aa0415529700353075ad1e1e47e5b" +version = "0.3.0" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb" dependencies = [ "app_units", "arrayvec", "atomic_refcell", - "bitflags 2.9.0", + "bitflags 2.9.1", "byteorder", "cssparser", "derive_more", @@ -7210,12 +7261,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", @@ -7249,12 +7299,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#0eaeea3dfd4aa0415529700353075ad1e1e47e5b" +version = "0.3.0" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb" dependencies = [ "string_cache", "string_cache_codegen", @@ -7262,13 +7313,13 @@ dependencies = [ [[package]] name = "stylo_config" -version = "0.2.0" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#0eaeea3dfd4aa0415529700353075ad1e1e47e5b" +version = "0.3.0" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb" [[package]] name = "stylo_derive" -version = "0.2.0" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#0eaeea3dfd4aa0415529700353075ad1e1e47e5b" +version = "0.3.0" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb" dependencies = [ "darling", "proc-macro2", @@ -7279,17 +7330,17 @@ dependencies = [ [[package]] name = "stylo_dom" -version = "0.2.0" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#0eaeea3dfd4aa0415529700353075ad1e1e47e5b" +version = "0.3.0" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "stylo_malloc_size_of", ] [[package]] name = "stylo_malloc_size_of" -version = "0.2.0" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#0eaeea3dfd4aa0415529700353075ad1e1e47e5b" +version = "0.3.0" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb" dependencies = [ "app_units", "cssparser", @@ -7305,16 +7356,16 @@ dependencies = [ [[package]] name = "stylo_static_prefs" -version = "0.2.0" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#0eaeea3dfd4aa0415529700353075ad1e1e47e5b" +version = "0.3.0" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb" [[package]] name = "stylo_traits" -version = "0.2.0" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#0eaeea3dfd4aa0415529700353075ad1e1e47e5b" +version = "0.3.0" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb" dependencies = [ "app_units", - "bitflags 2.9.0", + "bitflags 2.9.1", "cssparser", "euclid", "malloc_size_of_derive", @@ -7340,7 +7391,7 @@ name = "surfman" version = "0.9.8" source = "git+https://github.com/servo/surfman?rev=f7688b4585f9e0b5d4bf8ee8e4a91e82349610b1#f7688b4585f9e0b5d4bf8ee8e4a91e82349610b1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg_aliases", "cgl", "cocoa", @@ -7384,9 +7435,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", @@ -7395,9 +7446,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", @@ -7417,9 +7468,9 @@ dependencies = [ [[package]] name = "system-deps" -version = "7.0.3" +version = "7.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d23aaf9f331227789a99e8de4c91bf46703add012bdfd45fdecdfb2975a005" +checksum = "e4be53aa0cba896d2dc615bd42bbc130acdcffa239e0a2d965ea5b3b2a86ffdb" dependencies = [ "cfg-expr", "heck", @@ -7453,9 +7504,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.16" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" [[package]] name = "task_info" @@ -7694,7 +7745,7 @@ dependencies = [ [[package]] name = "to_shmem" version = "0.2.0" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#0eaeea3dfd4aa0415529700353075ad1e1e47e5b" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb" dependencies = [ "cssparser", "servo_arc", @@ -7707,7 +7758,7 @@ dependencies = [ [[package]] name = "to_shmem_derive" version = "0.1.0" -source = "git+https://github.com/servo/stylo?branch=2025-03-15#0eaeea3dfd4aa0415529700353075ad1e1e47e5b" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb" dependencies = [ "darling", "proc-macro2", @@ -7718,9 +7769,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.44.2" +version = "1.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" dependencies = [ "backtrace", "bytes", @@ -7766,9 +7817,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", @@ -7800,18 +7851,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", @@ -7918,9 +7969,9 @@ checksum = "ce607aae8ab0ab3abf3a2723a9ab6f09bb8639ed83fdd888d857b8e556c868d8" [[package]] name = "truetype" -version = "0.47.8" +version = "0.47.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9ffbd4cf26797938aa36b2d03ec051800e6886fbed4bf70333d96b230a575d" +checksum = "05ce9543b570c6e8a392274b67e1001816bce953aa89724e52a4639db02a10e0" dependencies = [ "typeface", ] @@ -7984,6 +8035,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" @@ -8026,12 +8118,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" @@ -8070,6 +8156,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" @@ -8262,9 +8360,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", @@ -8276,11 +8374,11 @@ 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", + "bitflags 2.9.1", "rustix", "wayland-backend", "wayland-scanner", @@ -8292,16 +8390,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cursor-icon", "wayland-backend", ] [[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", @@ -8310,11 +8408,11 @@ 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", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-scanner", @@ -8322,11 +8420,11 @@ 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", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -8335,11 +8433,11 @@ 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", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -8403,9 +8501,9 @@ dependencies = [ [[package]] name = "web_atoms" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "954c5a41f2bcb7314344079d0891505458cc2f4b422bdea1d5bfbe6d1a04903b" +checksum = "0b9c5f0bc545ea3b20b423e33b9b457764de0b3730cd957f6c6aa6c301785f6e" dependencies = [ "phf", "phf_codegen", @@ -8415,11 +8513,11 @@ dependencies = [ [[package]] name = "webdriver" -version = "0.51.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310ce9d3648c5ff1915ca7dd09c44eabb7eb17f9ff4a6e7e5f4a902c8d1e269f" +checksum = "91d53921e1bef27512fa358179c9a22428d55778d2c2ae3c5c37a52b82ce6e92" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "bytes", "cookie 0.16.2", "http 0.2.12", @@ -8452,9 +8550,7 @@ dependencies = [ "ipc-channel", "keyboard-types", "log", - "net_traits", "pixels", - "script_traits", "serde", "serde_json", "servo_config", @@ -8464,6 +8560,31 @@ dependencies = [ "webdriver", ] +[[package]] +name = "webgl" +version = "0.0.1" +dependencies = [ + "bitflags 2.9.1", + "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" @@ -8476,7 +8597,7 @@ dependencies = [ "log", "serde", "servo_config", - "servo_malloc_size_of", + "snapshot", "webgpu_traits", "webrender", "webrender_api", @@ -8493,6 +8614,7 @@ dependencies = [ "ipc-channel", "serde", "servo_malloc_size_of", + "snapshot", "webrender_api", "wgpu-core", "wgpu-types", @@ -8500,9 +8622,9 @@ dependencies = [ [[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", ] @@ -8510,11 +8632,11 @@ 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", - "bitflags 2.9.0", + "bitflags 2.9.1", "build-parallel", "byteorder", "derive_more", @@ -8545,10 +8667,10 @@ 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", + "bitflags 2.9.1", "byteorder", "crossbeam-channel", "euclid", @@ -8566,9 +8688,9 @@ 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", + "bitflags 2.9.1", "lazy_static", ] @@ -8577,7 +8699,6 @@ name = "webxr" version = "0.0.1" dependencies = [ "crossbeam-channel", - "embedder_traits", "euclid", "glow", "log", @@ -8616,7 +8737,7 @@ dependencies = [ "arrayvec", "bit-set", "bit-vec", - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg_aliases", "document-features", "hashbrown", @@ -8674,7 +8795,7 @@ dependencies = [ "arrayvec", "ash", "bit-set", - "bitflags 2.9.0", + "bitflags 2.9.1", "block", "bytemuck", "cfg-if", @@ -8715,7 +8836,7 @@ version = "25.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2aa49460c2a8ee8edba3fca54325540d904dd85b2e086ada762767e17d06e8bc" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "bytemuck", "js-sys", "log", @@ -9106,14 +9227,14 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winit" -version = "0.30.9" +version = "0.30.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a809eacf18c8eca8b6635091543f02a5a06ddf3dad846398795460e6e0ae3cc0" +checksum = "a4409c10174df8779dc29a4788cac85ed84024ccbc1743b776b21a520ee1aaf4" dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.9.0", + "bitflags 2.9.1", "block2", "bytemuck", "calloop", @@ -9127,9 +9248,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", @@ -9158,9 +9279,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.6" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] @@ -9186,7 +9307,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", @@ -9210,8 +9331,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", @@ -9314,7 +9435,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "dlib", "log", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index eb83cd3dfa2..1373440d67e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ publish = false rust-version = "1.85.0" [workspace.dependencies] -accountable-refcell = "0.2.0" +accountable-refcell = "0.2.2" aes = "0.8.4" aes-gcm = "0.10.3" aes-kw = { version = "0.2.1", features = ["alloc"] } @@ -84,7 +84,7 @@ image = "0.24" imsz = "0.2" 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" @@ -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" } +servo-tracing = { path = "components/servo_tracing" } +servo_arc = { git = "https://github.com/servo/stylo", branch = "2025-05-01" } smallbitvec = "2.6.0" 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_atoms = { 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,19 +162,20 @@ 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 = { 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 = "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.22" @@ -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/SECURITY.md b/SECURITY.md index 0c5dd453447..c4e75025880 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,4 +1,4 @@ # Security Policy -Given that Servo does not yet have customers or products, we are comfortable accepting the security related vulnerabilities as a [new GitHub issue](https://github.com/servo/servo/security/advisories/new) for now. +Given that Servo does not yet have customers or products, we are comfortable accepting the security related issues as [GitHub security reports](https://github.com/servo/servo/security/advisories/new) for now. diff --git a/components/canvas/Cargo.toml b/components/canvas/Cargo.toml index a9c06784d18..7aadc8a9bb5 100644 --- a/components/canvas/Cargo.toml +++ b/components/canvas/Cargo.toml @@ -11,37 +11,24 @@ 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" net_traits = { workspace = true } -num-traits = { workspace = true } 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 } -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 d6e35b2cbaf..ea30589d0af 100644 --- a/components/canvas/canvas_data.rs +++ b/components/canvas/canvas_data.rs @@ -2,6 +2,7 @@ * 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; @@ -16,19 +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 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,45 +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 set_line_dash(&mut self, items: Vec) { + pub(crate) fn set_line_dash(&mut self, items: Vec) { self.state.stroke_opts.set_line_dash(items); } - pub fn set_line_dash_offset(&mut self, offset: f32) { + pub(crate) fn set_line_dash_offset(&mut self, offset: f32) { self.state.stroke_opts.set_line_dash_offset(offset); } - pub fn get_transform(&self) -> Transform2D { + 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() { @@ -1394,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, @@ -1430,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 @@ -1442,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); @@ -1457,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; } @@ -1493,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, @@ -1507,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( @@ -1529,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)]); @@ -1555,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 @@ -1578,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; @@ -1614,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()), @@ -1641,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 8b1b5038334..82a221d560d 100644 --- a/components/canvas/canvas_paint_thread.rs +++ b/components/canvas/canvas_paint_thread.rs @@ -11,18 +11,21 @@ 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 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, @@ -244,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) @@ -273,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 e40367a4ee8..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,136 +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 set_line_dash(&mut self, items: Vec) { - match self { - StrokeOptions::Raqote(options) => options.dash_array = items, - } + fn set_line_dash(&mut self, items: Vec) { + self.dash_array = items; } - pub fn set_line_dash_offset(&mut self, offset: f32) { - match self { - StrokeOptions::Raqote(options) => options.dash_offset = offset, - } - } - pub fn as_raqote(&self) -> &raqote::StrokeStyle { - match self { - StrokeOptions::Raqote(options) => options, - } + 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()))) } } @@ -373,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( @@ -385,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, @@ -470,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 | @@ -505,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), } } @@ -532,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() { @@ -581,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, ); }) } @@ -591,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( @@ -602,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 { @@ -623,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, @@ -666,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( @@ -688,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)) } } } @@ -720,7 +671,7 @@ impl Filter { } } -struct PathBuilder(Option); +pub(crate) struct PathBuilder(Option); impl PathBuilder { fn new() -> PathBuilder { @@ -728,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, @@ -762,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)), @@ -875,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() } } @@ -988,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; @@ -1065,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 8a670a4b4a7..9b4ff54d29e 100644 --- a/components/compositing/Cargo.toml +++ b/components/compositing/Cargo.toml @@ -34,7 +34,6 @@ log = { workspace = true } 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" } diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index 4550188a7fa..855f60e57b2 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -7,7 +7,6 @@ use std::collections::HashMap; use std::env; use std::fs::create_dir_all; use std::iter::once; -use std::mem::take; use std::rc::Rc; use std::sync::Arc; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; @@ -21,14 +20,14 @@ use compositing_traits::rendering_context::RenderingContext; use compositing_traits::{ CompositionPipeline, CompositorMsg, ImageUpdate, SendableFrameTree, WebViewTrait, }; -use constellation_traits::{AnimationTickType, EmbedderToConstellationMessage, PaintMetricEvent}; +use constellation_traits::{EmbedderToConstellationMessage, PaintMetricEvent}; use crossbeam_channel::{Receiver, Sender}; use dpi::PhysicalSize; use embedder_traits::{ - AnimationState, CompositorHitTestResult, Cursor, InputEvent, MouseButtonEvent, MouseMoveEvent, - ShutdownState, TouchEventType, UntrustedNodeAddress, ViewportDetails, + CompositorHitTestResult, Cursor, InputEvent, MouseButtonEvent, MouseMoveEvent, ShutdownState, + TouchEventType, UntrustedNodeAddress, ViewportDetails, WheelDelta, WheelEvent, WheelMode, }; -use euclid::{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; @@ -55,7 +54,7 @@ use webrender_api::{ use crate::InitialCompositorState; use crate::webview_manager::WebViewManager; -use crate::webview_renderer::{UnknownWebView, WebViewRenderer}; +use crate::webview_renderer::{PinchZoomResult, UnknownWebView, WebViewRenderer}; #[derive(Debug, PartialEq)] enum UnableToComposite { @@ -197,9 +196,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, @@ -243,32 +239,12 @@ impl PipelineDetails { pub(crate) fn animating(&self) -> bool { !self.throttled && (self.animation_callbacks_running || self.animations_running) } - - pub(crate) fn tick_animations(&self, compositor: &IOCompositor) { - if !self.animating() { - return; - } - - let mut tick_type = AnimationTickType::empty(); - if self.animations_running { - tick_type.insert(AnimationTickType::CSS_ANIMATIONS_AND_TRANSITIONS); - } - if self.animation_callbacks_running { - tick_type.insert(AnimationTickType::REQUEST_ANIMATION_FRAME); - } - - let msg = EmbedderToConstellationMessage::TickAnimation(self.id, tick_type); - if let Err(e) = compositor.global.borrow().constellation_sender.send(msg) { - warn!("Sending tick to constellation failed ({:?}).", e); - } - } } 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, @@ -543,22 +519,14 @@ impl IOCompositor { pipeline_id, animation_state, ) => { - let mut throttled = true; if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) { - throttled = webview_renderer - .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_renderer) = self.webview_renderers.get(webview_id) { - webview_renderer.tick_animations_for_pipeline(pipeline_id, self); + 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); } } }, @@ -605,8 +573,13 @@ impl IOCompositor { CompositorMsg::SetThrottled(webview_id, pipeline_id, throttled) => { if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) { - webview_renderer.set_throttled(pipeline_id, throttled); - self.process_animations(true); + 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); + } } }, @@ -647,29 +620,57 @@ impl IOCompositor { } }, - CompositorMsg::WebDriverMouseButtonEvent(webview_id, action, button, x, y) => { + CompositorMsg::WebDriverMouseButtonEvent( + webview_id, + action, + button, + x, + y, + message_id, + ) => { let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else { warn!("Handling input event for unknown webview: {webview_id}"); return; }; 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, - })); + webview_renderer.dispatch_input_event( + InputEvent::MouseButton(MouseButtonEvent::new(action, button, point)) + .with_webdriver_message_id(Some(message_id)), + ); }, - CompositorMsg::WebDriverMouseMoveEvent(webview_id, x, y) => { + CompositorMsg::WebDriverMouseMoveEvent(webview_id, x, y, message_id) => { let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else { warn!("Handling input event for unknown webview: {webview_id}"); return; }; 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::new(point)) + .with_webdriver_message_id(Some(message_id)), + ); + }, + + 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::MouseMove(MouseMoveEvent { point })); + .dispatch_input_event(InputEvent::Wheel(WheelEvent { delta, point })); + webview_renderer.on_webdriver_wheel_action(scroll_delta, point); }, CompositorMsg::SendInitialTransaction(pipeline) => { @@ -1283,8 +1284,23 @@ impl IOCompositor { } self.last_animation_tick = Instant::now(); - for webview_renderer in self.webview_renderers.iter() { - webview_renderer.tick_all_animations(self); + let animating_webviews: Vec<_> = self + .webview_renderers + .iter() + .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:?})."); + } } } @@ -1448,10 +1464,11 @@ impl IOCompositor { format: PixelFormat::RGBA8, frames: vec![ImageFrame { delay: None, - bytes: ipc::IpcSharedMemory::from_bytes(&image), + byte_range: 0..image.len(), width: image.width(), height: image.height(), }], + bytes: ipc::IpcSharedMemory::from_bytes(&image), id: None, cors_status: CorsStatus::Safe, })) @@ -1659,11 +1676,39 @@ impl IOCompositor { if let Err(err) = self.rendering_context.make_current() { warn!("Failed to make the rendering context current: {:?}", err); } - let mut webview_renderers = take(&mut self.webview_renderers); - for webview_renderer in webview_renderers.iter_mut() { - webview_renderer.process_pending_scroll_events(self); + + let mut need_zoom = false; + let scroll_offset_updates: Vec<_> = self + .webview_renderers + .iter_mut() + .filter_map(|webview_renderer| { + let (zoom, scroll_result) = + webview_renderer.process_pending_scroll_and_pinch_zoom_events(); + need_zoom = need_zoom || (zoom == PinchZoomResult::DidPinchZoom); + scroll_result + }) + .collect(); + + if need_zoom || !scroll_offset_updates.is_empty() { + let mut transaction = Transaction::new(); + if need_zoom { + self.send_root_pipeline_display_list_in_transaction(&mut transaction); + } + for update in scroll_offset_updates { + let offset = LayoutVector2D::new(-update.offset.x, -update.offset.y); + transaction.set_scroll_offsets( + update.external_scroll_id, + vec![SampledScrollOffset { + offset, + generation: 0, + }], + ); + } + + self.generate_frame(&mut transaction, RenderReasons::APZ); + self.global.borrow_mut().send_transaction(transaction); } - self.webview_renderers = webview_renderers; + self.global.borrow().shutdown_state() != ShutdownState::FinishedShuttingDown } diff --git a/components/compositing/tracing.rs b/components/compositing/tracing.rs index ae7338106d0..a8bb8b42bb8 100644 --- a/components/compositing/tracing.rs +++ b/components/compositing/tracing.rs @@ -42,6 +42,7 @@ mod from_constellation { Self::LoadComplete(..) => target!("LoadComplete"), Self::WebDriverMouseButtonEvent(..) => target!("WebDriverMouseButtonEvent"), Self::WebDriverMouseMoveEvent(..) => target!("WebDriverMouseMoveEvent"), + Self::WebDriverWheelScrollEvent(..) => target!("WebDriverWheelScrollEvent"), Self::SendInitialTransaction(..) => target!("SendInitialTransaction"), Self::SendScrollNode(..) => target!("SendScrollNode"), Self::SendDisplayList { .. } => target!("SendDisplayList"), diff --git a/components/compositing/webview_renderer.rs b/components/compositing/webview_renderer.rs index 6ad77d46043..b0e91ccb02e 100644 --- a/components/compositing/webview_renderer.rs +++ b/components/compositing/webview_renderer.rs @@ -20,15 +20,11 @@ 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 webrender_api::{ExternalScrollId, HitTestFlags, ScrollLocation}; -use crate::IOCompositor; use crate::compositor::{PipelineDetails, ServoRenderer}; use crate::touch::{TouchHandler, TouchMoveAction, TouchMoveAllowed, TouchSequenceState}; @@ -55,6 +51,19 @@ enum ScrollZoomEvent { Scroll(ScrollEvent), } +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) struct ScrollResult { + pub pipeline_id: PipelineId, + pub external_scroll_id: ExternalScrollId, + pub offset: LayoutVector2D, +} + +#[derive(PartialEq)] +pub(crate) enum PinchZoomResult { + DidPinchZoom, + DidNotPinchZoom, +} + /// 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. @@ -86,6 +95,9 @@ pub(crate) struct WebViewRenderer { /// 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 { @@ -119,6 +131,7 @@ impl WebViewRenderer { min_viewport_zoom: Some(PinchZoomFactor::new(1.0)), max_viewport_zoom: None, hidpi_scale_factor: Scale::new(hidpi_scale_factor.0), + animating: false, } } @@ -138,6 +151,10 @@ impl WebViewRenderer { 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, @@ -148,14 +165,10 @@ impl WebViewRenderer { .borrow_mut() .pipeline_to_webview_map .insert(pipeline_id, self.id); - PipelineDetails::new(pipeline_id) + PipelineDetails::new() }) } - 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() @@ -245,51 +258,45 @@ impl WebViewRenderer { }) } - /// Sets or unsets the animations-running flag for the given pipeline. Returns true if - /// the pipeline is throttled. - pub(crate) fn change_running_animations_state( + /// 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 throttled = { - 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 - }; + 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); - self.webview.set_animating(animating); - throttled - } - - pub(crate) fn tick_all_animations(&self, compositor: &IOCompositor) { - for pipeline_details in self.pipelines.values() { - pipeline_details.tick_animations(compositor) - } - } - - 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); - } + 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) @@ -680,22 +687,17 @@ impl WebViewRenderer { /// 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 { + self.dispatch_input_event(InputEvent::MouseMove(MouseMoveEvent::new(point))); + self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent::new( + MouseButtonAction::Down, button, - action: MouseButtonAction::Down, point, - })); - self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent { + ))); + self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent::new( + MouseButtonAction::Up, button, - action: MouseButtonAction::Up, point, - })); - self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent { - button, - action: MouseButtonAction::Click, - point, - })); + ))); } pub(crate) fn notify_scroll_event( @@ -728,11 +730,35 @@ impl WebViewRenderer { })); } - pub(crate) fn process_pending_scroll_events(&mut self, compositor: &mut IOCompositor) { - if self.pending_scroll_zoom_events.is_empty() { + /// 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) + } + + /// Process pending scroll events for this [`WebViewRenderer`]. Returns a tuple containing: + /// + /// - A boolean that is true if a zoom occurred. + /// - An optional [`ScrollResult`] if a scroll occurred. + /// + /// It is up to the caller to ensure that these events update the rendering appropriately. + pub(crate) fn process_pending_scroll_and_pinch_zoom_events( + &mut self, + ) -> (PinchZoomResult, Option) { + if self.pending_scroll_zoom_events.is_empty() { + return (PinchZoomResult::DidNotPinchZoom, None); + } + // 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; @@ -781,37 +807,24 @@ impl WebViewRenderer { } } - 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; + if let Some(scroll_result) = scroll_result { + self.send_scroll_positions_to_layout_for_pipeline(scroll_result.pipeline_id); } - let mut transaction = Transaction::new(); - if zoom_changed { - compositor.send_root_pipeline_display_list_in_transaction(&mut transaction); - } + let pinch_zoom_result = match self + .set_pinch_zoom_level(self.pinch_zoom_level().get() * combined_magnification) + { + true => PinchZoomResult::DidPinchZoom, + false => PinchZoomResult::DidNotPinchZoom, + }; - 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); + (pinch_zoom_result, scroll_result) } /// Perform a hit test at the given [`DevicePoint`] and apply the [`ScrollLocation`] @@ -822,7 +835,7 @@ impl WebViewRenderer { &mut self, cursor: DevicePoint, scroll_location: ScrollLocation, - ) -> Option<(PipelineId, ExternalScrollId, LayoutVector2D)> { + ) -> Option { let scroll_location = match scroll_location { ScrollLocation::Delta(delta) => { let device_pixels_per_page = self.device_pixels_per_page_pixel(); @@ -862,8 +875,12 @@ impl WebViewRenderer { 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)); + if let Some((external_scroll_id, offset)) = scroll_result { + return Some(ScrollResult { + pipeline_id: *pipeline_id, + external_scroll_id, + offset, + }); } } } diff --git a/components/config/opts.rs b/components/config/opts.rs index 785b43b0acd..3db866a7443 100644 --- a/components/config/opts.rs +++ b/components/config/opts.rs @@ -104,7 +104,7 @@ pub struct DebugOptions { /// Dumps the rule tree. pub dump_rule_tree: bool, - /// Print the flow tree (Layout 2013) or fragment tree (Layout 2020) after each layout. + /// Print the fragment tree after each layout. pub dump_flow_tree: bool, /// Print the stacking context tree after each layout. diff --git a/components/config/prefs.rs b/components/config/prefs.rs index 96c40c91360..64dd9659e56 100644 --- a/components/config/prefs.rs +++ b/components/config/prefs.rs @@ -99,7 +99,6 @@ pub struct Preferences { pub dom_serviceworker_timeout_seconds: i64, pub dom_servo_helpers_enabled: bool, pub dom_servoparser_async_html_tokenizer_enabled: bool, - pub dom_shadowdom_enabled: bool, pub dom_svg_enabled: bool, pub dom_testable_crash_enabled: bool, pub dom_testbinding_enabled: bool, @@ -236,6 +235,8 @@ pub struct Preferences { /// 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 { @@ -275,7 +276,6 @@ impl Preferences { dom_serviceworker_timeout_seconds: 60, dom_servo_helpers_enabled: false, dom_servoparser_async_html_tokenizer_enabled: false, - dom_shadowdom_enabled: true, dom_svg_enabled: false, dom_testable_crash_enabled: false, dom_testbinding_enabled: false, @@ -398,6 +398,7 @@ impl Preferences { threadpools_webrender_workers_max: 4, webgl_testing_context_creation_error: false, user_agent: String::new(), + log_filter: String::new(), } } } diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index 3f70b0abb89..1816cf05373 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -112,11 +112,11 @@ use compositing_traits::{ CompositorMsg, CompositorProxy, SendableFrameTree, WebrenderExternalImageRegistry, }; use constellation_traits::{ - AnimationTickType, AuxiliaryWebViewCreationRequest, AuxiliaryWebViewCreationResponse, - BroadcastMsg, DocumentState, EmbedderToConstellationMessage, IFrameLoadInfo, - IFrameLoadInfoWithData, IFrameSandboxState, IFrameSizeMsg, Job, LoadData, LoadOrigin, LogEntry, - MessagePortMsg, NavigationHistoryBehavior, PaintMetricEvent, PortMessageTask, SWManagerMsg, - SWManagerSenders, ScriptToConstellationChan, ScriptToConstellationMessage, ScrollState, + 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, }; @@ -128,10 +128,11 @@ use devtools_traits::{ use embedder_traits::resources::{self, Resource}; use embedder_traits::user_content_manager::UserContentManager; use embedder_traits::{ - AnimationState, CompositorHitTestResult, Cursor, EmbedderMsg, EmbedderProxy, ImeEvent, - InputEvent, MediaSessionActionType, MediaSessionEvent, MediaSessionPlaybackState, MouseButton, - MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, WebDriverCommandMsg, - WebDriverLoadStatus, + AnimationState, CompositorHitTestResult, Cursor, EmbedderMsg, EmbedderProxy, + FocusSequenceNumber, ImeEvent, InputEvent, JSValue, JavaScriptEvaluationError, + JavaScriptEvaluationId, MediaSessionActionType, MediaSessionEvent, MediaSessionPlaybackState, + MouseButton, MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, WebDriverCommandMsg, + WebDriverCommandResponse, WebDriverLoadStatus, }; use euclid::Size2D; use euclid::default::Size2D as UntypedSize2D; @@ -203,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)] @@ -374,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 @@ -533,6 +532,8 @@ pub struct InitialConstellationState { struct WebDriverData { load_channel: Option<(PipelineId, IpcSender)>, resize_channel: Option>>, + // Forward responses from the script thread to the webdriver server. + input_command_response_sender: Option>, } impl WebDriverData { @@ -540,6 +541,7 @@ impl WebDriverData { WebDriverData { load_channel: None, resize_channel: None, + input_command_response_sender: None, } } } @@ -718,6 +720,7 @@ where phantom: PhantomData, webdriver: WebDriverData::new(), document_states: HashMap::new(), + #[cfg(feature = "webgpu")] webrender_document: state.webrender_document, #[cfg(feature = "webgpu")] webrender_wgpu, @@ -1044,6 +1047,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( @@ -1398,8 +1439,8 @@ where EmbedderToConstellationMessage::ThemeChange(theme) => { self.handle_theme_change(theme); }, - EmbedderToConstellationMessage::TickAnimation(pipeline_id, tick_type) => { - self.handle_tick_animation(pipeline_id, tick_type) + EmbedderToConstellationMessage::TickAnimation(webview_ids) => { + self.handle_tick_animation(webview_ids) }, EmbedderToConstellationMessage::WebDriverCommand(command) => { self.handle_webdriver_msg(command); @@ -1440,6 +1481,52 @@ where 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); + }, + } + } + + #[cfg_attr( + feature = "tracing", + tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") + )] + 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), + ); } } @@ -1487,12 +1574,12 @@ where ScriptToConstellationMessage::NewMessagePort(router_id, port_id) => { self.handle_new_messageport(router_id, port_id); }, - ScriptToConstellationMessage::RemoveMessagePort(port_id) => { - self.handle_remove_messageport(port_id); - }, ScriptToConstellationMessage::EntanglePorts(port1, port2) => { self.handle_entangle_messageports(port1, port2); }, + ScriptToConstellationMessage::DisentanglePorts(port1, port2) => { + self.handle_disentangle_messageports(port1, port2); + }, ScriptToConstellationMessage::NewBroadcastChannelRouter( router_id, response_sender, @@ -1622,8 +1709,15 @@ where data, ); }, - ScriptToConstellationMessage::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, + ); + }, + 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); @@ -1773,6 +1867,21 @@ where self.mem_profiler_chan .send(mem::ProfilerMsg::Report(sender)); }, + ScriptToConstellationMessage::FinishJavaScriptEvaluation(evaluation_id, result) => { + self.handle_finish_javascript_evaluation(evaluation_id, result) + }, + ScriptToConstellationMessage::WebDriverInputComplete(msg_id) => { + if let Some(ref reply_sender) = self.webdriver.input_command_response_sender { + reply_sender + .send(WebDriverCommandResponse { id: msg_id }) + .unwrap_or_else(|_| { + warn!("Failed to send WebDriverInputComplete {:?}", msg_id); + self.webdriver.input_command_response_sender = None; + }); + } else { + warn!("No WebDriver input_command_response_sender"); + } + }, } } @@ -2073,17 +2182,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. @@ -2107,9 +2205,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!( @@ -2121,11 +2219,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. @@ -2133,7 +2226,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. @@ -2152,7 +2245,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. @@ -2160,7 +2253,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() { @@ -2207,18 +2303,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. @@ -2297,10 +2389,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 - ), } } @@ -2362,59 +2450,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") @@ -2438,6 +2473,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 /// @@ -3157,6 +3243,22 @@ where } } + #[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") @@ -3528,15 +3630,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())); } } @@ -3736,8 +3847,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; } @@ -4062,6 +4173,7 @@ where } new_pipeline.set_throttled(false); + self.notify_focus_state(new_pipeline_id); } self.update_activity(old_pipeline_id); @@ -4267,22 +4379,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", @@ -4291,42 +4477,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( @@ -4495,6 +4772,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, @@ -4573,7 +4851,11 @@ where mouse_button, x, y, + msg_id, + response_sender, ) => { + self.webdriver.input_command_response_sender = Some(response_sender); + self.compositor_proxy .send(CompositorMsg::WebDriverMouseButtonEvent( webview_id, @@ -4581,11 +4863,22 @@ where mouse_button, x, y, + msg_id, )); }, - WebDriverCommandMsg::MouseMoveAction(webview_id, x, y) => { + WebDriverCommandMsg::MouseMoveAction(webview_id, x, y, msg_id, response_sender) => { + self.webdriver.input_command_response_sender = Some(response_sender); + self.compositor_proxy - .send(CompositorMsg::WebDriverMouseMoveEvent(webview_id, x, y)); + .send(CompositorMsg::WebDriverMouseMoveEvent( + webview_id, x, y, msg_id, + )); + }, + WebDriverCommandMsg::WheelScrollAction(webview, x, y, delta_x, delta_y) => { + self.compositor_proxy + .send(CompositorMsg::WebDriverWheelScrollEvent( + webview, x, y, delta_x, delta_y, + )); }, WebDriverCommandMsg::TakeScreenshot(webview_id, rect, response_sender) => { self.compositor_proxy.send(CompositorMsg::CreatePng( @@ -4921,10 +5214,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") @@ -5374,7 +5699,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/pipeline.rs b/components/constellation/pipeline.rs index 2e139578ffe..556ef9bd60f 100644 --- a/components/constellation/pipeline.rs +++ b/components/constellation/pipeline.rs @@ -25,7 +25,7 @@ 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, ViewportDetails}; +use embedder_traits::{AnimationState, FocusSequenceNumber, ViewportDetails}; use fonts::{SystemFontServiceProxy, SystemFontServiceProxySender}; use ipc_channel::Error; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; @@ -102,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. @@ -370,6 +372,7 @@ impl Pipeline { completely_loaded: false, title: String::new(), layout_epoch: Epoch(0), + focus_sequence: FocusSequenceNumber::default(), }; pipeline.set_throttled(throttled); diff --git a/components/constellation/sandboxing.rs b/components/constellation/sandboxing.rs index 3738b4f288b..b4c6e7a9a39 100644 --- a/components/constellation/sandboxing.rs +++ b/components/constellation/sandboxing.rs @@ -159,6 +159,7 @@ pub fn spawn_multiprocess(content: UnprivilegedContent) -> Result target!("SetWebViewThrottled"), Self::SetScrollStates(..) => target!("SetScrollStates"), Self::PaintMetric(..) => target!("PaintMetric"), + Self::EvaluateJavaScript(..) => target!("EvaluateJavaScript"), } } } @@ -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,8 @@ mod from_script { Self::TitleChanged(..) => target!("TitleChanged"), Self::IFrameSizes(..) => target!("IFrameSizes"), Self::ReportMemory(..) => target!("ReportMemory"), + Self::WebDriverInputComplete(..) => target!("WebDriverInputComplete"), + Self::FinishJavaScriptEvaluation(..) => target!("FinishJavaScriptEvaluation"), } } } @@ -236,7 +240,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::ShowFormControl(..) => target_variant!("ShowFormControl"), + Self::FinishJavaScriptEvaluation(..) => { + target_variant!("FinishJavaScriptEvaluation") + }, } } } diff --git a/components/devtools/actors/browsing_context.rs b/components/devtools/actors/browsing_context.rs index c4ead7272bd..81a00e82d47 100644 --- a/components/devtools/actors/browsing_context.rs +++ b/components/devtools/actors/browsing_context.rs @@ -31,6 +31,7 @@ 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)] @@ -56,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, @@ -152,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() @@ -358,26 +357,6 @@ impl BrowsingContextActor { }); } - pub(crate) fn resource_available(&self, resource: T, resource_type: String) { - self.resources_available(vec![resource], resource_type); - } - - pub(crate) fn resources_available( - &self, - resources: Vec, - resource_type: String, - ) { - let msg = ResourceAvailableReply:: { - from: self.name(), - type_: "resources-available-array".into(), - array: vec![(resource_type, resources)], - }; - - 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)) diff --git a/components/devtools/actors/console.rs b/components/devtools/actors/console.rs index ecd718e47d4..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 { @@ -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 d41777f56c6..28a4d7ccf94 100644 --- a/components/devtools/actors/inspector.rs +++ b/components/devtools/actors/inspector.rs @@ -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/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/thread.rs b/components/devtools/actors/thread.rs index 85ff2b732eb..7ff11dff675 100644 --- a/components/devtools/actors/thread.rs +++ b/components/devtools/actors/thread.rs @@ -2,14 +2,12 @@ * 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 std::net::TcpStream; use serde::Serialize; use serde_json::{Map, Value}; -use servo_url::ServoUrl; +use super::source::{Source, SourcesReply}; use crate::actor::{Actor, ActorMessageStatus, ActorRegistry}; use crate::protocol::JsonPacketStream; use crate::{EmptyReplyMsg, StreamId}; @@ -52,45 +50,18 @@ struct ThreadInterruptedReply { type_: String, } -#[derive(Serialize)] -struct SourcesReply { - from: String, - sources: Vec, -} - -#[derive(Eq, Ord, PartialEq, PartialOrd, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Source { - pub actor: String, - /// URL of the script, or URL of the page for inline scripts. - pub url: String, - pub is_black_boxed: bool, -} - pub struct ThreadActor { - name: String, - source_urls: RefCell>, + pub name: String, + pub source_manager: Source, } impl ThreadActor { pub fn new(name: String) -> ThreadActor { ThreadActor { - name, - source_urls: RefCell::new(BTreeSet::default()), + name: name.clone(), + source_manager: Source::new(name), } } - - pub fn add_source(&self, url: ServoUrl) { - self.source_urls.borrow_mut().insert(Source { - actor: self.name.clone(), - url: url.to_string(), - is_black_boxed: false, - }); - } - - pub fn sources(&self) -> Ref> { - self.source_urls.borrow() - } } impl Actor for ThreadActor { diff --git a/components/devtools/actors/watcher.rs b/components/devtools/actors/watcher.rs index 77f82c1023a..061ffc92336 100644 --- a/components/devtools/actors/watcher.rs +++ b/components/devtools/actors/watcher.rs @@ -20,8 +20,10 @@ 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, }; @@ -29,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; @@ -54,7 +57,7 @@ impl SessionContext { supported_targets: HashMap::from([ ("frame", true), ("process", false), - ("worker", false), + ("worker", true), ("service_worker", false), ("shared_worker", false), ]), @@ -101,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)] @@ -211,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 @@ -259,13 +291,29 @@ 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.sources(); - target.resources_available(sources.iter().collect(), "source".into()); + 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" => {}, _ => warn!("resource {} not handled yet", resource), diff --git a/components/devtools/actors/worker.rs b/components/devtools/actors/worker.rs index 046befe9dc9..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)] @@ -47,12 +48,20 @@ impl WorkerActor { 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)] @@ -162,4 +172,6 @@ pub(crate) struct WorkerMsg { traits: WorkerTraits, #[serde(rename = "type")] type_: u32, + #[serde(rename = "targetType")] + target_type: String, } diff --git a/components/devtools/lib.rs b/components/devtools/lib.rs index 74b028d5655..d097cb25e9d 100644 --- a/components/devtools/lib.rs +++ b/components/devtools/lib.rs @@ -19,7 +19,7 @@ use std::net::{Shutdown, TcpListener, TcpStream}; use std::sync::{Arc, Mutex}; use std::thread; -use actors::thread::Source; +use actors::source::SourceData; use base::id::{BrowsingContextId, PipelineId, WebViewId}; use crossbeam_channel::{Receiver, Sender, unbounded}; use devtools_traits::{ @@ -30,6 +30,7 @@ use devtools_traits::{ 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; @@ -65,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; @@ -75,6 +77,7 @@ mod actors { mod id; mod network_handler; mod protocol; +mod resource; #[derive(Clone, Debug, Eq, Hash, PartialEq)] enum UniqueId { @@ -411,7 +414,7 @@ impl DevtoolsInstance { } fn handle_page_error( - &self, + &mut self, pipeline_id: PipelineId, worker_id: Option, page_error: PageError, @@ -423,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, @@ -439,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( @@ -507,33 +514,60 @@ impl DevtoolsInstance { fn handle_script_source_info(&mut self, pipeline_id: PipelineId, source_info: SourceInfo) { let mut actors = self.actors.lock().unwrap(); - let browsing_context_id = match self.pipelines.get(&pipeline_id) { - Some(id) => id, - None => return, - }; + if let Some(worker_id) = source_info.worker_id { + let Some(worker_actor_name) = self.actor_workers.get(&worker_id) else { + return; + }; - let actor_name = match self.browsing_contexts.get(browsing_context_id) { - Some(name) => name, - None => return, - }; + let thread_actor_name = actors.find::(worker_actor_name).thread.clone(); - 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 thread_actor = actors.find_mut::(&thread_actor_name); - thread_actor.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 source = Source { - actor: thread_actor_name.clone(), - url: source_info.url.to_string(), - is_black_boxed: false, - }; + let worker_actor = actors.find::(worker_actor_name); - // Notify browsing context about the new source - let browsing_context = actors.find::(actor_name); - browsing_context.resource_available(source, "source".into()); + 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); + } + } } } 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 2323cb1b240..ce51a9f9112 100644 --- a/components/fonts/Cargo.toml +++ b/components/fonts/Cargo.toml @@ -38,6 +38,7 @@ 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 } 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/platform/macos/font.rs b/components/fonts/platform/macos/font.rs index 56eb8f4dd2c..5249621be81 100644 --- a/components/fonts/platform/macos/font.rs +++ b/components/fonts/platform/macos/font.rs @@ -287,7 +287,7 @@ impl PlatformFontMethods for PlatformFont { .unwrap_or(average_advance); let metrics = FontMetrics { - underline_size: Au::from_f64_au(underline_thickness), + underline_size: Au::from_f64_px(underline_thickness), // TODO(Issue #201): underline metrics are not reliable. Have to pull out of font table // directly. // diff --git a/components/fonts/platform/windows/font.rs b/components/fonts/platform/windows/font.rs index e33b2ad9d3e..74f592e63b9 100644 --- a/components/fonts/platform/windows/font.rs +++ b/components/fonts/platform/windows/font.rs @@ -132,7 +132,9 @@ impl PlatformFontMethods for PlatformFont { pt_size: Option, ) -> Result { let font_face = FontCollection::system() - .get_font_from_descriptor(&font_identifier.font_descriptor) + .font_from_descriptor(&font_identifier.font_descriptor) + .ok() + .flatten() .ok_or("Could not create Font from descriptor")? .create_font_face(); Self::new(font_face, pt_size) diff --git a/components/fonts/platform/windows/font_list.rs b/components/fonts/platform/windows/font_list.rs index d1aa19e178a..e9cdaac8562 100644 --- a/components/fonts/platform/windows/font_list.rs +++ b/components/fonts/platform/windows/font_list.rs @@ -25,7 +25,9 @@ where { let system_fc = FontCollection::system(); for family in system_fc.families_iter() { - callback(family.name()); + if let Ok(family_name) = family.family_name() { + callback(family_name); + } } } @@ -40,13 +42,17 @@ pub struct LocalFontIdentifier { impl LocalFontIdentifier { pub fn index(&self) -> u32 { FontCollection::system() - .get_font_from_descriptor(&self.font_descriptor) + .font_from_descriptor(&self.font_descriptor) + .ok() + .flatten() .map_or(0, |font| font.create_font_face().get_index()) } pub(crate) fn native_font_handle(&self) -> NativeFontHandle { let face = FontCollection::system() - .get_font_from_descriptor(&self.font_descriptor) + .font_from_descriptor(&self.font_descriptor) + .ok() + .flatten() .expect("Could not create Font from FontDescriptor") .create_font_face(); let path = face @@ -62,7 +68,9 @@ impl LocalFontIdentifier { } pub(crate) fn read_data_from_file(&self) -> Option> { - let font = FontCollection::system().get_font_from_descriptor(&self.font_descriptor)?; + let font = FontCollection::system() + .font_from_descriptor(&self.font_descriptor) + .ok()??; let face = font.create_font_face(); let files = face.get_files(); assert!(!files.is_empty()); @@ -86,10 +94,12 @@ where F: FnMut(FontTemplate), { let system_fc = FontCollection::system(); - if let Some(family) = system_fc.get_font_family_by_name(family_name) { + if let Ok(Some(family)) = system_fc.font_family_by_name(family_name) { let count = family.get_font_count(); for i in 0..count { - let font = family.get_font(i); + let Ok(font) = family.font(i) else { + continue; + }; let template_descriptor = (&font).into(); let local_font_identifier = LocalFontIdentifier { font_descriptor: Arc::new(font.to_descriptor()), diff --git a/components/fonts/system_font_service.rs b/components/fonts/system_font_service.rs index 91b2d810eff..f799affa7c8 100644 --- a/components/fonts/system_font_service.rs +++ b/components/fonts/system_font_service.rs @@ -6,16 +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; @@ -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 aeafa02bcc1..0793c1e4ce1 100644 --- a/components/fonts/tests/font_context.rs +++ b/components/fonts/tests/font_context.rs @@ -137,6 +137,7 @@ mod font_context { break; }, SystemFontServiceMessage::Ping => {}, + SystemFontServiceMessage::CollectMemoryReport(..) => {}, } } } diff --git a/components/layout/Cargo.toml b/components/layout/Cargo.toml index 0505581fba7..0e4cd0b79fd 100644 --- a/components/layout/Cargo.toml +++ b/components/layout/Cargo.toml @@ -21,7 +21,6 @@ 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 } diff --git a/components/layout/construct_modern.rs b/components/layout/construct_modern.rs index 22f179d146c..d09744b2031 100644 --- a/components/layout/construct_modern.rs +++ b/components/layout/construct_modern.rs @@ -12,7 +12,7 @@ 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}; @@ -24,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 @@ -73,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, @@ -87,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>, @@ -103,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 { @@ -148,7 +142,7 @@ where .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); @@ -156,7 +150,6 @@ where let inline_formatting_context = inline_formatting_context_builder.finish( self.context, - self.propagated_data, true, /* has_first_formatted_line */ false, /* is_single_line_text_box */ self.info.style.writing_mode.to_bidi_level(), @@ -165,7 +158,7 @@ where let block_formatting_context = BlockFormattingContext::from_block_container( BlockContainer::InlineFormattingContext(inline_formatting_context), ); - let info: &NodeAndStyleInfo<_> = &*anonymous_info; + 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 index 71372ffe224..54afd4ca209 100644 --- a/components/layout/context.rs +++ b/components/layout/context.rs @@ -46,6 +46,9 @@ pub struct LayoutContext<'a> { Arc>>, pub node_image_animation_map: Arc>>, + + /// The DOM node that is highlighted by the devtools inspector, if any + pub highlighted_dom_node: Option, } pub enum ResolvedImage<'a> { @@ -61,6 +64,20 @@ impl Drop for LayoutContext<'_> { } } +#[derive(Debug)] +pub enum ResolveImageError { + LoadError, + ImagePending, + ImageRequested, + OnlyMetadata, + InvalidUrl, + MissingNode, + ImageMissingFromImageSet, + FailedToResolveImageFromImageSet, + NotImplementedYet(&'static str), + None, +} + impl LayoutContext<'_> { #[inline(always)] pub fn shared_context(&self) -> &SharedStyleContext { @@ -72,7 +89,7 @@ impl LayoutContext<'_> { node: OpaqueNode, url: ServoUrl, use_placeholder: UsePlaceholder, - ) -> Option { + ) -> Result { // Check for available image or start tracking. let cache_result = self.image_cache.get_cached_image_status( url.clone(), @@ -82,7 +99,7 @@ impl LayoutContext<'_> { ); match cache_result { - ImageCacheResult::Available(img_or_meta) => Some(img_or_meta), + ImageCacheResult::Available(img_or_meta) => Ok(img_or_meta), // Image has been requested, is still pending. Return no image for this paint loop. // When the image loads it will trigger a reflow and/or repaint. ImageCacheResult::Pending(id) => { @@ -93,7 +110,7 @@ impl LayoutContext<'_> { origin: self.origin.clone(), }; self.pending_images.lock().push(image); - None + Result::Err(ResolveImageError::ImagePending) }, // Not yet requested - request image or metadata from the cache ImageCacheResult::ReadyForRequest(id) => { @@ -104,10 +121,10 @@ impl LayoutContext<'_> { origin: self.origin.clone(), }; self.pending_images.lock().push(image); - None + Result::Err(ResolveImageError::ImageRequested) }, // Image failed to load, so just return nothing - ImageCacheResult::LoadError => None, + ImageCacheResult::LoadError => Result::Err(ResolveImageError::LoadError), } } @@ -120,14 +137,23 @@ impl LayoutContext<'_> { if image_state.image_key() != image.id { if image.should_animate() { // i. Register/Replace tracking item in image_animation_manager. - store.insert(node, ImageAnimationState::new(image)); + store.insert( + node, + ImageAnimationState::new( + image, + self.shared_context().current_time_for_animations, + ), + ); } else { // ii. Cancel Action if the node's image is no longer animated. store.remove(&node); } } } else if image.should_animate() { - store.insert(node, ImageAnimationState::new(image)); + store.insert( + node, + ImageAnimationState::new(image, self.shared_context().current_time_for_animations), + ); } } @@ -136,31 +162,34 @@ impl LayoutContext<'_> { node: OpaqueNode, url: ServoUrl, use_placeholder: UsePlaceholder, - ) -> Option { + ) -> Result { if let Some(existing_webrender_image) = self .webrender_image_cache .read() .get(&(url.clone(), use_placeholder)) { - return Some(*existing_webrender_image); + return Ok(*existing_webrender_image); } - - match self.get_or_request_image_or_meta(node, url.clone(), use_placeholder) { - Some(ImageOrMetadataAvailable::ImageAvailable { image, .. }) => { + let image_or_meta = + self.get_or_request_image_or_meta(node, url.clone(), use_placeholder)?; + match image_or_meta { + ImageOrMetadataAvailable::ImageAvailable { image, .. } => { self.handle_animated_image(node, image.clone()); let image_info = WebRenderImageInfo { size: Size2D::new(image.width, image.height), key: image.id, }; if image_info.key.is_none() { - Some(image_info) + Ok(image_info) } else { let mut webrender_image_cache = self.webrender_image_cache.write(); webrender_image_cache.insert((url, use_placeholder), image_info); - Some(image_info) + Ok(image_info) } }, - None | Some(ImageOrMetadataAvailable::MetadataAvailable(..)) => None, + ImageOrMetadataAvailable::MetadataAvailable(..) => { + Result::Err(ResolveImageError::OnlyMetadata) + }, } } @@ -168,11 +197,15 @@ impl LayoutContext<'_> { &self, node: Option, image: &'a Image, - ) -> Option> { + ) -> Result, ResolveImageError> { match image { // TODO: Add support for PaintWorklet and CrossFade rendering. - Image::None | Image::CrossFade(_) | Image::PaintWorklet(_) => None, - Image::Gradient(gradient) => Some(ResolvedImage::Gradient(gradient)), + Image::None => Result::Err(ResolveImageError::None), + Image::CrossFade(_) => Result::Err(ResolveImageError::NotImplementedYet("CrossFade")), + Image::PaintWorklet(_) => { + Result::Err(ResolveImageError::NotImplementedYet("PaintWorklet")) + }, + Image::Gradient(gradient) => Ok(ResolvedImage::Gradient(gradient)), Image::Url(image_url) => { // FIXME: images won’t always have in intrinsic width or // height when support for SVG is added, or a WebRender @@ -180,18 +213,20 @@ impl LayoutContext<'_> { // // FIXME: It feels like this should take into account the pseudo // element and not just the node. - let image_url = image_url.url()?; + let image_url = image_url.url().ok_or(ResolveImageError::InvalidUrl)?; + let node = node.ok_or(ResolveImageError::MissingNode)?; let webrender_info = self.get_webrender_image_for_url( - node?, + node, image_url.clone().into(), UsePlaceholder::No, )?; - Some(ResolvedImage::Image(webrender_info)) + Ok(ResolvedImage::Image(webrender_info)) }, Image::ImageSet(image_set) => { image_set .items .get(image_set.selected_index) + .ok_or(ResolveImageError::ImageMissingFromImageSet) .and_then(|image| { self.resolve_image(node, &image.image) .map(|info| match info { diff --git a/components/layout/display_list/background.rs b/components/layout/display_list/background.rs index f49ddfbe6ce..563bce28450 100644 --- a/components/layout/display_list/background.rs +++ b/components/layout/display_list/background.rs @@ -66,7 +66,7 @@ impl<'a> BackgroundPainter<'a> { if &BackgroundAttachment::Fixed == get_cyclic(&background.background_attachment.0, layer_index) { - let viewport_size = builder.display_list.compositor_info.viewport_size; + let viewport_size = builder.compositor_info.viewport_size; return units::LayoutRect::from_origin_and_size(Point2D::origin(), viewport_size); } @@ -121,7 +121,7 @@ impl<'a> BackgroundPainter<'a> { if &BackgroundAttachment::Fixed == get_cyclic(&style.get_background().background_attachment.0, layer_index) { - common.spatial_id = builder.current_reference_frame_scroll_node_id.spatial_id; + common.spatial_id = builder.spatial_id(builder.current_reference_frame_scroll_node_id); } common } diff --git a/components/layout/display_list/clip.rs b/components/layout/display_list/clip.rs new file mode 100644 index 00000000000..d5bd0f52b69 --- /dev/null +++ b/components/layout/display_list/clip.rs @@ -0,0 +1,276 @@ +/* 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 app_units::Au; +use base::id::ScrollTreeNodeId; +use style::values::computed::basic_shape::{BasicShape, ClipPath}; +use style::values::computed::length_percentage::NonNegativeLengthPercentage; +use style::values::computed::position::Position; +use style::values::generics::basic_shape::{GenericShapeRadius, ShapeBox, ShapeGeometryBox}; +use style::values::generics::position::GenericPositionOrAuto; +use webrender_api::BorderRadius; +use webrender_api::units::{LayoutRect, LayoutSideOffsets, LayoutSize}; + +use super::{BuilderForBoxFragment, compute_margin_box_radius, normalize_radii}; + +/// An identifier for a clip used during StackingContextTree construction. This is a simple index in +/// a [`ClipStore`]s vector of clips. +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) struct ClipId(pub usize); + +impl ClipId { + /// Equivalent to [`ClipChainId::INVALID`]. This means "no clip." + pub(crate) const INVALID: ClipId = ClipId(usize::MAX); +} + +/// All the information needed to create a clip on a WebRender display list. These are created at +/// two times: during `StackingContextTree` creation and during WebRender display list construction. +/// Only the former are stored in a [`ClipStore`]. +#[derive(Clone)] +pub(crate) struct Clip { + pub id: ClipId, + pub radii: BorderRadius, + pub rect: LayoutRect, + pub parent_scroll_node_id: ScrollTreeNodeId, + pub parent_clip_id: ClipId, +} + +/// A simple vector of [`Clip`] that is built during `StackingContextTree` construction. +/// These are later turned into WebRender clips and clip chains during WebRender display +/// list construction. +#[derive(Clone, Default)] +pub(crate) struct StackingContextTreeClipStore(pub Vec); + +impl StackingContextTreeClipStore { + pub(crate) fn add( + &mut self, + radii: webrender_api::BorderRadius, + rect: LayoutRect, + parent_scroll_node_id: ScrollTreeNodeId, + parent_clip_id: ClipId, + ) -> ClipId { + let id = ClipId(self.0.len()); + self.0.push(Clip { + id, + radii, + rect, + parent_scroll_node_id, + parent_clip_id, + }); + id + } + + pub(super) fn add_for_clip_path( + &mut self, + clip_path: ClipPath, + parent_scroll_node_id: &ScrollTreeNodeId, + parent_clip_chain_id: &ClipId, + fragment_builder: BuilderForBoxFragment, + ) -> Option { + let geometry_box = match clip_path { + ClipPath::Shape(_, ShapeGeometryBox::ShapeBox(shape_box)) => shape_box, + ClipPath::Shape(_, ShapeGeometryBox::ElementDependent) => ShapeBox::BorderBox, + ClipPath::Box(ShapeGeometryBox::ShapeBox(shape_box)) => shape_box, + ClipPath::Box(ShapeGeometryBox::ElementDependent) => ShapeBox::BorderBox, + _ => return None, + }; + let layout_rect = match geometry_box { + ShapeBox::BorderBox => fragment_builder.border_rect, + ShapeBox::ContentBox => *fragment_builder.content_rect(), + ShapeBox::PaddingBox => *fragment_builder.padding_rect(), + ShapeBox::MarginBox => *fragment_builder.margin_rect(), + }; + if let ClipPath::Shape(shape, _) = clip_path { + match *shape { + BasicShape::Circle(_) | BasicShape::Ellipse(_) | BasicShape::Rect(_) => self + .add_for_basic_shape( + *shape, + layout_rect, + parent_scroll_node_id, + parent_clip_chain_id, + ), + BasicShape::Polygon(_) | BasicShape::PathOrShape(_) => None, + } + } else { + Some(self.add( + match geometry_box { + ShapeBox::MarginBox => compute_margin_box_radius( + fragment_builder.border_radius, + layout_rect.size(), + fragment_builder.fragment, + ), + _ => fragment_builder.border_radius, + }, + layout_rect, + *parent_scroll_node_id, + *parent_clip_chain_id, + )) + } + } + + #[cfg_attr( + feature = "tracing", + tracing::instrument( + name = "StackingContextClipStore::add_for_basic_shape", + skip_all, + fields(servo_profiling = true), + level = "trace", + ) + )] + fn add_for_basic_shape( + &mut self, + shape: BasicShape, + layout_box: LayoutRect, + parent_scroll_node_id: &ScrollTreeNodeId, + parent_clip_chain_id: &ClipId, + ) -> Option { + match shape { + BasicShape::Rect(rect) => { + let box_height = Au::from_f32_px(layout_box.height()); + let box_width = Au::from_f32_px(layout_box.width()); + let insets = LayoutSideOffsets::new( + rect.rect.0.to_used_value(box_height).to_f32_px(), + rect.rect.1.to_used_value(box_width).to_f32_px(), + rect.rect.2.to_used_value(box_height).to_f32_px(), + rect.rect.3.to_used_value(box_width).to_f32_px(), + ); + + // `inner_rect()` will cause an assertion failure if the insets are larger than the + // rectangle dimension. + let shape_rect = if insets.left + insets.right >= layout_box.width() || + insets.top + insets.bottom > layout_box.height() + { + LayoutRect::from_origin_and_size(layout_box.min, LayoutSize::zero()) + } else { + layout_box.to_rect().inner_rect(insets).to_box2d() + }; + + let corner = |corner: &style::values::computed::BorderCornerRadius| { + LayoutSize::new( + corner.0.width.0.to_used_value(box_width).to_f32_px(), + corner.0.height.0.to_used_value(box_height).to_f32_px(), + ) + }; + let mut radii = webrender_api::BorderRadius { + top_left: corner(&rect.round.top_left), + top_right: corner(&rect.round.top_right), + bottom_left: corner(&rect.round.bottom_left), + bottom_right: corner(&rect.round.bottom_right), + }; + normalize_radii(&layout_box, &mut radii); + Some(self.add( + radii, + shape_rect, + *parent_scroll_node_id, + *parent_clip_chain_id, + )) + }, + BasicShape::Circle(circle) => { + let center = match circle.position { + GenericPositionOrAuto::Position(position) => position, + GenericPositionOrAuto::Auto => Position::center(), + }; + let anchor_x = center + .horizontal + .to_used_value(Au::from_f32_px(layout_box.width())); + let anchor_y = center + .vertical + .to_used_value(Au::from_f32_px(layout_box.height())); + let center = layout_box + .min + .add_size(&LayoutSize::new(anchor_x.to_f32_px(), anchor_y.to_f32_px())); + + let horizontal = compute_shape_radius( + center.x, + &circle.radius, + layout_box.min.x, + layout_box.max.x, + ); + let vertical = compute_shape_radius( + center.y, + &circle.radius, + layout_box.min.y, + layout_box.max.y, + ); + + // If the value is `Length` then both values should be the same at this point. + let radius = match circle.radius { + GenericShapeRadius::FarthestSide => horizontal.max(vertical), + GenericShapeRadius::ClosestSide => horizontal.min(vertical), + GenericShapeRadius::Length(_) => horizontal, + }; + let radius = LayoutSize::new(radius, radius); + let mut radii = webrender_api::BorderRadius { + top_left: radius, + top_right: radius, + bottom_left: radius, + bottom_right: radius, + }; + let start = center.add_size(&-radius); + let rect = LayoutRect::from_origin_and_size(start, radius * 2.); + normalize_radii(&layout_box, &mut radii); + Some(self.add(radii, rect, *parent_scroll_node_id, *parent_clip_chain_id)) + }, + BasicShape::Ellipse(ellipse) => { + let center = match ellipse.position { + GenericPositionOrAuto::Position(position) => position, + GenericPositionOrAuto::Auto => Position::center(), + }; + let anchor_x = center + .horizontal + .to_used_value(Au::from_f32_px(layout_box.width())); + let anchor_y = center + .vertical + .to_used_value(Au::from_f32_px(layout_box.height())); + let center = layout_box + .min + .add_size(&LayoutSize::new(anchor_x.to_f32_px(), anchor_y.to_f32_px())); + + let width = compute_shape_radius( + center.x, + &ellipse.semiaxis_x, + layout_box.min.x, + layout_box.max.x, + ); + let height = compute_shape_radius( + center.y, + &ellipse.semiaxis_y, + layout_box.min.y, + layout_box.max.y, + ); + + let mut radii = webrender_api::BorderRadius { + top_left: LayoutSize::new(width, height), + top_right: LayoutSize::new(width, height), + bottom_left: LayoutSize::new(width, height), + bottom_right: LayoutSize::new(width, height), + }; + let size = LayoutSize::new(width, height); + let start = center.add_size(&-size); + let rect = LayoutRect::from_origin_and_size(start, size * 2.); + normalize_radii(&rect, &mut radii); + Some(self.add(radii, rect, *parent_scroll_node_id, *parent_clip_chain_id)) + }, + _ => None, + } + } +} + +fn compute_shape_radius( + center: f32, + radius: &GenericShapeRadius, + min_edge: f32, + max_edge: f32, +) -> f32 { + let distance_from_min_edge = (min_edge - center).abs(); + let distance_from_max_edge = (max_edge - center).abs(); + match radius { + GenericShapeRadius::FarthestSide => distance_from_min_edge.max(distance_from_max_edge), + GenericShapeRadius::ClosestSide => distance_from_min_edge.min(distance_from_max_edge), + GenericShapeRadius::Length(length) => length + .0 + .to_used_value(Au::from_f32_px(max_edge - min_edge)) + .to_f32_px(), + } +} diff --git a/components/layout/display_list/clip_path.rs b/components/layout/display_list/clip_path.rs deleted file mode 100644 index 419d15c3572..00000000000 --- a/components/layout/display_list/clip_path.rs +++ /dev/null @@ -1,259 +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 app_units::Au; -use base::id::ScrollTreeNodeId; -use style::values::computed::basic_shape::{BasicShape, ClipPath}; -use style::values::computed::length_percentage::NonNegativeLengthPercentage; -use style::values::computed::position::Position; -use style::values::generics::basic_shape::{GenericShapeRadius, ShapeBox, ShapeGeometryBox}; -use style::values::generics::position::GenericPositionOrAuto; -use webrender_api::ClipChainId; -use webrender_api::units::{LayoutRect, LayoutSideOffsets, LayoutSize}; - -use super::{BuilderForBoxFragment, DisplayList, compute_margin_box_radius, normalize_radii}; - -pub(super) fn build_clip_path_clip_chain_if_necessary( - clip_path: ClipPath, - display_list: &mut DisplayList, - parent_scroll_node_id: &ScrollTreeNodeId, - parent_clip_chain_id: &ClipChainId, - fragment_builder: BuilderForBoxFragment, -) -> Option { - let geometry_box = match clip_path { - ClipPath::Shape(_, ShapeGeometryBox::ShapeBox(shape_box)) => shape_box, - ClipPath::Shape(_, ShapeGeometryBox::ElementDependent) => ShapeBox::BorderBox, - ClipPath::Box(ShapeGeometryBox::ShapeBox(shape_box)) => shape_box, - ClipPath::Box(ShapeGeometryBox::ElementDependent) => ShapeBox::BorderBox, - _ => return None, - }; - let layout_rect = match geometry_box { - ShapeBox::BorderBox => fragment_builder.border_rect, - ShapeBox::ContentBox => *fragment_builder.content_rect(), - ShapeBox::PaddingBox => *fragment_builder.padding_rect(), - ShapeBox::MarginBox => *fragment_builder.margin_rect(), - }; - if let ClipPath::Shape(shape, _) = clip_path { - match *shape { - BasicShape::Circle(_) | BasicShape::Ellipse(_) | BasicShape::Rect(_) => { - build_simple_shape( - *shape, - layout_rect, - parent_scroll_node_id, - parent_clip_chain_id, - display_list, - ) - }, - BasicShape::Polygon(_) | BasicShape::PathOrShape(_) => None, - } - } else { - Some(create_rect_clip_chain( - match geometry_box { - ShapeBox::MarginBox => compute_margin_box_radius( - fragment_builder.border_radius, - layout_rect.size(), - fragment_builder.fragment, - ), - _ => fragment_builder.border_radius, - }, - layout_rect, - parent_scroll_node_id, - parent_clip_chain_id, - display_list, - )) - } -} - -#[cfg_attr( - feature = "tracing", - tracing::instrument( - name = "display_list::build_simple_shape", - skip_all, - fields(servo_profiling = true), - level = "trace", - ) -)] -fn build_simple_shape( - shape: BasicShape, - layout_box: LayoutRect, - parent_scroll_node_id: &ScrollTreeNodeId, - parent_clip_chain_id: &ClipChainId, - display_list: &mut DisplayList, -) -> Option { - match shape { - BasicShape::Rect(rect) => { - let box_height = Au::from_f32_px(layout_box.height()); - let box_width = Au::from_f32_px(layout_box.width()); - let insets = LayoutSideOffsets::new( - rect.rect.0.to_used_value(box_height).to_f32_px(), - rect.rect.1.to_used_value(box_width).to_f32_px(), - rect.rect.2.to_used_value(box_height).to_f32_px(), - rect.rect.3.to_used_value(box_width).to_f32_px(), - ); - - // `inner_rect()` will cause an assertion failure if the insets are larger than the - // rectangle dimension. - let shape_rect = if insets.left + insets.right >= layout_box.width() || - insets.top + insets.bottom > layout_box.height() - { - LayoutRect::from_origin_and_size(layout_box.min, LayoutSize::zero()) - } else { - layout_box.to_rect().inner_rect(insets).to_box2d() - }; - - let corner = |corner: &style::values::computed::BorderCornerRadius| { - LayoutSize::new( - corner.0.width.0.to_used_value(box_width).to_f32_px(), - corner.0.height.0.to_used_value(box_height).to_f32_px(), - ) - }; - let mut radii = webrender_api::BorderRadius { - top_left: corner(&rect.round.top_left), - top_right: corner(&rect.round.top_right), - bottom_left: corner(&rect.round.bottom_left), - bottom_right: corner(&rect.round.bottom_right), - }; - normalize_radii(&layout_box, &mut radii); - Some(create_rect_clip_chain( - radii, - shape_rect, - parent_scroll_node_id, - parent_clip_chain_id, - display_list, - )) - }, - BasicShape::Circle(circle) => { - let center = match circle.position { - GenericPositionOrAuto::Position(position) => position, - GenericPositionOrAuto::Auto => Position::center(), - }; - let anchor_x = center - .horizontal - .to_used_value(Au::from_f32_px(layout_box.width())); - let anchor_y = center - .vertical - .to_used_value(Au::from_f32_px(layout_box.height())); - let center = layout_box - .min - .add_size(&LayoutSize::new(anchor_x.to_f32_px(), anchor_y.to_f32_px())); - - let horizontal = - compute_shape_radius(center.x, &circle.radius, layout_box.min.x, layout_box.max.x); - let vertical = - compute_shape_radius(center.y, &circle.radius, layout_box.min.y, layout_box.max.y); - - // If the value is `Length` then both values should be the same at this point. - let radius = match circle.radius { - GenericShapeRadius::FarthestSide => horizontal.max(vertical), - GenericShapeRadius::ClosestSide => horizontal.min(vertical), - GenericShapeRadius::Length(_) => horizontal, - }; - let radius = LayoutSize::new(radius, radius); - let mut radii = webrender_api::BorderRadius { - top_left: radius, - top_right: radius, - bottom_left: radius, - bottom_right: radius, - }; - let start = center.add_size(&-radius); - let rect = LayoutRect::from_origin_and_size(start, radius * 2.); - normalize_radii(&layout_box, &mut radii); - Some(create_rect_clip_chain( - radii, - rect, - parent_scroll_node_id, - parent_clip_chain_id, - display_list, - )) - }, - BasicShape::Ellipse(ellipse) => { - let center = match ellipse.position { - GenericPositionOrAuto::Position(position) => position, - GenericPositionOrAuto::Auto => Position::center(), - }; - let anchor_x = center - .horizontal - .to_used_value(Au::from_f32_px(layout_box.width())); - let anchor_y = center - .vertical - .to_used_value(Au::from_f32_px(layout_box.height())); - let center = layout_box - .min - .add_size(&LayoutSize::new(anchor_x.to_f32_px(), anchor_y.to_f32_px())); - - let width = compute_shape_radius( - center.x, - &ellipse.semiaxis_x, - layout_box.min.x, - layout_box.max.x, - ); - let height = compute_shape_radius( - center.y, - &ellipse.semiaxis_y, - layout_box.min.y, - layout_box.max.y, - ); - - let mut radii = webrender_api::BorderRadius { - top_left: LayoutSize::new(width, height), - top_right: LayoutSize::new(width, height), - bottom_left: LayoutSize::new(width, height), - bottom_right: LayoutSize::new(width, height), - }; - let size = LayoutSize::new(width, height); - let start = center.add_size(&-size); - let rect = LayoutRect::from_origin_and_size(start, size * 2.); - normalize_radii(&rect, &mut radii); - Some(create_rect_clip_chain( - radii, - rect, - parent_scroll_node_id, - parent_clip_chain_id, - display_list, - )) - }, - _ => None, - } -} - -fn compute_shape_radius( - center: f32, - radius: &GenericShapeRadius, - min_edge: f32, - max_edge: f32, -) -> f32 { - let distance_from_min_edge = (min_edge - center).abs(); - let distance_from_max_edge = (max_edge - center).abs(); - match radius { - GenericShapeRadius::FarthestSide => distance_from_min_edge.max(distance_from_max_edge), - GenericShapeRadius::ClosestSide => distance_from_min_edge.min(distance_from_max_edge), - GenericShapeRadius::Length(length) => length - .0 - .to_used_value(Au::from_f32_px(max_edge - min_edge)) - .to_f32_px(), - } -} -fn create_rect_clip_chain( - radii: webrender_api::BorderRadius, - rect: LayoutRect, - parent_scroll_node_id: &ScrollTreeNodeId, - parent_clip_chain_id: &ClipChainId, - display_list: &mut DisplayList, -) -> ClipChainId { - let new_clip_id = if radii.is_zero() { - display_list - .wr - .define_clip_rect(parent_scroll_node_id.spatial_id, rect) - } else { - display_list.wr.define_clip_rounded_rect( - parent_scroll_node_id.spatial_id, - webrender_api::ComplexClipRegion { - rect, - radii, - mode: webrender_api::ClipMode::Clip, - }, - ) - }; - display_list.define_clip_chain(*parent_clip_chain_id, [new_clip_id]) -} diff --git a/components/layout/display_list/conversions.rs b/components/layout/display_list/conversions.rs index 6d78a17e886..a2517c863f1 100644 --- a/components/layout/display_list/conversions.rs +++ b/components/layout/display_list/conversions.rs @@ -125,11 +125,15 @@ impl ToWebRender for ComputedTextDecorationStyle { type Type = LineStyle; fn to_webrender(&self) -> Self::Type { match *self { - ComputedTextDecorationStyle::Solid => LineStyle::Solid, + ComputedTextDecorationStyle::Solid | ComputedTextDecorationStyle::Double => { + LineStyle::Solid + }, ComputedTextDecorationStyle::Dotted => LineStyle::Dotted, ComputedTextDecorationStyle::Dashed => LineStyle::Dashed, ComputedTextDecorationStyle::Wavy => LineStyle::Wavy, - _ => LineStyle::Solid, + ComputedTextDecorationStyle::MozNone => { + unreachable!("Should never try to draw a moz-none text decoration") + }, } } } diff --git a/components/layout/display_list/mod.rs b/components/layout/display_list/mod.rs index fa313b306f4..3716ff35b2c 100644 --- a/components/layout/display_list/mod.rs +++ b/components/layout/display_list/mod.rs @@ -8,17 +8,22 @@ use std::sync::Arc; use app_units::{AU_PER_PX, Au}; use base::WebRenderEpochToU16; use base::id::ScrollTreeNodeId; -use compositing_traits::display_list::{AxesScrollSensitivity, CompositorDisplayListInfo}; +use clip::{Clip, ClipId}; +use compositing_traits::display_list::{CompositorDisplayListInfo, SpatialTreeNodeInfo}; use embedder_traits::Cursor; -use euclid::{Point2D, SideOffsets2D, Size2D, UnknownUnit}; +use euclid::{Point2D, SideOffsets2D, Size2D, UnknownUnit, Vector2D}; use fonts::GlyphStore; use gradient::WebRenderGradient; use range::Range as ServoRange; +use servo_arc::Arc as ServoArc; +use servo_config::opts::DebugOptions; use servo_geometry::MaxRect; use style::Zero; use style::color::{AbsoluteColor, ColorSpace}; use style::computed_values::border_image_outset::T as BorderImageOutset; -use style::computed_values::text_decoration_style::T as ComputedTextDecorationStyle; +use style::computed_values::text_decoration_style::{ + T as ComputedTextDecorationStyle, T as TextDecorationStyle, +}; use style::dom::OpaqueNode; use style::properties::ComputedValues; use style::properties::longhands::visibility::computed_value::T as Visibility; @@ -31,15 +36,18 @@ use style::values::generics::NonNegative; use style::values::generics::rect::Rect; use style::values::specified::text::TextDecorationLine; use style::values::specified::ui::CursorKind; +use style_traits::CSSPixel; use webrender_api::units::{DevicePixel, LayoutPixel, LayoutRect, LayoutSize}; use webrender_api::{ - self as wr, BorderDetails, BoxShadowClipMode, ClipChainId, CommonItemProperties, - ImageRendering, NinePatchBorder, NinePatchBorderSource, units, + self as wr, BorderDetails, BoxShadowClipMode, BuiltDisplayList, ClipChainId, ClipMode, + CommonItemProperties, ComplexClipRegion, ImageRendering, NinePatchBorder, + NinePatchBorderSource, PropertyBinding, SpatialId, SpatialTreeItemKey, units, }; use wr::units::LayoutVector2D; +use crate::cell::ArcRefCell; use crate::context::{LayoutContext, ResolvedImage}; -use crate::display_list::conversions::ToWebRender; +pub use crate::display_list::conversions::ToWebRender; use crate::display_list::stacking_context::StackingContextSection; use crate::fragment_tree::{ BackgroundMode, BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo, Tag, @@ -52,7 +60,7 @@ use crate::replaced::NaturalSizes; use crate::style_ext::{BorderStyleColor, ComputedValuesExt}; mod background; -mod clip_path; +mod clip; mod conversions; mod gradient; mod stacking_context; @@ -71,68 +79,6 @@ type ItemTag = (u64, u16); type HitInfo = Option; const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(AU_PER_PX); -/// Where the information that's used to build display lists is stored. This -/// includes both a [wr::DisplayListBuilder] for building up WebRender-specific -/// display list information and a [CompositorDisplayListInfo] used to store -/// information used by the compositor, such as a compositor-side scroll tree. -pub struct DisplayList { - /// The [wr::DisplayListBuilder] used to collect display list items. - pub wr: wr::DisplayListBuilder, - - /// The information about the WebRender display list that the compositor - /// consumes. This curerntly contains the out-of-band hit testing information - /// data structure that the compositor uses to map hit tests to information - /// about the item hit. - pub compositor_info: CompositorDisplayListInfo, - - /// A count of the number of SpatialTree nodes pushed to the WebRender display - /// list. This is merely to ensure that the currently-unused SpatialTreeItemKey - /// produced for every SpatialTree node is unique. - pub spatial_tree_count: u64, -} - -impl DisplayList { - /// Create a new [DisplayList] given the dimensions of the layout and the WebRender - /// pipeline id. - pub fn new( - viewport_size: units::LayoutSize, - content_size: units::LayoutSize, - pipeline_id: wr::PipelineId, - epoch: wr::Epoch, - viewport_scroll_sensitivity: AxesScrollSensitivity, - first_reflow: bool, - ) -> Self { - Self { - wr: wr::DisplayListBuilder::new(pipeline_id), - compositor_info: CompositorDisplayListInfo::new( - viewport_size, - content_size, - pipeline_id, - epoch, - viewport_scroll_sensitivity, - first_reflow, - ), - spatial_tree_count: 0, - } - } - - pub fn define_clip_chain(&mut self, parent: ClipChainId, clips: I) -> ClipChainId - where - I: IntoIterator, - I::IntoIter: ExactSizeIterator + Clone, - { - // WebRender has two different ways of expressing "no clip." ClipChainId::INVALID should be - // used for primitives, but `None` is used for stacking contexts and clip chains. We convert - // to the `Option` representation here. Just passing Some(ClipChainId::INVALID) - // leads to a crash. - let parent = match parent { - ClipChainId::INVALID => None, - parent => Some(parent), - }; - self.wr.define_clip_chain(parent, clips) - } -} - pub(crate) struct DisplayListBuilder<'a> { /// The current [ScrollTreeNodeId] for this [DisplayListBuilder]. This /// allows only passing the builder instead passing the containing @@ -145,53 +91,290 @@ pub(crate) struct DisplayListBuilder<'a> { /// `background-attachment: fixed` need to not scroll while the rest of the fragment does. current_reference_frame_scroll_node_id: ScrollTreeNodeId, - /// The current [wr::ClipId] for this [DisplayListBuilder]. This allows + /// The current [`ClipId`] for this [DisplayListBuilder]. This allows /// only passing the builder instead passing the containing /// [stacking_context::StackingContextContent::Fragment] as an argument to display /// list building functions. - current_clip_chain_id: ClipChainId, - - /// The [OpaqueNode] handle to the node used to paint the page background - /// if the background was a canvas. - element_for_canvas_background: OpaqueNode, + current_clip_id: ClipId, /// A [LayoutContext] used to get information about the device pixel ratio /// and get handles to WebRender images. pub context: &'a LayoutContext<'a>, - /// The [DisplayList] used to collect display list items and metadata. - pub display_list: &'a mut DisplayList, + /// The [`wr::DisplayListBuilder`] for this Servo [`DisplayListBuilder`]. + pub webrender_display_list_builder: &'a mut wr::DisplayListBuilder, + + /// The [`CompositorDisplayListInfo`] used to collect display list items and metadata. + pub compositor_info: &'a mut CompositorDisplayListInfo, + + /// Data about the fragments that are highlighted by the inspector, if any. + /// + /// This data is collected during the traversal of the fragment tree and used + /// to paint the highlight at the very end. + inspector_highlight: Option, + + /// Whether or not the `` element should be painted. This is false if the root `` + /// element inherits the ``'s background to paint the page canvas background. + /// See . + paint_body_background: bool, + + /// A mapping from [`ClipId`] To WebRender [`ClipChainId`] used when building this WebRender + /// display list. + clip_map: Vec, } -impl DisplayList { - /// Build the display list, returning true if it was contentful. - pub fn build( - &mut self, - context: &LayoutContext, - fragment_tree: &FragmentTree, - root_stacking_context: &StackingContext, - ) { - #[cfg(feature = "tracing")] - let _span = tracing::trace_span!("display_list::build", servo_profiling = true).entered(); - let mut builder = DisplayListBuilder { - current_scroll_node_id: self.compositor_info.root_reference_frame_id, - current_reference_frame_scroll_node_id: self.compositor_info.root_reference_frame_id, - current_clip_chain_id: ClipChainId::INVALID, - element_for_canvas_background: fragment_tree.canvas_background.from_element, - context, - display_list: self, - }; - fragment_tree.build_display_list(&mut builder, root_stacking_context); +struct InspectorHighlight { + /// The node that should be highlighted + tag: Tag, + + /// Accumulates information about the fragments that belong to the highlighted node. + /// + /// This information is collected as the fragment tree is traversed to build the + /// display list. + state: Option, +} + +struct HighlightTraversalState { + /// The smallest rectangle that fully encloses all fragments created by the highlighted + /// dom node, if any. + content_box: euclid::Rect, + + spatial_id: SpatialId, + + clip_chain_id: ClipChainId, + + /// When the highlighted fragment is a box fragment we remember the information + /// needed to paint padding, border and margin areas. + maybe_box_fragment: Option>, +} + +impl InspectorHighlight { + fn for_node(node: OpaqueNode) -> Self { + Self { + tag: Tag::new(node), + state: None, + } } } impl DisplayListBuilder<'_> { + pub(crate) fn build( + context: &LayoutContext, + stacking_context_tree: &mut StackingContextTree, + fragment_tree: &FragmentTree, + debug: &DebugOptions, + ) -> BuiltDisplayList { + // Build the rest of the display list which inclues all of the WebRender primitives. + let compositor_info = &mut stacking_context_tree.compositor_info; + compositor_info.hit_test_info.clear(); + + let mut webrender_display_list_builder = + webrender_api::DisplayListBuilder::new(compositor_info.pipeline_id); + webrender_display_list_builder.begin(); + + // `dump_serialized_display_list` doesn't actually print anything. It sets up + // the display list for printing the serialized version when `finalize()` is called. + // We need to call this before adding any display items so that they are printed + // during `finalize()`. + if debug.dump_display_list { + webrender_display_list_builder.dump_serialized_display_list(); + } + + #[cfg(feature = "tracing")] + let _span = + tracing::trace_span!("DisplayListBuilder::build", servo_profiling = true).entered(); + let mut builder = DisplayListBuilder { + current_scroll_node_id: compositor_info.root_reference_frame_id, + current_reference_frame_scroll_node_id: compositor_info.root_reference_frame_id, + current_clip_id: ClipId::INVALID, + context, + webrender_display_list_builder: &mut webrender_display_list_builder, + compositor_info, + inspector_highlight: context + .highlighted_dom_node + .map(InspectorHighlight::for_node), + paint_body_background: true, + clip_map: Default::default(), + }; + + builder.add_all_spatial_nodes(); + + for clip in stacking_context_tree.clip_store.0.iter() { + builder.add_clip_to_display_list(clip); + } + + // Paint the canvas’ background (if any) before/under everything else + stacking_context_tree + .root_stacking_context + .build_canvas_background_display_list(&mut builder, fragment_tree); + stacking_context_tree + .root_stacking_context + .build_display_list(&mut builder); + builder.paint_dom_inspector_highlight(); + + webrender_display_list_builder.end().1 + } + fn wr(&mut self) -> &mut wr::DisplayListBuilder { - &mut self.display_list.wr + self.webrender_display_list_builder + } + + fn pipeline_id(&mut self) -> wr::PipelineId { + self.compositor_info.pipeline_id } fn mark_is_contentful(&mut self) { - self.display_list.compositor_info.is_contentful = true; + self.compositor_info.is_contentful = true; + } + + fn spatial_id(&self, id: ScrollTreeNodeId) -> SpatialId { + self.compositor_info.scroll_tree.webrender_id(&id) + } + + fn clip_chain_id(&self, id: ClipId) -> ClipChainId { + match id { + ClipId::INVALID => ClipChainId::INVALID, + _ => *self + .clip_map + .get(id.0) + .expect("Should never try to get clip before adding it to WebRender display list"), + } + } + + pub(crate) fn add_all_spatial_nodes(&mut self) { + // A count of the number of SpatialTree nodes pushed to the WebRender display + // list. This is merely to ensure that the currently-unused SpatialTreeItemKey + // produced for every SpatialTree node is unique. + let mut spatial_tree_count = 0; + let mut scroll_tree = std::mem::take(&mut self.compositor_info.scroll_tree); + let mut mapping = Vec::with_capacity(scroll_tree.nodes.len()); + + mapping.push(SpatialId::root_reference_frame(self.pipeline_id())); + mapping.push(SpatialId::root_scroll_node(self.pipeline_id())); + + let pipeline_id = self.pipeline_id(); + let pipeline_tag = ((pipeline_id.0 as u64) << 32) | pipeline_id.1 as u64; + + for node in scroll_tree.nodes.iter().skip(2) { + let parent_scroll_node_id = node + .parent + .expect("Should have already added root reference frame"); + let parent_spatial_node_id = mapping + .get(parent_scroll_node_id.index) + .expect("Should add spatial nodes to display list in order"); + + // Produce a new SpatialTreeItemKey. This is currently unused by WebRender, + // but has to be unique to the entire scene. + spatial_tree_count += 1; + let spatial_tree_item_key = SpatialTreeItemKey::new(pipeline_tag, spatial_tree_count); + + mapping.push(match &node.info { + SpatialTreeNodeInfo::ReferenceFrame(info) => { + let spatial_id = self.wr().push_reference_frame( + info.origin, + *parent_spatial_node_id, + info.transform_style, + PropertyBinding::Value(info.transform), + info.kind, + spatial_tree_item_key, + ); + self.wr().pop_reference_frame(); + spatial_id + }, + SpatialTreeNodeInfo::Scroll(info) => { + self.wr().define_scroll_frame( + *parent_spatial_node_id, + info.external_id, + info.content_rect, + info.clip_rect, + LayoutVector2D::zero(), /* external_scroll_offset */ + 0, /* scroll_offset_generation */ + wr::HasScrollLinkedEffect::No, + spatial_tree_item_key, + ) + }, + SpatialTreeNodeInfo::Sticky(info) => { + self.wr().define_sticky_frame( + *parent_spatial_node_id, + info.frame_rect, + info.margins, + info.vertical_offset_bounds, + info.horizontal_offset_bounds, + LayoutVector2D::zero(), /* previously_applied_offset */ + spatial_tree_item_key, + None, /* transform */ + ) + }, + }); + } + + scroll_tree.update_mapping(mapping); + self.compositor_info.scroll_tree = scroll_tree; + } + + /// Add the given [`Clip`] to the WebRender display list and create a mapping from + /// its [`ClipId`] to a WebRender [`ClipChainId`]. This happens: + /// - When WebRender display list construction starts: All clips created during the + /// `StackingContextTree` construction are added in one batch. These clips are used + /// for things such as `overflow: scroll` elements. + /// - When a clip is added during WebRender display list construction for individual + /// items. In that case, this is called by [`Self::maybe_create_clip`]. + pub(crate) fn add_clip_to_display_list(&mut self, clip: &Clip) -> ClipChainId { + assert_eq!( + clip.id.0, + self.clip_map.len(), + "Clips should be added in order" + ); + + let spatial_id = self.spatial_id(clip.parent_scroll_node_id); + let new_clip_id = if clip.radii.is_zero() { + self.wr().define_clip_rect(spatial_id, clip.rect) + } else { + self.wr().define_clip_rounded_rect( + spatial_id, + ComplexClipRegion { + rect: clip.rect, + radii: clip.radii, + mode: ClipMode::Clip, + }, + ) + }; + + // WebRender has two different ways of expressing "no clip." ClipChainId::INVALID should be + // used for primitives, but `None` is used for stacking contexts and clip chains. We convert + // to the `Option` representation here. Just passing Some(ClipChainId::INVALID) + // leads to a crash. + let parent_clip_chain_id = match self.clip_chain_id(clip.parent_clip_id) { + ClipChainId::INVALID => None, + parent => Some(parent), + }; + let clip_chain_id = self + .wr() + .define_clip_chain(parent_clip_chain_id, [new_clip_id]); + self.clip_map.push(clip_chain_id); + clip_chain_id + } + + /// Add a new clip to the WebRender display list being built. This only happens during + /// WebRender display list building and these clips should be added after all clips + /// from the `StackingContextTree` have already been processed. + fn maybe_create_clip( + &mut self, + radii: wr::BorderRadius, + rect: units::LayoutRect, + force_clip_creation: bool, + ) -> Option { + if radii.is_zero() && !force_clip_creation { + return None; + } + + Some(self.add_clip_to_display_list(&Clip { + id: ClipId(self.clip_map.len()), + radii, + rect, + parent_scroll_node_id: self.current_scroll_node_id, + parent_clip_id: self.current_clip_id, + })) } fn common_properties( @@ -204,8 +387,8 @@ impl DisplayListBuilder<'_> { // for fragments that paint their entire border rectangle. wr::CommonItemProperties { clip_rect, - spatial_id: self.current_scroll_node_id.spatial_id, - clip_chain_id: self.current_clip_chain_id, + spatial_id: self.spatial_id(self.current_scroll_node_id), + clip_chain_id: self.clip_chain_id(self.current_clip_id), flags: style.get_webrender_primitive_flags(), } } @@ -223,15 +406,163 @@ impl DisplayListBuilder<'_> { return None; } - let hit_test_index = self.display_list.compositor_info.add_hit_test_info( + let hit_test_index = self.compositor_info.add_hit_test_info( tag?.node.0 as u64, Some(cursor(inherited_ui.cursor.keyword, auto_cursor)), self.current_scroll_node_id, ); - Some(( - hit_test_index as u64, - self.display_list.compositor_info.epoch.as_u16(), - )) + Some((hit_test_index as u64, self.compositor_info.epoch.as_u16())) + } + + /// Draw highlights around the node that is currently hovered in the devtools. + fn paint_dom_inspector_highlight(&mut self) { + let Some(highlight) = self + .inspector_highlight + .take() + .and_then(|highlight| highlight.state) + else { + return; + }; + + const CONTENT_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF { + r: 0.23, + g: 0.7, + b: 0.87, + a: 0.5, + }; + + const PADDING_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF { + r: 0.49, + g: 0.3, + b: 0.7, + a: 0.5, + }; + + const BORDER_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF { + r: 0.2, + g: 0.2, + b: 0.2, + a: 0.5, + }; + + const MARGIN_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF { + r: 1., + g: 0.93, + b: 0., + a: 0.5, + }; + + // Highlight content box + let content_box = highlight.content_box.to_webrender(); + let properties = wr::CommonItemProperties { + clip_rect: content_box, + spatial_id: highlight.spatial_id, + clip_chain_id: highlight.clip_chain_id, + flags: wr::PrimitiveFlags::default(), + }; + + self.wr() + .push_rect(&properties, content_box, CONTENT_BOX_HIGHLIGHT_COLOR); + + // Highlight margin, border and padding + if let Some(box_fragment) = highlight.maybe_box_fragment { + let mut paint_highlight = + |color: webrender_api::ColorF, + fragment_relative_bounds: PhysicalRect, + widths: webrender_api::units::LayoutSideOffsets| { + if widths.is_zero() { + return; + } + + let bounds = box_fragment + .borrow() + .offset_by_containing_block(&fragment_relative_bounds) + .to_webrender(); + + // We paint each highlighted area as if it was a border for simplicity + let border_style = wr::BorderSide { + color, + style: webrender_api::BorderStyle::Solid, + }; + + let details = wr::BorderDetails::Normal(wr::NormalBorder { + top: border_style, + right: border_style, + bottom: border_style, + left: border_style, + radius: webrender_api::BorderRadius::default(), + do_aa: true, + }); + + let common = wr::CommonItemProperties { + clip_rect: bounds, + spatial_id: highlight.spatial_id, + clip_chain_id: highlight.clip_chain_id, + flags: wr::PrimitiveFlags::default(), + }; + self.wr().push_border(&common, bounds, widths, details) + }; + + let box_fragment = box_fragment.borrow(); + paint_highlight( + PADDING_BOX_HIGHLIGHT_COLOR, + box_fragment.padding_rect(), + box_fragment.padding.to_webrender(), + ); + paint_highlight( + BORDER_BOX_HIGHLIGHT_COLOR, + box_fragment.border_rect(), + box_fragment.border.to_webrender(), + ); + paint_highlight( + MARGIN_BOX_HIGHLIGHT_COLOR, + box_fragment.margin_rect(), + box_fragment.margin.to_webrender(), + ); + } + } +} + +impl InspectorHighlight { + fn register_fragment_of_highlighted_dom_node( + &mut self, + fragment: &Fragment, + spatial_id: SpatialId, + clip_chain_id: ClipChainId, + containing_block: &PhysicalRect, + ) { + let state = self.state.get_or_insert(HighlightTraversalState { + content_box: euclid::Rect::zero(), + spatial_id, + clip_chain_id, + maybe_box_fragment: None, + }); + + // We expect all fragments generated by one node to be in the same scroll tree node and clip node + debug_assert_eq!(spatial_id, state.spatial_id); + if clip_chain_id != ClipChainId::INVALID && state.clip_chain_id != ClipChainId::INVALID { + debug_assert_eq!( + clip_chain_id, state.clip_chain_id, + "Fragments of the same node must either have no clip chain or the same one" + ); + } + + let fragment_relative_rect = match fragment { + Fragment::Box(fragment) | Fragment::Float(fragment) => { + state.maybe_box_fragment = Some(fragment.clone()); + + fragment.borrow().content_rect + }, + Fragment::Positioning(fragment) => fragment.borrow().rect, + Fragment::Text(fragment) => fragment.borrow().rect, + Fragment::Image(image_fragment) => image_fragment.borrow().rect, + Fragment::AbsoluteOrFixedPositioned(_) => return, + Fragment::IFrame(iframe_fragment) => iframe_fragment.borrow().rect, + }; + + state.content_box = state + .content_box + .union(&fragment_relative_rect.translate(containing_block.origin.to_vector())); } } @@ -243,7 +574,21 @@ impl Fragment { section: StackingContextSection, is_hit_test_for_scrollable_overflow: bool, is_collapsed_table_borders: bool, + text_decorations: &Arc>, ) { + let spatial_id = builder.spatial_id(builder.current_scroll_node_id); + let clip_chain_id = builder.clip_chain_id(builder.current_clip_id); + if let Some(inspector_highlight) = &mut builder.inspector_highlight { + if self.tag() == Some(inspector_highlight.tag) { + inspector_highlight.register_fragment_of_highlighted_dom_node( + self, + spatial_id, + clip_chain_id, + containing_block, + ); + } + } + match self { Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => { let box_fragment = &*box_fragment.borrow(); @@ -262,18 +607,16 @@ impl Fragment { Fragment::AbsoluteOrFixedPositioned(_) => {}, Fragment::Positioning(positioning_fragment) => { let positioning_fragment = positioning_fragment.borrow(); - if let Some(style) = positioning_fragment.style.as_ref() { - let rect = positioning_fragment - .rect - .translate(containing_block.origin.to_vector()); - self.maybe_push_hit_test_for_style_and_tag( - builder, - style, - positioning_fragment.base.tag, - rect, - Cursor::Default, - ); - } + let rect = positioning_fragment + .rect + .translate(containing_block.origin.to_vector()); + self.maybe_push_hit_test_for_style_and_tag( + builder, + &positioning_fragment.style, + positioning_fragment.base.tag, + rect, + Cursor::Default, + ); }, Fragment::Image(image) => { let image = image.borrow(); @@ -336,10 +679,19 @@ impl Fragment { }, Fragment::Text(text) => { let text = &*text.borrow(); - match text.parent_style.get_inherited_box().visibility { - Visibility::Visible => { - self.build_display_list_for_text_fragment(text, builder, containing_block) - }, + match text + .inline_styles + .style + .borrow() + .get_inherited_box() + .visibility + { + Visibility::Visible => self.build_display_list_for_text_fragment( + text, + builder, + containing_block, + text_decorations, + ), Visibility::Hidden => (), Visibility::Collapse => (), } @@ -361,8 +713,8 @@ impl Fragment { None => return, }; - let clip_chain_id = builder.current_clip_chain_id; - let spatial_id = builder.current_scroll_node_id.spatial_id; + let clip_chain_id = builder.clip_chain_id(builder.current_clip_id); + let spatial_id = builder.spatial_id(builder.current_scroll_node_id); builder.wr().push_hit_test( rect.to_webrender(), clip_chain_id, @@ -377,6 +729,7 @@ impl Fragment { fragment: &TextFragment, builder: &mut DisplayListBuilder, containing_block: &PhysicalRect, + text_decorations: &Arc>, ) { // NB: The order of painting text components (CSS Text Decoration Module Level 3) is: // shadows, underline, overline, text, text-emphasis, and then line-through. @@ -386,6 +739,7 @@ impl Fragment { let rect = fragment.rect.translate(containing_block.origin.to_vector()); let mut baseline_origin = rect.origin; baseline_origin.y += fragment.font_metrics.ascent; + let glyphs = glyphs( &fragment.glyphs, baseline_origin, @@ -396,22 +750,23 @@ impl Fragment { return; } + let parent_style = fragment.inline_styles.style.borrow(); self.maybe_push_hit_test_for_style_and_tag( builder, - &fragment.parent_style, + &parent_style, fragment.base.tag, rect, Cursor::Text, ); - let color = fragment.parent_style.clone_color(); + let color = parent_style.clone_color(); let font_metrics = &fragment.font_metrics; let dppx = builder.context.style_context.device_pixel_ratio().get(); - let common = builder.common_properties(rect.to_webrender(), &fragment.parent_style); + let common = builder.common_properties(rect.to_webrender(), &parent_style); // Shadows. According to CSS-BACKGROUNDS, text shadows render in *reverse* order (front to // back). - let shadows = &fragment.parent_style.get_inherited_text().text_shadow; + let shadows = &parent_style.get_inherited_text().text_shadow; for shadow in shadows.0.iter().rev() { builder.wr().push_shadow( &wr::SpaceAndClipInfo { @@ -427,23 +782,36 @@ impl Fragment { ); } - if fragment - .text_decoration_line - .contains(TextDecorationLine::UNDERLINE) - { - let mut rect = rect; - rect.origin.y += font_metrics.ascent - font_metrics.underline_offset; - rect.size.height = Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx)); - self.build_display_list_for_text_decoration(fragment, builder, &rect, &color); + for text_decoration in text_decorations.iter() { + if text_decoration.line.contains(TextDecorationLine::UNDERLINE) { + let mut rect = rect; + rect.origin.y += font_metrics.ascent - font_metrics.underline_offset; + rect.size.height = + Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx)); + + self.build_display_list_for_text_decoration( + &parent_style, + builder, + &rect, + text_decoration, + TextDecorationLine::UNDERLINE, + ); + } } - if fragment - .text_decoration_line - .contains(TextDecorationLine::OVERLINE) - { - let mut rect = rect; - rect.size.height = Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx)); - self.build_display_list_for_text_decoration(fragment, builder, &rect, &color); + for text_decoration in text_decorations.iter() { + if text_decoration.line.contains(TextDecorationLine::OVERLINE) { + let mut rect = rect; + rect.size.height = + Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx)); + self.build_display_list_for_text_decoration( + &parent_style, + builder, + &rect, + text_decoration, + TextDecorationLine::OVERLINE, + ); + } } // TODO: This caret/text selection implementation currently does not account for vertical text @@ -470,12 +838,13 @@ impl Fragment { Point2D::new(end.x.to_f32_px(), containing_block.max_y().to_f32_px()), ); if let Some(selection_color) = fragment - .selected_style + .inline_styles + .selected + .borrow() .clone_background_color() .as_absolute() { - let selection_common = - builder.common_properties(selection_rect, &fragment.parent_style); + let selection_common = builder.common_properties(selection_rect, &parent_style); builder.wr().push_rect( &selection_common, selection_rect, @@ -501,7 +870,7 @@ impl Fragment { ), ); let insertion_point_common = - builder.common_properties(insertion_point_rect, &fragment.parent_style); + builder.common_properties(insertion_point_rect, &parent_style); // TODO: The color of the caret is currently hardcoded to the text color. // We should be retrieving the caret color from the style properly. builder @@ -519,14 +888,23 @@ impl Fragment { None, ); - if fragment - .text_decoration_line - .contains(TextDecorationLine::LINE_THROUGH) - { - let mut rect = rect; - rect.origin.y += font_metrics.ascent - font_metrics.strikeout_offset; - rect.size.height = Au::from_f32_px(font_metrics.strikeout_size.to_nearest_pixel(dppx)); - self.build_display_list_for_text_decoration(fragment, builder, &rect, &color); + for text_decoration in text_decorations.iter() { + if text_decoration + .line + .contains(TextDecorationLine::LINE_THROUGH) + { + let mut rect = rect; + rect.origin.y += font_metrics.ascent - font_metrics.strikeout_offset; + rect.size.height = + Au::from_f32_px(font_metrics.strikeout_size.to_nearest_pixel(dppx)); + self.build_display_list_for_text_decoration( + &parent_style, + builder, + &rect, + text_decoration, + TextDecorationLine::LINE_THROUGH, + ); + } } if !shadows.0.is_empty() { @@ -536,30 +914,50 @@ impl Fragment { fn build_display_list_for_text_decoration( &self, - fragment: &TextFragment, + parent_style: &ServoArc, builder: &mut DisplayListBuilder, rect: &PhysicalRect, - color: &AbsoluteColor, + text_decoration: &FragmentTextDecoration, + line: TextDecorationLine, ) { - let rect = rect.to_webrender(); - let wavy_line_thickness = (0.33 * rect.size().height).ceil(); - let text_decoration_color = fragment - .parent_style - .clone_text_decoration_color() - .resolve_to_absolute(color); - let text_decoration_style = fragment.parent_style.clone_text_decoration_style(); - if text_decoration_style == ComputedTextDecorationStyle::MozNone { + if text_decoration.style == ComputedTextDecorationStyle::MozNone { return; } - builder.display_list.wr.push_line( - &builder.common_properties(rect, &fragment.parent_style), + + let mut rect = rect.to_webrender(); + let line_thickness = rect.height().ceil(); + + if text_decoration.style == ComputedTextDecorationStyle::Wavy { + rect = rect.inflate(0.0, line_thickness * 1.0); + } + + let common_properties = builder.common_properties(rect, parent_style); + builder.wr().push_line( + &common_properties, &rect, - wavy_line_thickness, + line_thickness, wr::LineOrientation::Horizontal, - &rgba(text_decoration_color), - text_decoration_style.to_webrender(), + &rgba(text_decoration.color), + text_decoration.style.to_webrender(), ); - // XXX(ferjm) support text-decoration-style: double + + if text_decoration.style == TextDecorationStyle::Double { + let half_height = (rect.height() / 2.0).floor().max(1.0); + let y_offset = match line { + TextDecorationLine::OVERLINE => -rect.height() - half_height, + _ => rect.height() + half_height, + }; + let rect = rect.translate(Vector2D::new(0.0, y_offset)); + let common_properties = builder.common_properties(rect, parent_style); + builder.wr().push_line( + &common_properties, + &rect, + line_thickness, + wr::LineOrientation::Horizontal, + &rgba(text_decoration.color), + text_decoration.style.to_webrender(), + ); + } } } @@ -664,12 +1062,8 @@ impl<'a> BuilderForBoxFragment<'a> { return Some(clip); } - let maybe_clip = create_clip_chain( - self.border_radius, - self.border_rect, - builder, - force_clip_creation, - ); + let maybe_clip = + builder.maybe_create_clip(self.border_radius, self.border_rect, force_clip_creation); *self.border_edge_clip_chain_id.borrow_mut() = maybe_clip; maybe_clip } @@ -685,7 +1079,7 @@ impl<'a> BuilderForBoxFragment<'a> { let radii = inner_radii(self.border_radius, self.fragment.border.to_webrender()); let maybe_clip = - create_clip_chain(radii, *self.padding_rect(), builder, force_clip_creation); + builder.maybe_create_clip(radii, *self.padding_rect(), force_clip_creation); *self.padding_edge_clip_chain_id.borrow_mut() = maybe_clip; maybe_clip } @@ -704,14 +1098,19 @@ impl<'a> BuilderForBoxFragment<'a> { (self.fragment.border + self.fragment.padding).to_webrender(), ); let maybe_clip = - create_clip_chain(radii, *self.content_rect(), builder, force_clip_creation); + builder.maybe_create_clip(radii, *self.content_rect(), force_clip_creation); *self.content_edge_clip_chain_id.borrow_mut() = maybe_clip; maybe_clip } fn build(&mut self, builder: &mut DisplayListBuilder, section: StackingContextSection) { if self.is_hit_test_for_scrollable_overflow { - self.build_hit_test(builder, self.fragment.scrollable_overflow().to_webrender()); + self.build_hit_test( + builder, + self.fragment + .reachable_scrollable_overflow_region() + .to_webrender(), + ); return; } @@ -734,6 +1133,7 @@ impl<'a> BuilderForBoxFragment<'a> { { return; } + self.build_background(builder); self.build_box_shadow(builder); self.build_border(builder); @@ -786,12 +1186,17 @@ impl<'a> BuilderForBoxFragment<'a> { } fn build_background(&mut self, builder: &mut DisplayListBuilder) { - if self - .fragment - .base - .is_for_node(builder.element_for_canvas_background) + let flags = self.fragment.base.flags; + + // The root element's background is painted separately as it might inherit the ``'s + // background. + if flags.intersects(FragmentFlags::IS_ROOT_ELEMENT) { + return; + } + // If the `` background was inherited by the root element, don't paint it again here. + if !builder.paint_body_background && + flags.intersects(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) { - // This background is already painted for the canvas, don’t paint it again here. return; } @@ -807,7 +1212,7 @@ impl<'a> BuilderForBoxFragment<'a> { for extra_background in extra_backgrounds { let positioning_area = extra_background.rect; let painter = BackgroundPainter { - style: &extra_background.style, + style: &extra_background.style.borrow_mut(), painting_area_override: None, positioning_area_override: Some( positioning_area @@ -838,8 +1243,8 @@ impl<'a> BuilderForBoxFragment<'a> { // Reverse because the property is top layer first, we want to paint bottom layer first. for (index, image) in b.background_image.0.iter().enumerate().rev() { match builder.context.resolve_image(node, image) { - None => {}, - Some(ResolvedImage::Gradient(gradient)) => { + Err(_) => {}, + Ok(ResolvedImage::Gradient(gradient)) => { let intrinsic = NaturalSizes::empty(); let Some(layer) = &background::layout_layer(self, painter, builder, index, intrinsic) @@ -875,7 +1280,7 @@ impl<'a> BuilderForBoxFragment<'a> { }, } }, - Some(ResolvedImage::Image(image_info)) => { + Ok(ResolvedImage::Image(image_info)) => { // FIXME: https://drafts.csswg.org/css-images-4/#the-image-resolution let dppx = 1.0; let intrinsic = NaturalSizes::from_width_and_height( @@ -1063,8 +1468,8 @@ impl<'a> BuilderForBoxFragment<'a> { .context .resolve_image(node, &border.border_image_source) { - None => return false, - Some(ResolvedImage::Image(image_info)) => { + Err(_) => return false, + Ok(ResolvedImage::Image(image_info)) => { let Some(key) = image_info.key else { return false; }; @@ -1073,7 +1478,7 @@ impl<'a> BuilderForBoxFragment<'a> { height = image_info.size.height as f32; NinePatchBorderSource::Image(key, ImageRendering::Auto) }, - Some(ResolvedImage::Gradient(gradient)) => { + Ok(ResolvedImage::Gradient(gradient)) => { match gradient::build(&self.fragment.style, gradient, border_image_size, builder) { WebRenderGradient::Linear(gradient) => { NinePatchBorderSource::Gradient(gradient) @@ -1328,38 +1733,6 @@ fn offset_radii(mut radii: wr::BorderRadius, offset: f32) -> wr::BorderRadius { radii } -fn create_clip_chain( - radii: wr::BorderRadius, - rect: units::LayoutRect, - builder: &mut DisplayListBuilder, - force_clip_creation: bool, -) -> Option { - if radii.is_zero() && !force_clip_creation { - return None; - } - - let spatial_id = builder.current_scroll_node_id.spatial_id; - let parent_clip_chain_id = builder.current_clip_chain_id; - let new_clip_id = if radii.is_zero() { - builder.wr().define_clip_rect(spatial_id, rect) - } else { - builder.wr().define_clip_rounded_rect( - spatial_id, - wr::ComplexClipRegion { - rect, - radii, - mode: wr::ClipMode::Clip, - }, - ) - }; - - Some( - builder - .display_list - .define_clip_chain(parent_clip_chain_id, [new_clip_id]), - ) -} - /// Resolve the WebRender border-image outset area from the style values. fn resolve_border_image_outset( outset: BorderImageOutset, diff --git a/components/layout/display_list/stacking_context.rs b/components/layout/display_list/stacking_context.rs index 0c0def9a563..d22a2b6656a 100644 --- a/components/layout/display_list/stacking_context.rs +++ b/components/layout/display_list/stacking_context.rs @@ -5,35 +5,40 @@ use core::f32; use std::cell::RefCell; use std::mem; +use std::sync::Arc; use app_units::Au; use base::id::ScrollTreeNodeId; use base::print_tree::PrintTree; -use compositing_traits::display_list::{AxesScrollSensitivity, ScrollableNodeInfo}; +use compositing_traits::display_list::{ + AxesScrollSensitivity, CompositorDisplayListInfo, ReferenceFrameNodeInfo, ScrollableNodeInfo, + SpatialTreeNodeInfo, StickyNodeInfo, +}; use euclid::SideOffsets2D; use euclid::default::{Point2D, Rect, Size2D}; use log::warn; -use servo_arc::Arc as ServoArc; use servo_config::opts::DebugOptions; use style::Zero; +use style::color::AbsoluteColor; use style::computed_values::float::T as ComputedFloat; use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode; use style::computed_values::overflow_x::T as ComputedOverflow; use style::computed_values::position::T as ComputedPosition; -use style::properties::ComputedValues; +use style::computed_values::text_decoration_style::T as TextDecorationStyle; use style::values::computed::angle::Angle; use style::values::computed::basic_shape::ClipPath; -use style::values::computed::{ClipRectOrAuto, Length}; +use style::values::computed::{ClipRectOrAuto, Length, TextDecorationLine}; use style::values::generics::box_::Perspective; use style::values::generics::transform::{self, GenericRotate, GenericScale, GenericTranslate}; use style::values::specified::box_::DisplayOutside; use webrender_api::units::{LayoutPoint, LayoutRect, LayoutTransform, LayoutVector2D}; use webrender_api::{self as wr, BorderRadius}; +use wr::StickyOffsetBounds; use wr::units::{LayoutPixel, LayoutSize}; -use wr::{ClipChainId, SpatialTreeItemKey, StickyOffsetBounds}; -use super::DisplayList; -use super::clip_path::build_clip_path_clip_chain_if_necessary; +use super::ClipId; +use super::clip::StackingContextTreeClipStore; +use crate::ArcRefCell; use crate::display_list::conversions::{FilterToWebRender, ToWebRender}; use crate::display_list::{BuilderForBoxFragment, DisplayListBuilder, offset_radii}; use crate::fragment_tree::{ @@ -54,9 +59,8 @@ pub(crate) struct ContainingBlock { /// frame and sticky positioning isn't taken into account. scroll_frame_size: Option, - /// The WebRender ClipId to use for this children of this containing - /// block. - clip_chain_id: wr::ClipChainId, + /// The [`ClipId`] to use for the children of this containing block. + clip_id: ClipId, /// The physical rect of this containing block. rect: PhysicalRect, @@ -67,12 +71,12 @@ impl ContainingBlock { rect: PhysicalRect, scroll_node_id: ScrollTreeNodeId, scroll_frame_size: Option, - clip_chain_id: wr::ClipChainId, + clip_id: ClipId, ) -> Self { ContainingBlock { scroll_node_id, scroll_frame_size, - clip_chain_id, + clip_id, rect, } } @@ -95,40 +99,56 @@ pub(crate) enum StackingContextSection { Outline, } -impl DisplayList { - /// Produce a new SpatialTreeItemKey. This is currently unused by WebRender, - /// but has to be unique to the entire scene. - fn get_next_spatial_tree_item_key(&mut self) -> SpatialTreeItemKey { - self.spatial_tree_count += 1; - let pipeline_tag = ((self.wr.pipeline_id.0 as u64) << 32) | self.wr.pipeline_id.1 as u64; - SpatialTreeItemKey::new(pipeline_tag, self.spatial_tree_count) - } +pub(crate) struct StackingContextTree { + /// The root stacking context of this [`StackingContextTree`]. + pub root_stacking_context: StackingContext, - #[cfg_attr( - feature = "tracing", - tracing::instrument( - name = "display_list::build_stacking_context_tree", - skip_all, - fields(servo_profiling = true), - level = "trace", - ) - )] - pub fn build_stacking_context_tree( - &mut self, + /// The information about the WebRender display list that the compositor + /// consumes. This curerntly contains the out-of-band hit testing information + /// data structure that the compositor uses to map hit tests to information + /// about the item hit. + pub compositor_info: CompositorDisplayListInfo, + + /// All of the clips collected for this [`StackingContextTree`]. These are added + /// for things like `overflow`. More clips may be created later during WebRender + /// display list construction, but they are never added here. + pub clip_store: StackingContextTreeClipStore, +} + +impl StackingContextTree { + /// Create a new [DisplayList] given the dimensions of the layout and the WebRender + /// pipeline id. + pub fn new( fragment_tree: &FragmentTree, + viewport_size: LayoutSize, + content_size: LayoutSize, + pipeline_id: wr::PipelineId, + viewport_scroll_sensitivity: AxesScrollSensitivity, + first_reflow: bool, debug: &DebugOptions, - ) -> StackingContext { + ) -> Self { + let compositor_info = CompositorDisplayListInfo::new( + viewport_size, + content_size, + pipeline_id, + // This epoch is set when the WebRender display list is built. For now use a dummy value. + wr::Epoch(0), + viewport_scroll_sensitivity, + first_reflow, + ); + + let root_scroll_node_id = compositor_info.root_scroll_node_id; let cb_for_non_fixed_descendants = ContainingBlock::new( fragment_tree.initial_containing_block, - self.compositor_info.root_scroll_node_id, - Some(self.compositor_info.viewport_size), - ClipChainId::INVALID, + root_scroll_node_id, + Some(compositor_info.viewport_size), + ClipId::INVALID, ); let cb_for_fixed_descendants = ContainingBlock::new( fragment_tree.initial_containing_block, - self.compositor_info.root_reference_frame_id, + compositor_info.root_reference_frame_id, None, - ClipChainId::INVALID, + ClipId::INVALID, ); // We need to specify all three containing blocks here, because absolute @@ -143,17 +163,33 @@ impl DisplayList { for_absolute_and_fixed_descendants: &cb_for_fixed_descendants, }; - let mut root_stacking_context = StackingContext::create_root(&self.wr, debug); + let mut stacking_context_tree = Self { + // This is just a temporary value that will be replaced once we have finished building the tree. + root_stacking_context: StackingContext::create_root(root_scroll_node_id, debug), + compositor_info, + clip_store: Default::default(), + }; + + let mut root_stacking_context = StackingContext::create_root(root_scroll_node_id, debug); + let text_decorations = Default::default(); for fragment in &fragment_tree.root_fragments { fragment.build_stacking_context_tree( - self, + &mut stacking_context_tree, &containing_block_info, &mut root_stacking_context, StackingContextBuildMode::SkipHoisted, + &text_decorations, ); } root_stacking_context.sort(); - root_stacking_context + + if debug.dump_stacking_context_tree { + root_stacking_context.debug_print(); + } + + stacking_context_tree.root_stacking_context = root_stacking_context; + + stacking_context_tree } fn push_reference_frame( @@ -161,53 +197,20 @@ impl DisplayList { origin: LayoutPoint, parent_scroll_node_id: &ScrollTreeNodeId, transform_style: wr::TransformStyle, - transform: wr::PropertyBinding, + transform: LayoutTransform, kind: wr::ReferenceFrameKind, ) -> ScrollTreeNodeId { - let spatial_tree_item_key = self.get_next_spatial_tree_item_key(); - let new_spatial_id = self.wr.push_reference_frame( - origin, - parent_scroll_node_id.spatial_id, - transform_style, - transform, - kind, - spatial_tree_item_key, - ); self.compositor_info.scroll_tree.add_scroll_tree_node( Some(parent_scroll_node_id), - new_spatial_id, - None, + SpatialTreeNodeInfo::ReferenceFrame(ReferenceFrameNodeInfo { + origin, + transform_style, + transform, + kind, + }), ) } - fn pop_reference_frame(&mut self) { - self.wr.pop_reference_frame(); - } - - fn clip_overflow_frame( - &mut self, - parent_scroll_node_id: &ScrollTreeNodeId, - parent_clip_id: &ClipChainId, - clip_rect: LayoutRect, - radii: wr::BorderRadius, - ) -> ClipChainId { - let new_clip_id = if radii.is_zero() { - self.wr - .define_clip_rect(parent_scroll_node_id.spatial_id, clip_rect) - } else { - self.wr.define_clip_rounded_rect( - parent_scroll_node_id.spatial_id, - webrender_api::ComplexClipRegion { - rect: clip_rect, - radii, - mode: webrender_api::ClipMode::Clip, - }, - ) - }; - - self.define_clip_chain(*parent_clip_id, [new_clip_id]) - } - fn define_scroll_frame( &mut self, parent_scroll_node_id: &ScrollTreeNodeId, @@ -216,25 +219,12 @@ impl DisplayList { clip_rect: LayoutRect, scroll_sensitivity: AxesScrollSensitivity, ) -> ScrollTreeNodeId { - let spatial_tree_item_key = self.get_next_spatial_tree_item_key(); - - let new_spatial_id = self.wr.define_scroll_frame( - parent_scroll_node_id.spatial_id, - external_id, - content_rect, - clip_rect, - LayoutVector2D::zero(), /* external_scroll_offset */ - 0, /* scroll_offset_generation */ - wr::HasScrollLinkedEffect::No, - spatial_tree_item_key, - ); - self.compositor_info.scroll_tree.add_scroll_tree_node( Some(parent_scroll_node_id), - new_spatial_id, - Some(ScrollableNodeInfo { + SpatialTreeNodeInfo::Scroll(ScrollableNodeInfo { external_id, - scrollable_size: content_rect.size() - clip_rect.size(), + content_rect, + clip_rect, scroll_sensitivity, offset: LayoutVector2D::zero(), }), @@ -249,25 +239,26 @@ impl DisplayList { vertical_offset_bounds: StickyOffsetBounds, horizontal_offset_bounds: StickyOffsetBounds, ) -> ScrollTreeNodeId { - let spatial_tree_item_key = self.get_next_spatial_tree_item_key(); - let new_spatial_id = self.wr.define_sticky_frame( - parent_scroll_node_id.spatial_id, - frame_rect, - margins, - vertical_offset_bounds, - horizontal_offset_bounds, - LayoutVector2D::zero(), /* previously_applied_offset */ - spatial_tree_item_key, - None, /* transform */ - ); self.compositor_info.scroll_tree.add_scroll_tree_node( Some(parent_scroll_node_id), - new_spatial_id, - None, + SpatialTreeNodeInfo::Sticky(StickyNodeInfo { + frame_rect, + margins, + vertical_offset_bounds, + horizontal_offset_bounds, + }), ) } } +/// The text decorations for a Fragment, collecting during [`StackingContextTree`] construction. +#[derive(Clone, Debug)] +pub(crate) struct FragmentTextDecoration { + pub line: TextDecorationLine, + pub color: AbsoluteColor, + pub style: TextDecorationStyle, +} + /// A piece of content that directly belongs to a section of a stacking context. /// /// This is generally part of a fragment, like its borders or foreground, but it @@ -277,12 +268,13 @@ pub(crate) enum StackingContextContent { Fragment { scroll_node_id: ScrollTreeNodeId, reference_frame_scroll_node_id: ScrollTreeNodeId, - clip_chain_id: wr::ClipChainId, + clip_id: ClipId, section: StackingContextSection, containing_block: PhysicalRect, fragment: Fragment, is_hit_test_for_scrollable_overflow: bool, is_collapsed_table_borders: bool, + text_decorations: Arc>, }, /// An index into [StackingContext::atomic_inline_stacking_containers]. @@ -308,22 +300,24 @@ impl StackingContextContent { Self::Fragment { scroll_node_id, reference_frame_scroll_node_id, - clip_chain_id, + clip_id, section, containing_block, fragment, is_hit_test_for_scrollable_overflow, is_collapsed_table_borders, + text_decorations, } => { builder.current_scroll_node_id = *scroll_node_id; builder.current_reference_frame_scroll_node_id = *reference_frame_scroll_node_id; - builder.current_clip_chain_id = *clip_chain_id; + builder.current_clip_id = *clip_id; fragment.build_display_list( builder, containing_block, *section, *is_hit_test_for_scrollable_overflow, *is_collapsed_table_borders, + text_decorations, ); }, Self::AtomicInlineStackingContainer { index } => { @@ -349,16 +343,14 @@ pub(crate) enum StackingContextType { pub struct StackingContext { /// The spatial id of this fragment. This is used to properly handle /// things like preserve-3d. - spatial_id: wr::SpatialId, + scroll_tree_node_id: ScrollTreeNodeId, /// The clip chain id of this stacking context if it has one. Used for filter clipping. - clip_chain_id: Option, + clip_id: Option, - /// The style of the fragment that established this stacking context. - initializing_fragment_style: Option>, - - /// The [`FragmentFlags`] of the [`Fragment`] that established this stacking context. - initializing_fragment_flags: FragmentFlags, + /// The [`BoxFragment`] that established this stacking context. We store the fragment here + /// rather than just the style, so that incremental layout can automatically update the style. + initializing_fragment: Option>, /// The type of this stacking context. Used for collecting and sorting. context_type: StackingContextType, @@ -415,25 +407,23 @@ pub enum DebugPrintField { impl StackingContext { fn create_descendant( &self, - spatial_id: wr::SpatialId, - clip_chain_id: wr::ClipChainId, - initializing_fragment_style: ServoArc, - initializing_fragment_flags: FragmentFlags, + spatial_id: ScrollTreeNodeId, + clip_id: ClipId, + initializing_fragment: ArcRefCell, context_type: StackingContextType, ) -> Self { // WebRender has two different ways of expressing "no clip." ClipChainId::INVALID should be // used for primitives, but `None` is used for stacking contexts and clip chains. We convert - // to the `Option` representation here. Just passing Some(ClipChainId::INVALID) + // to the `Option` representation here. Just passing Some(ClipChainId::INVALID) // leads to a crash. - let clip_chain_id: Option = match clip_chain_id { - ClipChainId::INVALID => None, - clip_chain_id => Some(clip_chain_id), + let clip_id = match clip_id { + ClipId::INVALID => None, + clip_id => Some(clip_id), }; Self { - spatial_id, - clip_chain_id, - initializing_fragment_style: Some(initializing_fragment_style), - initializing_fragment_flags, + scroll_tree_node_id: spatial_id, + clip_id, + initializing_fragment: Some(initializing_fragment), context_type, contents: vec![], real_stacking_contexts_and_positioned_stacking_containers: vec![], @@ -443,12 +433,11 @@ impl StackingContext { } } - pub(crate) fn create_root(wr: &wr::DisplayListBuilder, debug: &DebugOptions) -> Self { + fn create_root(root_scroll_node_id: ScrollTreeNodeId, debug: &DebugOptions) -> Self { Self { - spatial_id: wr::SpaceAndClipInfo::root_scroll(wr.pipeline_id).spatial_id, - clip_chain_id: None, - initializing_fragment_style: None, - initializing_fragment_flags: FragmentFlags::empty(), + scroll_tree_node_id: root_scroll_node_id, + clip_id: None, + initializing_fragment: None, context_type: StackingContextType::RealStackingContext, contents: vec![], real_stacking_contexts_and_positioned_stacking_containers: vec![], @@ -476,11 +465,10 @@ impl StackingContext { } fn z_index(&self) -> i32 { - self.initializing_fragment_style - .as_ref() - .map_or(0, |style| { - style.effective_z_index(self.initializing_fragment_flags) - }) + self.initializing_fragment.as_ref().map_or(0, |fragment| { + let fragment = fragment.borrow(); + fragment.style.effective_z_index(fragment.base.flags) + }) } pub(crate) fn sort(&mut self) { @@ -519,18 +507,19 @@ impl StackingContext { &self, builder: &mut DisplayListBuilder, ) -> bool { - let style = match self.initializing_fragment_style.as_ref() { - Some(style) => style, + let fragment = match self.initializing_fragment.as_ref() { + Some(fragment) => fragment.borrow(), None => return false, }; // WebRender only uses the stacking context to apply certain effects. If we don't // actually need to create a stacking context, just avoid creating one. + let style = &fragment.style; let effects = style.get_effects(); if effects.filter.0.is_empty() && effects.opacity == 1.0 && effects.mix_blend_mode == ComputedMixBlendMode::Normal && - !style.has_transform_or_perspective(FragmentFlags::empty()) && + !style.has_effective_transform_or_perspective(FragmentFlags::empty()) && style.clone_clip_path() == ClipPath::None { return false; @@ -557,11 +546,13 @@ impl StackingContext { // This will require additional tracking during layout // before we start collecting stacking contexts so that // information will be available when we reach this point. + let spatial_id = builder.spatial_id(self.scroll_tree_node_id); + let clip_chain_id = self.clip_id.map(|clip_id| builder.clip_chain_id(clip_id)); builder.wr().push_stacking_context( LayoutPoint::zero(), // origin - self.spatial_id, + spatial_id, style.get_webrender_primitive_flags(), - self.clip_chain_id, + clip_chain_id, style.get_used_transform_style().to_webrender(), effects.mix_blend_mode.to_webrender(), &filters, @@ -582,15 +573,42 @@ impl StackingContext { &self, builder: &mut DisplayListBuilder, fragment_tree: &crate::FragmentTree, - containing_block_rect: &PhysicalRect, ) { - let style = if let Some(style) = &fragment_tree.canvas_background.style { - style - } else { - // The root element has `display: none`, - // or the canvas background is taken from `` which has `display: none` + let Some(root_fragment) = fragment_tree.root_fragments.iter().find(|fragment| { + fragment + .base() + .is_some_and(|base| base.flags.intersects(FragmentFlags::IS_ROOT_ELEMENT)) + }) else { return; }; + let root_fragment = match root_fragment { + Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => box_fragment, + _ => return, + } + .borrow(); + + let source_style = { + // > For documents whose root element is an HTML HTML element or an XHTML html element + // > [HTML]: if the computed value of background-image on the root element is none and its + // > background-color is transparent, user agents must instead propagate the computed + // > values of the background properties from that element’s first HTML BODY or XHTML body + // > child element. + if root_fragment.style.background_is_transparent() { + let body_fragment = fragment_tree.body_fragment(); + builder.paint_body_background = body_fragment.is_none(); + body_fragment + .map(|body_fragment| body_fragment.borrow().style.clone()) + .unwrap_or(root_fragment.style.clone()) + } else { + root_fragment.style.clone() + } + }; + + // This can happen if the root fragment does not have a `` child (either because it is + // `display: none` or `display: contents`) or if the ``'s background is transparent. + if source_style.background_is_transparent() { + return; + } // The painting area is theoretically the infinite 2D plane, // but we need a rectangle with finite coordinates. @@ -598,112 +616,27 @@ impl StackingContext { // If the document is smaller than the viewport (and doesn’t scroll), // we still want to paint the rest of the viewport. // If it’s larger, we also want to paint areas reachable after scrolling. - let mut painting_area = fragment_tree + let painting_area = fragment_tree .initial_containing_block .union(&fragment_tree.scrollable_overflow) .to_webrender(); - let background_color = style.resolve_color(&style.get_background().background_color); + let background_color = + source_style.resolve_color(&source_style.get_background().background_color); if background_color.alpha > 0.0 { - let common = builder.common_properties(painting_area, style); + let common = builder.common_properties(painting_area, &source_style); let color = super::rgba(background_color); - builder - .display_list - .wr - .push_rect(&common, painting_area, color) - } - - // `background-color` was comparatively easy, - // but `background-image` needs a positioning area based on the root element. - // Let’s find the corresponding fragment. - - // The fragment generated by the root element is the first one here, unless… - let first_if_any = self.contents.first().or_else(|| { - // There wasn’t any `StackingContextFragment` in the root `StackingContext`, - // because the root element generates a stacking context. Let’s find that one. - self.real_stacking_contexts_and_positioned_stacking_containers - .first() - .and_then(|first_child_stacking_context| { - first_child_stacking_context.contents.first() - }) - }); - - macro_rules! debug_panic { - ($msg: expr) => { - if cfg!(debug_assertions) { - panic!($msg); - } else { - warn!($msg); - return; - } - }; - } - - let first_stacking_context_fragment = if let Some(first) = first_if_any { - first - } else { - // This should only happen if the root element has `display: none` - // TODO(servo#30569) revert to debug_panic!() once underlying bug is fixed - log::warn!( - "debug assertion failed! `CanvasBackground::for_root_element` should have returned `style: None`", - ); - return; - }; - - let StackingContextContent::Fragment { - fragment, - scroll_node_id, - containing_block, - .. - } = first_stacking_context_fragment - else { - debug_panic!("Expected a fragment, not a stacking container"); - }; - let box_fragment = match fragment { - Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => box_fragment, - _ => debug_panic!("Expected a box-generated fragment"), - }; - let box_fragment = &*box_fragment.borrow(); - - // The `StackingContextFragment` we found is for the root DOM element: - debug_assert_eq!( - fragment.tag().map(|tag| tag.node), - Some(fragment_tree.canvas_background.root_element), - ); - - // The root element may have a CSS transform, and we want the canvas’ - // background image to be transformed. To do so, take its `SpatialId` - // (but not its `ClipId`) - builder.current_scroll_node_id = *scroll_node_id; - - // Now we need express the painting area rectangle in the local coordinate system, - // which differs from the top-level coordinate system based on… - - // Convert the painting area rectangle to the local coordinate system of this `SpatialId` - if let Some(reference_frame_data) = - box_fragment.reference_frame_data_if_necessary(containing_block_rect) - { - painting_area.min -= reference_frame_data.origin.to_webrender().to_vector(); - if let Some(transformed) = reference_frame_data - .transform - .inverse() - .and_then(|inversed| inversed.outer_transformed_rect(&painting_area.to_rect())) - { - painting_area = transformed.to_box2d(); - } else { - // The desired rect cannot be represented, so skip painting this background-image - return; - } + builder.wr().push_rect(&common, painting_area, color) } let mut fragment_builder = BuilderForBoxFragment::new( - box_fragment, - containing_block, + &root_fragment, + &fragment_tree.initial_containing_block, false, /* is_hit_test_for_scrollable_overflow */ false, /* is_collapsed_table_borders */ ); let painter = super::background::BackgroundPainter { - style, + style: &source_style, painting_area_override: Some(painting_area), positioning_area_override: None, }; @@ -796,7 +729,7 @@ impl StackingContext { } if pushed_context { - builder.display_list.wr.pop_stacking_context(); + builder.wr().pop_stacking_context(); } } @@ -879,10 +812,11 @@ pub(crate) enum StackingContextBuildMode { impl Fragment { pub(crate) fn build_stacking_context_tree( &self, - display_list: &mut DisplayList, + stacking_context_tree: &mut StackingContextTree, containing_block_info: &ContainingBlockInfo, stacking_context: &mut StackingContext, mode: StackingContextBuildMode, + text_decorations: &Arc>, ) { let containing_block = containing_block_info.get_containing_block_for_fragment(self); let fragment_clone = self.clone(); @@ -895,6 +829,11 @@ impl Fragment { return; } + let text_decorations = match self { + Fragment::Float(..) => &Default::default(), + _ => text_decorations, + }; + // If this fragment has a transform applied that makes it take up no space // then we don't need to create any stacking contexts for it. let has_non_invertible_transform = fragment @@ -907,10 +846,11 @@ impl Fragment { fragment.build_stacking_context_tree( fragment_clone, - display_list, + stacking_context_tree, containing_block, containing_block_info, stacking_context, + text_decorations, ); }, Fragment::AbsoluteOrFixedPositioned(fragment) => { @@ -921,19 +861,21 @@ impl Fragment { }; fragment_ref.build_stacking_context_tree( - display_list, + stacking_context_tree, containing_block_info, stacking_context, StackingContextBuildMode::IncludeHoisted, + &Default::default(), ); }, Fragment::Positioning(fragment) => { let fragment = fragment.borrow(); fragment.build_stacking_context_tree( - display_list, + stacking_context_tree, containing_block, containing_block_info, stacking_context, + text_decorations, ); }, Fragment::Text(_) | Fragment::Image(_) | Fragment::IFrame(_) => { @@ -945,11 +887,12 @@ impl Fragment { reference_frame_scroll_node_id: containing_block_info .for_absolute_and_fixed_descendants .scroll_node_id, - clip_chain_id: containing_block.clip_chain_id, + clip_id: containing_block.clip_id, containing_block: containing_block.rect, fragment: fragment_clone, is_hit_test_for_scrollable_overflow: false, is_collapsed_table_borders: false, + text_decorations: text_decorations.clone(), }); }, } @@ -967,7 +910,7 @@ struct ScrollFrameData { } struct OverflowFrameData { - clip_chain_id: wr::ClipChainId, + clip_id: ClipId, scroll_frame_data: Option, } @@ -1008,27 +951,30 @@ impl BoxFragment { fn build_stacking_context_tree( &self, fragment: Fragment, - display_list: &mut DisplayList, + stacking_context_tree: &mut StackingContextTree, containing_block: &ContainingBlock, containing_block_info: &ContainingBlockInfo, parent_stacking_context: &mut StackingContext, + text_decorations: &Arc>, ) { self.build_stacking_context_tree_maybe_creating_reference_frame( fragment, - display_list, + stacking_context_tree, containing_block, containing_block_info, parent_stacking_context, + text_decorations, ); } fn build_stacking_context_tree_maybe_creating_reference_frame( &self, fragment: Fragment, - display_list: &mut DisplayList, + stacking_context_tree: &mut StackingContextTree, containing_block: &ContainingBlock, containing_block_info: &ContainingBlockInfo, parent_stacking_context: &mut StackingContext, + text_decorations: &Arc>, ) { let reference_frame_data = match self.reference_frame_data_if_necessary(&containing_block.rect) { @@ -1036,19 +982,20 @@ impl BoxFragment { None => { return self.build_stacking_context_tree_maybe_creating_stacking_context( fragment, - display_list, + stacking_context_tree, containing_block, containing_block_info, parent_stacking_context, + text_decorations, ); }, }; - let new_spatial_id = display_list.push_reference_frame( + let new_spatial_id = stacking_context_tree.push_reference_frame( reference_frame_data.origin.to_webrender(), &containing_block.scroll_node_id, self.style.get_box().transform_style.to_webrender(), - wr::PropertyBinding::Value(reference_frame_data.transform), + reference_frame_data.transform, reference_frame_data.kind, ); @@ -1071,39 +1018,40 @@ impl BoxFragment { .translate(-reference_frame_data.origin.to_vector()), new_spatial_id, None, - containing_block.clip_chain_id, + containing_block.clip_id, ); let new_containing_block_info = containing_block_info.new_for_non_absolute_descendants(&adjusted_containing_block); self.build_stacking_context_tree_maybe_creating_stacking_context( fragment, - display_list, + stacking_context_tree, &adjusted_containing_block, &new_containing_block_info, parent_stacking_context, + text_decorations, ); - - display_list.pop_reference_frame(); } fn build_stacking_context_tree_maybe_creating_stacking_context( &self, fragment: Fragment, - display_list: &mut DisplayList, + stacking_context_tree: &mut StackingContextTree, containing_block: &ContainingBlock, containing_block_info: &ContainingBlockInfo, parent_stacking_context: &mut StackingContext, + text_decorations: &Arc>, ) { let context_type = match self.get_stacking_context_type() { Some(context_type) => context_type, None => { self.build_stacking_context_tree_for_children( fragment, - display_list, + stacking_context_tree, containing_block, containing_block_info, parent_stacking_context, + text_decorations, ); return; }, @@ -1123,33 +1071,41 @@ impl BoxFragment { // `clip-path` needs to be applied before filters and creates a stacking context, so it can be // applied directly to the stacking context itself. // before - let stacking_context_clip_chain_id = build_clip_path_clip_chain_if_necessary( - self.style.clone_clip_path(), - display_list, - &containing_block.scroll_node_id, - &containing_block.clip_chain_id, - BuilderForBoxFragment::new( - self, - &containing_block.rect, - false, /* is_hit_test_for_scrollable_overflow */ - false, /* is_collapsed_table_borders */ - ), - ) - .unwrap_or(containing_block.clip_chain_id); + let stacking_context_clip_id = stacking_context_tree + .clip_store + .add_for_clip_path( + self.style.clone_clip_path(), + &containing_block.scroll_node_id, + &containing_block.clip_id, + BuilderForBoxFragment::new( + self, + &containing_block.rect, + false, /* is_hit_test_for_scrollable_overflow */ + false, /* is_collapsed_table_borders */ + ), + ) + .unwrap_or(containing_block.clip_id); + + let box_fragment = match fragment { + Fragment::Box(ref box_fragment) | Fragment::Float(ref box_fragment) => { + box_fragment.clone() + }, + _ => unreachable!("Should never try to make stacking context for non-BoxFragment"), + }; let mut child_stacking_context = parent_stacking_context.create_descendant( - containing_block.scroll_node_id.spatial_id, - stacking_context_clip_chain_id, - self.style.clone(), - self.base.flags, + containing_block.scroll_node_id, + stacking_context_clip_id, + box_fragment, context_type, ); self.build_stacking_context_tree_for_children( fragment, - display_list, + stacking_context_tree, containing_block, containing_block_info, &mut child_stacking_context, + text_decorations, ); let mut stolen_children = vec![]; @@ -1171,19 +1127,20 @@ impl BoxFragment { fn build_stacking_context_tree_for_children( &self, fragment: Fragment, - display_list: &mut DisplayList, + stacking_context_tree: &mut StackingContextTree, containing_block: &ContainingBlock, containing_block_info: &ContainingBlockInfo, stacking_context: &mut StackingContext, + text_decorations: &Arc>, ) { let mut new_scroll_node_id = containing_block.scroll_node_id; - let mut new_clip_chain_id = containing_block.clip_chain_id; + let mut new_clip_id = containing_block.clip_id; let mut new_scroll_frame_size = containing_block_info .for_non_absolute_descendants .scroll_frame_size; if let Some(scroll_node_id) = self.build_sticky_frame_if_necessary( - display_list, + stacking_context_tree, &new_scroll_node_id, &containing_block.rect, &new_scroll_frame_size, @@ -1191,20 +1148,19 @@ impl BoxFragment { new_scroll_node_id = scroll_node_id; } - if let Some(clip_chain_id) = self.build_clip_frame_if_necessary( - display_list, + if let Some(clip_id) = self.build_clip_frame_if_necessary( + stacking_context_tree, &new_scroll_node_id, - &new_clip_chain_id, + new_clip_id, &containing_block.rect, ) { - new_clip_chain_id = clip_chain_id; + new_clip_id = clip_id; } - if let Some(clip_chain_id) = build_clip_path_clip_chain_if_necessary( + if let Some(clip_id) = stacking_context_tree.clip_store.add_for_clip_path( self.style.clone_clip_path(), - display_list, &new_scroll_node_id, - &new_clip_chain_id, + &new_clip_id, BuilderForBoxFragment::new( self, &containing_block.rect, @@ -1212,7 +1168,7 @@ impl BoxFragment { false, /* is_collapsed_table_borders */ ), ) { - new_clip_chain_id = clip_chain_id; + new_clip_id = clip_id; } let establishes_containing_block_for_all_descendants = self @@ -1237,12 +1193,13 @@ impl BoxFragment { .push(StackingContextContent::Fragment { scroll_node_id: new_scroll_node_id, reference_frame_scroll_node_id: reference_frame_scroll_node_id_for_fragments, - clip_chain_id: new_clip_chain_id, + clip_id: new_clip_id, section, containing_block: containing_block.rect, fragment: fragment.clone(), is_hit_test_for_scrollable_overflow: false, is_collapsed_table_borders: false, + text_decorations: text_decorations.clone(), }); }; @@ -1255,12 +1212,12 @@ impl BoxFragment { // We want to build the scroll frame after the background and border, because // they shouldn't scroll with the rest of the box content. if let Some(overflow_frame_data) = self.build_overflow_frame_if_necessary( - display_list, + stacking_context_tree, &new_scroll_node_id, - &new_clip_chain_id, + new_clip_id, &containing_block.rect, ) { - new_clip_chain_id = overflow_frame_data.clip_chain_id; + new_clip_id = overflow_frame_data.clip_id; if let Some(scroll_frame_data) = overflow_frame_data.scroll_frame_data { new_scroll_node_id = scroll_frame_data.scroll_tree_node_id; new_scroll_frame_size = Some(scroll_frame_data.scroll_frame_rect.size()); @@ -1271,12 +1228,13 @@ impl BoxFragment { scroll_node_id: new_scroll_node_id, reference_frame_scroll_node_id: reference_frame_scroll_node_id_for_fragments, - clip_chain_id: new_clip_chain_id, + clip_id: new_clip_id, section, containing_block: containing_block.rect, fragment: fragment.clone(), is_hit_test_for_scrollable_overflow: true, is_collapsed_table_borders: false, + text_decorations: text_decorations.clone(), }); } } @@ -1292,13 +1250,13 @@ impl BoxFragment { padding_rect, new_scroll_node_id, new_scroll_frame_size, - new_clip_chain_id, + new_clip_id, ); let for_non_absolute_descendants = ContainingBlock::new( content_rect, new_scroll_node_id, new_scroll_frame_size, - new_clip_chain_id, + new_clip_id, ); // Create a new `ContainingBlockInfo` for descendants depending on @@ -1318,12 +1276,46 @@ impl BoxFragment { containing_block_info.new_for_non_absolute_descendants(&for_non_absolute_descendants) }; + // Text decorations are not propagated to atomic inline-level descendants. + // From https://drafts.csswg.org/css2/#lining-striking-props: + // > Note that text decorations are not propagated to floating and absolutely + // > positioned descendants, nor to the contents of atomic inline-level descendants + // > such as inline blocks and inline tables. + let text_decorations = match self.is_atomic_inline_level() || + self.base + .flags + .contains(FragmentFlags::IS_OUTSIDE_LIST_ITEM_MARKER) + { + true => &Default::default(), + false => text_decorations, + }; + + let new_text_decoration; + let text_decorations = match self.style.clone_text_decoration_line() { + TextDecorationLine::NONE => text_decorations, + line => { + let mut new_vector = (**text_decorations).clone(); + let color = &self.style.get_inherited_text().color; + new_vector.push(FragmentTextDecoration { + line, + color: self + .style + .clone_text_decoration_color() + .resolve_to_absolute(color), + style: self.style.clone_text_decoration_style(), + }); + new_text_decoration = Arc::new(new_vector); + &new_text_decoration + }, + }; + for child in &self.children { child.build_stacking_context_tree( - display_list, + stacking_context_tree, &new_containing_block_info, stacking_context, StackingContextBuildMode::SkipHoisted, + text_decorations, ); } @@ -1336,23 +1328,24 @@ impl BoxFragment { .push(StackingContextContent::Fragment { scroll_node_id: new_scroll_node_id, reference_frame_scroll_node_id: reference_frame_scroll_node_id_for_fragments, - clip_chain_id: new_clip_chain_id, + clip_id: new_clip_id, section, containing_block: containing_block.rect, fragment: fragment.clone(), is_hit_test_for_scrollable_overflow: false, is_collapsed_table_borders: true, + text_decorations: text_decorations.clone(), }); } } fn build_clip_frame_if_necessary( &self, - display_list: &mut DisplayList, + stacking_context_tree: &mut StackingContextTree, parent_scroll_node_id: &ScrollTreeNodeId, - parent_clip_chain_id: &wr::ClipChainId, + parent_clip_id: ClipId, containing_block_rect: &PhysicalRect, - ) -> Option { + ) -> Option { let position = self.style.get_box().position; // https://drafts.csswg.org/css2/#clipping // The clip property applies only to absolutely positioned elements @@ -1371,18 +1364,19 @@ impl BoxFragment { .for_border_rect(border_rect) .translate(containing_block_rect.origin.to_vector()) .to_webrender(); - - let clip_id = display_list - .wr - .define_clip_rect(parent_scroll_node_id.spatial_id, clip_rect); - Some(display_list.define_clip_chain(*parent_clip_chain_id, [clip_id])) + Some(stacking_context_tree.clip_store.add( + BorderRadius::zero(), + clip_rect, + *parent_scroll_node_id, + parent_clip_id, + )) } fn build_overflow_frame_if_necessary( &self, - display_list: &mut DisplayList, + stacking_context_tree: &mut StackingContextTree, parent_scroll_node_id: &ScrollTreeNodeId, - parent_clip_chain_id: &wr::ClipChainId, + parent_clip_id: ClipId, containing_block_rect: &PhysicalRect, ) -> Option { let overflow = self.style.effective_overflow(self.base.flags); @@ -1422,15 +1416,15 @@ impl BoxFragment { radii = BorderRadius::zero(); } - let clip_chain_id = display_list.clip_overflow_frame( - parent_scroll_node_id, - parent_clip_chain_id, - overflow_clip_rect, + let clip_id = stacking_context_tree.clip_store.add( radii, + overflow_clip_rect, + *parent_scroll_node_id, + parent_clip_id, ); return Some(OverflowFrameData { - clip_chain_id, + clip_id, scroll_frame_data: None, }); } @@ -1459,17 +1453,17 @@ impl BoxFragment { .translate(containing_block_rect.origin.to_vector()) .to_webrender(); - let clip_chain_id = display_list.clip_overflow_frame( - parent_scroll_node_id, - parent_clip_chain_id, - scroll_frame_rect, + let clip_id = stacking_context_tree.clip_store.add( BuilderForBoxFragment::new(self, containing_block_rect, false, false).border_radius, + scroll_frame_rect, + *parent_scroll_node_id, + parent_clip_id, ); let tag = self.base.tag?; let external_id = wr::ExternalScrollId( tag.to_display_list_fragment_id(), - display_list.wr.pipeline_id, + stacking_context_tree.compositor_info.pipeline_id, ); let sensitivity = AxesScrollSensitivity { @@ -1477,9 +1471,9 @@ impl BoxFragment { y: overflow.y.into(), }; - let content_rect = self.scrollable_overflow().to_webrender(); + let content_rect = self.reachable_scrollable_overflow_region().to_webrender(); - let scroll_tree_node_id = display_list.define_scroll_frame( + let scroll_tree_node_id = stacking_context_tree.define_scroll_frame( parent_scroll_node_id, external_id, content_rect, @@ -1488,7 +1482,7 @@ impl BoxFragment { ); Some(OverflowFrameData { - clip_chain_id, + clip_id, scroll_frame_data: Some(ScrollFrameData { scroll_tree_node_id, scroll_frame_rect, @@ -1498,7 +1492,7 @@ impl BoxFragment { fn build_sticky_frame_if_necessary( &self, - display_list: &mut DisplayList, + stacking_context_tree: &mut StackingContextTree, parent_scroll_node_id: &ScrollTreeNodeId, containing_block_rect: &PhysicalRect, scroll_frame_size: &Option, @@ -1511,7 +1505,7 @@ impl BoxFragment { Some(size) => size, None => { // This is a direct descendant of a reference frame. - &display_list.compositor_info.viewport_size + &stacking_context_tree.compositor_info.viewport_size }, }; @@ -1568,7 +1562,7 @@ impl BoxFragment { offsets.left.non_auto().map(|v| v.to_f32_px()), ); - let sticky_node_id = display_list.define_sticky_frame( + let sticky_node_id = stacking_context_tree.define_sticky_frame( parent_scroll_node_id, frame_rect, margins, @@ -1584,7 +1578,10 @@ impl BoxFragment { &self, containing_block_rect: &PhysicalRect, ) -> Option { - if !self.style.has_transform_or_perspective(self.base.flags) { + if !self + .style + .has_effective_transform_or_perspective(self.base.flags) + { return None; } @@ -1717,10 +1714,11 @@ impl BoxFragment { impl PositioningFragment { fn build_stacking_context_tree( &self, - display_list: &mut DisplayList, + stacking_context_tree: &mut StackingContextTree, containing_block: &ContainingBlock, containing_block_info: &ContainingBlockInfo, stacking_context: &mut StackingContext, + text_decorations: &Arc>, ) { let rect = self .rect @@ -1731,10 +1729,11 @@ impl PositioningFragment { for child in &self.children { child.build_stacking_context_tree( - display_list, + stacking_context_tree, &new_containing_block_info, stacking_context, StackingContextBuildMode::SkipHoisted, + text_decorations, ); } } diff --git a/components/layout/dom.rs b/components/layout/dom.rs index 6db4dbccd41..88176ffbbb0 100644 --- a/components/layout/dom.rs +++ b/components/layout/dom.rs @@ -2,36 +2,39 @@ * 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::any::Any; use std::marker::PhantomData; use std::sync::Arc; use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; use base::id::{BrowsingContextId, PipelineId}; use html5ever::{local_name, ns}; +use malloc_size_of_derive::MallocSizeOf; use pixels::Image; +use script::layout_dom::ServoLayoutNode; use script_layout_interface::wrapper_traits::{ LayoutDataTrait, LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode, }; use script_layout_interface::{ - HTMLCanvasDataSource, LayoutElementType, LayoutNodeType as ScriptLayoutNodeType, + GenericLayoutDataTrait, LayoutElementType, LayoutNodeType as ScriptLayoutNodeType, }; use servo_arc::Arc as ServoArc; +use style::context::SharedStyleContext; use style::properties::ComputedValues; use style::selector_parser::PseudoElement; use crate::cell::ArcRefCell; -use crate::context::LayoutContext; use crate::flexbox::FlexLevelBox; use crate::flow::BlockLevelBox; -use crate::flow::inline::InlineItem; +use crate::flow::inline::{InlineItem, SharedInlineStyles}; use crate::fragment_tree::Fragment; use crate::geom::PhysicalSize; -use crate::replaced::{CanvasInfo, CanvasSource}; +use crate::replaced::CanvasInfo; use crate::table::TableLevelBox; use crate::taffy::TaffyItemBox; /// The data that is stored in each DOM node that is used by layout. -#[derive(Default)] +#[derive(Default, MallocSizeOf)] pub struct InnerDOMLayoutData { pub(super) self_box: ArcRefCell>, pub(super) pseudo_before_box: ArcRefCell>, @@ -54,10 +57,11 @@ impl InnerDOMLayoutData { } /// A box that is stored in one of the `DOMLayoutData` slots. +#[derive(MallocSizeOf)] pub(super) enum LayoutBox { - DisplayContents, + DisplayContents(SharedInlineStyles), BlockLevel(ArcRefCell), - InlineLevel(ArcRefCell), + InlineLevel(Vec>), FlexLevel(ArcRefCell), TableLevelBox(TableLevelBox), TaffyItemBox(ArcRefCell), @@ -66,12 +70,14 @@ pub(super) enum LayoutBox { impl LayoutBox { fn invalidate_cached_fragment(&self) { match self { - LayoutBox::DisplayContents => {}, + LayoutBox::DisplayContents(..) => {}, LayoutBox::BlockLevel(block_level_box) => { block_level_box.borrow().invalidate_cached_fragment() }, - LayoutBox::InlineLevel(inline_item) => { - inline_item.borrow().invalidate_cached_fragment() + LayoutBox::InlineLevel(inline_items) => { + for inline_item in inline_items.iter() { + inline_item.borrow().invalidate_cached_fragment() + } }, LayoutBox::FlexLevel(flex_level_box) => { flex_level_box.borrow().invalidate_cached_fragment() @@ -85,24 +91,67 @@ impl LayoutBox { pub(crate) fn fragments(&self) -> Vec { match self { - LayoutBox::DisplayContents => vec![], + LayoutBox::DisplayContents(..) => vec![], LayoutBox::BlockLevel(block_level_box) => block_level_box.borrow().fragments(), - LayoutBox::InlineLevel(inline_item) => inline_item.borrow().fragments(), + LayoutBox::InlineLevel(inline_items) => inline_items + .iter() + .flat_map(|inline_item| inline_item.borrow().fragments()) + .collect(), LayoutBox::FlexLevel(flex_level_box) => flex_level_box.borrow().fragments(), LayoutBox::TaffyItemBox(taffy_item_box) => taffy_item_box.borrow().fragments(), LayoutBox::TableLevelBox(table_box) => table_box.fragments(), } } + + fn repair_style( + &self, + context: &SharedStyleContext, + node: &ServoLayoutNode, + new_style: &ServoArc, + ) { + match self { + LayoutBox::DisplayContents(inline_shared_styles) => { + *inline_shared_styles.style.borrow_mut() = new_style.clone(); + *inline_shared_styles.selected.borrow_mut() = node.to_threadsafe().selected_style(); + }, + LayoutBox::BlockLevel(block_level_box) => { + block_level_box + .borrow_mut() + .repair_style(context, node, new_style); + }, + LayoutBox::InlineLevel(inline_items) => { + for inline_item in inline_items { + inline_item + .borrow_mut() + .repair_style(context, node, new_style); + } + }, + LayoutBox::FlexLevel(flex_level_box) => flex_level_box + .borrow_mut() + .repair_style(context, node, new_style), + LayoutBox::TableLevelBox(table_level_box) => { + table_level_box.repair_style(context, node, new_style) + }, + LayoutBox::TaffyItemBox(taffy_item_box) => taffy_item_box + .borrow_mut() + .repair_style(context, node, new_style), + } + } } /// A wrapper for [`InnerDOMLayoutData`]. This is necessary to give the entire data /// structure interior mutability, as we will need to mutate the layout data of /// non-mutable DOM nodes. -#[derive(Default)] +#[derive(Default, MallocSizeOf)] pub struct DOMLayoutData(AtomicRefCell); // The implementation of this trait allows the data to be stored in the DOM. impl LayoutDataTrait for DOMLayoutData {} +impl GenericLayoutDataTrait for DOMLayoutData { + fn as_any(&self) -> &dyn Any { + self + } +} pub struct BoxSlot<'dom> { pub(crate) slot: Option>>, @@ -145,34 +194,33 @@ impl Drop for BoxSlot<'_> { } } -pub(crate) trait NodeExt<'dom>: 'dom + LayoutNode<'dom> { +pub(crate) trait NodeExt<'dom> { /// Returns the image if it’s loaded, and its size in image pixels /// adjusted for `image_density`. - fn as_image(self) -> Option<(Option>, PhysicalSize)>; - fn as_canvas(self) -> Option<(CanvasInfo, PhysicalSize)>; - fn as_iframe(self) -> Option<(PipelineId, BrowsingContextId)>; - fn as_video(self) -> Option<(Option, Option>)>; - fn as_typeless_object_with_data_attribute(self) -> Option; - fn style(self, context: &LayoutContext) -> ServoArc; + fn as_image(&self) -> Option<(Option>, PhysicalSize)>; + fn as_canvas(&self) -> Option<(CanvasInfo, PhysicalSize)>; + fn as_iframe(&self) -> Option<(PipelineId, BrowsingContextId)>; + fn as_video(&self) -> Option<(Option, Option>)>; + fn as_typeless_object_with_data_attribute(&self) -> Option; + fn style(&self, context: &SharedStyleContext) -> ServoArc; - fn layout_data_mut(self) -> AtomicRefMut<'dom, InnerDOMLayoutData>; - fn layout_data(self) -> Option>; + fn layout_data_mut(&self) -> AtomicRefMut<'dom, InnerDOMLayoutData>; + fn layout_data(&self) -> Option>; fn element_box_slot(&self) -> BoxSlot<'dom>; fn pseudo_element_box_slot(&self, which: PseudoElement) -> BoxSlot<'dom>; - fn unset_pseudo_element_box(self, which: PseudoElement); + fn unset_pseudo_element_box(&self, which: PseudoElement); /// Remove boxes for the element itself, and its `:before` and `:after` if any. - fn unset_all_boxes(self); + fn unset_all_boxes(&self); fn fragments_for_pseudo(&self, pseudo_element: Option) -> Vec; - fn invalidate_cached_fragment(self); + fn invalidate_cached_fragment(&self); + + fn repair_style(&self, context: &SharedStyleContext); } -impl<'dom, LayoutNodeType> NodeExt<'dom> for LayoutNodeType -where - LayoutNodeType: 'dom + LayoutNode<'dom>, -{ - fn as_image(self) -> Option<(Option>, PhysicalSize)> { +impl<'dom> NodeExt<'dom> for ServoLayoutNode<'dom> { + fn as_image(&self) -> Option<(Option>, PhysicalSize)> { let node = self.to_threadsafe(); let (resource, metadata) = node.image_data()?; let (width, height) = resource @@ -188,7 +236,7 @@ where Some((resource, PhysicalSize::new(width, height))) } - fn as_video(self) -> Option<(Option, Option>)> { + fn as_video(&self) -> Option<(Option, Option>)> { let node = self.to_threadsafe(); let data = node.media_data()?; let natural_size = if let Some(frame) = data.current_frame { @@ -203,22 +251,17 @@ where )) } - fn as_canvas(self) -> Option<(CanvasInfo, PhysicalSize)> { + fn as_canvas(&self) -> Option<(CanvasInfo, PhysicalSize)> { let node = self.to_threadsafe(); let canvas_data = node.canvas_data()?; - let source = match canvas_data.source { - HTMLCanvasDataSource::WebGL(texture_id) => CanvasSource::WebGL(texture_id), - HTMLCanvasDataSource::Image(image_key) => CanvasSource::Image(image_key), - HTMLCanvasDataSource::WebGPU(image_key) => CanvasSource::WebGPU(image_key), - HTMLCanvasDataSource::Empty => CanvasSource::Empty, - }; + let source = canvas_data.source; Some(( CanvasInfo { source }, PhysicalSize::new(canvas_data.width.into(), canvas_data.height.into()), )) } - fn as_iframe(self) -> Option<(PipelineId, BrowsingContextId)> { + fn as_iframe(&self) -> Option<(PipelineId, BrowsingContextId)> { let node = self.to_threadsafe(); match (node.iframe_pipeline_id(), node.iframe_browsing_context_id()) { (Some(pipeline_id), Some(browsing_context_id)) => { @@ -228,8 +271,10 @@ where } } - fn as_typeless_object_with_data_attribute(self) -> Option { - if self.type_id() != ScriptLayoutNodeType::Element(LayoutElementType::HTMLObjectElement) { + fn as_typeless_object_with_data_attribute(&self) -> Option { + if LayoutNode::type_id(self) != + ScriptLayoutNodeType::Element(LayoutElementType::HTMLObjectElement) + { return None; } @@ -245,25 +290,31 @@ where .map(|string| string.to_owned()) } - fn style(self, context: &LayoutContext) -> ServoArc { - self.to_threadsafe().style(context.shared_context()) + fn style(&self, context: &SharedStyleContext) -> ServoArc { + self.to_threadsafe().style(context) } - fn layout_data_mut(self) -> AtomicRefMut<'dom, InnerDOMLayoutData> { - if LayoutNode::layout_data(&self).is_none() { + fn layout_data_mut(&self) -> AtomicRefMut<'dom, InnerDOMLayoutData> { + if LayoutNode::layout_data(self).is_none() { self.initialize_layout_data::(); } - LayoutNode::layout_data(&self) + LayoutNode::layout_data(self) .unwrap() + .as_any() .downcast_ref::() .unwrap() .0 .borrow_mut() } - fn layout_data(self) -> Option> { - LayoutNode::layout_data(&self) - .map(|data| data.downcast_ref::().unwrap().0.borrow()) + fn layout_data(&self) -> Option> { + LayoutNode::layout_data(self).map(|data| { + data.as_any() + .downcast_ref::() + .unwrap() + .0 + .borrow() + }) } fn element_box_slot(&self) -> BoxSlot<'dom> { @@ -284,7 +335,7 @@ where BoxSlot::new(cell.clone()) } - fn unset_pseudo_element_box(self, pseudo_element_type: PseudoElement) { + fn unset_pseudo_element_box(&self, pseudo_element_type: PseudoElement) { let data = self.layout_data_mut(); let cell = match pseudo_element_type { PseudoElement::Before => &data.pseudo_before_box, @@ -298,7 +349,7 @@ where *cell.borrow_mut() = None; } - fn unset_all_boxes(self) { + fn unset_all_boxes(&self) { let data = self.layout_data_mut(); *data.self_box.borrow_mut() = None; *data.pseudo_before_box.borrow_mut() = None; @@ -308,7 +359,7 @@ where // for DOM descendants of elements with `display: none`. } - fn invalidate_cached_fragment(self) { + fn invalidate_cached_fragment(&self) { let data = self.layout_data_mut(); if let Some(data) = data.self_box.borrow_mut().as_mut() { data.invalidate_cached_fragment(); @@ -316,7 +367,7 @@ where } fn fragments_for_pseudo(&self, pseudo_element: Option) -> Vec { - NodeExt::layout_data(*self) + NodeExt::layout_data(self) .and_then(|layout_data| { layout_data .for_pseudo(pseudo_element) @@ -325,4 +376,30 @@ where }) .unwrap_or_default() } + + fn repair_style(&self, context: &SharedStyleContext) { + let data = self.layout_data_mut(); + if let Some(layout_object) = &*data.self_box.borrow() { + let style = self.to_threadsafe().style(context); + layout_object.repair_style(context, self, &style); + } + + if let Some(layout_object) = &*data.pseudo_before_box.borrow() { + if let Some(node) = self.to_threadsafe().with_pseudo(PseudoElement::Before) { + layout_object.repair_style(context, self, &node.style(context)); + } + } + + if let Some(layout_object) = &*data.pseudo_after_box.borrow() { + if let Some(node) = self.to_threadsafe().with_pseudo(PseudoElement::After) { + layout_object.repair_style(context, self, &node.style(context)); + } + } + + if let Some(layout_object) = &*data.pseudo_marker_box.borrow() { + if let Some(node) = self.to_threadsafe().with_pseudo(PseudoElement::Marker) { + layout_object.repair_style(context, self, &node.style(context)); + } + } + } } diff --git a/components/layout/dom_traversal.rs b/components/layout/dom_traversal.rs index 42101e3edbc..8bf6d919fa3 100644 --- a/components/layout/dom_traversal.rs +++ b/components/layout/dom_traversal.rs @@ -8,11 +8,14 @@ use std::iter::FusedIterator; use fonts::ByteIndex; use html5ever::{LocalName, local_name}; use range::Range; -use script_layout_interface::wrapper_traits::{ThreadSafeLayoutElement, ThreadSafeLayoutNode}; +use script::layout_dom::ServoLayoutNode; +use script_layout_interface::wrapper_traits::{ + LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode, +}; use script_layout_interface::{LayoutElementType, LayoutNodeType}; use selectors::Element as SelectorsElement; use servo_arc::Arc as ServoArc; -use style::dom::{TElement, TShadowRoot}; +use style::dom::{NodeInfo, TElement, TNode, TShadowRoot}; use style::properties::ComputedValues; use style::selector_parser::PseudoElement; use style::values::generics::counters::{Content, ContentItem}; @@ -20,6 +23,7 @@ use style::values::specified::Quotes; use crate::context::LayoutContext; use crate::dom::{BoxSlot, LayoutBox, NodeExt}; +use crate::flow::inline::SharedInlineStyles; use crate::fragment_tree::{BaseFragmentInfo, FragmentFlags, Tag}; use crate::quotes::quotes_for_lang; use crate::replaced::ReplacedContents; @@ -28,15 +32,15 @@ use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside, DisplayOuts /// A data structure used to pass and store related layout information together to /// avoid having to repeat the same arguments in argument lists. #[derive(Clone)] -pub(crate) struct NodeAndStyleInfo { - pub node: Node, +pub(crate) struct NodeAndStyleInfo<'dom> { + pub node: ServoLayoutNode<'dom>, pub pseudo_element_type: Option, pub style: ServoArc, } -impl<'dom, Node: NodeExt<'dom>> NodeAndStyleInfo { +impl<'dom> NodeAndStyleInfo<'dom> { fn new_with_pseudo( - node: Node, + node: ServoLayoutNode<'dom>, pseudo_element_type: PseudoElement, style: ServoArc, ) -> Self { @@ -47,7 +51,7 @@ impl<'dom, Node: NodeExt<'dom>> NodeAndStyleInfo { } } - pub(crate) fn new(node: Node, style: ServoArc) -> Self { + pub(crate) fn new(node: ServoLayoutNode<'dom>, style: ServoArc) -> Self { Self { node, pseudo_element_type: None, @@ -86,11 +90,8 @@ impl<'dom, Node: NodeExt<'dom>> NodeAndStyleInfo { } } -impl<'dom, Node> From<&NodeAndStyleInfo> for BaseFragmentInfo -where - Node: NodeExt<'dom>, -{ - fn from(info: &NodeAndStyleInfo) -> Self { +impl<'dom> From<&NodeAndStyleInfo<'dom>> for BaseFragmentInfo { + fn from(info: &NodeAndStyleInfo<'dom>) -> Self { let node = info.node; let pseudo = info.pseudo_element_type; let threadsafe_node = node.to_threadsafe(); @@ -174,43 +175,37 @@ pub(super) enum PseudoElementContentItem { Replaced(ReplacedContents), } -pub(super) trait TraversalHandler<'dom, Node> -where - Node: 'dom, -{ - fn handle_text(&mut self, info: &NodeAndStyleInfo, text: Cow<'dom, str>); +pub(super) trait TraversalHandler<'dom> { + fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: Cow<'dom, str>); /// Or pseudo-element fn handle_element( &mut self, - info: &NodeAndStyleInfo, + info: &NodeAndStyleInfo<'dom>, display: DisplayGeneratingBox, contents: Contents, box_slot: BoxSlot<'dom>, ); + + /// Notify the handler that we are about to recurse into a `display: contents` element. + fn enter_display_contents(&mut self, _: SharedInlineStyles) {} + + /// Notify the handler that we have finished a `display: contents` element. + fn leave_display_contents(&mut self) {} } -fn traverse_children_of<'dom, Node>( - parent_element: Node, +fn traverse_children_of<'dom>( + parent_element: ServoLayoutNode<'dom>, context: &LayoutContext, - handler: &mut impl TraversalHandler<'dom, Node>, -) where - Node: NodeExt<'dom>, -{ + handler: &mut impl TraversalHandler<'dom>, +) { traverse_eager_pseudo_element(PseudoElement::Before, parent_element, context, handler); - let is_text_input_element = matches!( - parent_element.type_id(), - LayoutNodeType::Element(LayoutElementType::HTMLInputElement) - ); - - let is_textarea_element = matches!( - parent_element.type_id(), - LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) - ); - - if is_text_input_element || is_textarea_element { - let info = NodeAndStyleInfo::new(parent_element, parent_element.style(context)); + if parent_element.is_text_input() { + let info = NodeAndStyleInfo::new( + parent_element, + parent_element.style(context.shared_context()), + ); let node_text_content = parent_element.to_threadsafe().node_text_content(); if node_text_content.is_empty() { // The addition of zero-width space here forces the text input to have an inline formatting @@ -224,12 +219,10 @@ fn traverse_children_of<'dom, Node>( } else { handler.handle_text(&info, node_text_content); } - } - - if !is_text_input_element && !is_textarea_element { + } else { for child in iter_child_nodes(parent_element) { if child.is_text_node() { - let info = NodeAndStyleInfo::new(child, child.style(context)); + let info = NodeAndStyleInfo::new(child, child.style(context.shared_context())); handler.handle_text(&info, child.to_threadsafe().node_text_content()); } else if child.is_element() { traverse_element(child, context, handler); @@ -240,19 +233,17 @@ fn traverse_children_of<'dom, Node>( traverse_eager_pseudo_element(PseudoElement::After, parent_element, context, handler); } -fn traverse_element<'dom, Node>( - element: Node, +fn traverse_element<'dom>( + element: ServoLayoutNode<'dom>, context: &LayoutContext, - handler: &mut impl TraversalHandler<'dom, Node>, -) where - Node: NodeExt<'dom>, -{ + handler: &mut impl TraversalHandler<'dom>, +) { // Clear any existing pseudo-element box slot, because markers are not handled like // `::before`` and `::after`. They are processed during box tree creation. element.unset_pseudo_element_box(PseudoElement::Marker); let replaced = ReplacedContents::for_element(element, context); - let style = element.style(context); + let style = element.style(context.shared_context()); match Display::from(style.get_box().display) { Display::None => element.unset_all_boxes(), Display::Contents => { @@ -261,8 +252,15 @@ fn traverse_element<'dom, Node>( // element.unset_all_boxes() } else { - element.element_box_slot().set(LayoutBox::DisplayContents); - traverse_children_of(element, context, handler) + let shared_inline_styles: SharedInlineStyles = + (&NodeAndStyleInfo::new(element, style)).into(); + element + .element_box_slot() + .set(LayoutBox::DisplayContents(shared_inline_styles.clone())); + + handler.enter_display_contents(shared_inline_styles); + traverse_children_of(element, context, handler); + handler.leave_display_contents(); } }, Display::GeneratingBox(display) => { @@ -286,14 +284,12 @@ fn traverse_element<'dom, Node>( } } -fn traverse_eager_pseudo_element<'dom, Node>( +fn traverse_eager_pseudo_element<'dom>( pseudo_element_type: PseudoElement, - node: Node, + node: ServoLayoutNode<'dom>, context: &LayoutContext, - handler: &mut impl TraversalHandler<'dom, Node>, -) where - Node: NodeExt<'dom>, -{ + handler: &mut impl TraversalHandler<'dom>, +) { assert!(pseudo_element_type.is_eager()); // First clear any old contents from the node. @@ -317,8 +313,12 @@ fn traverse_eager_pseudo_element<'dom, Node>( Display::Contents => { let items = generate_pseudo_element_content(&info.style, node, context); let box_slot = node.pseudo_element_box_slot(pseudo_element_type); - box_slot.set(LayoutBox::DisplayContents); + let shared_inline_styles: SharedInlineStyles = (&info).into(); + box_slot.set(LayoutBox::DisplayContents(shared_inline_styles.clone())); + + handler.enter_display_contents(shared_inline_styles); traverse_pseudo_element_contents(&info, context, handler, items); + handler.leave_display_contents(); }, Display::GeneratingBox(display) => { let items = generate_pseudo_element_content(&info.style, node, context); @@ -329,14 +329,12 @@ fn traverse_eager_pseudo_element<'dom, Node>( } } -fn traverse_pseudo_element_contents<'dom, Node>( - info: &NodeAndStyleInfo, +fn traverse_pseudo_element_contents<'dom>( + info: &NodeAndStyleInfo<'dom>, context: &LayoutContext, - handler: &mut impl TraversalHandler<'dom, Node>, + handler: &mut impl TraversalHandler<'dom>, items: Vec, -) where - Node: NodeExt<'dom>, -{ +) { let mut anonymous_info = None; for item in items { match item { @@ -396,14 +394,12 @@ impl std::convert::TryFrom for NonReplacedContents { } impl NonReplacedContents { - pub(crate) fn traverse<'dom, Node>( + pub(crate) fn traverse<'dom>( self, context: &LayoutContext, - info: &NodeAndStyleInfo, - handler: &mut impl TraversalHandler<'dom, Node>, - ) where - Node: NodeExt<'dom>, - { + info: &NodeAndStyleInfo<'dom>, + handler: &mut impl TraversalHandler<'dom>, + ) { match self { NonReplacedContents::OfElement | NonReplacedContents::OfTextControl => { traverse_children_of(info.node, context, handler) @@ -427,14 +423,11 @@ where } /// -fn generate_pseudo_element_content<'dom, Node>( +fn generate_pseudo_element_content( pseudo_element_style: &ComputedValues, - element: Node, + element: ServoLayoutNode<'_>, context: &LayoutContext, -) -> Vec -where - Node: NodeExt<'dom>, -{ +) -> Vec { match &pseudo_element_style.get_counters().content { Content::Items(items) => { let mut vec = vec![]; @@ -517,18 +510,14 @@ where } } -pub enum ChildNodeIterator { +pub enum ChildNodeIterator<'dom> { /// Iterating over the children of a node - Node(Option), + Node(Option>), /// Iterating over the assigned nodes of a `HTMLSlotElement` - Slottables( as IntoIterator>::IntoIter), + Slottables(> as IntoIterator>::IntoIter), } -#[allow(clippy::unnecessary_to_owned)] // Clippy is wrong. -pub(crate) fn iter_child_nodes<'dom, Node>(parent: Node) -> ChildNodeIterator -where - Node: NodeExt<'dom>, -{ +pub(crate) fn iter_child_nodes(parent: ServoLayoutNode<'_>) -> ChildNodeIterator<'_> { if let Some(element) = parent.as_element() { if let Some(shadow) = element.shadow_root() { return iter_child_nodes(shadow.as_node()); @@ -536,6 +525,7 @@ where let slotted_nodes = element.slotted_nodes(); if !slotted_nodes.is_empty() { + #[allow(clippy::unnecessary_to_owned)] // Clippy is wrong. return ChildNodeIterator::Slottables(slotted_nodes.to_owned().into_iter()); } } @@ -544,11 +534,8 @@ where ChildNodeIterator::Node(first) } -impl<'dom, Node> Iterator for ChildNodeIterator -where - Node: NodeExt<'dom>, -{ - type Item = Node; +impl<'dom> Iterator for ChildNodeIterator<'dom> { + type Item = ServoLayoutNode<'dom>; fn next(&mut self) -> Option { match self { @@ -562,4 +549,4 @@ where } } -impl<'dom, Node> FusedIterator for ChildNodeIterator where Node: NodeExt<'dom> {} +impl FusedIterator for ChildNodeIterator<'_> {} diff --git a/components/layout/flexbox/layout.rs b/components/layout/flexbox/layout.rs index 77069236787..985f5f3cf65 100644 --- a/components/layout/flexbox/layout.rs +++ b/components/layout/flexbox/layout.rs @@ -29,8 +29,10 @@ use super::{FlexContainer, FlexContainerConfig, FlexItemBox, FlexLevelBox}; use crate::cell::ArcRefCell; use crate::context::LayoutContext; use crate::formatting_contexts::{Baselines, IndependentFormattingContextContents}; -use crate::fragment_tree::{BoxFragment, CollapsedBlockMargins, Fragment, FragmentFlags}; -use crate::geom::{AuOrAuto, LogicalRect, LogicalSides, LogicalVec2, Size, Sizes}; +use crate::fragment_tree::{ + BoxFragment, CollapsedBlockMargins, Fragment, FragmentFlags, SpecificLayoutInfo, +}; +use crate::geom::{AuOrAuto, LazySize, LogicalRect, LogicalSides, LogicalVec2, Size, Sizes}; use crate::layout_box_base::CacheableLayoutResult; use crate::positioned::{ AbsolutelyPositionedBox, PositioningContext, PositioningContextLength, relative_adjustement, @@ -49,7 +51,6 @@ use crate::{ struct FlexContext<'a> { config: FlexContainerConfig, layout_context: &'a LayoutContext<'a>, - positioning_context: &'a mut PositioningContext, containing_block: &'a ContainingBlock<'a>, // For items container_inner_size_constraint: FlexRelativeVec2, } @@ -143,6 +144,9 @@ struct FlexItemLayoutResult { // Whether or not this layout had a child that dependeded on block constraints. has_child_which_depends_on_block_constraints: bool, + + // The specific layout info that this flex item had. + specific_layout_info: Option, } impl FlexItemLayoutResult { @@ -296,7 +300,8 @@ impl FlexLineItem<'_> { .sides_to_flow_relative(item_margin) .to_physical(container_writing_mode), None, /* clearance */ - ); + ) + .with_specific_layout_info(self.layout_result.specific_layout_info); // If this flex item establishes a containing block for absolutely-positioned // descendants, then lay out any relevant absolutely-positioned children. This @@ -412,9 +417,8 @@ struct DesiredFlexFractionAndGrowOrShrinkFactor { #[derive(Default)] struct FlexItemBoxInlineContentSizesInfo { outer_flex_base_size: Au, - content_min_main_size: Au, - content_max_main_size: Option, - pbm_auto_is_zero: FlexRelativeVec2, + outer_min_main_size: Au, + outer_max_main_size: Option, min_flex_factors: DesiredFlexFractionAndGrowOrShrinkFactor, max_flex_factors: DesiredFlexFractionAndGrowOrShrinkFactor, min_content_main_size_for_multiline_container: Au, @@ -578,9 +582,8 @@ impl FlexContainer { for FlexItemBoxInlineContentSizesInfo { outer_flex_base_size, - content_min_main_size, - content_max_main_size, - pbm_auto_is_zero, + outer_min_main_size, + outer_max_main_size, min_flex_factors, max_flex_factors, min_content_main_size_for_multiline_container, @@ -590,16 +593,13 @@ impl FlexContainer { // > 4. Add each item’s flex base size to the product of its flex grow factor (scaled flex shrink // > factor, if shrinking) and the chosen flex fraction, then clamp that result by the max main size // > floored by the min main size. - let outer_min_main_size = *content_min_main_size + pbm_auto_is_zero.main; - let outer_max_main_size = content_max_main_size.map(|v| v + pbm_auto_is_zero.main); - // > 5. The flex container’s max-content size is the largest sum (among all the lines) of the // > afore-calculated sizes of all items within a single line. container_max_content_size += (*outer_flex_base_size + Au::from_f32_px( max_flex_factors.flex_grow_or_shrink_factor * chosen_max_flex_fraction, )) - .clamp_between_extremums(outer_min_main_size, outer_max_main_size); + .clamp_between_extremums(*outer_min_main_size, *outer_max_main_size); // > The min-content main size of a single-line flex container is calculated // > identically to the max-content main size, except that the flex items’ @@ -616,7 +616,7 @@ impl FlexContainer { Au::from_f32_px( min_flex_factors.flex_grow_or_shrink_factor * chosen_min_flex_fraction, )) - .clamp_between_extremums(outer_min_main_size, outer_max_main_size); + .clamp_between_extremums(*outer_min_main_size, *outer_max_main_size); } else { container_min_content_size .max_assign(*min_content_main_size_for_multiline_container); @@ -650,6 +650,7 @@ impl FlexContainer { positioning_context: &mut PositioningContext, containing_block: &ContainingBlock, depends_on_block_constraints: bool, + lazy_block_size: &LazySize, ) -> CacheableLayoutResult { let depends_on_block_constraints = depends_on_block_constraints || self.config.flex_direction == FlexDirection::Column; @@ -657,7 +658,6 @@ impl FlexContainer { let mut flex_context = FlexContext { config: self.config.clone(), layout_context, - positioning_context, containing_block, // https://drafts.csswg.org/css-flexbox/#definite-sizes container_inner_size_constraint: self.config.flex_axis.vec2_to_flex_relative( @@ -672,14 +672,13 @@ impl FlexContainer { // https://drafts.csswg.org/css-flexbox/#algo-main-container let container_main_size = match self.config.flex_axis { FlexAxis::Row => containing_block.size.inline, - FlexAxis::Column => match containing_block.size.block { - SizeConstraint::Definite(size) => size, - SizeConstraint::MinMax(min, max) => self - .main_content_sizes(layout_context, &containing_block.into(), || &flex_context) + FlexAxis::Column => lazy_block_size.resolve(|| { + let mut containing_block = IndefiniteContainingBlock::from(containing_block); + containing_block.size.block = None; + self.main_content_sizes(layout_context, &containing_block, || &flex_context) .sizes .max_content - .clamp_between_extremums(min, max), - }, + }), }; // Actual length may be less, but we guess that usually not by a lot @@ -762,30 +761,23 @@ impl FlexContainer { .map(|layout| layout.line_size.cross) .sum::() + cross_gap * (line_count as i32 - 1); + let content_block_size = match self.config.flex_axis { + FlexAxis::Row => content_cross_size, + FlexAxis::Column => container_main_size, + }; // https://drafts.csswg.org/css-flexbox/#algo-cross-container - let container_cross_size = match flex_context.container_inner_size_constraint.cross { - SizeConstraint::Definite(cross_size) => cross_size, - SizeConstraint::MinMax(min, max) => { - content_cross_size.clamp_between_extremums(min, max) - }, + let container_cross_size = match self.config.flex_axis { + FlexAxis::Row => lazy_block_size.resolve(|| content_cross_size), + FlexAxis::Column => containing_block.size.inline, }; let container_size = FlexRelativeVec2 { main: container_main_size, cross: container_cross_size, }; - let content_block_size = flex_context - .config - .flex_axis - .vec2_to_flow_relative(container_size) - .block; - let mut remaining_free_cross_space = - match flex_context.container_inner_size_constraint.cross { - SizeConstraint::Definite(cross_size) => cross_size - content_cross_size, - _ => Au::zero(), - }; + let mut remaining_free_cross_space = container_cross_size - content_cross_size; // Implement fallback alignment. // @@ -1774,16 +1766,8 @@ impl FlexItem<'_> { non_stretch_layout_result: Option<&mut FlexItemLayoutResult>, ) -> Option { let containing_block = flex_context.containing_block; - let mut positioning_context = PositioningContext::new_for_style(self.box_.style()) - .unwrap_or_else(|| { - PositioningContext::new_for_subtree( - flex_context - .positioning_context - .collects_for_nearest_positioned_ancestor(), - ) - }); - let independent_formatting_context = &self.box_.independent_formatting_context; + let mut positioning_context = PositioningContext::default(); let item_writing_mode = independent_formatting_context.style().writing_mode; let item_is_horizontal = item_writing_mode.is_horizontal(); let flex_axis = flex_context.config.flex_axis; @@ -1920,6 +1904,7 @@ impl FlexItem<'_> { // size can differ from the hypothetical cross size, we should defer // synthesizing until needed. baseline_relative_to_margin_box: None, + specific_layout_info: None, }) }, IndependentFormattingContextContents::NonReplaced(non_replaced) => { @@ -1939,6 +1924,29 @@ impl FlexItem<'_> { } } + let lazy_block_size = if !cross_axis_is_item_block_axis { + used_main_size.into() + } else if let Some(cross_size) = used_cross_size_override { + cross_size.into() + } else { + // This means that an auto size with stretch alignment will behave different than + // a stretch size. That's not what the spec says, but matches other browsers. + // To be discussed in https://github.com/w3c/csswg-drafts/issues/11784. + let stretch_size = containing_block + .size + .block + .to_definite() + .map(|size| Au::zero().max(size - self.pbm_auto_is_zero.cross)); + LazySize::new( + &self.content_cross_sizes, + Direction::Block, + Size::FitContent, + Au::zero, + stretch_size, + self.is_table(), + ) + }; + let layout = non_replaced.layout( flex_context.layout_context, &mut positioning_context, @@ -1948,12 +1956,14 @@ impl FlexItem<'_> { flex_axis == FlexAxis::Column || self.cross_size_stretches_to_line || self.depends_on_block_constraints, + &lazy_block_size, ); let CacheableLayoutResult { fragments, content_block_size, baselines: content_box_baselines, depends_on_block_constraints, + specific_layout_info, .. } = layout; @@ -1964,22 +1974,7 @@ impl FlexItem<'_> { }); let hypothetical_cross_size = if cross_axis_is_item_block_axis { - // This means that an auto size with stretch alignment will behave different than - // a stretch size. That's not what the spec says, but matches other browsers. - // To be discussed in https://github.com/w3c/csswg-drafts/issues/11784. - let stretch_size = containing_block - .size - .block - .to_definite() - .map(|size| Au::zero().max(size - self.pbm_auto_is_zero.cross)); - self.content_cross_sizes.resolve( - Direction::Block, - Size::FitContent, - Au::zero, - stretch_size, - || content_block_size.into(), - self.is_table(), - ) + lazy_block_size.resolve(|| content_block_size) } else { inline_size }; @@ -2022,6 +2017,7 @@ impl FlexItem<'_> { containing_block_block_size: item_as_containing_block.size.block, depends_on_block_constraints, has_child_which_depends_on_block_constraints, + specific_layout_info, }) }, } @@ -2457,6 +2453,8 @@ impl FlexItemBox { }; let outer_flex_base_size = flex_base_size + pbm_auto_is_zero.main; + let outer_min_main_size = content_min_main_size + pbm_auto_is_zero.main; + let outer_max_main_size = content_max_main_size.map(|v| v + pbm_auto_is_zero.main); let max_flex_factors = self.desired_flex_factors_for_preferred_width( content_contribution_sizes.max_content, flex_base_size, @@ -2482,20 +2480,19 @@ impl FlexItemBox { content_contribution_sizes.min_content; let style_position = &self.style().get_position(); if style_position.flex_grow.is_zero() { - min_content_main_size_for_multiline_container.min_assign(flex_base_size); + min_content_main_size_for_multiline_container.min_assign(outer_flex_base_size); } if style_position.flex_shrink.is_zero() { - min_content_main_size_for_multiline_container.max_assign(flex_base_size); + min_content_main_size_for_multiline_container.max_assign(outer_flex_base_size); } min_content_main_size_for_multiline_container = min_content_main_size_for_multiline_container - .clamp_between_extremums(content_min_main_size, content_max_main_size); + .clamp_between_extremums(outer_min_main_size, outer_max_main_size); FlexItemBoxInlineContentSizesInfo { outer_flex_base_size, - content_min_main_size, - content_max_main_size, - pbm_auto_is_zero, + outer_min_main_size, + outer_max_main_size, min_flex_factors, max_flex_factors, min_content_main_size_for_multiline_container, @@ -2616,15 +2613,7 @@ impl FlexItemBox { cross_size_stretches_to_container_size: bool, intrinsic_sizing_mode: IntrinsicSizingMode, ) -> Au { - let mut positioning_context = PositioningContext::new_for_style(self.style()) - .unwrap_or_else(|| { - PositioningContext::new_for_subtree( - flex_context - .positioning_context - .collects_for_nearest_positioned_ancestor(), - ) - }); - + let mut positioning_context = PositioningContext::default(); let style = self.independent_formatting_context.style(); match &self.independent_formatting_context.contents { IndependentFormattingContextContents::Replaced(replaced) => { @@ -2696,6 +2685,7 @@ impl FlexItemBox { flex_context.containing_block, &self.independent_formatting_context.base, false, /* depends_on_block_constraints */ + &LazySize::intrinsic(), ) .content_block_size }; diff --git a/components/layout/flexbox/mod.rs b/components/layout/flexbox/mod.rs index e1f8213f1e9..7f4a869a944 100644 --- a/components/layout/flexbox/mod.rs +++ b/components/layout/flexbox/mod.rs @@ -4,7 +4,9 @@ use geom::{FlexAxis, MainStartCrossStart}; use malloc_size_of_derive::MallocSizeOf; +use script::layout_dom::ServoLayoutNode; use servo_arc::Arc as ServoArc; +use style::context::SharedStyleContext; use style::logical_geometry::WritingMode; use style::properties::ComputedValues; use style::properties::longhands::align_items::computed_value::T as AlignItems; @@ -17,7 +19,7 @@ use crate::PropagatedBoxTreeData; use crate::cell::ArcRefCell; use crate::construct_modern::{ModernContainerBuilder, ModernItemKind}; use crate::context::LayoutContext; -use crate::dom::{LayoutBox, NodeExt}; +use crate::dom::LayoutBox; use crate::dom_traversal::{NodeAndStyleInfo, NonReplacedContents}; use crate::formatting_contexts::IndependentFormattingContext; use crate::fragment_tree::{BaseFragmentInfo, Fragment}; @@ -90,7 +92,6 @@ impl FlexContainerConfig { pub(crate) struct FlexContainer { children: Vec>, - #[conditional_malloc_size_of] style: ServoArc, /// The configuration of this [`FlexContainer`]. @@ -98,14 +99,13 @@ pub(crate) struct FlexContainer { } impl FlexContainer { - pub fn construct<'dom>( + pub fn construct( context: &LayoutContext, - info: &NodeAndStyleInfo>, + info: &NodeAndStyleInfo<'_>, contents: NonReplacedContents, propagated_data: PropagatedBoxTreeData, ) -> Self { - let mut builder = - ModernContainerBuilder::new(context, info, propagated_data.union(&info.style)); + let mut builder = ModernContainerBuilder::new(context, info, propagated_data); contents.traverse(context, info, &mut builder); let items = builder.finish(); @@ -137,6 +137,11 @@ impl FlexContainer { config: FlexContainerConfig::new(&info.style), } } + + pub(crate) fn repair_style(&mut self, new_style: &ServoArc) { + self.config = FlexContainerConfig::new(new_style); + self.style = new_style.clone(); + } } #[derive(Debug, MallocSizeOf)] @@ -146,6 +151,23 @@ pub(crate) enum FlexLevelBox { } impl FlexLevelBox { + pub(crate) fn repair_style( + &mut self, + context: &SharedStyleContext, + node: &ServoLayoutNode, + new_style: &ServoArc, + ) { + match self { + FlexLevelBox::FlexItem(flex_item_box) => flex_item_box + .independent_formatting_context + .repair_style(context, node, new_style), + FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => positioned_box + .borrow_mut() + .context + .repair_style(context, node, new_style), + } + } + pub(crate) fn invalidate_cached_fragment(&self) { match self { FlexLevelBox::FlexItem(flex_item_box) => flex_item_box diff --git a/components/layout/flow/construct.rs b/components/layout/flow/construct.rs index a6471756db8..cc3fe0e6f44 100644 --- a/components/layout/flow/construct.rs +++ b/components/layout/flow/construct.rs @@ -13,9 +13,9 @@ use style::selector_parser::PseudoElement; use style::str::char_is_whitespace; use super::OutsideMarker; -use super::inline::InlineFormattingContext; use super::inline::construct::InlineFormattingContextBuilder; use super::inline::inline_box::InlineBox; +use super::inline::{InlineFormattingContext, SharedInlineStyles}; use crate::PropagatedBoxTreeData; use crate::cell::ArcRefCell; use crate::context::LayoutContext; @@ -33,16 +33,13 @@ use crate::style_ext::{ComputedValuesExt, DisplayGeneratingBox, DisplayInside, D use crate::table::{AnonymousTableContent, Table}; impl BlockFormattingContext { - pub(crate) fn construct<'dom, Node>( + pub(crate) fn construct( context: &LayoutContext, - info: &NodeAndStyleInfo, + info: &NodeAndStyleInfo<'_>, contents: NonReplacedContents, propagated_data: PropagatedBoxTreeData, is_list_item: bool, - ) -> Self - where - Node: NodeExt<'dom>, - { + ) -> Self { Self::from_block_container(BlockContainer::construct( context, info, @@ -61,8 +58,8 @@ impl BlockFormattingContext { } } -struct BlockLevelJob<'dom, Node> { - info: NodeAndStyleInfo, +struct BlockLevelJob<'dom> { + info: NodeAndStyleInfo<'dom>, box_slot: BoxSlot<'dom>, propagated_data: PropagatedBoxTreeData, kind: BlockLevelCreator, @@ -111,12 +108,12 @@ enum IntermediateBlockContainer { /// /// This builder starts from the first child of a given DOM node /// and does a preorder traversal of all of its inclusive siblings. -pub(crate) struct BlockContainerBuilder<'dom, 'style, Node> { +pub(crate) struct BlockContainerBuilder<'dom, 'style> { context: &'style LayoutContext<'style>, /// This NodeAndStyleInfo contains the root node, the corresponding pseudo /// content designator, and the block container style. - info: &'style NodeAndStyleInfo, + info: &'style NodeAndStyleInfo<'dom>, /// The list of block-level boxes to be built for the final block container. /// @@ -131,7 +128,7 @@ pub(crate) struct BlockContainerBuilder<'dom, 'style, Node> { /// doesn't have a next sibling, we either reached the end of the container /// root or there are ongoing inline-level boxes /// (see `handle_block_level_element`). - block_level_boxes: Vec>, + block_level_boxes: Vec>, /// Whether or not this builder has yet produced a block which would be /// be considered the first line for the purposes of `text-indent`. @@ -140,29 +137,35 @@ pub(crate) struct BlockContainerBuilder<'dom, 'style, Node> { /// The propagated data to use for BoxTree construction. propagated_data: PropagatedBoxTreeData, - inline_formatting_context_builder: InlineFormattingContextBuilder, + /// The [`InlineFormattingContextBuilder`] if we have encountered any inline items, + /// otherwise None. + /// + /// TODO: This can be `OnceCell` once `OnceCell::get_mut_or_init` is stabilized. + inline_formatting_context_builder: Option, /// The [`NodeAndStyleInfo`] to use for anonymous block boxes pushed to the list of - /// block-level boxes, lazily initialized (see `end_ongoing_inline_formatting_context`). - anonymous_box_info: Option>, + /// block-level boxes, lazily initialized. + anonymous_box_info: Option>, /// A collection of content that is being added to an anonymous table. This is /// composed of any sequence of internal table elements or table captions that /// are found outside of a table. - anonymous_table_content: Vec>, + anonymous_table_content: Vec>, + + /// Any [`InlineFormattingContexts`] created need to know about the ongoing `display: contents` + /// ancestors that have been processed. This `Vec` allows passing those into new + /// [`InlineFormattingContext`]s that we create. + display_contents_shared_styles: Vec, } impl BlockContainer { - pub fn construct<'dom, Node>( + pub fn construct( context: &LayoutContext, - info: &NodeAndStyleInfo, + info: &NodeAndStyleInfo<'_>, contents: NonReplacedContents, propagated_data: PropagatedBoxTreeData, is_list_item: bool, - ) -> BlockContainer - where - Node: NodeExt<'dom>, - { + ) -> BlockContainer { let mut builder = BlockContainerBuilder::new(context, info, propagated_data); if is_list_item { @@ -186,43 +189,57 @@ impl BlockContainer { } } -impl<'dom, 'style, Node> BlockContainerBuilder<'dom, 'style, Node> -where - Node: NodeExt<'dom>, -{ +impl<'dom, 'style> BlockContainerBuilder<'dom, 'style> { pub(crate) fn new( context: &'style LayoutContext, - info: &'style NodeAndStyleInfo, + info: &'style NodeAndStyleInfo<'dom>, propagated_data: PropagatedBoxTreeData, ) -> Self { BlockContainerBuilder { context, info, block_level_boxes: Vec::new(), - propagated_data: propagated_data.union(&info.style), + propagated_data, have_already_seen_first_line_for_text_indent: false, anonymous_box_info: None, anonymous_table_content: Vec::new(), - inline_formatting_context_builder: InlineFormattingContextBuilder::new(), + inline_formatting_context_builder: None, + display_contents_shared_styles: Vec::new(), } } - pub(crate) fn finish(mut self) -> BlockContainer { - debug_assert!( - !self - .inline_formatting_context_builder - .currently_processing_inline_box() - ); + fn currently_processing_inline_box(&self) -> bool { + self.inline_formatting_context_builder + .as_ref() + .is_some_and(InlineFormattingContextBuilder::currently_processing_inline_box) + } - self.finish_anonymous_table_if_needed(); + fn ensure_inline_formatting_context_builder(&mut self) -> &mut InlineFormattingContextBuilder { + self.inline_formatting_context_builder + .get_or_insert_with(|| { + let mut builder = InlineFormattingContextBuilder::new(self.info); + for shared_inline_styles in self.display_contents_shared_styles.iter() { + builder.enter_display_contents(shared_inline_styles.clone()); + } + builder + }) + } - if let Some(inline_formatting_context) = self.inline_formatting_context_builder.finish( + fn finish_ongoing_inline_formatting_context(&mut self) -> Option { + self.inline_formatting_context_builder.take()?.finish( self.context, - self.propagated_data, !self.have_already_seen_first_line_for_text_indent, self.info.is_single_line_text_input(), self.info.style.writing_mode.to_bidi_level(), - ) { + ) + } + + pub(crate) fn finish(mut self) -> BlockContainer { + debug_assert!(!self.currently_processing_inline_box()); + + self.finish_anonymous_table_if_needed(); + + if let Some(inline_formatting_context) = self.finish_ongoing_inline_formatting_context() { // There are two options here. This block was composed of both one or more inline formatting contexts // and child blocks OR this block was a single inline formatting context. In the latter case, we // just return the inline formatting context as the block itself. @@ -260,21 +277,9 @@ where // // Note that text content in the inline formatting context isn't enough to force the // creation of an inline table. It requires the parent to be an inline box. - let inline_table = self - .inline_formatting_context_builder - .currently_processing_inline_box(); + let inline_table = self.currently_processing_inline_box(); - // Text decorations are not propagated to atomic inline-level descendants. - // From https://drafts.csswg.org/css2/#lining-striking-props: - // > Note that text decorations are not propagated to floating and absolutely - // > positioned descendants, nor to the contents of atomic inline-level descendants - // > such as inline blocks and inline tables. - let propagated_data = match inline_table { - true => self.propagated_data.without_text_decorations(), - false => self.propagated_data, - }; - - let contents: Vec> = + let contents: Vec> = self.anonymous_table_content.drain(..).collect(); let last_text = match contents.last() { Some(AnonymousTableContent::Text(info, text)) => Some((info.clone(), text.clone())), @@ -282,18 +287,24 @@ where }; let (table_info, ifc) = - Table::construct_anonymous(self.context, self.info, contents, propagated_data); + Table::construct_anonymous(self.context, self.info, contents, self.propagated_data); if inline_table { - self.inline_formatting_context_builder.push_atomic(ifc); + self.ensure_inline_formatting_context_builder() + .push_atomic(ifc); } else { let table_block = ArcRefCell::new(BlockLevelBox::Independent(ifc)); - self.end_ongoing_inline_formatting_context(); + + if let Some(inline_formatting_context) = self.finish_ongoing_inline_formatting_context() + { + self.push_block_level_job_for_inline_formatting_context(inline_formatting_context); + } + self.block_level_boxes.push(BlockLevelJob { info: table_info, box_slot: BoxSlot::dummy(), kind: BlockLevelCreator::AnonymousTable { table_block }, - propagated_data, + propagated_data: self.propagated_data, }); } @@ -312,13 +323,10 @@ where } } -impl<'dom, Node> TraversalHandler<'dom, Node> for BlockContainerBuilder<'dom, '_, Node> -where - Node: NodeExt<'dom>, -{ +impl<'dom> TraversalHandler<'dom> for BlockContainerBuilder<'dom, '_> { fn handle_element( &mut self, - info: &NodeAndStyleInfo, + info: &NodeAndStyleInfo<'dom>, display: DisplayGeneratingBox, contents: Contents, box_slot: BoxSlot<'dom>, @@ -359,7 +367,7 @@ where } } - fn handle_text(&mut self, info: &NodeAndStyleInfo, text: Cow<'dom, str>) { + fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: Cow<'dom, str>) { if text.is_empty() { return; } @@ -375,18 +383,30 @@ where self.finish_anonymous_table_if_needed(); } - self.inline_formatting_context_builder.push_text(text, info); + self.ensure_inline_formatting_context_builder() + .push_text(text, info); + } + + fn enter_display_contents(&mut self, styles: SharedInlineStyles) { + self.display_contents_shared_styles.push(styles.clone()); + if let Some(builder) = self.inline_formatting_context_builder.as_mut() { + builder.enter_display_contents(styles); + } + } + + fn leave_display_contents(&mut self) { + self.display_contents_shared_styles.pop(); + if let Some(builder) = self.inline_formatting_context_builder.as_mut() { + builder.leave_display_contents(); + } } } -impl<'dom, Node> BlockContainerBuilder<'dom, '_, Node> -where - Node: NodeExt<'dom>, -{ +impl<'dom> BlockContainerBuilder<'dom, '_> { fn handle_list_item_marker_inside( &mut self, - marker_info: &NodeAndStyleInfo, - container_info: &NodeAndStyleInfo, + marker_info: &NodeAndStyleInfo<'dom>, + container_info: &NodeAndStyleInfo<'dom>, contents: Vec, ) { // TODO: We do not currently support saving box slots for ::marker pseudo-elements @@ -411,8 +431,8 @@ where fn handle_list_item_marker_outside( &mut self, - marker_info: &NodeAndStyleInfo, - container_info: &NodeAndStyleInfo, + marker_info: &NodeAndStyleInfo<'dom>, + container_info: &NodeAndStyleInfo<'dom>, contents: Vec, list_item_style: Arc, ) { @@ -433,13 +453,13 @@ where contents, list_item_style, }, - propagated_data: self.propagated_data.without_text_decorations(), + propagated_data: self.propagated_data, }); } fn handle_inline_level_element( &mut self, - info: &NodeAndStyleInfo, + info: &NodeAndStyleInfo<'dom>, display_inside: DisplayInside, contents: Contents, box_slot: BoxSlot<'dom>, @@ -448,25 +468,25 @@ where (display_inside, contents.is_replaced()) else { // If this inline element is an atomic, handle it and return. - let atomic = self.inline_formatting_context_builder.push_atomic( + let context = self.context; + let propagated_data = self.propagated_data; + let atomic = self.ensure_inline_formatting_context_builder().push_atomic( IndependentFormattingContext::construct( - self.context, + context, info, display_inside, contents, - // Text decorations are not propagated to atomic inline-level descendants. - self.propagated_data.without_text_decorations(), + propagated_data, ), ); - box_slot.set(LayoutBox::InlineLevel(atomic)); + box_slot.set(LayoutBox::InlineLevel(vec![atomic])); return; }; // Otherwise, this is just a normal inline box. Whatever happened before, all we need to do // before recurring is to remember this ongoing inline level box. - let inline_item = self - .inline_formatting_context_builder - .start_inline_box(InlineBox::new(info)); + self.ensure_inline_formatting_context_builder() + .start_inline_box(InlineBox::new(info), None); if is_list_item { if let Some((marker_info, marker_contents)) = @@ -486,13 +506,22 @@ where self.finish_anonymous_table_if_needed(); - self.inline_formatting_context_builder.end_inline_box(); - box_slot.set(LayoutBox::InlineLevel(inline_item)); + // As we are ending this inline box, during the course of the `traverse()` above, the ongoing + // inline formatting context may have been split around block-level elements. In that case, + // more than a single inline box tree item may have been produced for this inline-level box. + // `InlineFormattingContextBuilder::end_inline_box()` is returning all of those box tree + // items. + box_slot.set(LayoutBox::InlineLevel( + self.inline_formatting_context_builder + .as_mut() + .expect("Should be building an InlineFormattingContext") + .end_inline_box(), + )); } fn handle_block_level_element( &mut self, - info: &NodeAndStyleInfo, + info: &NodeAndStyleInfo<'dom>, display_inside: DisplayInside, contents: Contents, box_slot: BoxSlot<'dom>, @@ -505,12 +534,14 @@ where // that we want to have after we push the block below. if let Some(inline_formatting_context) = self .inline_formatting_context_builder - .split_around_block_and_finish( - self.context, - self.propagated_data, - !self.have_already_seen_first_line_for_text_indent, - self.info.style.writing_mode.to_bidi_level(), - ) + .as_mut() + .and_then(|builder| { + builder.split_around_block_and_finish( + self.context, + !self.have_already_seen_first_line_for_text_indent, + self.info.style.writing_mode.to_bidi_level(), + ) + }) { self.push_block_level_job_for_inline_formatting_context(inline_formatting_context); } @@ -560,22 +591,23 @@ where fn handle_absolutely_positioned_element( &mut self, - info: &NodeAndStyleInfo, + info: &NodeAndStyleInfo<'dom>, display_inside: DisplayInside, contents: Contents, box_slot: BoxSlot<'dom>, ) { - if !self.inline_formatting_context_builder.is_empty() { - let inline_level_box = self - .inline_formatting_context_builder - .push_absolutely_positioned_box(AbsolutelyPositionedBox::construct( - self.context, - info, - display_inside, - contents, - )); - box_slot.set(LayoutBox::InlineLevel(inline_level_box)); - return; + if let Some(builder) = self.inline_formatting_context_builder.as_mut() { + if !builder.is_empty() { + let inline_level_box = + builder.push_absolutely_positioned_box(AbsolutelyPositionedBox::construct( + self.context, + info, + display_inside, + contents, + )); + box_slot.set(LayoutBox::InlineLevel(vec![inline_level_box])); + return; + } } let kind = BlockLevelCreator::OutOfFlowAbsolutelyPositionedBox { @@ -586,29 +618,29 @@ where info: info.clone(), box_slot, kind, - propagated_data: self.propagated_data.without_text_decorations(), + propagated_data: self.propagated_data, }); } fn handle_float_element( &mut self, - info: &NodeAndStyleInfo, + info: &NodeAndStyleInfo<'dom>, display_inside: DisplayInside, contents: Contents, box_slot: BoxSlot<'dom>, ) { - if !self.inline_formatting_context_builder.is_empty() { - let inline_level_box = - self.inline_formatting_context_builder - .push_float_box(FloatBox::construct( - self.context, - info, - display_inside, - contents, - self.propagated_data, - )); - box_slot.set(LayoutBox::InlineLevel(inline_level_box)); - return; + if let Some(builder) = self.inline_formatting_context_builder.as_mut() { + if !builder.is_empty() { + let inline_level_box = builder.push_float_box(FloatBox::construct( + self.context, + info, + display_inside, + contents, + self.propagated_data, + )); + box_slot.set(LayoutBox::InlineLevel(vec![inline_level_box])); + return; + } } let kind = BlockLevelCreator::OutOfFlowFloatBox { @@ -619,22 +651,10 @@ where info: info.clone(), box_slot, kind, - propagated_data: self.propagated_data.without_text_decorations(), + propagated_data: self.propagated_data, }); } - fn end_ongoing_inline_formatting_context(&mut self) { - if let Some(inline_formatting_context) = self.inline_formatting_context_builder.finish( - self.context, - self.propagated_data, - !self.have_already_seen_first_line_for_text_indent, - self.info.is_single_line_text_input(), - self.info.style.writing_mode.to_bidi_level(), - ) { - self.push_block_level_job_for_inline_formatting_context(inline_formatting_context); - } - } - fn push_block_level_job_for_inline_formatting_context( &mut self, inline_formatting_context: InlineFormattingContext, @@ -665,10 +685,7 @@ where } } -impl<'dom, Node> BlockLevelJob<'dom, Node> -where - Node: NodeExt<'dom>, -{ +impl BlockLevelJob<'_> { fn finish(self, context: &LayoutContext) -> ArcRefCell { let info = &self.info; let block_level_box = match self.kind { @@ -724,7 +741,7 @@ where context, info, contents, - self.propagated_data.without_text_decorations(), + self.propagated_data, false, /* is_list_item */ ); ArcRefCell::new(BlockLevelBox::OutsideMarker(OutsideMarker { @@ -742,14 +759,7 @@ where } impl IntermediateBlockContainer { - fn finish<'dom, Node>( - self, - context: &LayoutContext, - info: &NodeAndStyleInfo, - ) -> BlockContainer - where - Node: NodeExt<'dom>, - { + fn finish(self, context: &LayoutContext, info: &NodeAndStyleInfo<'_>) -> BlockContainer { match self { IntermediateBlockContainer::Deferred { contents, diff --git a/components/layout/flow/float.rs b/components/layout/flow/float.rs index 0570ce0d0f4..bb6c7ca8983 100644 --- a/components/layout/flow/float.rs +++ b/components/layout/flow/float.rs @@ -22,7 +22,6 @@ use style::properties::ComputedValues; use style::values::computed::Clear as StyleClear; use crate::context::LayoutContext; -use crate::dom::NodeExt; use crate::dom_traversal::{Contents, NodeAndStyleInfo}; use crate::formatting_contexts::IndependentFormattingContext; use crate::fragment_tree::{BoxFragment, CollapsedMargin}; @@ -885,9 +884,9 @@ impl FloatBandLink { impl FloatBox { /// Creates a new float box. - pub fn construct<'dom>( + pub fn construct( context: &LayoutContext, - info: &NodeAndStyleInfo>, + info: &NodeAndStyleInfo<'_>, display_inside: DisplayInside, contents: Contents, propagated_data: PropagatedBoxTreeData, @@ -898,8 +897,7 @@ impl FloatBox { info, display_inside, contents, - // Text decorations are not propagated to any out-of-flow descendants - propagated_data.without_text_decorations(), + propagated_data, ), } } @@ -913,11 +911,10 @@ impl FloatBox { positioning_context: &mut PositioningContext, containing_block: &ContainingBlock, ) -> BoxFragment { - let style = self.contents.style().clone(); positioning_context.layout_maybe_position_relative_fragment( layout_context, containing_block, - &style, + &self.contents.base, |positioning_context| { self.contents .layout_float_or_atomic_inline( diff --git a/components/layout/flow/inline/construct.rs b/components/layout/flow/inline/construct.rs index 7c668751ef6..07a2e914835 100644 --- a/components/layout/flow/inline/construct.rs +++ b/components/layout/flow/inline/construct.rs @@ -6,17 +6,18 @@ use std::borrow::Cow; use std::char::{ToLowercase, ToUppercase}; use icu_segmenter::WordSegmenter; -use servo_arc::Arc; +use itertools::izip; use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse; use style::values::specified::text::TextTransformCase; use unicode_bidi::Level; use super::text_run::TextRun; -use super::{InlineBox, InlineBoxIdentifier, InlineBoxes, InlineFormattingContext, InlineItem}; -use crate::PropagatedBoxTreeData; +use super::{ + InlineBox, InlineBoxIdentifier, InlineBoxes, InlineFormattingContext, InlineItem, + SharedInlineStyles, +}; use crate::cell::ArcRefCell; use crate::context::LayoutContext; -use crate::dom::NodeExt; use crate::dom_traversal::NodeAndStyleInfo; use crate::flow::float::FloatBox; use crate::formatting_contexts::IndependentFormattingContext; @@ -25,6 +26,12 @@ use crate::style_ext::ComputedValuesExt; #[derive(Default)] pub(crate) struct InlineFormattingContextBuilder { + /// A stack of [`SharedInlineStyles`] including one for the root, one for each inline box on the + /// inline box stack, and importantly, one for every `display: contents` element that we are + /// currently processing. Normally `display: contents` elements don't affect the structure of + /// the [`InlineFormattingContext`], but the styles they provide do style their children. + pub shared_inline_styles_stack: Vec, + /// The collection of text strings that make up this [`InlineFormattingContext`] under /// construction. pub text_segments: Vec, @@ -63,20 +70,37 @@ pub(crate) struct InlineFormattingContextBuilder { /// The traversal is at all times as deep in the tree as this stack is, /// which is why the code doesn't need to keep track of the actual /// container root (see `handle_inline_level_element`). - /// + //_ /// When an inline box ends, it's removed from this stack. inline_box_stack: Vec, + /// Normally, an inline box produces a single box tree [`InlineItem`]. When a block + /// element causes an inline box [to be split], it can produce multiple + /// [`InlineItem`]s, all inserted into different [`InlineFormattingContext`]s. + /// [`Self::block_in_inline_splits`] is responsible for tracking all of these split + /// inline box results, so that they can be inserted into the [`crate::dom::BoxSlot`] + /// for the DOM element once it has been processed for BoxTree construction. + /// + /// [to be split]: https://www.w3.org/TR/CSS2/visuren.html#anonymous-block-level + block_in_inline_splits: Vec>>, + /// Whether or not the inline formatting context under construction has any /// uncollapsible text content. pub has_uncollapsible_text_content: bool, } impl InlineFormattingContextBuilder { - pub(crate) fn new() -> Self { - // For the purposes of `text-transform: capitalize` the start of the IFC is a word boundary. + pub(crate) fn new(info: &NodeAndStyleInfo) -> Self { + Self::new_for_shared_styles(vec![info.into()]) + } + + pub(crate) fn new_for_shared_styles( + shared_inline_styles_stack: Vec, + ) -> Self { Self { + // For the purposes of `text-transform: capitalize` the start of the IFC is a word boundary. on_word_boundary: true, + shared_inline_styles_stack, ..Default::default() } } @@ -90,6 +114,13 @@ impl InlineFormattingContextBuilder { self.current_text_offset += string_to_push.len(); } + fn shared_inline_styles(&self) -> SharedInlineStyles { + self.shared_inline_styles_stack + .last() + .expect("Should always have at least one SharedInlineStyles") + .clone() + } + /// Return true if this [`InlineFormattingContextBuilder`] is empty for the purposes of ignoring /// during box tree construction. An IFC is empty if it only contains TextRuns with /// completely collapsible whitespace. When that happens it can be ignored completely. @@ -125,7 +156,7 @@ impl InlineFormattingContextBuilder { independent_formatting_context: IndependentFormattingContext, ) -> ArcRefCell { let inline_level_box = ArcRefCell::new(InlineItem::Atomic( - Arc::new(independent_formatting_context), + ArcRefCell::new(independent_formatting_context), self.current_text_offset, Level::ltr(), /* This will be assigned later if necessary. */ )); @@ -156,35 +187,59 @@ impl InlineFormattingContextBuilder { } pub(crate) fn push_float_box(&mut self, float_box: FloatBox) -> ArcRefCell { - let inline_level_box = ArcRefCell::new(InlineItem::OutOfFlowFloatBox(Arc::new(float_box))); + let inline_level_box = + ArcRefCell::new(InlineItem::OutOfFlowFloatBox(ArcRefCell::new(float_box))); self.inline_items.push(inline_level_box.clone()); self.contains_floats = true; inline_level_box } - pub(crate) fn start_inline_box(&mut self, inline_box: InlineBox) -> ArcRefCell { + pub(crate) fn start_inline_box( + &mut self, + inline_box: InlineBox, + block_in_inline_splits: Option>>, + ) { self.push_control_character_string(inline_box.base.style.bidi_control_chars().0); + // Don't push a `SharedInlineStyles` if we are pushing this box when splitting + // an IFC for a block-in-inline split. Shared styles are pushed as part of setting + // up the second split of the IFC. + if inline_box.is_first_split { + self.shared_inline_styles_stack + .push(inline_box.shared_inline_styles.clone()); + } + let (identifier, inline_box) = self.inline_boxes.start_inline_box(inline_box); let inline_level_box = ArcRefCell::new(InlineItem::StartInlineBox(inline_box)); self.inline_items.push(inline_level_box.clone()); self.inline_box_stack.push(identifier); - inline_level_box + + let mut block_in_inline_splits = block_in_inline_splits.unwrap_or_default(); + block_in_inline_splits.push(inline_level_box); + self.block_in_inline_splits.push(block_in_inline_splits); } - pub(crate) fn end_inline_box(&mut self) -> ArcRefCell { - let identifier = self.end_inline_box_internal(); + /// End the ongoing inline box in this [`InlineFormattingContextBuilder`], returning + /// shared references to all of the box tree items that were created for it. More than + /// a single box tree items may be produced for a single inline box when that inline + /// box is split around a block-level element. + pub(crate) fn end_inline_box(&mut self) -> Vec> { + self.shared_inline_styles_stack.pop(); + + let (identifier, block_in_inline_splits) = self.end_inline_box_internal(); let inline_level_box = self.inline_boxes.get(&identifier); - inline_level_box.borrow_mut().is_last_fragment = true; + { + let mut inline_level_box = inline_level_box.borrow_mut(); + inline_level_box.is_last_split = true; + self.push_control_character_string(inline_level_box.base.style.bidi_control_chars().1); + } - self.push_control_character_string( - inline_level_box.borrow().base.style.bidi_control_chars().1, - ); - - inline_level_box + block_in_inline_splits.unwrap_or_default() } - fn end_inline_box_internal(&mut self) -> InlineBoxIdentifier { + fn end_inline_box_internal( + &mut self, + ) -> (InlineBoxIdentifier, Option>>) { let identifier = self .inline_box_stack .pop() @@ -193,14 +248,15 @@ impl InlineFormattingContextBuilder { .push(ArcRefCell::new(InlineItem::EndInlineBox)); self.inline_boxes.end_inline_box(identifier); - identifier + + // This might be `None` if this builder has already drained its block-in-inline-splits + // into the new builder on the other side of a new block-in-inline split. + let block_in_inline_splits = self.block_in_inline_splits.pop(); + + (identifier, block_in_inline_splits) } - pub(crate) fn push_text<'dom, Node: NodeExt<'dom>>( - &mut self, - text: Cow<'dom, str>, - info: &NodeAndStyleInfo, - ) { + pub(crate) fn push_text<'dom>(&mut self, text: Cow<'dom, str>, info: &NodeAndStyleInfo<'dom>) { let white_space_collapse = info.style.clone_white_space_collapse(); let collapsed = WhitespaceCollapse::new( text.chars(), @@ -248,8 +304,6 @@ impl InlineFormattingContextBuilder { } let selection_range = info.get_selection_range(); - let selected_style = info.get_selected_style(); - if let Some(last_character) = new_text.chars().next_back() { self.on_word_boundary = last_character.is_whitespace(); self.last_inline_box_ended_with_collapsible_white_space = @@ -271,18 +325,24 @@ impl InlineFormattingContextBuilder { .push(ArcRefCell::new(InlineItem::TextRun(ArcRefCell::new( TextRun::new( info.into(), - info.style.clone(), + self.shared_inline_styles(), new_range, selection_range, - selected_style, ), )))); } + pub(crate) fn enter_display_contents(&mut self, shared_inline_styles: SharedInlineStyles) { + self.shared_inline_styles_stack.push(shared_inline_styles); + } + + pub(crate) fn leave_display_contents(&mut self) { + self.shared_inline_styles_stack.pop(); + } + pub(crate) fn split_around_block_and_finish( &mut self, layout_context: &LayoutContext, - propagated_data: PropagatedBoxTreeData, has_first_formatted_line: bool, default_bidi_level: Level, ) -> Option { @@ -294,13 +354,23 @@ impl InlineFormattingContextBuilder { // context. It has the same inline box structure as this builder, except the boxes are // marked as not being the first fragment. No inline content is carried over to this new // builder. - let mut new_builder = InlineFormattingContextBuilder::new(); - for identifier in self.inline_box_stack.iter() { + let mut new_builder = Self::new_for_shared_styles(self.shared_inline_styles_stack.clone()); + + let block_in_inline_splits = std::mem::take(&mut self.block_in_inline_splits); + for (identifier, historical_inline_boxes) in + izip!(self.inline_box_stack.iter(), block_in_inline_splits) + { + // Start a new inline box for every ongoing inline box in this + // InlineFormattingContext once we are done processing this block element, + // being sure to give the block-in-inline-split to the new + // InlineFormattingContext. These will finally be inserted into the DOM's + // BoxSlot once the inline box has been fully processed. new_builder.start_inline_box( self.inline_boxes .get(identifier) .borrow() .split_around_block(), + Some(historical_inline_boxes), ); } let mut inline_builder_from_before_split = std::mem::replace(self, new_builder); @@ -314,7 +384,6 @@ impl InlineFormattingContextBuilder { inline_builder_from_before_split.finish( layout_context, - propagated_data, has_first_formatted_line, /* is_single_line_text_input = */ false, default_bidi_level, @@ -323,9 +392,8 @@ impl InlineFormattingContextBuilder { /// Finish the current inline formatting context, returning [`None`] if the context was empty. pub(crate) fn finish( - &mut self, + self, layout_context: &LayoutContext, - propagated_data: PropagatedBoxTreeData, has_first_formatted_line: bool, is_single_line_text_input: bool, default_bidi_level: Level, @@ -334,13 +402,10 @@ impl InlineFormattingContextBuilder { return None; } - let old_builder = std::mem::replace(self, InlineFormattingContextBuilder::new()); - assert!(old_builder.inline_box_stack.is_empty()); - + assert!(self.inline_box_stack.is_empty()); Some(InlineFormattingContext::new_with_builder( - old_builder, + self, layout_context, - propagated_data, has_first_formatted_line, is_single_line_text_input, default_bidi_level, diff --git a/components/layout/flow/inline/inline_box.rs b/components/layout/flow/inline/inline_box.rs index 97398d6e708..a9642d3b222 100644 --- a/components/layout/flow/inline/inline_box.rs +++ b/components/layout/flow/inline/inline_box.rs @@ -7,12 +7,18 @@ use std::vec::IntoIter; use app_units::Au; use fonts::FontMetrics; use malloc_size_of_derive::MallocSizeOf; +use script::layout_dom::ServoLayoutNode; +use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode}; +use servo_arc::Arc as ServoArc; +use style::properties::ComputedValues; -use super::{InlineContainerState, InlineContainerStateFlags, inline_container_needs_strut}; +use super::{ + InlineContainerState, InlineContainerStateFlags, SharedInlineStyles, + inline_container_needs_strut, +}; use crate::ContainingBlock; use crate::cell::ArcRefCell; use crate::context::LayoutContext; -use crate::dom::NodeExt; use crate::dom_traversal::NodeAndStyleInfo; use crate::fragment_tree::BaseFragmentInfo; use crate::layout_box_base::LayoutBoxBase; @@ -21,23 +27,31 @@ use crate::style_ext::{LayoutStyle, PaddingBorderMargin}; #[derive(Debug, MallocSizeOf)] pub(crate) struct InlineBox { pub base: LayoutBoxBase, + /// The [`SharedInlineStyles`] for this [`InlineBox`] that are used to share styles + /// with all [`super::TextRun`] children. + pub(super) shared_inline_styles: SharedInlineStyles, /// The identifier of this inline box in the containing [`super::InlineFormattingContext`]. pub(super) identifier: InlineBoxIdentifier, - pub is_first_fragment: bool, - pub is_last_fragment: bool, + /// Whether or not this is the first instance of an [`InlineBox`] before a possible + /// block-in-inline split. When no split occurs, this is always true. + pub is_first_split: bool, + /// Whether or not this is the last instance of an [`InlineBox`] before a possible + /// block-in-inline split. When no split occurs, this is always true. + pub is_last_split: bool, /// The index of the default font in the [`super::InlineFormattingContext`]'s font metrics store. /// This is initialized during IFC shaping. pub default_font_index: Option, } impl InlineBox { - pub(crate) fn new<'dom, Node: NodeExt<'dom>>(info: &NodeAndStyleInfo) -> Self { + pub(crate) fn new(info: &NodeAndStyleInfo) -> Self { Self { base: LayoutBoxBase::new(info.into(), info.style.clone()), + shared_inline_styles: info.into(), // This will be assigned later, when the box is actually added to the IFC. identifier: InlineBoxIdentifier::default(), - is_first_fragment: true, - is_last_fragment: false, + is_first_split: true, + is_last_split: false, default_font_index: None, } } @@ -45,8 +59,9 @@ impl InlineBox { pub(crate) fn split_around_block(&self) -> Self { Self { base: LayoutBoxBase::new(self.base.base_fragment_info, self.base.style.clone()), - is_first_fragment: false, - is_last_fragment: false, + shared_inline_styles: self.shared_inline_styles.clone(), + is_first_split: false, + is_last_split: false, ..*self } } @@ -55,6 +70,16 @@ impl InlineBox { pub(crate) fn layout_style(&self) -> LayoutStyle { LayoutStyle::Default(&self.base.style) } + + pub(crate) fn repair_style( + &mut self, + node: &ServoLayoutNode, + new_style: &ServoArc, + ) { + self.base.repair_style(new_style); + *self.shared_inline_styles.style.borrow_mut() = new_style.clone(); + *self.shared_inline_styles.selected.borrow_mut() = node.to_threadsafe().selected_style(); + } } #[derive(Debug, Default, MallocSizeOf)] @@ -231,13 +256,7 @@ impl InlineBoxContainerState { } Self { - base: InlineContainerState::new( - style, - flags, - Some(parent_container), - parent_container.text_decoration_line, - font_metrics, - ), + base: InlineContainerState::new(style, flags, Some(parent_container), font_metrics), identifier: inline_box.identifier, base_fragment_info: inline_box.base.base_fragment_info, pbm, diff --git a/components/layout/flow/inline/line.rs b/components/layout/flow/inline/line.rs index c42f32c9242..14a1531883f 100644 --- a/components/layout/flow/inline/line.rs +++ b/components/layout/flow/inline/line.rs @@ -7,7 +7,6 @@ use bitflags::bitflags; use fonts::{ByteIndex, FontMetrics, GlyphStore}; use itertools::Either; use range::Range; -use servo_arc::Arc; use style::Zero; use style::computed_values::position::T as Position; use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse; @@ -16,12 +15,11 @@ use style::values::generics::box_::{GenericVerticalAlign, VerticalAlignKeyword}; use style::values::generics::font::LineHeight; use style::values::specified::align::AlignFlags; use style::values::specified::box_::DisplayOutside; -use style::values::specified::text::TextDecorationLine; use unicode_bidi::{BidiInfo, Level}; use webrender_api::FontInstanceKey; use super::inline_box::{InlineBoxContainerState, InlineBoxIdentifier, InlineBoxTreePathToken}; -use super::{InlineFormattingContextLayout, LineBlockSizes}; +use super::{InlineFormattingContextLayout, LineBlockSizes, SharedInlineStyles}; use crate::cell::ArcRefCell; use crate::fragment_tree::{BaseFragmentInfo, BoxFragment, Fragment, TextFragment}; use crate::geom::{LogicalRect, LogicalVec2, PhysicalRect, ToLogical}; @@ -326,13 +324,12 @@ impl LineItemLayout<'_, '_> { let inline_box = self.layout.ifc.inline_boxes.get(identifier); let inline_box = &*(inline_box.borrow()); - let style = &inline_box.base.style; let space_above_baseline = inline_box_state.calculate_space_above_baseline(); let block_start_offset = self.calculate_inline_box_block_start(inline_box_state, space_above_baseline); let positioning_context_or_start_offset_in_parent = - match PositioningContext::new_for_style(style) { + match PositioningContext::new_for_layout_box_base(&inline_box.base) { Some(positioning_context) => Either::Left(positioning_context), None => Either::Right(self.current_positioning_context_mut().len()), }; @@ -569,15 +566,13 @@ impl LineItemLayout<'_, '_> { self.current_state.fragments.push(( Fragment::Text(ArcRefCell::new(TextFragment { base: text_item.base_fragment_info.into(), - parent_style: text_item.parent_style, + inline_styles: text_item.inline_styles.clone(), rect: PhysicalRect::zero(), font_metrics: text_item.font_metrics, font_key: text_item.font_key, glyphs: text_item.text, - text_decoration_line: text_item.text_decoration_line, justification_adjustment: self.justification_adjustment, selection_range: text_item.selection_range, - selected_style: text_item.selected_style, })), content_rect, )); @@ -764,21 +759,23 @@ impl LineItem { pub(super) struct TextRunLineItem { pub base_fragment_info: BaseFragmentInfo, - pub parent_style: Arc, + pub inline_styles: SharedInlineStyles, pub text: Vec>, pub font_metrics: FontMetrics, pub font_key: FontInstanceKey, - pub text_decoration_line: TextDecorationLine, /// The BiDi level of this [`TextRunLineItem`] to enable reordering. pub bidi_level: Level, pub selection_range: Option>, - pub selected_style: Arc, } impl TextRunLineItem { fn trim_whitespace_at_end(&mut self, whitespace_trimmed: &mut Au) -> bool { if matches!( - self.parent_style.get_inherited_text().white_space_collapse, + self.inline_styles + .style + .borrow() + .get_inherited_text() + .white_space_collapse, WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces ) { return false; @@ -804,7 +801,11 @@ impl TextRunLineItem { fn trim_whitespace_at_start(&mut self, whitespace_trimmed: &mut Au) -> bool { if matches!( - self.parent_style.get_inherited_text().white_space_collapse, + self.inline_styles + .style + .borrow() + .get_inherited_text() + .white_space_collapse, WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces ) { return false; diff --git a/components/layout/flow/inline/mod.rs b/components/layout/flow/inline/mod.rs index 490917d95a3..6fd4a51a526 100644 --- a/components/layout/flow/inline/mod.rs +++ b/components/layout/flow/inline/mod.rs @@ -90,18 +90,20 @@ use line::{ use line_breaker::LineBreaker; use malloc_size_of_derive::MallocSizeOf; use range::Range; +use script::layout_dom::ServoLayoutNode; +use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode}; use servo_arc::Arc; use style::Zero; use style::computed_values::text_wrap_mode::T as TextWrapMode; use style::computed_values::vertical_align::T as VerticalAlign; use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse; -use style::context::QuirksMode; +use style::context::{QuirksMode, SharedStyleContext}; use style::properties::ComputedValues; use style::properties::style_structs::InheritedText; use style::values::generics::box_::VerticalAlignKeyword; use style::values::generics::font::LineHeight; use style::values::specified::box_::BaselineSource; -use style::values::specified::text::{TextAlignKeyword, TextDecorationLine}; +use style::values::specified::text::TextAlignKeyword; use style::values::specified::{TextAlignLast, TextJustify}; use text_run::{ TextRun, XI_LINE_BREAKING_CLASS_GL, XI_LINE_BREAKING_CLASS_WJ, XI_LINE_BREAKING_CLASS_ZWJ, @@ -118,6 +120,7 @@ use super::{ }; use crate::cell::ArcRefCell; use crate::context::LayoutContext; +use crate::dom_traversal::NodeAndStyleInfo; use crate::flow::CollapsibleWithParentStartMargin; use crate::flow::float::{FloatBox, SequentialLayoutState}; use crate::formatting_contexts::{ @@ -131,7 +134,7 @@ use crate::geom::{LogicalRect, LogicalVec2, ToLogical}; use crate::positioned::{AbsolutelyPositionedBox, PositioningContext}; use crate::sizing::{ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult}; use crate::style_ext::{ComputedValuesExt, PaddingBorderMargin}; -use crate::{ConstraintSpace, ContainingBlock, PropagatedBoxTreeData}; +use crate::{ConstraintSpace, ContainingBlock, SharedStyle}; // From gfxFontConstants.h in Firefox. static FONT_SUBSCRIPT_OFFSET_RATIO: f32 = 0.20; @@ -156,7 +159,9 @@ pub(crate) struct InlineFormattingContext { /// context in order to avoid duplicating this information. pub font_metrics: Vec, - pub(super) text_decoration_line: TextDecorationLine, + /// The [`SharedInlineStyles`] for the root of this [`InlineFormattingContext`] that are used to + /// share styles with all [`TextRun`] children. + pub(super) shared_inline_styles: SharedInlineStyles, /// Whether this IFC contains the 1st formatted line of an element: /// . @@ -173,6 +178,25 @@ pub(crate) struct InlineFormattingContext { pub(super) has_right_to_left_content: bool, } +/// [`TextRun`] and `TextFragment`s need a handle on their parent inline box (or inline +/// formatting context root)'s style. In order to implement incremental layout, these are +/// wrapped in [`SharedStyle`]. This allows updating the parent box tree element without +/// updating every single descendant box tree node and fragment. +#[derive(Clone, Debug, MallocSizeOf)] +pub(crate) struct SharedInlineStyles { + pub style: SharedStyle, + pub selected: SharedStyle, +} + +impl From<&NodeAndStyleInfo<'_>> for SharedInlineStyles { + fn from(info: &NodeAndStyleInfo) -> Self { + Self { + style: SharedStyle::new(info.style.clone()), + selected: SharedStyle::new(info.get_selected_style()), + } + } +} + /// A collection of data used to cache [`FontMetrics`] in the [`InlineFormattingContext`] #[derive(Debug, MallocSizeOf)] pub(crate) struct FontKeyAndMetrics { @@ -190,15 +214,43 @@ pub(crate) enum InlineItem { ArcRefCell, usize, /* offset_in_text */ ), - OutOfFlowFloatBox(#[conditional_malloc_size_of] Arc), + OutOfFlowFloatBox(ArcRefCell), Atomic( - #[conditional_malloc_size_of] Arc, + ArcRefCell, usize, /* offset_in_text */ Level, /* bidi_level */ ), } impl InlineItem { + pub(crate) fn repair_style( + &self, + context: &SharedStyleContext, + node: &ServoLayoutNode, + new_style: &Arc, + ) { + match self { + InlineItem::StartInlineBox(inline_box) => { + inline_box.borrow_mut().repair_style(node, new_style); + }, + InlineItem::EndInlineBox => {}, + // TextRun holds a handle the `InlineSharedStyles` which is updated when repairing inline box + // and `display: contents` styles. + InlineItem::TextRun(..) => {}, + InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => positioned_box + .borrow_mut() + .context + .repair_style(context, node, new_style), + InlineItem::OutOfFlowFloatBox(float_box) => float_box + .borrow_mut() + .contents + .repair_style(context, node, new_style), + InlineItem::Atomic(atomic, ..) => { + atomic.borrow_mut().repair_style(context, node, new_style) + }, + } + } + pub(crate) fn invalidate_cached_fragment(&self) { match self { InlineItem::StartInlineBox(inline_box) => { @@ -212,11 +264,14 @@ impl InlineItem { .base .invalidate_cached_fragment(); }, - InlineItem::OutOfFlowFloatBox(float_box) => { - float_box.contents.base.invalidate_cached_fragment() - }, + InlineItem::OutOfFlowFloatBox(float_box) => float_box + .borrow() + .contents + .base + .invalidate_cached_fragment(), InlineItem::Atomic(independent_formatting_context, ..) => { independent_formatting_context + .borrow() .base .invalidate_cached_fragment(); }, @@ -232,9 +287,11 @@ impl InlineItem { InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => { positioned_box.borrow().context.base.fragments() }, - InlineItem::OutOfFlowFloatBox(float_box) => float_box.contents.base.fragments(), + InlineItem::OutOfFlowFloatBox(float_box) => { + float_box.borrow().contents.base.fragments() + }, InlineItem::Atomic(independent_formatting_context, ..) => { - independent_formatting_context.base.fragments() + independent_formatting_context.borrow().base.fragments() }, } } @@ -569,12 +626,6 @@ pub(super) struct InlineContainerState { /// this inline box on the current line OR any previous line. has_content: RefCell, - /// Indicates whether this nesting level have text decorations in effect. - /// From - // "When specified on or propagated to a block container that establishes - // an IFC..." - text_decoration_line: TextDecorationLine, - /// The block size contribution of this container's default font ie the size of the /// "strut." Whether this is integrated into the [`Self::nested_strut_block_sizes`] /// depends on the line-height quirk described in @@ -744,7 +795,7 @@ impl InlineFormattingContextLayout<'_> { self.containing_block, self.layout_context, self.current_inline_container_state(), - inline_box.is_last_fragment, + inline_box.is_last_split, inline_box .default_font_index .map(|index| &self.ifc.font_metrics[index].metrics), @@ -773,7 +824,7 @@ impl InlineFormattingContextLayout<'_> { ); } - if inline_box.is_first_fragment { + if inline_box.is_first_split { self.current_line_segment.inline_size += inline_box_state.pbm.padding.inline_start + inline_box_state.pbm.border.inline_start + inline_box_state.pbm.margin.inline_start.auto_is(Au::zero); @@ -958,6 +1009,7 @@ impl InlineFormattingContextLayout<'_> { .as_physical(Some(self.containing_block)); self.fragments .push(Fragment::Positioning(PositioningFragment::new_anonymous( + self.root_nesting_level.style.clone(), physical_line_rect, fragments, ))); @@ -1313,7 +1365,7 @@ impl InlineFormattingContextLayout<'_> { ) { let inline_advance = glyph_store.total_advance(); let flags = if glyph_store.is_whitespace() { - SegmentContentFlags::from(text_run.parent_style.get_inherited_text()) + SegmentContentFlags::from(text_run.inline_styles.style.borrow().get_inherited_text()) } else { SegmentContentFlags::empty() }; @@ -1398,13 +1450,11 @@ impl InlineFormattingContextLayout<'_> { TextRunLineItem { text: vec![glyph_store], base_fragment_info: text_run.base_fragment_info, - parent_style: text_run.parent_style.clone(), + inline_styles: text_run.inline_styles.clone(), font_metrics, font_key: ifc_font_info.key, - text_decoration_line: self.current_inline_container_state().text_decoration_line, bidi_level, selection_range, - selected_style: text_run.selected_style.clone(), }, )); } @@ -1596,7 +1646,6 @@ impl InlineFormattingContext { pub(super) fn new_with_builder( builder: InlineFormattingContextBuilder, layout_context: &LayoutContext, - propagated_data: PropagatedBoxTreeData, has_first_formatted_line: bool, is_single_line_text_input: bool, starting_bidi_level: Level, @@ -1647,7 +1696,11 @@ impl InlineFormattingContext { inline_items: builder.inline_items, inline_boxes: builder.inline_boxes, font_metrics, - text_decoration_line: propagated_data.text_decoration, + shared_inline_styles: builder + .shared_inline_styles_stack + .last() + .expect("Should have at least one SharedInlineStyle for the root of an IFC") + .clone(), has_first_formatted_line, contains_floats: builder.contains_floats, is_single_line_text_input, @@ -1655,6 +1708,11 @@ impl InlineFormattingContext { } } + pub(crate) fn repair_style(&self, node: &ServoLayoutNode, new_style: &Arc) { + *self.shared_inline_styles.style.borrow_mut() = new_style.clone(); + *self.shared_inline_styles.selected.borrow_mut() = node.to_threadsafe().selected_style(); + } + pub(super) fn layout( &self, layout_context: &LayoutContext, @@ -1712,7 +1770,6 @@ impl InlineFormattingContext { style.to_arc(), inline_container_state_flags, None, /* parent_container */ - self.text_decoration_line, default_font_metrics.as_ref(), ), inline_box_state_stack: Vec::new(), @@ -1751,7 +1808,7 @@ impl InlineFormattingContext { InlineItem::EndInlineBox => layout.finish_inline_box(), InlineItem::TextRun(run) => run.borrow().layout_into_line_items(&mut layout), InlineItem::Atomic(atomic_formatting_context, offset_in_text, bidi_level) => { - atomic_formatting_context.layout_into_line_items( + atomic_formatting_context.borrow().layout_into_line_items( &mut layout, *offset_in_text, *bidi_level, @@ -1766,7 +1823,7 @@ impl InlineFormattingContext { )); }, InlineItem::OutOfFlowFloatBox(float_box) => { - float_box.layout_into_line_items(&mut layout); + float_box.borrow().layout_into_line_items(&mut layout); }, } } @@ -1810,10 +1867,8 @@ impl InlineContainerState { style: Arc, flags: InlineContainerStateFlags, parent_container: Option<&InlineContainerState>, - parent_text_decoration_line: TextDecorationLine, font_metrics: Option<&FontMetrics>, ) -> Self { - let text_decoration_line = parent_text_decoration_line | style.clone_text_decoration_line(); let font_metrics = font_metrics.cloned().unwrap_or_else(FontMetrics::empty); let line_height = line_height( &style, @@ -1850,7 +1905,6 @@ impl InlineContainerState { style, flags, has_content: RefCell::new(false), - text_decoration_line, nested_strut_block_sizes: nested_block_sizes, strut_block_sizes, baseline_offset, @@ -2004,8 +2058,7 @@ impl IndependentFormattingContext { bidi_level: Level, ) { // We need to know the inline size of the atomic before deciding whether to do the line break. - let mut child_positioning_context = PositioningContext::new_for_style(self.style()) - .unwrap_or_else(|| PositioningContext::new_for_subtree(true)); + let mut child_positioning_context = PositioningContext::default(); let IndependentFloatOrAtomicLayoutResult { mut fragment, baselines, @@ -2349,10 +2402,10 @@ impl<'layout_data> ContentSizesComputation<'layout_data> { .auto_is(Au::zero); let pbm = margin + padding + border; - if inline_box.is_first_fragment { + if inline_box.is_first_split { self.add_inline_size(pbm.inline_start); } - if inline_box.is_last_fragment { + if inline_box.is_last_split { self.ending_inline_pbm_stack.push(pbm.inline_end); } else { self.ending_inline_pbm_stack.push(Au::zero()); @@ -2364,8 +2417,9 @@ impl<'layout_data> ContentSizesComputation<'layout_data> { }, InlineItem::TextRun(text_run) => { let text_run = &*text_run.borrow(); + let parent_style = text_run.inline_styles.style.borrow(); for segment in text_run.shaped_text.iter() { - let style_text = text_run.parent_style.get_inherited_text(); + let style_text = parent_style.get_inherited_text(); let can_wrap = style_text.text_wrap_mode == TextWrapMode::Wrap; // TODO: This should take account whether or not the first and last character prevent @@ -2429,7 +2483,7 @@ impl<'layout_data> ContentSizesComputation<'layout_data> { let InlineContentSizesResult { sizes: outer, depends_on_block_constraints, - } = atomic.outer_inline_content_sizes( + } = atomic.borrow().outer_inline_content_sizes( self.layout_context, &self.constraint_space.into(), &LogicalVec2::zero(), diff --git a/components/layout/flow/inline/text_run.rs b/components/layout/flow/inline/text_run.rs index 0d0c6398017..591c7b9b5e2 100644 --- a/components/layout/flow/inline/text_run.rs +++ b/components/layout/flow/inline/text_run.rs @@ -26,7 +26,7 @@ use unicode_script::Script; use xi_unicode::linebreak_property; use super::line_breaker::LineBreaker; -use super::{FontKeyAndMetrics, InlineFormattingContextLayout}; +use super::{FontKeyAndMetrics, InlineFormattingContextLayout, SharedInlineStyles}; use crate::fragment_tree::BaseFragmentInfo; // These constants are the xi-unicode line breaking classes that are defined in @@ -37,22 +37,6 @@ pub(crate) const XI_LINE_BREAKING_CLASS_ZW: u8 = 28; pub(crate) const XI_LINE_BREAKING_CLASS_WJ: u8 = 30; pub(crate) const XI_LINE_BREAKING_CLASS_ZWJ: u8 = 42; -/// -#[derive(Debug, MallocSizeOf)] -pub(crate) struct TextRun { - pub base_fragment_info: BaseFragmentInfo, - #[conditional_malloc_size_of] - pub parent_style: Arc, - pub text_range: Range, - - /// The text of this [`TextRun`] with a font selected, broken into unbreakable - /// segments, and shaped. - pub shaped_text: Vec, - pub selection_range: Option>, - #[conditional_malloc_size_of] - pub selected_style: Arc, -} - // There are two reasons why we might want to break at the start: // // 1. The line breaker told us that a break was necessary between two separate @@ -334,21 +318,49 @@ impl TextRunSegment { } } +/// A single [`TextRun`] for the box tree. These are all descendants of +/// [`super::InlineBox`] or the root of the [`super::InlineFormattingContext`]. During +/// box tree construction, text is split into [`TextRun`]s based on their font, script, +/// etc. When these are created text is already shaped. +/// +/// +#[derive(Debug, MallocSizeOf)] +pub(crate) struct TextRun { + /// The [`BaseFragmentInfo`] for this [`TextRun`]. Usually this comes from the + /// original text node in the DOM for the text. + pub base_fragment_info: BaseFragmentInfo, + + /// The [`crate::SharedStyle`] from this [`TextRun`]s parent element. This is + /// shared so that incremental layout can simply update the parent element and + /// this [`TextRun`] will be updated automatically. + pub inline_styles: SharedInlineStyles, + + /// The range of text in [`super::InlineFormattingContext::text_content`] of the + /// [`super::InlineFormattingContext`] that owns this [`TextRun`]. These are UTF-8 offsets. + pub text_range: Range, + + /// The text of this [`TextRun`] with a font selected, broken into unbreakable + /// segments, and shaped. + pub shaped_text: Vec, + + /// The selection range for the DOM text node that originated this [`TextRun`]. This + /// comes directly from the DOM. + pub selection_range: Option>, +} + impl TextRun { pub(crate) fn new( base_fragment_info: BaseFragmentInfo, - parent_style: Arc, + inline_styles: SharedInlineStyles, text_range: Range, selection_range: Option>, - selected_style: Arc, ) -> Self { Self { base_fragment_info, - parent_style, + inline_styles, text_range, shaped_text: Vec::new(), selection_range, - selected_style, } } @@ -360,11 +372,12 @@ impl TextRun { font_cache: &mut Vec, bidi_info: &BidiInfo, ) { - let inherited_text_style = self.parent_style.get_inherited_text().clone(); + let parent_style = self.inline_styles.style.borrow().clone(); + let inherited_text_style = parent_style.get_inherited_text().clone(); let letter_spacing = inherited_text_style .letter_spacing .0 - .resolve(self.parent_style.clone_font().font_size.computed_size()); + .resolve(parent_style.clone_font().font_size.computed_size()); let letter_spacing = if letter_spacing.px() != 0. { Some(app_units::Au::from(letter_spacing)) } else { @@ -384,7 +397,13 @@ impl TextRun { let style_word_spacing: Option = specified_word_spacing.to_length().map(|l| l.into()); let segments = self - .segment_text_by_font(formatting_context_text, font_context, font_cache, bidi_info) + .segment_text_by_font( + formatting_context_text, + font_context, + font_cache, + bidi_info, + &parent_style, + ) .into_iter() .map(|(mut segment, font)| { let word_spacing = style_word_spacing.unwrap_or_else(|| { @@ -407,7 +426,7 @@ impl TextRun { }; segment.shape_text( - &self.parent_style, + &parent_style, formatting_context_text, linebreaker, &shaping_options, @@ -430,8 +449,9 @@ impl TextRun { font_context: &FontContext, font_cache: &mut Vec, bidi_info: &BidiInfo, + parent_style: &Arc, ) -> Vec<(TextRunSegment, FontRef)> { - let font_group = font_context.font_group(self.parent_style.clone_font()); + let font_group = font_context.font_group(parent_style.clone_font()); let mut current: Option<(TextRunSegment, FontRef)> = None; let mut results = Vec::new(); diff --git a/components/layout/flow/mod.rs b/components/layout/flow/mod.rs index f92650ef340..4776b65771c 100644 --- a/components/layout/flow/mod.rs +++ b/components/layout/flow/mod.rs @@ -9,9 +9,11 @@ use app_units::{Au, MAX_AU}; use inline::InlineFormattingContext; use malloc_size_of_derive::MallocSizeOf; use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; +use script::layout_dom::ServoLayoutNode; use servo_arc::Arc; use style::Zero; use style::computed_values::clear::T as StyleClear; +use style::context::SharedStyleContext; use style::logical_geometry::Direction; use style::properties::ComputedValues; use style::servo::selector_parser::PseudoElement; @@ -21,6 +23,7 @@ use style::values::specified::{Display, TextAlignKeyword}; use crate::cell::ArcRefCell; use crate::context::LayoutContext; +use crate::dom::NodeExt; use crate::flow::float::{ Clear, ContainingBlockPositionInfo, FloatBox, FloatSide, PlacementAmongFloats, SequentialLayoutState, @@ -33,8 +36,8 @@ use crate::fragment_tree::{ BaseFragmentInfo, BoxFragment, CollapsedBlockMargins, CollapsedMargin, Fragment, FragmentFlags, }; use crate::geom::{ - AuOrAuto, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalPoint, PhysicalRect, - PhysicalSides, Size, Sizes, ToLogical, ToLogicalWithContainingBlock, + AuOrAuto, LazySize, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalPoint, + PhysicalRect, PhysicalSides, Size, Sizes, ToLogical, ToLogicalWithContainingBlock, }; use crate::layout_box_base::{CacheableLayoutResult, LayoutBoxBase}; use crate::positioned::{AbsolutelyPositionedBox, PositioningContext, PositioningContextLength}; @@ -52,7 +55,7 @@ pub mod inline; mod root; pub(crate) use construct::BlockContainerBuilder; -pub use root::{BoxTree, CanvasBackground}; +pub use root::BoxTree; #[derive(Debug, MallocSizeOf)] pub(crate) struct BlockFormattingContext { @@ -75,6 +78,15 @@ impl BlockContainer { BlockContainer::InlineFormattingContext(context) => context.contains_floats, } } + + pub(crate) fn repair_style(&mut self, node: &ServoLayoutNode, new_style: &Arc) { + match self { + BlockContainer::BlockLevelBoxes(..) => {}, + BlockContainer::InlineFormattingContext(inline_formatting_context) => { + inline_formatting_context.repair_style(node, new_style) + }, + } + } } #[derive(Debug, MallocSizeOf)] @@ -91,6 +103,37 @@ pub(crate) enum BlockLevelBox { } impl BlockLevelBox { + pub(crate) fn repair_style( + &mut self, + context: &SharedStyleContext, + node: &ServoLayoutNode, + new_style: &Arc, + ) { + self.with_base_mut(|base| { + base.repair_style(new_style); + }); + + match self { + BlockLevelBox::Independent(independent_formatting_context) => { + independent_formatting_context.repair_style(context, node, new_style) + }, + BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => positioned_box + .borrow_mut() + .context + .repair_style(context, node, new_style), + BlockLevelBox::OutOfFlowFloatBox(float_box) => { + float_box.contents.repair_style(context, node, new_style) + }, + BlockLevelBox::OutsideMarker(outside_marker) => { + outside_marker.repair_style(context, node, new_style) + }, + BlockLevelBox::SameFormattingContextBlock { base, contents, .. } => { + base.repair_style(new_style); + contents.repair_style(node, new_style); + }, + } + } + pub(crate) fn invalidate_cached_fragment(&self) { self.with_base(LayoutBoxBase::invalidate_cached_fragment); } @@ -113,6 +156,20 @@ impl BlockLevelBox { } } + pub(crate) fn with_base_mut(&mut self, callback: impl Fn(&mut LayoutBoxBase) -> T) -> T { + match self { + BlockLevelBox::Independent(independent_formatting_context) => { + callback(&mut independent_formatting_context.base) + }, + BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => { + callback(&mut positioned_box.borrow_mut().context.base) + }, + BlockLevelBox::OutOfFlowFloatBox(float_box) => callback(&mut float_box.contents.base), + BlockLevelBox::OutsideMarker(outside_marker) => callback(&mut outside_marker.base), + BlockLevelBox::SameFormattingContextBlock { base, .. } => callback(base), + } + } + fn contains_floats(&self) -> bool { match self { BlockLevelBox::SameFormattingContextBlock { @@ -249,7 +306,6 @@ pub(crate) struct CollapsibleWithParentStartMargin(bool); /// for a list that has `list-style-position: outside`. #[derive(Debug, MallocSizeOf)] pub(crate) struct OutsideMarker { - #[conditional_malloc_size_of] pub list_item_style: Arc, pub base: LayoutBoxBase, pub block_container: BlockContainer, @@ -361,6 +417,16 @@ impl OutsideMarker { None, ))) } + + fn repair_style( + &mut self, + context: &SharedStyleContext, + node: &ServoLayoutNode, + new_style: &Arc, + ) { + self.list_item_style = node.style(context); + self.base.repair_style(new_style); + } } impl BlockFormattingContext { @@ -421,6 +487,10 @@ impl BlockFormattingContext { pub(crate) fn layout_style<'a>(&self, base: &'a LayoutBoxBase) -> LayoutStyle<'a> { LayoutStyle::Default(&base.style) } + + pub(crate) fn repair_style(&mut self, node: &ServoLayoutNode, new_style: &Arc) { + self.contents.repair_style(node, new_style); + } } /// Finds the min/max-content inline size of the block-level children of a block container. @@ -689,16 +759,13 @@ fn layout_block_level_children_in_parallel( placement_state: &mut PlacementState, ignore_block_margins_for_stretch: LogicalSides1D, ) -> Vec { - let collects_for_nearest_positioned_ancestor = - positioning_context.collects_for_nearest_positioned_ancestor(); let mut layout_results: Vec<(Fragment, PositioningContext)> = Vec::with_capacity(child_boxes.len()); child_boxes .par_iter() .map(|child_box| { - let mut child_positioning_context = - PositioningContext::new_for_subtree(collects_for_nearest_positioned_ancestor); + let mut child_positioning_context = PositioningContext::default(); let fragment = child_box.borrow().layout( layout_context, &mut child_positioning_context, @@ -779,7 +846,7 @@ impl BlockLevelBox { ArcRefCell::new(positioning_context.layout_maybe_position_relative_fragment( layout_context, containing_block, - &base.style, + base, |positioning_context| { layout_in_flow_non_replaced_block_level_same_formatting_context( layout_context, @@ -798,7 +865,7 @@ impl BlockLevelBox { positioning_context.layout_maybe_position_relative_fragment( layout_context, containing_block, - independent.style(), + &independent.base, |positioning_context| { independent.layout_in_flow_block_level( layout_context, @@ -896,6 +963,7 @@ fn layout_in_flow_non_replaced_block_level_same_formatting_context( block_sizes, depends_on_block_constraints, available_block_size, + justify_self, } = solve_containing_block_padding_and_border_for_in_flow_box( containing_block, &layout_style, @@ -909,6 +977,7 @@ fn layout_in_flow_non_replaced_block_level_same_formatting_context( containing_block, &pbm, containing_block_for_children.size.inline, + justify_self, ); let computed_block_size = style.content_block_size(); @@ -1154,6 +1223,7 @@ impl IndependentNonReplacedContents { block_sizes, depends_on_block_constraints, available_block_size, + justify_self, } = solve_containing_block_padding_and_border_for_in_flow_box( containing_block, &layout_style, @@ -1161,6 +1231,15 @@ impl IndependentNonReplacedContents { ignore_block_margins_for_stretch, ); + let lazy_block_size = LazySize::new( + &block_sizes, + Direction::Block, + Size::FitContent, + Au::zero, + available_block_size, + layout_style.is_table(), + ); + let layout = self.layout( layout_context, positioning_context, @@ -1168,24 +1247,18 @@ impl IndependentNonReplacedContents { containing_block, base, false, /* depends_on_block_constraints */ + &lazy_block_size, ); let inline_size = layout .content_inline_size_for_table .unwrap_or(containing_block_for_children.size.inline); - let block_size = block_sizes.resolve( - Direction::Block, - Size::FitContent, - Au::zero, - available_block_size, - || layout.content_block_size.into(), - layout_style.is_table(), - ); + let block_size = lazy_block_size.resolve(|| layout.content_block_size); let ResolvedMargins { margin, effective_margin_inline_start, - } = solve_margins(containing_block, &pbm, inline_size); + } = solve_margins(containing_block, &pbm, inline_size, justify_self); let content_rect = LogicalRect { start_corner: LogicalVec2 { @@ -1300,17 +1373,12 @@ impl IndependentNonReplacedContents { .sizes }; - // TODO: the automatic inline size should take `justify-self` into account. + let justify_self = resolve_justify_self(style, containing_block.style); let is_table = self.is_table(); - let automatic_inline_size = if is_table { - Size::FitContent - } else { - Size::Stretch - }; let compute_inline_size = |stretch_size| { content_box_sizes.inline.resolve( Direction::Inline, - automatic_inline_size, + automatic_inline_size(justify_self, is_table), Au::zero, Some(stretch_size), get_inline_content_sizes, @@ -1318,16 +1386,14 @@ impl IndependentNonReplacedContents { ) }; - let compute_block_size = |layout: &CacheableLayoutResult| { - content_box_sizes.block.resolve( - Direction::Block, - Size::FitContent, - Au::zero, - available_block_size, - || layout.content_block_size.into(), - is_table, - ) - }; + let lazy_block_size = LazySize::new( + &content_box_sizes.block, + Direction::Block, + Size::FitContent, + Au::zero, + available_block_size, + is_table, + ); // The final inline size can depend on the available space, which depends on where // we are placing the box, since floats reduce the available space. @@ -1356,10 +1422,11 @@ impl IndependentNonReplacedContents { containing_block, base, false, /* depends_on_block_constraints */ + &lazy_block_size, ); content_size = LogicalVec2 { - block: compute_block_size(&layout), + block: lazy_block_size.resolve(|| layout.content_block_size), inline: layout.content_inline_size_for_table.unwrap_or(inline_size), }; @@ -1421,6 +1488,7 @@ impl IndependentNonReplacedContents { containing_block, base, false, /* depends_on_block_constraints */ + &lazy_block_size, ); let inline_size = if let Some(inline_size) = layout.content_inline_size_for_table { @@ -1434,7 +1502,7 @@ impl IndependentNonReplacedContents { proposed_inline_size }; content_size = LogicalVec2 { - block: compute_block_size(&layout), + block: lazy_block_size.resolve(|| layout.content_block_size), inline: inline_size, }; @@ -1472,6 +1540,7 @@ impl IndependentNonReplacedContents { &pbm, content_size.inline + pbm.padding_border_sums.inline, placement_rect, + justify_self, ); let margin = LogicalSides { @@ -1558,6 +1627,7 @@ impl ReplacedContents { let effective_margin_inline_start; let (margin_block_start, margin_block_end) = solve_block_margins_for_in_flow_block_level(pbm); + let justify_self = resolve_justify_self(&base.style, containing_block.style); let containing_block_writing_mode = containing_block.style.writing_mode; let physical_content_size = content_size.to_physical_size(containing_block_writing_mode); @@ -1597,6 +1667,7 @@ impl ReplacedContents { pbm, size.inline, placement_rect, + justify_self, ); // Clearance prevents margin collapse between this block and previous ones, @@ -1620,6 +1691,7 @@ impl ReplacedContents { containing_block, pbm, content_size.inline, + justify_self, ); }; @@ -1671,6 +1743,7 @@ struct ContainingBlockPaddingAndBorder<'a> { block_sizes: Sizes, depends_on_block_constraints: bool, available_block_size: Option, + justify_self: AlignFlags, } struct ResolvedMargins { @@ -1719,6 +1792,9 @@ fn solve_containing_block_padding_and_border_for_in_flow_box<'a>( // The available block size may actually be definite, but it should be irrelevant // since the sizing properties are set to their initial value. available_block_size: None, + // The initial `justify-self` is `auto`, but use `normal` (behaving as `stretch`). + // This is being discussed in . + justify_self: AlignFlags::NORMAL, }; } @@ -1755,16 +1831,11 @@ fn solve_containing_block_padding_and_border_for_in_flow_box<'a>( None, /* TODO: support preferred aspect ratios on non-replaced boxes */ )) }; - // TODO: the automatic inline size should take `justify-self` into account. + let justify_self = resolve_justify_self(style, containing_block.style); let is_table = layout_style.is_table(); - let automatic_inline_size = if is_table { - Size::FitContent - } else { - Size::Stretch - }; let inline_size = content_box_sizes.inline.resolve( Direction::Inline, - automatic_inline_size, + automatic_inline_size(justify_self, is_table), Au::zero, Some(available_inline_size), get_inline_content_sizes, @@ -1793,6 +1864,7 @@ fn solve_containing_block_padding_and_border_for_in_flow_box<'a>( block_sizes: content_box_sizes.block, depends_on_block_constraints, available_block_size, + justify_self, } } @@ -1804,9 +1876,15 @@ fn solve_margins( containing_block: &ContainingBlock<'_>, pbm: &PaddingBorderMargin, inline_size: Au, + justify_self: AlignFlags, ) -> ResolvedMargins { let (inline_margins, effective_margin_inline_start) = - solve_inline_margins_for_in_flow_block_level(containing_block, pbm, inline_size); + solve_inline_margins_for_in_flow_block_level( + containing_block, + pbm, + inline_size, + justify_self, + ); let block_margins = solve_block_margins_for_in_flow_block_level(pbm); ResolvedMargins { margin: LogicalSides { @@ -1829,14 +1907,63 @@ fn solve_block_margins_for_in_flow_block_level(pbm: &PaddingBorderMargin) -> (Au ) } -/// This is supposed to handle 'justify-self', but no browser supports it on block boxes. -/// Instead, `
` and `
` are implemented via internal 'text-align' values. +/// Resolves the `justify-self` value, preserving flags. +fn resolve_justify_self(style: &ComputedValues, parent_style: &ComputedValues) -> AlignFlags { + let is_ltr = |style: &ComputedValues| style.writing_mode.line_left_is_inline_start(); + let alignment = match style.clone_justify_self().0.0 { + AlignFlags::AUTO => parent_style.clone_justify_items().computed.0, + alignment => alignment, + }; + let alignment_value = match alignment.value() { + AlignFlags::LEFT if is_ltr(parent_style) => AlignFlags::START, + AlignFlags::LEFT => AlignFlags::END, + AlignFlags::RIGHT if is_ltr(parent_style) => AlignFlags::END, + AlignFlags::RIGHT => AlignFlags::START, + AlignFlags::SELF_START if is_ltr(parent_style) == is_ltr(style) => AlignFlags::START, + AlignFlags::SELF_START => AlignFlags::END, + AlignFlags::SELF_END if is_ltr(parent_style) == is_ltr(style) => AlignFlags::END, + AlignFlags::SELF_END => AlignFlags::START, + alignment_value => alignment_value, + }; + alignment.flags() | alignment_value +} + +/// Determines the automatic size for the inline axis of a block-level box. +/// +#[inline] +fn automatic_inline_size(justify_self: AlignFlags, is_table: bool) -> Size { + match justify_self { + AlignFlags::STRETCH => Size::Stretch, + AlignFlags::NORMAL if !is_table => Size::Stretch, + _ => Size::FitContent, + } +} + +/// Justifies a block-level box, distributing the free space according to `justify-self`. +/// Note `
` and `
` are implemented via internal 'text-align' values, +/// which are also handled here. /// The provided free space should already take margins into account. In particular, /// it should be zero if there is an auto margin. /// -fn justify_self_alignment(containing_block: &ContainingBlock, free_space: Au) -> Au { +fn justify_self_alignment( + containing_block: &ContainingBlock, + free_space: Au, + justify_self: AlignFlags, +) -> Au { + let mut alignment = justify_self.value(); + let is_safe = justify_self.flags() == AlignFlags::SAFE || alignment == AlignFlags::NORMAL; + if is_safe && free_space <= Au::zero() { + alignment = AlignFlags::START + } + match alignment { + AlignFlags::NORMAL => {}, + AlignFlags::CENTER => return free_space / 2, + AlignFlags::END => return free_space, + _ => return Au::zero(), + } + + // For `justify-self: normal`, fall back to the special 'text-align' values. let style = containing_block.style; - debug_assert!(free_space >= Au::zero()); match style.clone_text_align() { TextAlignKeyword::MozCenter => free_space / 2, TextAlignKeyword::MozLeft if !style.writing_mode.line_left_is_inline_start() => free_space, @@ -1861,6 +1988,7 @@ fn solve_inline_margins_for_in_flow_block_level( containing_block: &ContainingBlock, pbm: &PaddingBorderMargin, inline_size: Au, + justify_self: AlignFlags, ) -> ((Au, Au), Au) { let free_space = containing_block.size.inline - pbm.padding_border_sums.inline - inline_size; let mut justification = Au::zero(); @@ -1878,8 +2006,8 @@ fn solve_inline_margins_for_in_flow_block_level( // But here we may still have some free space to perform 'justify-self' alignment. // This aligns the margin box within the containing block, or in other words, // aligns the border box within the margin-shrunken containing block. - let free_space = Au::zero().max(free_space - start - end); - justification = justify_self_alignment(containing_block, free_space); + justification = + justify_self_alignment(containing_block, free_space - start - end, justify_self); (start, end) }, }; @@ -1902,6 +2030,7 @@ fn solve_inline_margins_avoiding_floats( pbm: &PaddingBorderMargin, inline_size: Au, placement_rect: LogicalRect, + justify_self: AlignFlags, ) -> ((Au, Au), Au) { let free_space = placement_rect.size.inline - inline_size; debug_assert!(free_space >= Au::zero()); @@ -1922,7 +2051,7 @@ fn solve_inline_margins_avoiding_floats( // and Blink and WebKit are broken anyways. So we match Gecko instead: this aligns // the border box within the instersection of the float-shrunken containing-block // and the margin-shrunken containing-block. - justification = justify_self_alignment(containing_block, free_space); + justification = justify_self_alignment(containing_block, free_space, justify_self); (start, end) }, }; @@ -2182,7 +2311,9 @@ fn block_size_is_zero_or_intrinsic(size: &StyleSize, containing_block: &Containi lp.is_definitely_zero() || (lp.0.has_percentage() && !containing_block.size.block.is_definite()) }, - StyleSize::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"), + StyleSize::AnchorSizeFunction(_) | StyleSize::AnchorContainingCalcFunction(_) => { + unreachable!("anchor-size() should be disabled") + }, } } @@ -2305,6 +2436,15 @@ impl IndependentFormattingContext { "Mixed horizontal and vertical writing modes are not supported yet" ); + let lazy_block_size = LazySize::new( + &content_box_sizes_and_pbm.content_box_sizes.block, + Direction::Block, + Size::FitContent, + Au::zero, + available_block_size, + is_table, + ); + let independent_layout = non_replaced.layout( layout_context, child_positioning_context, @@ -2312,18 +2452,12 @@ impl IndependentFormattingContext { containing_block, &self.base, false, /* depends_on_block_constraints */ + &lazy_block_size, ); let inline_size = independent_layout .content_inline_size_for_table .unwrap_or(inline_size); - let block_size = content_box_sizes_and_pbm.content_box_sizes.block.resolve( - Direction::Block, - Size::FitContent, - Au::zero, - available_block_size, - || independent_layout.content_block_size.into(), - is_table, - ); + let block_size = lazy_block_size.resolve(|| independent_layout.content_block_size); let content_size = LogicalVec2 { block: block_size, diff --git a/components/layout/flow/root.rs b/components/layout/flow/root.rs index 390b4664e60..fb9884a4f01 100644 --- a/components/layout/flow/root.rs +++ b/components/layout/flow/root.rs @@ -5,13 +5,16 @@ use app_units::Au; use atomic_refcell::AtomicRef; use compositing_traits::display_list::AxesScrollSensitivity; +use euclid::Rect; +use euclid::default::Size2D as UntypedSize2D; use malloc_size_of_derive::MallocSizeOf; +use script::layout_dom::ServoLayoutNode; use script_layout_interface::wrapper_traits::{ LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode, }; use script_layout_interface::{LayoutElementType, LayoutNodeType}; use servo_arc::Arc; -use style::dom::OpaqueNode; +use style::dom::{NodeInfo, TNode}; use style::properties::ComputedValues; use style::values::computed::Overflow; use style_traits::CSSPixel; @@ -26,10 +29,10 @@ use crate::flow::inline::InlineItem; use crate::flow::{BlockContainer, BlockFormattingContext, BlockLevelBox}; use crate::formatting_contexts::IndependentFormattingContext; use crate::fragment_tree::FragmentTree; -use crate::geom::{LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSize}; +use crate::geom::{LogicalVec2, PhysicalRect, PhysicalSize}; use crate::positioned::{AbsolutelyPositionedBox, PositioningContext}; use crate::replaced::ReplacedContents; -use crate::style_ext::{ComputedValuesExt, Display, DisplayGeneratingBox, DisplayInside}; +use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside}; use crate::taffy::{TaffyItemBox, TaffyItemBoxInner}; use crate::{DefiniteContainingBlock, PropagatedBoxTreeData}; @@ -39,18 +42,12 @@ pub struct BoxTree { /// There may be zero if that element has `display: none`. root: BlockFormattingContext, - /// - canvas_background: CanvasBackground, - /// Whether or not the viewport should be sensitive to scrolling input events in two axes viewport_scroll_sensitivity: AxesScrollSensitivity, } impl BoxTree { - pub fn construct<'dom, Node>(context: &LayoutContext, root_element: Node) -> Self - where - Node: 'dom + Copy + LayoutNode<'dom> + Send + Sync, - { + pub fn construct(context: &LayoutContext, root_element: ServoLayoutNode<'_>) -> Self { let boxes = construct_for_root_element(context, root_element); // Zero box for `:root { display: none }`, one for the root element otherwise. @@ -64,7 +61,7 @@ impl BoxTree { // > none, user agents must instead apply the overflow-* values of the first such child // > element to the viewport. The element from which the value is propagated must then have a // > used overflow value of visible. - let root_style = root_element.style(context); + let root_style = root_element.style(context.shared_context()); let mut viewport_overflow_x = root_style.clone_overflow_x(); let mut viewport_overflow_y = root_style.clone_overflow_y(); @@ -81,7 +78,7 @@ impl BoxTree { continue; } - let style = child.style(context); + let style = child.style(context.shared_context()); if !style.get_box().display.is_none() { viewport_overflow_x = style.clone_overflow_x(); viewport_overflow_y = style.clone_overflow_y(); @@ -98,7 +95,6 @@ impl BoxTree { contents, contains_floats, }, - canvas_background: CanvasBackground::for_root_element(context, root_element), // From https://www.w3.org/TR/css-overflow-3/#overflow-propagation: // > If visible is applied to the viewport, it must be interpreted as auto. // > If clip is applied to the viewport, it must be interpreted as hidden. @@ -129,10 +125,7 @@ impl BoxTree { /// * how intrinsic content sizes are computed eagerly makes it hard /// to update those sizes for ancestors of the node from which we /// made an incremental update. - pub fn update<'dom, Node>(context: &LayoutContext, mut dirty_node: Node) -> bool - where - Node: 'dom + Copy + LayoutNode<'dom> + Send + Sync, - { + pub fn update(context: &LayoutContext, mut dirty_node: ServoLayoutNode<'_>) -> bool { #[allow(clippy::enum_variant_names)] enum UpdatePoint { AbsolutelyPositionedBlockLevelBox(ArcRefCell), @@ -141,12 +134,9 @@ impl BoxTree { AbsolutelyPositionedTaffyLevelBox(ArcRefCell), } - fn update_point<'dom, Node>( - node: Node, - ) -> Option<(Arc, DisplayInside, UpdatePoint)> - where - Node: NodeExt<'dom>, - { + fn update_point( + node: ServoLayoutNode<'_>, + ) -> Option<(Arc, DisplayInside, UpdatePoint)> { if !node.is_element() { return None; } @@ -162,7 +152,7 @@ impl BoxTree { return None; } - let layout_data = node.layout_data()?; + let layout_data = NodeExt::layout_data(&node)?; if layout_data.pseudo_before_box.borrow().is_some() { return None; } @@ -186,7 +176,7 @@ impl BoxTree { let update_point = match &*AtomicRef::filter_map(layout_data.self_box.borrow(), Option::as_ref)? { - LayoutBox::DisplayContents => return None, + LayoutBox::DisplayContents(..) => return None, LayoutBox::BlockLevel(block_level_box) => match &*block_level_box.borrow() { BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(_) if box_style.position.is_absolutely_positioned() => @@ -195,16 +185,17 @@ impl BoxTree { }, _ => return None, }, - LayoutBox::InlineLevel(inline_level_box) => match &*inline_level_box.borrow() { - InlineItem::OutOfFlowAbsolutelyPositionedBox(_, text_offset_index) - if box_style.position.is_absolutely_positioned() => - { - UpdatePoint::AbsolutelyPositionedInlineLevelBox( - inline_level_box.clone(), - *text_offset_index, - ) - }, - _ => return None, + LayoutBox::InlineLevel(inline_level_items) => { + let inline_level_box = inline_level_items.first()?; + let InlineItem::OutOfFlowAbsolutelyPositionedBox(_, text_offset_index) = + &*inline_level_box.borrow() + else { + return None; + }; + UpdatePoint::AbsolutelyPositionedInlineLevelBox( + inline_level_box.clone(), + *text_offset_index, + ) }, LayoutBox::FlexLevel(flex_level_box) => match &*flex_level_box.borrow() { FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(_) @@ -300,11 +291,11 @@ impl BoxTree { } } -fn construct_for_root_element<'dom>( +fn construct_for_root_element( context: &LayoutContext, - root_element: impl NodeExt<'dom>, + root_element: ServoLayoutNode<'_>, ) -> Vec> { - let info = NodeAndStyleInfo::new(root_element, root_element.style(context)); + let info = NodeAndStyleInfo::new(root_element, root_element.style(context.shared_context())); let box_style = info.style.get_box(); let display_inside = match Display::from(box_style.display) { @@ -325,7 +316,7 @@ fn construct_for_root_element<'dom>( let contents = ReplacedContents::for_element(root_element, context) .map_or_else(|| NonReplacedContents::OfElement.into(), Contents::Replaced); - let propagated_data = PropagatedBoxTreeData::default().union(&info.style); + let propagated_data = PropagatedBoxTreeData::default(); let root_box = if box_style.position.is_absolutely_positioned() { BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(ArcRefCell::new( AbsolutelyPositionedBox::construct(context, &info, display_inside, contents), @@ -359,7 +350,7 @@ impl BoxTree { pub fn layout( &self, layout_context: &LayoutContext, - viewport: euclid::Size2D, + viewport: UntypedSize2D, ) -> FragmentTree { let style = layout_context .style_context @@ -369,13 +360,8 @@ impl BoxTree { // FIXME: use the document’s mode: // https://drafts.csswg.org/css-writing-modes/#principal-flow - let physical_containing_block = PhysicalRect::new( - PhysicalPoint::zero(), - PhysicalSize::new( - Au::from_f32_px(viewport.width), - Au::from_f32_px(viewport.height), - ), - ); + let physical_containing_block: Rect = + PhysicalSize::from_untyped(viewport).into(); let initial_containing_block = DefiniteContainingBlock { size: LogicalVec2 { inline: physical_containing_block.size.width, @@ -384,8 +370,7 @@ impl BoxTree { style, }; - let mut positioning_context = - PositioningContext::new_for_containing_block_for_all_descendants(); + let mut positioning_context = PositioningContext::default(); let independent_layout = self.root.layout( layout_context, &mut positioning_context, @@ -410,7 +395,7 @@ impl BoxTree { let scrollable_overflow = root_fragments .iter() .fold(PhysicalRect::zero(), |acc, child| { - let child_overflow = child.scrollable_overflow(); + let child_overflow = child.scrollable_overflow_for_parent(); // https://drafts.csswg.org/css-overflow/#scrolling-direction // We want to clip scrollable overflow on box-start and inline-start @@ -428,73 +413,12 @@ impl BoxTree { acc.union(&child_overflow) }); - FragmentTree { + FragmentTree::new( + layout_context, root_fragments, scrollable_overflow, - initial_containing_block: physical_containing_block, - canvas_background: self.canvas_background.clone(), - viewport_scroll_sensitivity: self.viewport_scroll_sensitivity, - } - } -} - -/// -#[derive(Clone, MallocSizeOf)] -pub struct CanvasBackground { - /// DOM node for the root element - pub root_element: OpaqueNode, - - /// The element whose style the canvas takes background properties from (see next field). - /// This can be the root element (same as the previous field), or the HTML `` element. - /// See - pub from_element: OpaqueNode, - - /// The computed styles to take background properties from. - #[conditional_malloc_size_of] - pub style: Option>, -} - -impl CanvasBackground { - fn for_root_element<'dom>(context: &LayoutContext, root_element: impl NodeExt<'dom>) -> Self { - let root_style = root_element.style(context); - - let mut style = root_style; - let mut from_element = root_element; - - // https://drafts.csswg.org/css-backgrounds/#body-background - // “if the computed value of background-image on the root element is none - // and its background-color is transparent” - if style.background_is_transparent() && - // “For documents whose root element is an HTML `HTML` element - // or an XHTML `html` element” - root_element.type_id() == LayoutNodeType::Element(LayoutElementType::HTMLHtmlElement) && - // Don’t try to access styles for an unstyled subtree - !matches!(style.clone_display().into(), Display::None) - { - // “that element’s first HTML `BODY` or XHTML `body` child element” - if let Some(body) = iter_child_nodes(root_element).find(|child| { - child.is_element() && - child.type_id() == - LayoutNodeType::Element(LayoutElementType::HTMLBodyElement) - }) { - style = body.style(context); - from_element = body; - } - } - - Self { - root_element: root_element.opaque(), - from_element: from_element.opaque(), - - // “However, if no boxes are generated for the element - // whose background would be used for the canvas - // (for example, if the root element has display: none), - // then the canvas background is transparent.” - style: if let Display::GeneratingBox(_) = style.clone_display().into() { - Some(style) - } else { - None - }, - } + physical_containing_block, + self.viewport_scroll_sensitivity, + ) } } diff --git a/components/layout/formatting_contexts.rs b/components/layout/formatting_contexts.rs index 4661c44592c..2b242c00361 100644 --- a/components/layout/formatting_contexts.rs +++ b/components/layout/formatting_contexts.rs @@ -4,16 +4,18 @@ use app_units::Au; use malloc_size_of_derive::MallocSizeOf; +use script::layout_dom::{ServoLayoutElement, ServoLayoutNode}; use servo_arc::Arc; +use style::context::SharedStyleContext; use style::properties::ComputedValues; use style::selector_parser::PseudoElement; use crate::context::LayoutContext; -use crate::dom::NodeExt; use crate::dom_traversal::{Contents, NodeAndStyleInfo}; use crate::flexbox::FlexContainer; use crate::flow::BlockFormattingContext; use crate::fragment_tree::{BaseFragmentInfo, FragmentFlags}; +use crate::geom::LazySize; use crate::layout_box_base::{ CacheableLayoutResult, CacheableLayoutResultAndInputs, LayoutBoxBase, }; @@ -69,9 +71,9 @@ impl Baselines { } impl IndependentFormattingContext { - pub fn construct<'dom, Node: NodeExt<'dom>>( + pub fn construct( context: &LayoutContext, - node_and_style_info: &NodeAndStyleInfo, + node_and_style_info: &NodeAndStyleInfo, display_inside: DisplayInside, contents: Contents, propagated_data: PropagatedBoxTreeData, @@ -111,11 +113,11 @@ impl IndependentFormattingContext { let table_grid_style = context .shared_context() .stylist - .style_for_anonymous::( - &context.shared_context().guards, - &PseudoElement::ServoTableGrid, - &node_and_style_info.style, - ); + .style_for_anonymous::( + &context.shared_context().guards, + &PseudoElement::ServoTableGrid, + &node_and_style_info.style, + ); base_fragment_info.flags.insert(FragmentFlags::DO_NOT_PAINT); IndependentNonReplacedContents::Table(Table::construct( context, @@ -217,6 +219,21 @@ impl IndependentFormattingContext { }, } } + + pub(crate) fn repair_style( + &mut self, + context: &SharedStyleContext, + node: &ServoLayoutNode, + new_style: &Arc, + ) { + self.base.repair_style(new_style); + match &mut self.contents { + IndependentFormattingContextContents::NonReplaced(content) => { + content.repair_style(context, node, new_style); + }, + IndependentFormattingContextContents::Replaced(..) => {}, + } + } } impl IndependentNonReplacedContents { @@ -227,6 +244,7 @@ impl IndependentNonReplacedContents { containing_block_for_children: &ContainingBlock, containing_block: &ContainingBlock, depends_on_block_constraints: bool, + lazy_block_size: &LazySize, ) -> CacheableLayoutResult { match self { IndependentNonReplacedContents::Flow(bfc) => bfc.layout( @@ -240,6 +258,7 @@ impl IndependentNonReplacedContents { positioning_context, containing_block_for_children, depends_on_block_constraints, + lazy_block_size, ), IndependentNonReplacedContents::Grid(fc) => fc.layout( layout_context, @@ -266,6 +285,7 @@ impl IndependentNonReplacedContents { level = "trace", ) )] + #[allow(clippy::too_many_arguments)] pub fn layout( &self, layout_context: &LayoutContext, @@ -274,6 +294,7 @@ impl IndependentNonReplacedContents { containing_block: &ContainingBlock, base: &LayoutBoxBase, depends_on_block_constraints: bool, + lazy_block_size: &LazySize, ) -> CacheableLayoutResult { if let Some(cache) = base.cached_layout_result.borrow().as_ref() { let cache = &**cache; @@ -295,16 +316,14 @@ impl IndependentNonReplacedContents { ); } - let mut child_positioning_context = PositioningContext::new_for_subtree( - positioning_context.collects_for_nearest_positioned_ancestor(), - ); - + let mut child_positioning_context = PositioningContext::default(); let result = self.layout_without_caching( layout_context, &mut child_positioning_context, containing_block_for_children, containing_block, depends_on_block_constraints, + lazy_block_size, ); *base.cached_layout_result.borrow_mut() = Some(Box::new(CacheableLayoutResultAndInputs { @@ -337,6 +356,26 @@ impl IndependentNonReplacedContents { pub(crate) fn is_table(&self) -> bool { matches!(self, Self::Table(_)) } + + fn repair_style( + &mut self, + context: &SharedStyleContext, + node: &ServoLayoutNode, + new_style: &Arc, + ) { + match self { + IndependentNonReplacedContents::Flow(block_formatting_context) => { + block_formatting_context.repair_style(node, new_style); + }, + IndependentNonReplacedContents::Flex(flex_container) => { + flex_container.repair_style(new_style) + }, + IndependentNonReplacedContents::Grid(taffy_container) => { + taffy_container.repair_style(new_style) + }, + IndependentNonReplacedContents::Table(table) => table.repair_style(context, new_style), + } + } } impl ComputeInlineContentSizes for IndependentNonReplacedContents { diff --git a/components/layout/fragment_tree/base_fragment.rs b/components/layout/fragment_tree/base_fragment.rs index 48d672a8547..ff5df44c225 100644 --- a/components/layout/fragment_tree/base_fragment.rs +++ b/components/layout/fragment_tree/base_fragment.rs @@ -32,10 +32,8 @@ impl BaseFragment { } } - /// Returns true if this fragment is non-anonymous and it is for the given - /// OpaqueNode, regardless of the pseudo element. - pub(crate) fn is_for_node(&self, node: OpaqueNode) -> bool { - self.tag.map(|tag| tag.node == node).unwrap_or(false) + pub(crate) fn is_anonymous(&self) -> bool { + self.tag.is_none() } } @@ -132,11 +130,6 @@ impl Tag { Tag { node, pseudo } } - /// Returns true if this tag is for a pseudo element. - pub(crate) fn is_pseudo(&self) -> bool { - self.pseudo.is_some() - } - pub(crate) fn to_display_list_fragment_id(self) -> u64 { combine_id_with_fragment_type(self.node.id(), self.pseudo.into()) } diff --git a/components/layout/fragment_tree/box_fragment.rs b/components/layout/fragment_tree/box_fragment.rs index 30be154caf1..b7c3a2a3524 100644 --- a/components/layout/fragment_tree/box_fragment.rs +++ b/components/layout/fragment_tree/box_fragment.rs @@ -2,11 +2,12 @@ * 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 app_units::Au; +use app_units::{Au, MAX_AU, MIN_AU}; use atomic_refcell::AtomicRefCell; use base::print_tree::PrintTree; use malloc_size_of_derive::MallocSizeOf; use servo_arc::Arc as ServoArc; +use servo_geometry::f32_rect_to_au_rect; use style::Zero; use style::computed_values::border_collapse::T as BorderCollapse; use style::computed_values::overflow_x::T as ComputedOverflow; @@ -15,7 +16,9 @@ use style::logical_geometry::WritingMode; use style::properties::ComputedValues; use style::values::specified::box_::DisplayOutside; -use super::{BaseFragment, BaseFragmentInfo, CollapsedBlockMargins, Fragment}; +use super::{BaseFragment, BaseFragmentInfo, CollapsedBlockMargins, Fragment, FragmentFlags}; +use crate::SharedStyle; +use crate::display_list::ToWebRender; use crate::formatting_contexts::Baselines; use crate::geom::{ AuOrAuto, LengthPercentageOrAuto, PhysicalPoint, PhysicalRect, PhysicalSides, ToLogical, @@ -37,11 +40,9 @@ pub(crate) enum BackgroundMode { /// Draw the background normally, getting information from the Fragment style. Normal, } - #[derive(MallocSizeOf)] pub(crate) struct ExtraBackground { - #[conditional_malloc_size_of] - pub style: ServoArc, + pub style: SharedStyle, pub rect: PhysicalRect, } @@ -57,7 +58,6 @@ pub(crate) enum SpecificLayoutInfo { pub(crate) struct BoxFragment { pub base: BaseFragment, - #[conditional_malloc_size_of] pub style: ServoArc, pub children: Vec, @@ -65,6 +65,10 @@ pub(crate) struct BoxFragment { /// does not include padding, border, or margin -- it only includes content. pub content_rect: PhysicalRect, + /// This [`BoxFragment`]'s containing block rectangle in coordinates relative to + /// the initial containing block, but not taking into account any transforms. + pub cumulative_containing_block_rect: PhysicalRect, + pub padding: PhysicalSides, pub border: PhysicalSides, pub margin: PhysicalSides, @@ -88,7 +92,7 @@ pub(crate) struct BoxFragment { pub scrollable_overflow_from_children: PhysicalRect, /// The resolved box insets if this box is `position: sticky`. These are calculated - /// during stacking context tree construction because they rely on the size of the + /// during `StackingContextTree` construction because they rely on the size of the /// scroll container. pub(crate) resolved_sticky_insets: AtomicRefCell>>, @@ -112,7 +116,7 @@ impl BoxFragment { ) -> BoxFragment { let scrollable_overflow_from_children = children.iter().fold(PhysicalRect::zero(), |acc, child| { - acc.union(&child.scrollable_overflow()) + acc.union(&child.scrollable_overflow_for_parent()) }); BoxFragment { @@ -120,6 +124,7 @@ impl BoxFragment { style, children, content_rect, + cumulative_containing_block_rect: Default::default(), padding, border, margin, @@ -195,6 +200,8 @@ impl BoxFragment { self } + /// Get the scrollable overflow for this [`BoxFragment`] relative to its + /// containing block. pub fn scrollable_overflow(&self) -> PhysicalRect { let physical_padding_rect = self.padding_rect(); let content_origin = self.content_rect.origin.to_vector(); @@ -205,6 +212,14 @@ impl BoxFragment { ) } + pub(crate) fn set_containing_block(&mut self, containing_block: &PhysicalRect) { + self.cumulative_containing_block_rect = *containing_block; + } + + pub fn offset_by_containing_block(&self, rect: &PhysicalRect) -> PhysicalRect { + rect.translate(self.cumulative_containing_block_rect.origin.to_vector()) + } + pub(crate) fn padding_rect(&self) -> PhysicalRect { self.content_rect.outer_rect(self.padding) } @@ -221,6 +236,16 @@ impl BoxFragment { self.margin + self.border + self.padding } + pub(crate) fn is_root_element(&self) -> bool { + self.base.flags.intersects(FragmentFlags::IS_ROOT_ELEMENT) + } + + pub(crate) fn is_body_element_of_html_element_root(&self) -> bool { + self.base + .flags + .intersects(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) + } + pub fn print(&self, tree: &mut PrintTree) { tree.new_level(format!( "Box\ @@ -252,36 +277,99 @@ impl BoxFragment { pub fn scrollable_overflow_for_parent(&self) -> PhysicalRect { let mut overflow = self.border_rect(); - if self.style.establishes_scroll_container(self.base.flags) { - return overflow; + if !self.style.establishes_scroll_container(self.base.flags) { + // https://www.w3.org/TR/css-overflow-3/#scrollable + // Only include the scrollable overflow of a child box if it has overflow: visible. + let scrollable_overflow = self.scrollable_overflow(); + let bottom_right = PhysicalPoint::new( + overflow.max_x().max(scrollable_overflow.max_x()), + overflow.max_y().max(scrollable_overflow.max_y()), + ); + + let overflow_style = self.style.effective_overflow(self.base.flags); + if overflow_style.y == ComputedOverflow::Visible { + overflow.origin.y = overflow.origin.y.min(scrollable_overflow.origin.y); + overflow.size.height = bottom_right.y - overflow.origin.y; + } + + if overflow_style.x == ComputedOverflow::Visible { + overflow.origin.x = overflow.origin.x.min(scrollable_overflow.origin.x); + overflow.size.width = bottom_right.x - overflow.origin.x; + } } - // https://www.w3.org/TR/css-overflow-3/#scrollable - // Only include the scrollable overflow of a child box if it has overflow: visible. - let scrollable_overflow = self.scrollable_overflow(); - let bottom_right = PhysicalPoint::new( - overflow.max_x().max(scrollable_overflow.max_x()), - overflow.max_y().max(scrollable_overflow.max_y()), - ); - - let overflow_style = self.style.effective_overflow(self.base.flags); - if overflow_style.y == ComputedOverflow::Visible { - overflow.origin.y = overflow.origin.y.min(scrollable_overflow.origin.y); - overflow.size.height = bottom_right.y - overflow.origin.y; - } - - if overflow_style.x == ComputedOverflow::Visible { - overflow.origin.x = overflow.origin.x.min(scrollable_overflow.origin.x); - overflow.size.width = bottom_right.x - overflow.origin.x; + // + // > ...accounting for transforms by projecting each box onto the plane of + // > the element that establishes its 3D rendering context. [CSS3-TRANSFORMS] + // Both boxes and its scrollable overflow (if it is included) should be transformed accordingly. + // + // TODO(stevennovaryo): We are supposed to handle perspective transform and 3d context, but it is yet to happen. + if self + .style + .has_effective_transform_or_perspective(self.base.flags) + { + if let Some(transform) = + self.calculate_transform_matrix(&self.border_rect().to_untyped()) + { + if let Some(transformed_overflow_box) = + transform.outer_transformed_rect(&overflow.to_webrender().to_rect()) + { + overflow = + f32_rect_to_au_rect(transformed_overflow_box.to_untyped()).cast_unit(); + } + } } overflow } - pub(crate) fn calculate_resolved_insets_if_positioned( + /// + /// > area beyond the scroll origin in either axis is considered the unreachable scrollable overflow region + /// + /// Return the clipped the scrollable overflow based on its scroll origin, determined by overflow direction. + /// For an element, the clip rect is the padding rect and for viewport, it is the initial containing block. + pub fn clip_unreachable_scrollable_overflow_region( &self, - containing_block: &PhysicalRect, - ) -> PhysicalSides { + scrollable_overflow: PhysicalRect, + clipping_rect: PhysicalRect, + ) -> PhysicalRect { + let scrolling_direction = self.style.overflow_direction(); + let mut scrollable_overflow_box = scrollable_overflow.to_box2d(); + let mut clipping_box = clipping_rect.to_box2d(); + + if scrolling_direction.rightward { + clipping_box.max.x = MAX_AU; + } else { + clipping_box.min.x = MIN_AU; + } + + if scrolling_direction.downward { + clipping_box.max.y = MAX_AU; + } else { + clipping_box.min.y = MIN_AU; + } + + scrollable_overflow_box = scrollable_overflow_box.intersection_unchecked(&clipping_box); + + match scrollable_overflow_box.is_negative() { + true => PhysicalRect::zero(), + false => scrollable_overflow_box.to_rect(), + } + } + + /// + /// > area beyond the scroll origin in either axis is considered the unreachable scrollable overflow region + /// + /// Return the clipped the scrollable overflow based on its scroll origin, determined by overflow direction. + /// This will coincides with the scrollport if the fragment is a scroll container. + pub fn reachable_scrollable_overflow_region(&self) -> PhysicalRect { + self.clip_unreachable_scrollable_overflow_region( + self.scrollable_overflow(), + self.padding_rect(), + ) + } + + pub(crate) fn calculate_resolved_insets_if_positioned(&self) -> PhysicalSides { let position = self.style.get_box().position; debug_assert_ne!( position, @@ -309,7 +397,10 @@ impl BoxFragment { // used value. Otherwise the resolved value is the computed value." // https://drafts.csswg.org/cssom/#resolved-values let insets = self.style.physical_box_offsets(); - let (cb_width, cb_height) = (containing_block.width(), containing_block.height()); + let (cb_width, cb_height) = ( + self.cumulative_containing_block_rect.width(), + self.cumulative_containing_block_rect.height(), + ); if position == ComputedPosition::Relative { let get_resolved_axis = |start: &LengthPercentageOrAuto, end: &LengthPercentageOrAuto, diff --git a/components/layout/fragment_tree/fragment.rs b/components/layout/fragment_tree/fragment.rs index d0d1b9b1104..c81fd59e36b 100644 --- a/components/layout/fragment_tree/fragment.rs +++ b/components/layout/fragment_tree/fragment.rs @@ -7,13 +7,13 @@ use std::sync::Arc; use app_units::Au; use base::id::PipelineId; use base::print_tree::PrintTree; +use euclid::{Point2D, Rect, Size2D, UnknownUnit}; use fonts::{ByteIndex, FontMetrics, GlyphStore}; use malloc_size_of_derive::MallocSizeOf; use range::Range as ServoRange; use servo_arc::Arc as ServoArc; use style::Zero; use style::properties::ComputedValues; -use style::values::specified::text::TextDecorationLine; use webrender_api::{FontInstanceKey, ImageKey}; use super::{ @@ -21,7 +21,8 @@ use super::{ Tag, }; use crate::cell::ArcRefCell; -use crate::geom::{LogicalSides, PhysicalRect}; +use crate::flow::inline::SharedInlineStyles; +use crate::geom::{LogicalSides, PhysicalPoint, PhysicalRect}; use crate::style_ext::ComputedValuesExt; #[derive(Clone, MallocSizeOf)] @@ -63,28 +64,21 @@ pub(crate) struct CollapsedMargin { #[derive(MallocSizeOf)] pub(crate) struct TextFragment { pub base: BaseFragment, - #[conditional_malloc_size_of] - pub parent_style: ServoArc, + pub inline_styles: SharedInlineStyles, pub rect: PhysicalRect, pub font_metrics: FontMetrics, pub font_key: FontInstanceKey, #[conditional_malloc_size_of] pub glyphs: Vec>, - /// A flag that represents the _used_ value of the text-decoration property. - pub text_decoration_line: TextDecorationLine, - /// Extra space to add for each justification opportunity. pub justification_adjustment: Au, pub selection_range: Option>, - #[conditional_malloc_size_of] - pub selected_style: ServoArc, } #[derive(MallocSizeOf)] pub(crate) struct ImageFragment { pub base: BaseFragment, - #[conditional_malloc_size_of] pub style: ServoArc, pub rect: PhysicalRect, pub clip: PhysicalRect, @@ -96,7 +90,6 @@ pub(crate) struct IFrameFragment { pub base: BaseFragment, pub pipeline_id: PipelineId, pub rect: PhysicalRect, - #[conditional_malloc_size_of] pub style: ServoArc, } @@ -112,6 +105,7 @@ impl Fragment { Fragment::Float(fragment) => fragment.borrow().base.clone(), }) } + pub(crate) fn mutate_content_rect(&mut self, callback: impl FnOnce(&mut PhysicalRect)) { match self { Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => { @@ -124,6 +118,28 @@ impl Fragment { } } + pub(crate) fn set_containing_block(&self, containing_block: &PhysicalRect) { + match self { + Fragment::Box(box_fragment) => box_fragment + .borrow_mut() + .set_containing_block(containing_block), + Fragment::Float(float_fragment) => float_fragment + .borrow_mut() + .set_containing_block(containing_block), + Fragment::Positioning(positioning_fragment) => positioning_fragment + .borrow_mut() + .set_containing_block(containing_block), + Fragment::AbsoluteOrFixedPositioned(hoisted_shared_fragment) => { + if let Some(ref fragment) = hoisted_shared_fragment.borrow().fragment { + fragment.set_containing_block(containing_block); + } + }, + Fragment::Text(_) => {}, + Fragment::Image(_) => {}, + Fragment::IFrame(_) => {}, + } + } + pub fn tag(&self) -> Option { self.base().and_then(|base| base.tag) } @@ -146,17 +162,28 @@ impl Fragment { } } - pub fn scrolling_area(&self, containing_block: &PhysicalRect) -> PhysicalRect { + pub fn unclipped_scrolling_area(&self) -> PhysicalRect { match self { - Fragment::Box(fragment) | Fragment::Float(fragment) => fragment - .borrow() - .scrollable_overflow() - .translate(containing_block.origin.to_vector()), - _ => self.scrollable_overflow(), + Fragment::Box(fragment) | Fragment::Float(fragment) => { + let fragment = fragment.borrow(); + fragment.offset_by_containing_block(&fragment.scrollable_overflow()) + }, + _ => self.scrollable_overflow_for_parent(), } } - pub fn scrollable_overflow(&self) -> PhysicalRect { + pub fn scrolling_area(&self) -> PhysicalRect { + match self { + Fragment::Box(fragment) | Fragment::Float(fragment) => { + let fragment = fragment.borrow(); + fragment + .offset_by_containing_block(&fragment.reachable_scrollable_overflow_region()) + }, + _ => self.scrollable_overflow_for_parent(), + } + } + + pub fn scrollable_overflow_for_parent(&self) -> PhysicalRect { match self { Fragment::Box(fragment) | Fragment::Float(fragment) => { fragment.borrow().scrollable_overflow_for_parent() @@ -169,6 +196,59 @@ impl Fragment { } } + pub(crate) fn cumulative_border_box_rect(&self) -> Option> { + match self { + Fragment::Box(fragment) | Fragment::Float(fragment) => { + let fragment = fragment.borrow(); + Some(fragment.offset_by_containing_block(&fragment.border_rect())) + }, + Fragment::Positioning(fragment) => { + let fragment = fragment.borrow(); + Some(fragment.offset_by_containing_block(&fragment.rect)) + }, + Fragment::Text(_) | + Fragment::AbsoluteOrFixedPositioned(_) | + Fragment::Image(_) | + Fragment::IFrame(_) => None, + } + } + + pub(crate) fn client_rect(&self) -> Rect { + let rect = match self { + Fragment::Box(fragment) | Fragment::Float(fragment) => { + // https://drafts.csswg.org/cssom-view/#dom-element-clienttop + // " If the element has no associated CSS layout box or if the + // CSS layout box is inline, return zero." For this check we + // also explicitly ignore the list item portion of the display + // style. + let fragment = fragment.borrow(); + if fragment.is_inline_box() { + return Rect::zero(); + } + + if fragment.is_table_wrapper() { + // For tables the border actually belongs to the table grid box, + // so we need to include it in the dimension of the table wrapper box. + let mut rect = fragment.border_rect(); + rect.origin = PhysicalPoint::zero(); + rect + } else { + let mut rect = fragment.padding_rect(); + rect.origin = PhysicalPoint::new(fragment.border.left, fragment.border.top); + rect + } + }, + _ => return Rect::zero(), + } + .to_untyped(); + + let rect = Rect::new( + Point2D::new(rect.origin.x.to_f32_px(), rect.origin.y.to_f32_px()), + Size2D::new(rect.size.width.to_f32_px(), rect.size.height.to_f32_px()), + ); + rect.round().to_i32() + } + pub(crate) fn find( &self, manager: &ContainingBlockManager>, @@ -220,6 +300,25 @@ impl Fragment { _ => None, } } + + pub(crate) fn repair_style(&self, style: &ServoArc) { + match self { + Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => { + box_fragment.borrow_mut().style = style.clone() + }, + Fragment::Positioning(positioning_fragment) => { + positioning_fragment.borrow_mut().style = style.clone(); + }, + Fragment::AbsoluteOrFixedPositioned(positioned_fragment) => { + if let Some(ref fragment) = positioned_fragment.borrow().fragment { + fragment.repair_style(style); + } + }, + Fragment::Text(..) => unreachable!("Should never try to repair style of TextFragment"), + Fragment::Image(image_fragment) => image_fragment.borrow_mut().style = style.clone(), + Fragment::IFrame(iframe_fragment) => iframe_fragment.borrow_mut().style = style.clone(), + } + } } impl TextFragment { diff --git a/components/layout/fragment_tree/fragment_tree.rs b/components/layout/fragment_tree/fragment_tree.rs index bb3c659466c..ba03a72ac21 100644 --- a/components/layout/fragment_tree/fragment_tree.rs +++ b/components/layout/fragment_tree/fragment_tree.rs @@ -5,17 +5,16 @@ use app_units::Au; use base::print_tree::PrintTree; use compositing_traits::display_list::AxesScrollSensitivity; -use euclid::default::{Point2D, Rect, Size2D}; +use euclid::default::Size2D; use fxhash::FxHashSet; use malloc_size_of_derive::MallocSizeOf; use style::animation::AnimationSetKey; -use style::dom::OpaqueNode; use webrender_api::units; -use super::{ContainingBlockManager, Fragment, Tag}; -use crate::display_list::StackingContext; -use crate::flow::CanvasBackground; -use crate::geom::{PhysicalPoint, PhysicalRect}; +use super::{BoxFragment, ContainingBlockManager, Fragment}; +use crate::ArcRefCell; +use crate::context::LayoutContext; +use crate::geom::PhysicalRect; #[derive(MallocSizeOf)] pub struct FragmentTree { @@ -36,26 +35,59 @@ pub struct FragmentTree { /// The containing block used in the layout of this fragment tree. pub(crate) initial_containing_block: PhysicalRect, - /// - pub(crate) canvas_background: CanvasBackground, - /// Whether or not the viewport is sensitive to scroll input events. pub viewport_scroll_sensitivity: AxesScrollSensitivity, } impl FragmentTree { - pub(crate) fn build_display_list( - &self, - builder: &mut crate::display_list::DisplayListBuilder, - root_stacking_context: &StackingContext, - ) { - // Paint the canvas’ background (if any) before/under everything else - root_stacking_context.build_canvas_background_display_list( - builder, - self, - &self.initial_containing_block, - ); - root_stacking_context.build_display_list(builder); + pub(crate) fn new( + layout_context: &LayoutContext, + root_fragments: Vec, + scrollable_overflow: PhysicalRect, + initial_containing_block: PhysicalRect, + viewport_scroll_sensitivity: AxesScrollSensitivity, + ) -> Self { + let fragment_tree = Self { + root_fragments, + scrollable_overflow, + initial_containing_block, + viewport_scroll_sensitivity, + }; + + // As part of building the fragment tree, we want to stop animating elements and + // pseudo-elements that used to be animating or had animating images attached to + // them. Create a set of all elements that used to be animating. + let mut animations = layout_context.style_context.animations.sets.write(); + let mut invalid_animating_nodes: FxHashSet<_> = animations.keys().cloned().collect(); + let mut image_animations = layout_context.node_image_animation_map.write().to_owned(); + let mut invalid_image_animating_nodes: FxHashSet<_> = image_animations + .keys() + .cloned() + .map(|node| AnimationSetKey::new(node, None)) + .collect(); + + fragment_tree.find(|fragment, _level, containing_block| { + if let Some(tag) = fragment.tag() { + invalid_animating_nodes.remove(&AnimationSetKey::new(tag.node, tag.pseudo)); + invalid_image_animating_nodes.remove(&AnimationSetKey::new(tag.node, tag.pseudo)); + } + + fragment.set_containing_block(containing_block); + None::<()> + }); + + // Cancel animations for any elements and pseudo-elements that are no longer found + // in the fragment tree. + for node in &invalid_animating_nodes { + if let Some(state) = animations.get_mut(node) { + state.cancel_all_animations(); + } + } + for node in &invalid_image_animating_nodes { + image_animations.remove(&node.node); + } + + fragment_tree } pub fn print(&self) { @@ -86,109 +118,67 @@ impl FragmentTree { .find_map(|child| child.find(&info, 0, &mut process_func)) } - pub fn remove_nodes_in_fragment_tree_from_set(&self, set: &mut FxHashSet) { - self.find(|fragment, _, _| { - let tag = fragment.tag()?; - set.remove(&AnimationSetKey::new(tag.node, tag.pseudo)); - None::<()> - }); - } - - /// Get the vector of rectangles that surrounds the fragments of the node with the given address. - /// This function answers the `getClientRects()` query and the union of the rectangles answers - /// the `getBoundingClientRect()` query. + /// /// - /// TODO: This function is supposed to handle scroll offsets, but that isn't happening at all. - pub fn get_content_boxes_for_node(&self, requested_node: OpaqueNode) -> Vec> { - let mut content_boxes = Vec::new(); - let tag_to_find = Tag::new(requested_node); - self.find(|fragment, _, containing_block| { - if fragment.tag() != Some(tag_to_find) { - return None::<()>; - } - - let fragment_relative_rect = match fragment { - Fragment::Box(fragment) | Fragment::Float(fragment) => { - fragment.borrow().border_rect() - }, - Fragment::Positioning(fragment) => fragment.borrow().rect, - Fragment::Text(fragment) => fragment.borrow().rect, - Fragment::AbsoluteOrFixedPositioned(_) | - Fragment::Image(_) | - Fragment::IFrame(_) => return None, - }; - - let rect = fragment_relative_rect.translate(containing_block.origin.to_vector()); - - content_boxes.push(rect.to_untyped()); - None::<()> - }); - content_boxes - } - - pub fn get_border_dimensions_for_node(&self, requested_node: OpaqueNode) -> Rect { - let tag_to_find = Tag::new(requested_node); - self.find(|fragment, _, _containing_block| { - if fragment.tag() != Some(tag_to_find) { - return None; - } - - let rect = match fragment { - Fragment::Box(fragment) | Fragment::Float(fragment) => { - // https://drafts.csswg.org/cssom-view/#dom-element-clienttop - // " If the element has no associated CSS layout box or if the - // CSS layout box is inline, return zero." For this check we - // also explicitly ignore the list item portion of the display - // style. - let fragment = fragment.borrow(); - if fragment.is_inline_box() { - return Some(Rect::zero()); - } - if fragment.is_table_wrapper() { - // For tables the border actually belongs to the table grid box, - // so we need to include it in the dimension of the table wrapper box. - let mut rect = fragment.border_rect(); - rect.origin = PhysicalPoint::zero(); - rect - } else { - let mut rect = fragment.padding_rect(); - rect.origin = PhysicalPoint::new(fragment.border.left, fragment.border.top); - rect - } - }, - Fragment::Positioning(fragment) => fragment.borrow().rect.cast_unit(), - Fragment::Text(text_fragment) => text_fragment.borrow().rect, - _ => return None, - }; - - let rect = Rect::new( - Point2D::new(rect.origin.x.to_f32_px(), rect.origin.y.to_f32_px()), - Size2D::new(rect.size.width.to_f32_px(), rect.size.height.to_f32_px()), - ); - Some(rect.round().to_i32().to_untyped()) - }) - .unwrap_or_else(Rect::zero) - } - + /// Scrolling area for a viewport that is clipped according to overflow direction of root element. pub fn get_scrolling_area_for_viewport(&self) -> PhysicalRect { let mut scroll_area = self.initial_containing_block; - for fragment in self.root_fragments.iter() { - scroll_area = fragment - .scrolling_area(&self.initial_containing_block) - .union(&scroll_area); + if let Some(root_fragment) = self.root_fragments.first() { + for fragment in self.root_fragments.iter() { + scroll_area = fragment.unclipped_scrolling_area().union(&scroll_area); + } + match root_fragment { + Fragment::Box(fragment) | Fragment::Float(fragment) => fragment + .borrow() + .clip_unreachable_scrollable_overflow_region( + scroll_area, + self.initial_containing_block, + ), + _ => scroll_area, + } + } else { + scroll_area } - scroll_area } - pub fn get_scrolling_area_for_node(&self, requested_node: OpaqueNode) -> PhysicalRect { - let tag_to_find = Tag::new(requested_node); - let scroll_area = self.find(|fragment, _, containing_block| { - if fragment.tag() == Some(tag_to_find) { - Some(fragment.scrolling_area(containing_block)) - } else { - None - } - }); - scroll_area.unwrap_or_else(PhysicalRect::::zero) + /// Find the `` element's [`Fragment`], if it exists in this [`FragmentTree`]. + pub(crate) fn body_fragment(&self) -> Option> { + fn find_body(children: &[Fragment]) -> Option> { + children.iter().find_map(|fragment| { + match fragment { + Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => { + let borrowed_box_fragment = box_fragment.borrow(); + if borrowed_box_fragment.is_body_element_of_html_element_root() { + return Some(box_fragment.clone()); + } + + // The fragment for the `` element is typically a child of the root (though, + // not if it's absolutely positioned), so we need to recurse into the children of + // the root to find it. + // + // Additionally, recurse into any anonymous fragments, as the `` fragment may + // have created anonymous parents (for instance by creating an inline formatting context). + if borrowed_box_fragment.is_root_element() || + borrowed_box_fragment.base.is_anonymous() + { + find_body(&borrowed_box_fragment.children) + } else { + None + } + }, + Fragment::Positioning(positioning_context) + if positioning_context.borrow().base.is_anonymous() => + { + // If the `` element is a `display: inline` then it might be nested inside of a + // `PositioningFragment` for the purposes of putting it on the first line of the implied + // inline formatting context. + find_body(&positioning_context.borrow().children) + }, + _ => None, + } + }) + } + + find_body(&self.root_fragments) } } diff --git a/components/layout/fragment_tree/positioning_fragment.rs b/components/layout/fragment_tree/positioning_fragment.rs index 853caed6709..e45a6137bff 100644 --- a/components/layout/fragment_tree/positioning_fragment.rs +++ b/components/layout/fragment_tree/positioning_fragment.rs @@ -20,17 +20,25 @@ pub(crate) struct PositioningFragment { pub base: BaseFragment, pub rect: PhysicalRect, pub children: Vec, + /// The scrollable overflow of this anonymous fragment's children. pub scrollable_overflow: PhysicalRect, - /// If this fragment was created with a style, the style of the fragment. - #[conditional_malloc_size_of] - pub style: Option>, + /// The style of the fragment. + pub style: ServoArc, + + /// This [`PositioningFragment`]'s containing block rectangle in coordinates relative to + /// the initial containing block, but not taking into account any transforms. + pub cumulative_containing_block_rect: PhysicalRect, } impl PositioningFragment { - pub fn new_anonymous(rect: PhysicalRect, children: Vec) -> ArcRefCell { - Self::new_with_base_fragment(BaseFragment::anonymous(), None, rect, children) + pub fn new_anonymous( + style: ServoArc, + rect: PhysicalRect, + children: Vec, + ) -> ArcRefCell { + Self::new_with_base_fragment(BaseFragment::anonymous(), style, rect, children) } pub fn new_empty( @@ -38,12 +46,12 @@ impl PositioningFragment { rect: PhysicalRect, style: ServoArc, ) -> ArcRefCell { - Self::new_with_base_fragment(base_fragment_info.into(), Some(style), rect, Vec::new()) + Self::new_with_base_fragment(base_fragment_info.into(), style, rect, Vec::new()) } fn new_with_base_fragment( base: BaseFragment, - style: Option>, + style: ServoArc, rect: PhysicalRect, children: Vec, ) -> ArcRefCell { @@ -51,7 +59,7 @@ impl PositioningFragment { let scrollable_overflow = children.iter().fold(PhysicalRect::zero(), |acc, child| { acc.union( &child - .scrollable_overflow() + .scrollable_overflow_for_parent() .translate(content_origin.to_vector()), ) }); @@ -61,9 +69,18 @@ impl PositioningFragment { rect, children, scrollable_overflow, + cumulative_containing_block_rect: PhysicalRect::zero(), }) } + pub(crate) fn set_containing_block(&mut self, containing_block: &PhysicalRect) { + self.cumulative_containing_block_rect = *containing_block; + } + + pub fn offset_by_containing_block(&self, rect: &PhysicalRect) -> PhysicalRect { + rect.translate(self.cumulative_containing_block_rect.origin.to_vector()) + } + pub fn print(&self, tree: &mut PrintTree) { tree.new_level(format!( "PositioningFragment\ diff --git a/components/layout/geom.rs b/components/layout/geom.rs index 49f031cbd18..4065b785832 100644 --- a/components/layout/geom.rs +++ b/components/layout/geom.rs @@ -2,7 +2,7 @@ * 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::LazyCell; +use std::cell::{LazyCell, OnceCell}; use std::convert::From; use std::fmt; use std::ops::{Add, AddAssign, Neg, Sub, SubAssign}; @@ -767,7 +767,9 @@ impl From for Size { StyleSize::FitContent => Size::FitContent, StyleSize::FitContentFunction(lp) => Size::FitContentFunction(lp.0), StyleSize::Stretch => Size::Stretch, - StyleSize::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"), + StyleSize::AnchorSizeFunction(_) | StyleSize::AnchorContainingCalcFunction(_) => { + unreachable!("anchor-size() should be disabled") + }, } } } @@ -782,7 +784,9 @@ impl From for Size { StyleMaxSize::FitContent => Size::FitContent, StyleMaxSize::FitContentFunction(lp) => Size::FitContentFunction(lp.0), StyleMaxSize::Stretch => Size::Stretch, - StyleMaxSize::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"), + StyleMaxSize::AnchorSizeFunction(_) | StyleMaxSize::AnchorContainingCalcFunction(_) => { + unreachable!("anchor-size() should be disabled") + }, } } } @@ -1098,3 +1102,87 @@ impl Sizes { ) } } + +struct LazySizeData<'a> { + sizes: &'a Sizes, + axis: Direction, + automatic_size: Size, + get_automatic_minimum_size: fn() -> Au, + stretch_size: Option, + is_table: bool, +} + +/// Represents a size that can't be fully resolved until the intrinsic size +/// is known. This is useful in the block axis, since the intrinsic size +/// depends on layout, but the other inputs are known beforehand. +pub(crate) struct LazySize<'a> { + result: OnceCell, + data: Option>, +} + +impl<'a> LazySize<'a> { + pub(crate) fn new( + sizes: &'a Sizes, + axis: Direction, + automatic_size: Size, + get_automatic_minimum_size: fn() -> Au, + stretch_size: Option, + is_table: bool, + ) -> Self { + Self { + result: OnceCell::new(), + data: Some(LazySizeData { + sizes, + axis, + automatic_size, + get_automatic_minimum_size, + stretch_size, + is_table, + }), + } + } + + /// Creates a [`LazySize`] that will resolve to the intrinsic size. + /// Should be equivalent to [`LazySize::new()`] with default parameters, + /// but avoiding the trouble of getting a reference to a [`Sizes::default()`] + /// which lives long enough. + /// + /// TODO: It's not clear what this should do if/when [`LazySize::resolve()`] + /// is changed to accept a [`ContentSizes`] as the intrinsic size. + pub(crate) fn intrinsic() -> Self { + Self { + result: OnceCell::new(), + data: None, + } + } + + /// Resolves the [`LazySize`] into [`Au`], caching the result. + /// The argument is a callback that computes the intrinsic size lazily. + /// + /// TODO: The intrinsic size should probably be a [`ContentSizes`] instead of [`Au`]. + pub(crate) fn resolve(&self, get_content_size: impl FnOnce() -> Au) -> Au { + *self.result.get_or_init(|| { + let Some(ref data) = self.data else { + return get_content_size(); + }; + data.sizes.resolve( + data.axis, + data.automatic_size, + data.get_automatic_minimum_size, + data.stretch_size, + || get_content_size().into(), + data.is_table, + ) + }) + } +} + +impl From for LazySize<'_> { + /// Creates a [`LazySize`] that will resolve to the given [`Au`], + /// ignoring the intrinsic size. + fn from(value: Au) -> Self { + let result = OnceCell::new(); + result.set(value).unwrap(); + LazySize { result, data: None } + } +} diff --git a/components/layout/layout_box_base.rs b/components/layout/layout_box_base.rs index 71fbfdeced1..161aee0f9bb 100644 --- a/components/layout/layout_box_base.rs +++ b/components/layout/layout_box_base.rs @@ -27,7 +27,6 @@ use crate::{ConstraintSpace, ContainingBlockSize}; #[derive(MallocSizeOf)] pub(crate) struct LayoutBoxBase { pub base_fragment_info: BaseFragmentInfo, - #[conditional_malloc_size_of] pub style: Arc, pub cached_inline_content_size: AtomicRefCell>>, @@ -90,6 +89,13 @@ impl LayoutBoxBase { pub(crate) fn clear_fragments(&self) { self.fragments.borrow_mut().clear(); } + + pub(crate) fn repair_style(&mut self, new_style: &Arc) { + self.style = new_style.clone(); + for fragment in self.fragments.borrow_mut().iter_mut() { + fragment.repair_style(new_style); + } + } } impl Debug for LayoutBoxBase { diff --git a/components/layout/layout_impl.rs b/components/layout/layout_impl.rs index 61550df2723..4ab69976671 100644 --- a/components/layout/layout_impl.rs +++ b/components/layout/layout_impl.rs @@ -4,9 +4,8 @@ #![allow(unsafe_code)] -use std::cell::{Cell, LazyCell, RefCell}; -use std::collections::{HashMap, HashSet}; -use std::ffi::c_void; +use std::cell::{Cell, RefCell}; +use std::collections::HashMap; use std::fmt::Debug; use std::process; use std::sync::{Arc, LazyLock}; @@ -16,14 +15,13 @@ use base::Epoch; use base::id::{PipelineId, WebViewId}; use compositing_traits::CrossProcessCompositorApi; use constellation_traits::ScrollState; -use embedder_traits::resources::{self, Resource}; use embedder_traits::{UntrustedNodeAddress, ViewportDetails}; -use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect, Size2D as UntypedSize2D}; +use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect}; use euclid::{Point2D, Scale, Size2D, Vector2D}; use fnv::FnvHashMap; use fonts::{FontContext, FontContextWebFontMethods}; use fonts_traits::StylesheetWebFontLoadFinishedCallback; -use fxhash::{FxHashMap, FxHashSet}; +use fxhash::FxHashMap; use ipc_channel::ipc::IpcSender; use log::{debug, error}; use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOf, MallocSizeOfOps}; @@ -34,21 +32,22 @@ use profile_traits::time::{ self as profile_time, TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType, }; use profile_traits::{path, time_profile}; -use script::layout_dom::{ServoLayoutElement, ServoLayoutNode}; +use rayon::ThreadPool; +use script::layout_dom::{ServoLayoutDocument, ServoLayoutElement, ServoLayoutNode}; use script_layout_interface::{ - ImageAnimationState, Layout, LayoutConfig, LayoutFactory, NodesFromPointQueryType, - OffsetParentResponse, ReflowGoal, ReflowRequest, ReflowResult, TrustedNodeAddress, + Layout, LayoutConfig, LayoutFactory, NodesFromPointQueryType, OffsetParentResponse, ReflowGoal, + ReflowRequest, ReflowResult, TrustedNodeAddress, }; use script_traits::{DrawAPaintImageResult, PaintWorkletError, Painter, ScriptThreadMessage}; use servo_arc::Arc as ServoArc; use servo_config::opts::{self, DebugOptions}; use servo_config::pref; use servo_url::ServoUrl; -use style::animation::{AnimationSetKey, DocumentAnimationSet}; +use style::animation::DocumentAnimationSet; use style::context::{ QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters, SharedStyleContext, }; -use style::dom::{OpaqueNode, TElement, TNode}; +use style::dom::{OpaqueNode, ShowSubtreeDataAndPrimaryValues, TElement, TNode}; use style::error_reporting::RustLogReporter; use style::font_metrics::FontMetrics; use style::global_style_data::GLOBAL_STYLE_DATA; @@ -57,7 +56,7 @@ use style::media_queries::{Device, MediaList, MediaType}; use style::properties::style_structs::Font; use style::properties::{ComputedValues, PropertyId}; use style::queries::values::PrefersColorScheme; -use style::selector_parser::{PseudoElement, SnapshotMap}; +use style::selector_parser::{PseudoElement, RestyleDamage, SnapshotMap}; use style::servo::media_queries::FontMetricsProvider; use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard, StylesheetGuards}; use style::stylesheets::{ @@ -69,7 +68,7 @@ use style::traversal::DomTraversal; use style::traversal_flags::TraversalFlags; use style::values::computed::font::GenericFontFamily; use style::values::computed::{CSSPixelLength, FontSize, Length, NonNegativeLength}; -use style::values::specified::font::KeywordInfo; +use style::values::specified::font::{KeywordInfo, QueryFontMetricsFlags}; use style::{Zero, driver}; use style_traits::{CSSPixel, SpeculativePainter}; use stylo_atoms::Atom; @@ -78,13 +77,13 @@ use webrender_api::units::{DevicePixel, DevicePoint, LayoutPixel, LayoutPoint, L use webrender_api::{ExternalScrollId, HitTestFlags}; use crate::context::LayoutContext; -use crate::display_list::{DisplayList, WebRenderImageInfo}; +use crate::display_list::{DisplayListBuilder, StackingContextTree, WebRenderImageInfo}; use crate::query::{ - get_the_text_steps, process_content_box_request, process_content_boxes_request, - process_node_geometry_request, process_node_scroll_area_request, process_offset_parent_query, + get_the_text_steps, process_client_rect_request, process_content_box_request, + process_content_boxes_request, process_node_scroll_area_request, process_offset_parent_query, process_resolved_font_style_query, process_resolved_style_request, process_text_index_request, }; -use crate::traversal::RecalcStyle; +use crate::traversal::{RecalcStyle, compute_damage_and_repair_style}; use crate::{BoxTree, FragmentTree}; // This mutex is necessary due to syncronisation issues between two different types of thread-local storage @@ -95,6 +94,18 @@ use crate::{BoxTree, FragmentTree}; static STYLE_THREAD_POOL: Mutex<&style::global_style_data::STYLE_THREAD_POOL> = Mutex::new(&style::global_style_data::STYLE_THREAD_POOL); +/// A CSS file to style the user agent stylesheet. +static USER_AGENT_CSS: &[u8] = include_bytes!("./stylesheets/user-agent.css"); + +/// A CSS file to style the Servo browser. +static SERVO_CSS: &[u8] = include_bytes!("./stylesheets/servo.css"); + +/// A CSS file to style the presentational hints. +static PRESENTATIONAL_HINTS_CSS: &[u8] = include_bytes!("./stylesheets/presentational-hints.css"); + +/// A CSS file to style the quirks mode. +static QUIRKS_MODE_CSS: &[u8] = include_bytes!("./stylesheets/quirks-mode.css"); + /// Information needed by layout. pub struct LayoutThread { /// The ID of the pipeline that we belong to. @@ -133,13 +144,12 @@ pub struct LayoutThread { /// The fragment tree. fragment_tree: RefCell>>, + /// The [`StackingContextTree`] cached from previous layouts. + stacking_context_tree: RefCell>, + /// A counter for epoch messages epoch: Cell, - /// The size of the viewport. This may be different from the size of the screen due to viewport - /// constraints. - viewport_size: UntypedSize2D, - /// Scroll offsets of nodes that scroll. scroll_offsets: RefCell>>, @@ -230,24 +240,27 @@ impl Layout for LayoutThread { feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn query_content_box(&self, node: OpaqueNode) -> Option> { - process_content_box_request(node, self.fragment_tree.borrow().clone()) + fn query_content_box(&self, node: TrustedNodeAddress) -> Option> { + let node = unsafe { ServoLayoutNode::new(&node) }; + process_content_box_request(node) } #[cfg_attr( feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn query_content_boxes(&self, node: OpaqueNode) -> Vec> { - process_content_boxes_request(node, self.fragment_tree.borrow().clone()) + fn query_content_boxes(&self, node: TrustedNodeAddress) -> Vec> { + let node = unsafe { ServoLayoutNode::new(&node) }; + process_content_boxes_request(node) } #[cfg_attr( feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn query_client_rect(&self, node: OpaqueNode) -> UntypedRect { - process_node_geometry_request(node, self.fragment_tree.borrow().clone()) + fn query_client_rect(&self, node: TrustedNodeAddress) -> UntypedRect { + let node = unsafe { ServoLayoutNode::new(&node) }; + process_client_rect_request(node) } #[cfg_attr( @@ -292,8 +305,9 @@ impl Layout for LayoutThread { feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn query_offset_parent(&self, node: OpaqueNode) -> OffsetParentResponse { - process_offset_parent_query(node, self.fragment_tree.borrow().clone()) + fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse { + let node = unsafe { ServoLayoutNode::new(&node) }; + process_offset_parent_query(node).unwrap_or_default() } #[cfg_attr( @@ -325,14 +339,7 @@ impl Layout for LayoutThread { TraversalFlags::empty(), ); - let fragment_tree = self.fragment_tree.borrow().clone(); - process_resolved_style_request( - &shared_style_context, - node, - &pseudo, - &property_id, - fragment_tree, - ) + process_resolved_style_request(&shared_style_context, node, &pseudo, &property_id) } #[cfg_attr( @@ -375,7 +382,8 @@ impl Layout for LayoutThread { feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn query_scrolling_area(&self, node: Option) -> UntypedRect { + fn query_scrolling_area(&self, node: Option) -> UntypedRect { + let node = node.map(|node| unsafe { ServoLayoutNode::new(&node) }); process_node_scroll_area_request(node, self.fragment_tree.borrow().clone()) } @@ -438,6 +446,8 @@ impl Layout for LayoutThread { .map(|tree| tree.conditional_size_of(ops)) .unwrap_or_default(), }); + + reports.push(self.image_cache.memory_report(formatted_url, ops)); } fn set_quirks_mode(&mut self, quirks_mode: QuirksMode) { @@ -510,12 +520,9 @@ impl LayoutThread { first_reflow: Cell::new(true), box_tree: Default::default(), fragment_tree: Default::default(), + stacking_context_tree: Default::default(), // Epoch starts at 1 because of the initial display list for epoch 0 that we send to WR epoch: Cell::new(Epoch(1)), - viewport_size: Size2D::new( - Au::from_f32_px(config.viewport_details.size.width), - Au::from_f32_px(config.viewport_details.size.height), - ), compositor_api: config.compositor_api, scroll_offsets: Default::default(), stylist: Stylist::new(device, QuirksMode::NoQuirks), @@ -545,43 +552,6 @@ impl LayoutThread { } } - #[allow(clippy::too_many_arguments)] - // Create a layout context for use in building display lists, hit testing, &c. - #[allow(clippy::too_many_arguments)] - fn build_layout_context<'a>( - &'a self, - guards: StylesheetGuards<'a>, - snapshot_map: &'a SnapshotMap, - reflow_request: &mut ReflowRequest, - use_rayon: bool, - ) -> LayoutContext<'a> { - let traversal_flags = match reflow_request.stylesheets_changed { - true => TraversalFlags::ForCSSRuleChanges, - false => TraversalFlags::empty(), - }; - - LayoutContext { - id: self.id, - origin: reflow_request.origin.clone(), - style_context: self.build_shared_style_context( - guards, - snapshot_map, - reflow_request.animation_timeline_value, - &reflow_request.animations, - traversal_flags, - ), - image_cache: self.image_cache.clone(), - font_context: self.font_context.clone(), - webrender_image_cache: self.webrender_image_cache.clone(), - pending_images: Mutex::default(), - node_image_animation_map: Arc::new(RwLock::new(std::mem::take( - &mut reflow_request.node_to_image_animation_map, - ))), - iframe_sizes: Mutex::default(), - use_rayon, - } - } - fn load_all_web_fonts_from_stylesheet_with_guard( &self, stylesheet: &DocumentStyleSheet, @@ -621,51 +591,132 @@ impl LayoutThread { return None; }; - // Calculate the actual viewport as per DEVICE-ADAPT § 6 - // If the entire flow tree is invalid, then it will be reflowed anyhow. let document_shared_lock = document.style_shared_lock(); let author_guard = document_shared_lock.read(); - let ua_stylesheets = &*UA_STYLESHEETS; let ua_or_user_guard = ua_stylesheets.shared_lock.read(); + let rayon_pool = STYLE_THREAD_POOL.lock(); + let rayon_pool = rayon_pool.pool(); + let rayon_pool = rayon_pool.as_ref(); let guards = StylesheetGuards { author: &author_guard, ua_or_user: &ua_or_user_guard, }; - let had_used_viewport_units = self.stylist.device().used_viewport_units(); - let viewport_size_changed = self.viewport_did_change(reflow_request.viewport_details); - let theme_changed = self.theme_did_change(reflow_request.theme); - - if viewport_size_changed || theme_changed { - self.update_device( - reflow_request.viewport_details, - reflow_request.theme, - &guards, - ); - } - - if viewport_size_changed && had_used_viewport_units { + let viewport_changed = self.viewport_did_change(reflow_request.viewport_details); + if self.update_device_if_necessary(&reflow_request, viewport_changed, &guards) { if let Some(mut data) = root_element.mutate_data() { data.hint.insert(RestyleHint::recascade_subtree()); } } + let mut snapshot_map = SnapshotMap::new(); + let _snapshot_setter = SnapshotSetter::new(&mut reflow_request, &mut snapshot_map); + self.prepare_stylist_for_reflow( + &reflow_request, + document, + root_element, + &guards, + ua_stylesheets, + &snapshot_map, + ); + + let mut layout_context = LayoutContext { + id: self.id, + origin: reflow_request.origin.clone(), + style_context: self.build_shared_style_context( + guards, + &snapshot_map, + reflow_request.animation_timeline_value, + &reflow_request.animations, + match reflow_request.stylesheets_changed { + true => TraversalFlags::ForCSSRuleChanges, + false => TraversalFlags::empty(), + }, + ), + image_cache: self.image_cache.clone(), + font_context: self.font_context.clone(), + webrender_image_cache: self.webrender_image_cache.clone(), + pending_images: Mutex::default(), + node_image_animation_map: Arc::new(RwLock::new(std::mem::take( + &mut reflow_request.node_to_image_animation_map, + ))), + iframe_sizes: Mutex::default(), + use_rayon: rayon_pool.is_some(), + highlighted_dom_node: reflow_request.highlighted_dom_node, + }; + + let did_reflow = self.restyle_and_build_trees( + &reflow_request, + root_element, + rayon_pool, + &mut layout_context, + viewport_changed, + ); + + self.build_stacking_context_tree(&reflow_request, did_reflow); + self.build_display_list(&reflow_request, &mut layout_context); + + self.first_reflow.set(false); + if let ReflowGoal::UpdateScrollNode(scroll_state) = reflow_request.reflow_goal { + self.update_scroll_node_state(&scroll_state); + } + + let pending_images = std::mem::take(&mut *layout_context.pending_images.lock()); + let iframe_sizes = std::mem::take(&mut *layout_context.iframe_sizes.lock()); + let node_to_image_animation_map = + std::mem::take(&mut *layout_context.node_image_animation_map.write()); + + Some(ReflowResult { + pending_images, + iframe_sizes, + node_to_image_animation_map, + }) + } + + fn update_device_if_necessary( + &mut self, + reflow_request: &ReflowRequest, + viewport_changed: bool, + guards: &StylesheetGuards, + ) -> bool { + let had_used_viewport_units = self.stylist.device().used_viewport_units(); + let theme_changed = self.theme_did_change(reflow_request.theme); + if !viewport_changed && !theme_changed { + return false; + } + self.update_device( + reflow_request.viewport_details, + reflow_request.theme, + guards, + ); + (viewport_changed && had_used_viewport_units) || theme_changed + } + + fn prepare_stylist_for_reflow<'dom>( + &mut self, + reflow_request: &ReflowRequest, + document: ServoLayoutDocument<'dom>, + root_element: ServoLayoutElement<'dom>, + guards: &StylesheetGuards, + ua_stylesheets: &UserAgentStylesheets, + snapshot_map: &SnapshotMap, + ) { if self.first_reflow.get() { for stylesheet in &ua_stylesheets.user_or_user_agent_stylesheets { self.stylist - .append_stylesheet(stylesheet.clone(), &ua_or_user_guard); - self.load_all_web_fonts_from_stylesheet_with_guard(stylesheet, &ua_or_user_guard); + .append_stylesheet(stylesheet.clone(), guards.ua_or_user); + self.load_all_web_fonts_from_stylesheet_with_guard(stylesheet, guards.ua_or_user); } if self.stylist.quirks_mode() != QuirksMode::NoQuirks { self.stylist.append_stylesheet( ua_stylesheets.quirks_mode_stylesheet.clone(), - &ua_or_user_guard, + guards.ua_or_user, ); self.load_all_web_fonts_from_stylesheet_with_guard( &ua_stylesheets.quirks_mode_stylesheet, - &ua_or_user_guard, + guards.ua_or_user, ); } } @@ -678,153 +729,180 @@ impl LayoutThread { // Flush shadow roots stylesheets if dirty. document.flush_shadow_roots_stylesheets(&mut self.stylist, guards.author); - let restyles = std::mem::take(&mut reflow_request.pending_restyles); - debug!("Draining restyles: {}", restyles.len()); - - let mut map = SnapshotMap::new(); - let elements_with_snapshot: Vec<_> = restyles - .iter() - .filter(|r| r.1.snapshot.is_some()) - .map(|r| unsafe { ServoLayoutNode::new(&r.0).as_element().unwrap() }) - .collect(); - - for (el, restyle) in restyles { - let el = unsafe { ServoLayoutNode::new(&el).as_element().unwrap() }; - - // If we haven't styled this node yet, we don't need to track a - // restyle. - let mut style_data = match el.mutate_data() { - Some(d) => d, - None => { - unsafe { el.unset_snapshot_flags() }; - continue; - }, - }; - - if let Some(s) = restyle.snapshot { - unsafe { el.set_has_snapshot() }; - map.insert(el.as_node().opaque(), s); - } - - // Stash the data on the element for processing by the style system. - style_data.hint.insert(restyle.hint); - style_data.damage = restyle.damage; - debug!("Noting restyle for {:?}: {:?}", el, style_data); - } - - self.stylist.flush(&guards, Some(root_element), Some(&map)); - - let rayon_pool = STYLE_THREAD_POOL.lock(); - let rayon_pool = rayon_pool.pool(); - let rayon_pool = rayon_pool.as_ref(); - - // Create a layout context for use throughout the following passes. - let mut layout_context = self.build_layout_context( - guards.clone(), - &map, - &mut reflow_request, - rayon_pool.is_some(), - ); + self.stylist + .flush(guards, Some(root_element), Some(snapshot_map)); + } + #[cfg_attr( + feature = "tracing", + tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") + )] + fn restyle_and_build_trees( + &self, + reflow_request: &ReflowRequest, + root_element: ServoLayoutElement<'_>, + rayon_pool: Option<&ThreadPool>, + layout_context: &mut LayoutContext<'_>, + viewport_changed: bool, + ) -> bool { let dirty_root = unsafe { ServoLayoutNode::new(&reflow_request.dirty_root.unwrap()) .as_element() .unwrap() }; - let traversal = RecalcStyle::new(layout_context); + let recalc_style_traversal = RecalcStyle::new(layout_context); let token = { - let shared = DomTraversal::::shared_context(&traversal); + let shared = + DomTraversal::::shared_context(&recalc_style_traversal); RecalcStyle::pre_traverse(dirty_root, shared) }; - if token.should_traverse() { - #[cfg(feature = "tracing")] - let _span = - tracing::trace_span!("driver::traverse_dom", servo_profiling = true).entered(); - let dirty_root: ServoLayoutNode = - driver::traverse_dom(&traversal, token, rayon_pool).as_node(); - - let root_node = root_element.as_node(); - let mut box_tree = self.box_tree.borrow_mut(); - let box_tree = &mut *box_tree; - let mut build_box_tree = || { - if !BoxTree::update(traversal.context(), dirty_root) { - *box_tree = Some(Arc::new(BoxTree::construct(traversal.context(), root_node))); - } - }; - if let Some(pool) = rayon_pool { - pool.install(build_box_tree) - } else { - build_box_tree() - }; - - let viewport_size = Size2D::new( - self.viewport_size.width.to_f32_px(), - self.viewport_size.height.to_f32_px(), - ); - let run_layout = || { - box_tree - .as_ref() - .unwrap() - .layout(traversal.context(), viewport_size) - }; - let fragment_tree = Arc::new(if let Some(pool) = rayon_pool { - pool.install(run_layout) - } else { - run_layout() - }); - *self.fragment_tree.borrow_mut() = Some(fragment_tree); + if !token.should_traverse() { + layout_context.style_context.stylist.rule_tree().maybe_gc(); + return false; } - layout_context = traversal.destroy(); + let dirty_root: ServoLayoutNode = + driver::traverse_dom(&recalc_style_traversal, token, rayon_pool).as_node(); - for element in elements_with_snapshot { - unsafe { element.unset_snapshot_flags() } + let root_node = root_element.as_node(); + let damage = compute_damage_and_repair_style(layout_context.shared_context(), root_node); + if !viewport_changed && (damage.is_empty() || damage == RestyleDamage::REPAINT) { + layout_context.style_context.stylist.rule_tree().maybe_gc(); + return false; } + let mut box_tree = self.box_tree.borrow_mut(); + let box_tree = &mut *box_tree; + let mut build_box_tree = || { + if !BoxTree::update(recalc_style_traversal.context(), dirty_root) { + *box_tree = Some(Arc::new(BoxTree::construct( + recalc_style_traversal.context(), + root_node, + ))); + } + }; + if let Some(pool) = rayon_pool { + pool.install(build_box_tree) + } else { + build_box_tree() + }; + + let viewport_size = self.stylist.device().au_viewport_size(); + let run_layout = || { + box_tree + .as_ref() + .unwrap() + .layout(recalc_style_traversal.context(), viewport_size) + }; + let fragment_tree = Arc::new(if let Some(pool) = rayon_pool { + pool.install(run_layout) + } else { + run_layout() + }); + + if self.debug.dump_flow_tree { + fragment_tree.print(); + } + *self.fragment_tree.borrow_mut() = Some(fragment_tree); + + // The FragmentTree has been updated, so any existing StackingContext tree that layout + // had is now out of date and should be rebuilt. + *self.stacking_context_tree.borrow_mut() = None; + if self.debug.dump_style_tree { println!( "{:?}", - style::dom::ShowSubtreeDataAndPrimaryValues(root_element.as_node()) + ShowSubtreeDataAndPrimaryValues(root_element.as_node()) ); } - if self.debug.dump_rule_tree { - layout_context + recalc_style_traversal + .context() .style_context .stylist .rule_tree() - .dump_stdout(&guards); + .dump_stdout(&layout_context.shared_context().guards); } // GC the rule tree if some heuristics are met. layout_context.style_context.stylist.rule_tree().maybe_gc(); + true + } - // Perform post-style recalculation layout passes. - if let Some(root) = &*self.fragment_tree.borrow() { - self.perform_post_style_recalc_layout_passes( - root.clone(), - &reflow_request.reflow_goal, - &mut layout_context, - ); + fn build_stacking_context_tree(&self, reflow_request: &ReflowRequest, did_reflow: bool) { + if !reflow_request.reflow_goal.needs_display_list() && + !reflow_request.reflow_goal.needs_display() + { + return; + } + let Some(fragment_tree) = &*self.fragment_tree.borrow() else { + return; + }; + if !did_reflow && self.stacking_context_tree.borrow().is_some() { + return; } - self.first_reflow.set(false); + let viewport_size = self.stylist.device().au_viewport_size(); + let viewport_size = LayoutSize::new( + viewport_size.width.to_f32_px(), + viewport_size.height.to_f32_px(), + ); - if let ReflowGoal::UpdateScrollNode(scroll_state) = reflow_request.reflow_goal { - self.update_scroll_node_state(&scroll_state); + // Build the StackingContextTree. This turns the `FragmentTree` into a + // tree of fragments in CSS painting order and also creates all + // applicable spatial and clip nodes. + *self.stacking_context_tree.borrow_mut() = Some(StackingContextTree::new( + fragment_tree, + viewport_size, + fragment_tree.scrollable_overflow(), + self.id.into(), + fragment_tree.viewport_scroll_sensitivity, + self.first_reflow.get(), + &self.debug, + )); + } + + fn build_display_list( + &self, + reflow_request: &ReflowRequest, + layout_context: &mut LayoutContext<'_>, + ) { + if !reflow_request.reflow_goal.needs_display() { + return; } + let Some(fragment_tree) = &*self.fragment_tree.borrow() else { + return; + }; - let pending_images = std::mem::take(&mut *layout_context.pending_images.lock()); - let iframe_sizes = std::mem::take(&mut *layout_context.iframe_sizes.lock()); - let node_to_image_animation_map = - std::mem::take(&mut *layout_context.node_image_animation_map.write()); - Some(ReflowResult { - pending_images, - iframe_sizes, - node_to_image_animation_map, - }) + let mut stacking_context_tree = self.stacking_context_tree.borrow_mut(); + let Some(stacking_context_tree) = stacking_context_tree.as_mut() else { + return; + }; + + let mut epoch = self.epoch.get(); + epoch.next(); + self.epoch.set(epoch); + stacking_context_tree.compositor_info.epoch = epoch.into(); + + let built_display_list = DisplayListBuilder::build( + layout_context, + stacking_context_tree, + fragment_tree, + &self.debug, + ); + self.compositor_api.send_display_list( + self.webview_id, + &stacking_context_tree.compositor_info, + built_display_list, + ); + + let (keys, instance_keys) = self + .font_context + .collect_unused_webrender_resources(false /* all */); + self.compositor_api + .remove_unused_font_resources(keys, instance_keys) } fn update_scroll_node_state(&self, state: &ScrollState) { @@ -840,84 +918,6 @@ impl LayoutThread { ); } - fn perform_post_style_recalc_layout_passes( - &self, - fragment_tree: Arc, - reflow_goal: &ReflowGoal, - context: &mut LayoutContext, - ) { - Self::cancel_animations_for_nodes_not_in_fragment_tree( - &context.style_context.animations, - &fragment_tree, - ); - - Self::cancel_image_animation_for_nodes_not_in_fragment_tree( - context.node_image_animation_map.clone(), - &fragment_tree, - ); - - if !reflow_goal.needs_display_list() { - return; - } - - let mut epoch = self.epoch.get(); - epoch.next(); - self.epoch.set(epoch); - - let viewport_size = LayoutSize::from_untyped(Size2D::new( - self.viewport_size.width.to_f32_px(), - self.viewport_size.height.to_f32_px(), - )); - let mut display_list = DisplayList::new( - viewport_size, - fragment_tree.scrollable_overflow(), - self.id.into(), - epoch.into(), - fragment_tree.viewport_scroll_sensitivity, - self.first_reflow.get(), - ); - display_list.wr.begin(); - - // `dump_serialized_display_list` doesn't actually print anything. It sets up - // the display list for printing the serialized version when `finalize()` is called. - // We need to call this before adding any display items so that they are printed - // during `finalize()`. - if self.debug.dump_display_list { - display_list.wr.dump_serialized_display_list(); - } - - // Build the root stacking context. This turns the `FragmentTree` into a - // tree of fragments in CSS painting order and also creates all - // applicable spatial and clip nodes. - let root_stacking_context = - display_list.build_stacking_context_tree(&fragment_tree, &self.debug); - - // Build the rest of the display list which inclues all of the WebRender primitives. - display_list.build(context, &fragment_tree, &root_stacking_context); - - if self.debug.dump_flow_tree { - fragment_tree.print(); - } - if self.debug.dump_stacking_context_tree { - root_stacking_context.debug_print(); - } - debug!("Layout done!"); - - if reflow_goal.needs_display() { - self.compositor_api.send_display_list( - self.webview_id, - display_list.compositor_info, - display_list.wr.end().1, - ); - - let (keys, instance_keys) = self - .font_context - .collect_unused_webrender_resources(false /* all */); - self.compositor_api - .remove_unused_font_resources(keys, instance_keys) - } - } - /// Returns profiling information which is passed to the time profiler. fn profiler_metadata(&self) -> Option { Some(TimerMetadata { @@ -935,42 +935,6 @@ impl LayoutThread { }) } - /// Cancel animations for any nodes which have been removed from fragment tree. - /// TODO(mrobinson): We should look into a way of doing this during flow tree construction. - /// This also doesn't yet handles nodes that have been reparented. - fn cancel_animations_for_nodes_not_in_fragment_tree( - animations: &DocumentAnimationSet, - root: &FragmentTree, - ) { - // Assume all nodes have been removed until proven otherwise. - let mut animations = animations.sets.write(); - let mut invalid_nodes = animations.keys().cloned().collect(); - root.remove_nodes_in_fragment_tree_from_set(&mut invalid_nodes); - - // Cancel animations for any nodes that are no longer in the fragment tree. - for node in &invalid_nodes { - if let Some(state) = animations.get_mut(node) { - state.cancel_all_animations(); - } - } - } - - fn cancel_image_animation_for_nodes_not_in_fragment_tree( - image_animation_set: Arc>>, - root: &FragmentTree, - ) { - let mut image_animations = image_animation_set.write().to_owned(); - let mut invalid_nodes: FxHashSet = image_animations - .keys() - .cloned() - .map(|node| AnimationSetKey::new(node, None)) - .collect(); - root.remove_nodes_in_fragment_tree_from_set(&mut invalid_nodes); - for node in &invalid_nodes { - image_animations.remove(&node.node); - } - } - fn viewport_did_change(&mut self, viewport_details: ViewportDetails) -> bool { let new_pixel_ratio = viewport_details.hidpi_scale_factor.get(); let new_viewport_size = Size2D::new( @@ -978,9 +942,6 @@ impl LayoutThread { Au::from_f32_px(viewport_details.size.height), ); - // TODO: eliminate self.viewport_size in favour of using self.device.au_viewport_size() - self.viewport_size = new_viewport_size; - let device = self.stylist.device(); let size_did_change = device.au_viewport_size() != new_viewport_size; let pixel_ratio_did_change = device.device_pixel_ratio().get() != new_pixel_ratio; @@ -1046,20 +1007,12 @@ fn get_ua_stylesheets() -> Result { // FIXME: presentational-hints.css should be at author origin with zero specificity. // (Does it make a difference?) let mut user_or_user_agent_stylesheets = vec![ - parse_ua_stylesheet( - shared_lock, - "user-agent.css", - &resources::read_bytes(Resource::UserAgentCSS), - )?, - parse_ua_stylesheet( - shared_lock, - "servo.css", - &resources::read_bytes(Resource::ServoCSS), - )?, + parse_ua_stylesheet(shared_lock, "user-agent.css", USER_AGENT_CSS)?, + parse_ua_stylesheet(shared_lock, "servo.css", SERVO_CSS)?, parse_ua_stylesheet( shared_lock, "presentational-hints.css", - &resources::read_bytes(Resource::PresentationalHintsCSS), + PRESENTATIONAL_HINTS_CSS, )?, ]; @@ -1080,11 +1033,8 @@ fn get_ua_stylesheets() -> Result { ))); } - let quirks_mode_stylesheet = parse_ua_stylesheet( - shared_lock, - "quirks-mode.css", - &resources::read_bytes(Resource::QuirksModeCSS), - )?; + let quirks_mode_stylesheet = + parse_ua_stylesheet(shared_lock, "quirks-mode.css", QUIRKS_MODE_CSS)?; Ok(UserAgentStylesheets { shared_lock: shared_lock.clone(), @@ -1160,8 +1110,7 @@ impl FontMetricsProvider for LayoutFontMetricsProvider { _vertical: bool, font: &Font, base_size: CSSPixelLength, - _in_media_query: bool, - _retrieve_math_scales: bool, + _flags: QueryFontMetricsFlags, ) -> FontMetrics { let font_context = &self.0; let font_group = self @@ -1230,6 +1179,54 @@ impl Debug for LayoutFontMetricsProvider { } } -thread_local!(static SEEN_POINTERS: LazyCell>> = const { - LazyCell::new(|| RefCell::new(HashSet::new())) -}); +struct SnapshotSetter<'dom> { + elements_with_snapshot: Vec>, +} + +impl SnapshotSetter<'_> { + fn new(reflow_request: &mut ReflowRequest, snapshot_map: &mut SnapshotMap) -> Self { + debug!( + "Draining restyles: {}", + reflow_request.pending_restyles.len() + ); + let restyles = std::mem::take(&mut reflow_request.pending_restyles); + + let elements_with_snapshot: Vec<_> = restyles + .iter() + .filter(|r| r.1.snapshot.is_some()) + .map(|r| unsafe { ServoLayoutNode::new(&r.0).as_element().unwrap() }) + .collect(); + + for (element, restyle) in restyles { + let element = unsafe { ServoLayoutNode::new(&element).as_element().unwrap() }; + + // If we haven't styled this node yet, we don't need to track a + // restyle. + let Some(mut style_data) = element.mutate_data() else { + unsafe { element.unset_snapshot_flags() }; + continue; + }; + + debug!("Noting restyle for {:?}: {:?}", element, style_data); + if let Some(s) = restyle.snapshot { + unsafe { element.set_has_snapshot() }; + snapshot_map.insert(element.as_node().opaque(), s); + } + + // Stash the data on the element for processing by the style system. + style_data.hint.insert(restyle.hint); + style_data.damage = restyle.damage; + } + Self { + elements_with_snapshot, + } + } +} + +impl Drop for SnapshotSetter<'_> { + fn drop(&mut self) { + for element in &self.elements_with_snapshot { + unsafe { element.unset_snapshot_flags() } + } + } +} diff --git a/components/layout/lib.rs b/components/layout/lib.rs index af7d432c4d8..ea254723dcb 100644 --- a/components/layout/lib.rs +++ b/components/layout/lib.rs @@ -38,13 +38,23 @@ pub use flow::BoxTree; pub use fragment_tree::FragmentTree; pub use layout_impl::LayoutFactoryImpl; use malloc_size_of_derive::MallocSizeOf; +use servo_arc::Arc as ServoArc; use style::logical_geometry::WritingMode; use style::properties::ComputedValues; -use style::values::computed::TextDecorationLine; use crate::geom::{LogicalVec2, SizeConstraint}; use crate::style_ext::AspectRatio; +/// At times, a style is "owned" by more than one layout object. For example, text +/// fragments need a handle on their parent inline box's style. In order to make +/// incremental layout easier to implement, another layer of shared ownership is added via +/// [`SharedStyle`]. This allows updating the style in originating layout object and +/// having all "depdendent" objects update automatically. +/// +/// Note that this is not a cost-free data structure, so should only be +/// used when necessary. +pub(crate) type SharedStyle = ArcRefCell>; + /// Represents the set of constraints that we use when computing the min-content /// and max-content inline sizes of an element. pub(crate) struct ConstraintSpace { @@ -152,39 +162,20 @@ impl<'a> From<&'_ DefiniteContainingBlock<'a>> for ContainingBlock<'a> { /// propoagation, but only during `BoxTree` construction. #[derive(Clone, Copy, Debug)] struct PropagatedBoxTreeData { - text_decoration: TextDecorationLine, allow_percentage_column_in_tables: bool, } impl Default for PropagatedBoxTreeData { fn default() -> Self { Self { - text_decoration: Default::default(), allow_percentage_column_in_tables: true, } } } impl PropagatedBoxTreeData { - pub(crate) fn union(&self, style: &ComputedValues) -> Self { - Self { - // FIXME(#31736): This is only taking into account the line style and not the decoration - // color. This should collect information about both so that they can be rendered properly. - text_decoration: self.text_decoration | style.clone_text_decoration_line(), - allow_percentage_column_in_tables: self.allow_percentage_column_in_tables, - } - } - - pub(crate) fn without_text_decorations(&self) -> Self { - Self { - text_decoration: TextDecorationLine::NONE, - allow_percentage_column_in_tables: self.allow_percentage_column_in_tables, - } - } - fn disallowing_percentage_table_columns(&self) -> PropagatedBoxTreeData { Self { - text_decoration: self.text_decoration, allow_percentage_column_in_tables: false, } } diff --git a/components/layout/lists.rs b/components/layout/lists.rs index d5a1f863865..8c653cb6858 100644 --- a/components/layout/lists.rs +++ b/components/layout/lists.rs @@ -7,18 +7,14 @@ use style::properties::style_structs; use style::values::computed::Image; use crate::context::LayoutContext; -use crate::dom::NodeExt; use crate::dom_traversal::{NodeAndStyleInfo, PseudoElementContentItem}; use crate::replaced::ReplacedContents; /// -pub(crate) fn make_marker<'dom, Node>( +pub(crate) fn make_marker<'dom>( context: &LayoutContext, - info: &NodeAndStyleInfo, -) -> Option<(NodeAndStyleInfo, Vec)> -where - Node: NodeExt<'dom>, -{ + info: &NodeAndStyleInfo<'dom>, +) -> Option<(NodeAndStyleInfo<'dom>, Vec)> { let marker_info = info.pseudo(context, style::selector_parser::PseudoElement::Marker)?; let style = &marker_info.style; let list_style = style.get_list(); diff --git a/components/layout/positioned.rs b/components/layout/positioned.rs index 5f08e4e86c5..6280864d533 100644 --- a/components/layout/positioned.rs +++ b/components/layout/positioned.rs @@ -16,7 +16,6 @@ use style::values::specified::align::AlignFlags; use crate::cell::ArcRefCell; use crate::context::LayoutContext; -use crate::dom::NodeExt; use crate::dom_traversal::{Contents, NodeAndStyleInfo}; use crate::formatting_contexts::{ IndependentFormattingContext, IndependentFormattingContextContents, @@ -25,11 +24,11 @@ use crate::fragment_tree::{ BoxFragment, Fragment, FragmentFlags, HoistedSharedFragment, SpecificLayoutInfo, }; use crate::geom::{ - AuOrAuto, LengthPercentageOrAuto, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, - PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, PhysicalVec, Size, Sizes, ToLogical, - ToLogicalWithContainingBlock, + AuOrAuto, LazySize, LengthPercentageOrAuto, LogicalRect, LogicalSides, LogicalSides1D, + LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, PhysicalVec, Size, + Sizes, ToLogical, ToLogicalWithContainingBlock, }; -use crate::sizing::ContentSizes; +use crate::layout_box_base::LayoutBoxBase; use crate::style_ext::{Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, DisplayInside}; use crate::{ ConstraintSpace, ContainingBlock, ContainingBlockSize, DefiniteContainingBlock, @@ -41,16 +40,6 @@ pub(crate) struct AbsolutelyPositionedBox { pub context: IndependentFormattingContext, } -#[derive(Clone, MallocSizeOf)] -pub(crate) struct PositioningContext { - for_nearest_positioned_ancestor: Option>, - - // For nearest `containing block for all descendants` as defined by the CSS transforms - // spec. - // https://www.w3.org/TR/css-transforms-1/#containing-block-for-all-descendants - for_nearest_containing_block_for_all_descendants: Vec, -} - #[derive(Clone, MallocSizeOf)] pub(crate) struct HoistedAbsolutelyPositionedBox { absolutely_positioned_box: ArcRefCell, @@ -66,9 +55,9 @@ impl AbsolutelyPositionedBox { Self { context } } - pub fn construct<'dom>( + pub fn construct( context: &LayoutContext, - node_info: &NodeAndStyleInfo>, + node_info: &NodeAndStyleInfo, display_inside: DisplayInside, contents: Contents, ) -> Self { @@ -103,45 +92,26 @@ impl AbsolutelyPositionedBox { } } +#[derive(Clone, Default, MallocSizeOf)] +pub(crate) struct PositioningContext { + absolutes: Vec, +} + impl PositioningContext { - pub(crate) fn new_for_containing_block_for_all_descendants() -> Self { - Self { - for_nearest_positioned_ancestor: None, - for_nearest_containing_block_for_all_descendants: Vec::new(), - } + #[inline] + pub(crate) fn new_for_layout_box_base(layout_box_base: &LayoutBoxBase) -> Option { + Self::new_for_style_and_fragment_flags( + &layout_box_base.style, + &layout_box_base.base_fragment_info.flags, + ) } - /// Create a [PositioningContext] to use for laying out a subtree. The idea is that - /// when subtree layout is finished, the newly hoisted boxes can be processed - /// (normally adjusting their static insets) and then appended to the parent - /// [PositioningContext]. - pub(crate) fn new_for_subtree(collects_for_nearest_positioned_ancestor: bool) -> Self { - Self { - for_nearest_positioned_ancestor: if collects_for_nearest_positioned_ancestor { - Some(Vec::new()) - } else { - None - }, - for_nearest_containing_block_for_all_descendants: Vec::new(), - } - } - - pub(crate) fn collects_for_nearest_positioned_ancestor(&self) -> bool { - self.for_nearest_positioned_ancestor.is_some() - } - - pub(crate) fn new_for_style(style: &ComputedValues) -> Option { - // NB: We never make PositioningContexts for replaced elements, which is why we always - // pass false here. - if style.establishes_containing_block_for_all_descendants(FragmentFlags::empty()) { - Some(Self::new_for_containing_block_for_all_descendants()) - } else if style - .establishes_containing_block_for_absolute_descendants(FragmentFlags::empty()) - { - Some(Self { - for_nearest_positioned_ancestor: Some(Vec::new()), - for_nearest_containing_block_for_all_descendants: Vec::new(), - }) + fn new_for_style_and_fragment_flags( + style: &ComputedValues, + flags: &FragmentFlags, + ) -> Option { + if style.establishes_containing_block_for_absolute_descendants(*flags) { + Some(Self::default()) } else { None } @@ -184,20 +154,9 @@ impl PositioningContext { offset: &PhysicalVec, index: PositioningContextLength, ) { - if let Some(hoisted_boxes) = self.for_nearest_positioned_ancestor.as_mut() { - hoisted_boxes - .iter_mut() - .skip(index.for_nearest_positioned_ancestor) - .for_each(|hoisted_fragment| { - hoisted_fragment - .fragment - .borrow_mut() - .adjust_offsets(offset) - }) - } - self.for_nearest_containing_block_for_all_descendants + self.absolutes .iter_mut() - .skip(index.for_nearest_containing_block_for_all_descendants) + .skip(index.0) .for_each(|hoisted_fragment| { hoisted_fragment .fragment @@ -213,38 +172,89 @@ impl PositioningContext { &mut self, layout_context: &LayoutContext, containing_block: &ContainingBlock, - style: &ComputedValues, + base: &LayoutBoxBase, fragment_layout_fn: impl FnOnce(&mut Self) -> BoxFragment, ) -> BoxFragment { - // Try to create a context, but if one isn't necessary, simply create the fragment - // using the given closure and the current `PositioningContext`. - let mut new_context = match Self::new_for_style(style) { - Some(new_context) => new_context, - None => return fragment_layout_fn(self), - }; + // If a new `PositioningContext` isn't necessary, simply create the fragment using + // the given closure and the current `PositioningContext`. + let establishes_containing_block_for_absolutes = base + .style + .establishes_containing_block_for_absolute_descendants(base.base_fragment_info.flags); + if !establishes_containing_block_for_absolutes { + return fragment_layout_fn(self); + } + let mut new_context = PositioningContext::default(); let mut new_fragment = fragment_layout_fn(&mut new_context); - new_context.layout_collected_children(layout_context, &mut new_fragment); - // If the new context has any hoisted boxes for the nearest containing block for - // pass them up the tree. + // Lay out all of the absolutely positioned children for this fragment, and, if it + // isn't a containing block for fixed elements, then pass those up to the parent. + new_context.layout_collected_children(layout_context, &mut new_fragment); self.append(new_context); - if style.clone_position() == Position::Relative { - new_fragment.content_rect.origin += relative_adjustement(style, containing_block) + if base.style.clone_position() == Position::Relative { + new_fragment.content_rect.origin += relative_adjustement(&base.style, containing_block) .to_physical_vector(containing_block.style.writing_mode) } new_fragment } + fn take_boxes_for_fragment( + &mut self, + new_fragment: &BoxFragment, + boxes_to_layout_out: &mut Vec, + boxes_to_continue_hoisting_out: &mut Vec, + ) { + debug_assert!( + new_fragment + .style + .establishes_containing_block_for_absolute_descendants(new_fragment.base.flags) + ); + + if new_fragment + .style + .establishes_containing_block_for_all_descendants(new_fragment.base.flags) + { + boxes_to_layout_out.append(&mut self.absolutes); + return; + } + + // TODO: This could potentially use `extract_if` when that is stabilized. + let (mut boxes_to_layout, mut boxes_to_continue_hoisting) = self + .absolutes + .drain(..) + .partition(|hoisted_box| hoisted_box.position() != Position::Fixed); + boxes_to_layout_out.append(&mut boxes_to_layout); + boxes_to_continue_hoisting_out.append(&mut boxes_to_continue_hoisting); + } + // Lay out the hoisted boxes collected into this `PositioningContext` and add them // to the given `BoxFragment`. - pub fn layout_collected_children( + pub(crate) fn layout_collected_children( &mut self, layout_context: &LayoutContext, new_fragment: &mut BoxFragment, ) { + if self.absolutes.is_empty() { + return; + } + + // Sometimes we create temporary PositioningContexts just to collect hoisted absolutes and + // then these are processed later. In that case and if this fragment doesn't establish a + // containing block for absolutes at all, we just do nothing. All hoisted fragments will + // later be passed up to a parent PositioningContext. + // + // Handling this case here, when the PositioningContext is completely ineffectual other than + // as a temporary container for hoisted boxes, means that callers can execute less conditional + // code. + if !new_fragment + .style + .establishes_containing_block_for_absolute_descendants(new_fragment.base.flags) + { + return; + } + let padding_rect = PhysicalRect::new( // Ignore the content rect’s position in its own containing block: PhysicalPoint::origin(), @@ -258,83 +268,58 @@ impl PositioningContext { style: &new_fragment.style, }; - let take_hoisted_boxes_pending_layout = - |context: &mut Self| match context.for_nearest_positioned_ancestor.as_mut() { - Some(fragments) => mem::take(fragments), - None => mem::take(&mut context.for_nearest_containing_block_for_all_descendants), - }; + let mut fixed_position_boxes_to_hoist = Vec::new(); + let mut boxes_to_layout = Vec::new(); + self.take_boxes_for_fragment( + new_fragment, + &mut boxes_to_layout, + &mut fixed_position_boxes_to_hoist, + ); - // Loop because it’s possible that we discover (the static position of) - // more absolutely-positioned boxes while doing layout for others. - let mut hoisted_boxes = take_hoisted_boxes_pending_layout(self); - let mut laid_out_child_fragments = Vec::new(); - while !hoisted_boxes.is_empty() { + // Laying out a `position: absolute` child (which only establishes a containing block for + // `position: absolute` descendants) can result in more `position: fixed` descendants + // collecting in `self.absolutes`. We need to loop here in order to keep either laying them + // out or putting them into `fixed_position_boxes_to_hoist`. We know there aren't any more + // when `self.absolutes` is empty. + while !boxes_to_layout.is_empty() { HoistedAbsolutelyPositionedBox::layout_many( layout_context, - &mut hoisted_boxes, - &mut laid_out_child_fragments, - &mut self.for_nearest_containing_block_for_all_descendants, + std::mem::take(&mut boxes_to_layout), + &mut new_fragment.children, + &mut self.absolutes, &containing_block, new_fragment.padding, ); - hoisted_boxes = take_hoisted_boxes_pending_layout(self); + + self.take_boxes_for_fragment( + new_fragment, + &mut boxes_to_layout, + &mut fixed_position_boxes_to_hoist, + ); } - new_fragment.children.extend(laid_out_child_fragments); + // We replace here instead of simply preserving these in `take_boxes_for_fragment` + // so that we don't have to continually re-iterate over them when laying out in the + // loop above. + self.absolutes = fixed_position_boxes_to_hoist; } - pub(crate) fn push(&mut self, box_: HoistedAbsolutelyPositionedBox) { - if let Some(nearest) = &mut self.for_nearest_positioned_ancestor { - let position = box_ - .absolutely_positioned_box - .borrow() - .context - .style() - .clone_position(); - match position { - Position::Fixed => {}, // fall through - Position::Absolute => return nearest.push(box_), - Position::Static | Position::Relative | Position::Sticky => unreachable!(), - } - } - self.for_nearest_containing_block_for_all_descendants - .push(box_) + pub(crate) fn push(&mut self, hoisted_box: HoistedAbsolutelyPositionedBox) { + debug_assert!(matches!( + hoisted_box.position(), + Position::Absolute | Position::Fixed + )); + self.absolutes.push(hoisted_box); } - pub(crate) fn is_empty(&self) -> bool { - self.for_nearest_containing_block_for_all_descendants - .is_empty() && - self.for_nearest_positioned_ancestor - .as_ref() - .is_none_or(|vector| vector.is_empty()) - } - - pub(crate) fn append(&mut self, other: Self) { - if other.is_empty() { + pub(crate) fn append(&mut self, mut other: Self) { + if other.absolutes.is_empty() { return; } - - vec_append_owned( - &mut self.for_nearest_containing_block_for_all_descendants, - other.for_nearest_containing_block_for_all_descendants, - ); - - match ( - self.for_nearest_positioned_ancestor.as_mut(), - other.for_nearest_positioned_ancestor, - ) { - (Some(us), Some(them)) => vec_append_owned(us, them), - (None, Some(them)) => { - // This is the case where we have laid out the absolute children in a containing - // block for absolutes and we then are passing up the fixed-position descendants - // to the containing block for all descendants. - vec_append_owned( - &mut self.for_nearest_containing_block_for_all_descendants, - them, - ); - }, - (None, None) => {}, - _ => unreachable!(), + if self.absolutes.is_empty() { + self.absolutes = other.absolutes; + } else { + self.absolutes.append(&mut other.absolutes) } } @@ -344,19 +329,16 @@ impl PositioningContext { initial_containing_block: &DefiniteContainingBlock, fragments: &mut Vec, ) { - debug_assert!(self.for_nearest_positioned_ancestor.is_none()); - - // Loop because it’s possible that we discover (the static position of) - // more absolutely-positioned boxes while doing layout for others. - while !self - .for_nearest_containing_block_for_all_descendants - .is_empty() - { + // Laying out a `position: absolute` child (which only establishes a containing block for + // `position: absolute` descendants) can result in more `position: fixed` descendants + // collecting in `self.absolutes`. We need to loop here in order to keep laying them out. We + // know there aren't any more when `self.absolutes` is empty. + while !self.absolutes.is_empty() { HoistedAbsolutelyPositionedBox::layout_many( layout_context, - &mut mem::take(&mut self.for_nearest_containing_block_for_all_descendants), + mem::take(&mut self.absolutes), fragments, - &mut self.for_nearest_containing_block_for_all_descendants, + &mut self.absolutes, initial_containing_block, Default::default(), ) @@ -365,58 +347,46 @@ impl PositioningContext { /// Get the length of this [PositioningContext]. pub(crate) fn len(&self) -> PositioningContextLength { - PositioningContextLength { - for_nearest_positioned_ancestor: self - .for_nearest_positioned_ancestor - .as_ref() - .map_or(0, |vec| vec.len()), - for_nearest_containing_block_for_all_descendants: self - .for_nearest_containing_block_for_all_descendants - .len(), - } + PositioningContextLength(self.absolutes.len()) } /// Truncate this [PositioningContext] to the given [PositioningContextLength]. This /// is useful for "unhoisting" boxes in this context and returning it to the state at /// the time that [`PositioningContext::len()`] was called. pub(crate) fn truncate(&mut self, length: &PositioningContextLength) { - if let Some(vec) = self.for_nearest_positioned_ancestor.as_mut() { - vec.truncate(length.for_nearest_positioned_ancestor); - } - self.for_nearest_containing_block_for_all_descendants - .truncate(length.for_nearest_containing_block_for_all_descendants); + self.absolutes.truncate(length.0) } } /// A data structure which stores the size of a positioning context. #[derive(Clone, Copy, Debug, PartialEq)] -pub(crate) struct PositioningContextLength { - /// The number of boxes that will be hoisted the the nearest positioned ancestor for - /// layout. - for_nearest_positioned_ancestor: usize, - /// The number of boxes that will be hoisted the the nearest ancestor which - /// establishes a containing block for all descendants for layout. - for_nearest_containing_block_for_all_descendants: usize, -} +pub(crate) struct PositioningContextLength(usize); impl Zero for PositioningContextLength { fn zero() -> Self { - PositioningContextLength { - for_nearest_positioned_ancestor: 0, - for_nearest_containing_block_for_all_descendants: 0, - } + Self(0) } fn is_zero(&self) -> bool { - self.for_nearest_positioned_ancestor == 0 && - self.for_nearest_containing_block_for_all_descendants == 0 + self.0.is_zero() } } impl HoistedAbsolutelyPositionedBox { + fn position(&self) -> Position { + let position = self + .absolutely_positioned_box + .borrow() + .context + .style() + .clone_position(); + assert!(position == Position::Fixed || position == Position::Absolute); + position + } + pub(crate) fn layout_many( layout_context: &LayoutContext, - boxes: &mut [Self], + mut boxes: Vec, fragments: &mut Vec, for_nearest_containing_block_for_all_descendants: &mut Vec, containing_block: &DefiniteContainingBlock, @@ -463,7 +433,7 @@ impl HoistedAbsolutelyPositionedBox { pub(crate) fn layout( &mut self, layout_context: &LayoutContext, - for_nearest_containing_block_for_all_descendants: &mut Vec, + hoisted_absolutes_from_children: &mut Vec, containing_block: &DefiniteContainingBlock, containing_block_padding: PhysicalSides, ) -> Fragment { @@ -502,7 +472,7 @@ impl HoistedAbsolutelyPositionedBox { false => shared_fragment.resolved_alignment.inline, }; - let mut inline_axis_solver = AbsoluteAxisSolver { + let inline_axis_solver = AbsoluteAxisSolver { axis: Direction::Inline, containing_size: cbis, padding_border_sum: pbm.padding_border_sums.inline, @@ -525,7 +495,7 @@ impl HoistedAbsolutelyPositionedBox { true => style.clone_align_self().0.0, false => shared_fragment.resolved_alignment.block, }; - let mut block_axis_solver = AbsoluteAxisSolver { + let block_axis_solver = AbsoluteAxisSolver { axis: Direction::Block, containing_size: cbbs, padding_border_sum: pbm.padding_border_sums.block, @@ -540,53 +510,7 @@ impl HoistedAbsolutelyPositionedBox { is_table, }; - if let IndependentFormattingContextContents::Replaced(replaced) = &context.contents { - // https://drafts.csswg.org/css2/visudet.html#abs-replaced-width - // https://drafts.csswg.org/css2/visudet.html#abs-replaced-height - let inset_sums = LogicalVec2 { - inline: inline_axis_solver.inset_sum(), - block: block_axis_solver.inset_sum(), - }; - let automatic_size = |alignment: AlignFlags, offsets: &LogicalSides1D<_>| { - if alignment.value() == AlignFlags::STRETCH && !offsets.either_auto() { - Size::Stretch - } else { - Size::FitContent - } - }; - let used_size = replaced.used_size_as_if_inline_element_from_content_box_sizes( - containing_block, - &style, - context.preferred_aspect_ratio(&pbm.padding_border_sums), - LogicalVec2 { - inline: &inline_axis_solver.computed_sizes, - block: &block_axis_solver.computed_sizes, - }, - LogicalVec2 { - inline: automatic_size(inline_alignment, &inline_axis_solver.box_offsets), - block: automatic_size(block_alignment, &block_axis_solver.box_offsets), - }, - pbm.padding_border_sums + pbm.margin.auto_is(Au::zero).sum() + inset_sums, - ); - inline_axis_solver.override_size(used_size.inline); - block_axis_solver.override_size(used_size.block); - } - - // The block axis can depend on layout results, so we only solve it tentatively, - // we may have to resolve it properly later on. - let mut block_axis = block_axis_solver.solve_tentatively(); - - // The inline axis can be fully resolved, computing intrinsic sizes using the - // tentative block size. - let mut inline_axis = inline_axis_solver.solve(Some(|| { - let ratio = context.preferred_aspect_ratio(&pbm.padding_border_sums); - let constraint_space = ConstraintSpace::new(block_axis.size, style.writing_mode, ratio); - context - .inline_content_sizes(layout_context, &constraint_space) - .sizes - })); - - let mut positioning_context = PositioningContext::new_for_style(&style).unwrap(); + let mut positioning_context = PositioningContext::default(); let mut new_fragment = { let content_size: LogicalVec2; let fragments; @@ -595,10 +519,34 @@ impl HoistedAbsolutelyPositionedBox { IndependentFormattingContextContents::Replaced(replaced) => { // https://drafts.csswg.org/css2/visudet.html#abs-replaced-width // https://drafts.csswg.org/css2/visudet.html#abs-replaced-height - content_size = LogicalVec2 { - inline: inline_axis.size.to_definite().unwrap(), - block: block_axis.size.to_definite().unwrap(), + let inset_sums = LogicalVec2 { + inline: inline_axis_solver.inset_sum(), + block: block_axis_solver.inset_sum(), }; + let automatic_size = |alignment: AlignFlags, offsets: &LogicalSides1D<_>| { + if alignment.value() == AlignFlags::STRETCH && !offsets.either_auto() { + Size::Stretch + } else { + Size::FitContent + } + }; + content_size = replaced.used_size_as_if_inline_element_from_content_box_sizes( + containing_block, + &style, + context.preferred_aspect_ratio(&pbm.padding_border_sums), + LogicalVec2 { + inline: &inline_axis_solver.computed_sizes, + block: &block_axis_solver.computed_sizes, + }, + LogicalVec2 { + inline: automatic_size( + inline_alignment, + &inline_axis_solver.box_offsets, + ), + block: automatic_size(block_alignment, &block_axis_solver.box_offsets), + }, + pbm.padding_border_sums + pbm.margin.auto_is(Au::zero).sum() + inset_sums, + ); fragments = replaced.make_fragments( layout_context, &style, @@ -608,11 +556,40 @@ impl HoistedAbsolutelyPositionedBox { IndependentFormattingContextContents::NonReplaced(non_replaced) => { // https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-width // https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-height - let inline_size = inline_axis.size.to_definite().unwrap(); + + // The block size can depend on layout results, so we only solve it extrinsically, + // we may have to resolve it properly later on. + let block_automatic_size = block_axis_solver.automatic_size(); + let block_stretch_size = Some(block_axis_solver.stretch_size()); + let extrinsic_block_size = block_axis_solver.computed_sizes.resolve_extrinsic( + block_automatic_size, + Au::zero(), + block_stretch_size, + ); + + // The inline axis can be fully resolved, computing intrinsic sizes using the + // extrinsic block size. + let get_inline_content_size = || { + let ratio = context.preferred_aspect_ratio(&pbm.padding_border_sums); + let constraint_space = + ConstraintSpace::new(extrinsic_block_size, style.writing_mode, ratio); + context + .inline_content_sizes(layout_context, &constraint_space) + .sizes + }; + let inline_size = inline_axis_solver.computed_sizes.resolve( + Direction::Inline, + inline_axis_solver.automatic_size(), + Au::zero, + Some(inline_axis_solver.stretch_size()), + get_inline_content_size, + is_table, + ); + let containing_block_for_children = ContainingBlock { size: ContainingBlockSize { inline: inline_size, - block: block_axis.size, + block: extrinsic_block_size, }, style: &style, }; @@ -623,6 +600,14 @@ impl HoistedAbsolutelyPositionedBox { "Mixed horizontal and vertical writing modes are not supported yet" ); + let lazy_block_size = LazySize::new( + &block_axis_solver.computed_sizes, + Direction::Block, + block_automatic_size, + Au::zero, + block_stretch_size, + is_table, + ); let independent_layout = non_replaced.layout( layout_context, &mut positioning_context, @@ -630,24 +615,17 @@ impl HoistedAbsolutelyPositionedBox { containing_block, &context.base, false, /* depends_on_block_constraints */ + &lazy_block_size, ); - let inline_size = if let Some(inline_size) = - independent_layout.content_inline_size_for_table - { - // Tables can become narrower than predicted due to collapsed columns, - // so we need to solve again to update margins. - inline_axis_solver.override_size(inline_size); - inline_axis = inline_axis_solver.solve_tentatively(); - inline_size - } else { - inline_size - }; + // Tables can become narrower than predicted due to collapsed columns + let inline_size = independent_layout + .content_inline_size_for_table + .unwrap_or(inline_size); // Now we can properly solve the block size. - block_axis = block_axis_solver - .solve(Some(|| independent_layout.content_block_size.into())); - let block_size = block_axis.size.to_definite().unwrap(); + let block_size = + lazy_block_size.resolve(|| independent_layout.content_block_size); content_size = LogicalVec2 { inline: inline_size, @@ -658,11 +636,13 @@ impl HoistedAbsolutelyPositionedBox { }, }; + let inline_margins = inline_axis_solver.solve_margins(content_size.inline); + let block_margins = block_axis_solver.solve_margins(content_size.block); let margin = LogicalSides { - inline_start: inline_axis.margin_start, - inline_end: inline_axis.margin_end, - block_start: block_axis.margin_start, - block_end: block_axis.margin_end, + inline_start: inline_margins.start, + inline_end: inline_margins.end, + block_start: block_margins.start, + block_end: block_margins.end, }; let pb = pbm.padding + pbm.border; @@ -699,6 +679,10 @@ impl HoistedAbsolutelyPositionedBox { ) .with_specific_layout_info(specific_layout_info) }; + + // This is an absolutely positioned element, which means it also establishes a + // containing block for absolutes. We lay out any absolutely positioned children + // here and pass the rest to `hoisted_absolutes_from_children.` positioning_context.layout_collected_children(layout_context, &mut new_fragment); // Any hoisted boxes that remain in this positioning context are going to be hoisted @@ -711,8 +695,7 @@ impl HoistedAbsolutelyPositionedBox { PositioningContextLength::zero(), ); - for_nearest_containing_block_for_all_descendants - .extend(positioning_context.for_nearest_containing_block_for_all_descendants); + hoisted_absolutes_from_children.extend(positioning_context.absolutes); let fragment = Fragment::Box(ArcRefCell::new(new_fragment)); context.base.set_fragment(fragment.clone()); @@ -741,12 +724,6 @@ impl LogicalRect { } } -struct AxisResult { - size: SizeConstraint, - margin_start: Au, - margin_end: Au, -} - struct AbsoluteAxisSolver<'a> { axis: Direction, containing_size: Au, @@ -789,101 +766,56 @@ impl AbsoluteAxisSolver<'_> { } } - /// This unifies some of the parts in common in: - /// - /// * - /// * - /// - /// … and: - /// - /// * - /// * - /// - /// In the replaced case, `size` is never `Auto`. - fn solve(&self, get_content_size: Option ContentSizes>) -> AxisResult { - let solve_size = |initial_behavior, stretch_size: Au| -> SizeConstraint { - let stretch_size = stretch_size.max(Au::zero()); - if let Some(get_content_size) = get_content_size { - SizeConstraint::Definite(self.computed_sizes.resolve( - self.axis, - initial_behavior, - Au::zero, - Some(stretch_size), - get_content_size, - self.is_table, - )) - } else { - self.computed_sizes.resolve_extrinsic( - initial_behavior, - Au::zero(), - Some(stretch_size), - ) - } - }; - if self.box_offsets.either_auto() { - let margin_start = self.computed_margin_start.auto_is(Au::zero); - let margin_end = self.computed_margin_end.auto_is(Au::zero); - let stretch_size = self.containing_size - - self.inset_sum() - - self.padding_border_sum - - margin_start - - margin_end; - let size = solve_size(Size::FitContent, stretch_size); - AxisResult { - size, - margin_start, - margin_end, - } - } else { - let mut free_space = self.containing_size - self.inset_sum() - self.padding_border_sum; - let stretch_size = free_space - - self.computed_margin_start.auto_is(Au::zero) - - self.computed_margin_end.auto_is(Au::zero); - let initial_behavior = match self.alignment.value() { - AlignFlags::NORMAL | AlignFlags::AUTO if !self.is_table => Size::Stretch, - AlignFlags::STRETCH => Size::Stretch, - _ => Size::FitContent, - }; - let size = solve_size(initial_behavior, stretch_size); - if let Some(used_size) = size.to_definite() { - free_space -= used_size; - } else { - free_space = Au::zero(); - } - let (margin_start, margin_end) = - match (self.computed_margin_start, self.computed_margin_end) { - (AuOrAuto::Auto, AuOrAuto::Auto) => { - if self.avoid_negative_margin_start && free_space < Au::zero() { - (Au::zero(), free_space) - } else { - let margin_start = free_space / 2; - (margin_start, free_space - margin_start) - } - }, - (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => (free_space - end, end), - (AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => { - (start, free_space - start) - }, - (AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => { - (start, end) - }, - }; - AxisResult { - size, - margin_start, - margin_end, - } + #[inline] + fn automatic_size(&self) -> Size { + match self.alignment.value() { + _ if self.box_offsets.either_auto() => Size::FitContent, + AlignFlags::NORMAL | AlignFlags::AUTO if !self.is_table => Size::Stretch, + AlignFlags::STRETCH => Size::Stretch, + _ => Size::FitContent, } } - fn solve_tentatively(&mut self) -> AxisResult { - self.solve(None:: ContentSizes>) + #[inline] + fn stretch_size(&self) -> Au { + Au::zero().max( + self.containing_size - + self.inset_sum() - + self.padding_border_sum - + self.computed_margin_start.auto_is(Au::zero) - + self.computed_margin_end.auto_is(Au::zero), + ) } - fn override_size(&mut self, size: Au) { - self.computed_sizes.preferred = Size::Numeric(size); - self.computed_sizes.min = Size::default(); - self.computed_sizes.max = Size::default(); + fn solve_margins(&self, size: Au) -> LogicalSides1D { + if self.box_offsets.either_auto() { + LogicalSides1D::new( + self.computed_margin_start.auto_is(Au::zero), + self.computed_margin_end.auto_is(Au::zero), + ) + } else { + let free_space = + self.containing_size - self.inset_sum() - self.padding_border_sum - size; + match (self.computed_margin_start, self.computed_margin_end) { + (AuOrAuto::Auto, AuOrAuto::Auto) => { + if self.avoid_negative_margin_start && free_space < Au::zero() { + LogicalSides1D::new(Au::zero(), free_space) + } else { + let margin_start = free_space / 2; + LogicalSides1D::new(margin_start, free_space - margin_start) + } + }, + (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => { + LogicalSides1D::new(free_space - end, end) + }, + (AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => { + LogicalSides1D::new(start, free_space - start) + }, + (AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => { + LogicalSides1D::new(start, end) + }, + } + } } fn origin_for_margin_box( @@ -1014,14 +946,6 @@ impl AbsoluteAxisSolver<'_> { } } -fn vec_append_owned(a: &mut Vec, mut b: Vec) { - if a.is_empty() { - *a = b - } else { - a.append(&mut b) - } -} - /// pub(crate) fn relative_adjustement( style: &ComputedValues, diff --git a/components/layout/query.rs b/components/layout/query.rs index 08b264deea9..ca9db9ceaf1 100644 --- a/components/layout/query.rs +++ b/components/layout/query.rs @@ -7,8 +7,9 @@ use std::sync::Arc; use app_units::Au; use euclid::default::{Point2D, Rect}; -use euclid::{SideOffsets2D, Size2D, Vector2D}; +use euclid::{SideOffsets2D, Size2D}; use itertools::Itertools; +use script::layout_dom::ServoLayoutNode; use script_layout_interface::wrapper_traits::{ LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode, }; @@ -20,7 +21,7 @@ use style::computed_values::position::T as Position; use style::computed_values::visibility::T as Visibility; use style::computed_values::white_space_collapse::T as WhiteSpaceCollapseValue; use style::context::{QuirksMode, SharedStyleContext, StyleContext, ThreadLocalStyleContext}; -use style::dom::{OpaqueNode, TElement}; +use style::dom::{NodeInfo, OpaqueNode, TElement, TNode}; use style::properties::style_structs::Font; use style::properties::{ ComputedValues, Importance, LonghandId, PropertyDeclarationBlock, PropertyDeclarationId, @@ -38,59 +39,60 @@ use style::values::specified::GenericGridTemplateComponent; use style::values::specified::box_::DisplayInside; use style_traits::{ParsingMode, ToCss}; +use crate::ArcRefCell; use crate::dom::NodeExt; use crate::flow::inline::construct::{TextTransformation, WhitespaceCollapse}; use crate::fragment_tree::{ - BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo, Tag, + BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo, }; -use crate::geom::{PhysicalRect, PhysicalVec}; use crate::taffy::SpecificTaffyGridInfo; -pub fn process_content_box_request( - requested_node: OpaqueNode, - fragment_tree: Option>, -) -> Option> { - let rects = fragment_tree?.get_content_boxes_for_node(requested_node); +pub fn process_content_box_request(node: ServoLayoutNode<'_>) -> Option> { + let rects: Vec<_> = node + .fragments_for_pseudo(None) + .iter() + .filter_map(Fragment::cumulative_border_box_rect) + .collect(); if rects.is_empty() { return None; } - Some( - rects - .iter() - .fold(Rect::zero(), |unioned_rect, rect| rect.union(&unioned_rect)), - ) + Some(rects.iter().fold(Rect::zero(), |unioned_rect, rect| { + rect.to_untyped().union(&unioned_rect) + })) } -pub fn process_content_boxes_request( - requested_node: OpaqueNode, - fragment_tree: Option>, -) -> Vec> { - fragment_tree - .map(|tree| tree.get_content_boxes_for_node(requested_node)) +pub fn process_content_boxes_request(node: ServoLayoutNode<'_>) -> Vec> { + node.fragments_for_pseudo(None) + .iter() + .filter_map(Fragment::cumulative_border_box_rect) + .map(|rect| rect.to_untyped()) + .collect() +} + +pub fn process_client_rect_request(node: ServoLayoutNode<'_>) -> Rect { + node.fragments_for_pseudo(None) + .first() + .map(Fragment::client_rect) .unwrap_or_default() } -pub fn process_node_geometry_request( - requested_node: OpaqueNode, - fragment_tree: Option>, -) -> Rect { - if let Some(fragment_tree) = fragment_tree { - fragment_tree.get_border_dimensions_for_node(requested_node) - } else { - Rect::zero() - } -} - /// pub fn process_node_scroll_area_request( - requested_node: Option, + requested_node: Option>, fragment_tree: Option>, ) -> Rect { - let rect = match (fragment_tree, requested_node) { - (Some(tree), Some(node)) => tree.get_scrolling_area_for_node(node), - (Some(tree), None) => tree.get_scrolling_area_for_viewport(), - _ => return Rect::zero(), + let Some(tree) = fragment_tree else { + return Rect::zero(); + }; + + let rect = match requested_node { + Some(node) => node + .fragments_for_pseudo(None) + .first() + .map(Fragment::scrolling_area) + .unwrap_or_default(), + None => tree.get_scrolling_area_for_viewport(), }; Rect::new( @@ -104,12 +106,11 @@ pub fn process_node_scroll_area_request( /// Return the resolved value of property for a given (pseudo)element. /// -pub fn process_resolved_style_request<'dom>( +pub fn process_resolved_style_request( context: &SharedStyleContext, - node: impl LayoutNode<'dom> + 'dom, + node: ServoLayoutNode<'_>, pseudo: &Option, property: &PropertyId, - fragment_tree: Option>, ) -> String { if !node.as_element().unwrap().has_data() { return process_resolved_style_request_for_unstyled_node(context, node, pseudo, property); @@ -161,8 +162,6 @@ pub fn process_resolved_style_request<'dom>( _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)), }; - let tag_to_find = Tag::new_pseudo(node.opaque(), *pseudo); - // https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle // Here we are trying to conform to the specification that says that getComputedStyle // should return the used values in certain circumstances. For size and positional @@ -191,107 +190,87 @@ pub fn process_resolved_style_request<'dom>( return computed_style(None); } - let resolve_for_fragment = - |fragment: &Fragment, containing_block: Option<&PhysicalRect>| { - let (content_rect, margins, padding, specific_layout_info) = match fragment { - Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => { - let box_fragment = box_fragment.borrow(); - if style.get_box().position != Position::Static { - let resolved_insets = || { - box_fragment - .calculate_resolved_insets_if_positioned(containing_block.unwrap()) - }; - match longhand_id { - LonghandId::Top => return resolved_insets().top.to_css_string(), - LonghandId::Right => { - return resolved_insets().right.to_css_string(); - }, - LonghandId::Bottom => { - return resolved_insets().bottom.to_css_string(); - }, - LonghandId::Left => { - return resolved_insets().left.to_css_string(); - }, - _ => {}, - } - } - let content_rect = box_fragment.content_rect; - let margins = box_fragment.margin; - let padding = box_fragment.padding; - let specific_layout_info = box_fragment.specific_layout_info.clone(); - (content_rect, margins, padding, specific_layout_info) - }, - Fragment::Positioning(positioning_fragment) => { - let content_rect = positioning_fragment.borrow().rect; - ( - content_rect, - SideOffsets2D::zero(), - SideOffsets2D::zero(), - None, - ) - }, - _ => return computed_style(Some(fragment)), - }; - - // https://drafts.csswg.org/css-grid/#resolved-track-list - // > The grid-template-rows and grid-template-columns properties are - // > resolved value special case properties. - // - // > When an element generates a grid container box... - if display.inside() == DisplayInside::Grid { - if let Some(SpecificLayoutInfo::Grid(info)) = specific_layout_info { - if let Some(value) = resolve_grid_template(&info, style, longhand_id) { - return value; + let resolve_for_fragment = |fragment: &Fragment| { + let (content_rect, margins, padding, specific_layout_info) = match fragment { + Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => { + let box_fragment = box_fragment.borrow(); + if style.get_box().position != Position::Static { + let resolved_insets = || box_fragment.calculate_resolved_insets_if_positioned(); + match longhand_id { + LonghandId::Top => return resolved_insets().top.to_css_string(), + LonghandId::Right => { + return resolved_insets().right.to_css_string(); + }, + LonghandId::Bottom => { + return resolved_insets().bottom.to_css_string(); + }, + LonghandId::Left => { + return resolved_insets().left.to_css_string(); + }, + _ => {}, } } - } - - // https://drafts.csswg.org/cssom/#resolved-value-special-case-property-like-height - // > If the property applies to the element or pseudo-element and the resolved value of the - // > display property is not none or contents, then the resolved value is the used value. - // > Otherwise the resolved value is the computed value. - // - // However, all browsers ignore that for margin and padding properties, and resolve to a length - // even if the property doesn't apply: https://github.com/w3c/csswg-drafts/issues/10391 - match longhand_id { - LonghandId::Width if resolved_size_should_be_used_value(fragment) => { - content_rect.size.width - }, - LonghandId::Height if resolved_size_should_be_used_value(fragment) => { - content_rect.size.height - }, - LonghandId::MarginBottom => margins.bottom, - LonghandId::MarginTop => margins.top, - LonghandId::MarginLeft => margins.left, - LonghandId::MarginRight => margins.right, - LonghandId::PaddingBottom => padding.bottom, - LonghandId::PaddingTop => padding.top, - LonghandId::PaddingLeft => padding.left, - LonghandId::PaddingRight => padding.right, - _ => return computed_style(Some(fragment)), - } - .to_css_string() + let content_rect = box_fragment.content_rect; + let margins = box_fragment.margin; + let padding = box_fragment.padding; + let specific_layout_info = box_fragment.specific_layout_info.clone(); + (content_rect, margins, padding, specific_layout_info) + }, + Fragment::Positioning(positioning_fragment) => { + let content_rect = positioning_fragment.borrow().rect; + ( + content_rect, + SideOffsets2D::zero(), + SideOffsets2D::zero(), + None, + ) + }, + _ => return computed_style(Some(fragment)), }; - if !matches!( - longhand_id, - LonghandId::Top | LonghandId::Bottom | LonghandId::Left | LonghandId::Right - ) { - if let Some(fragment) = node.fragments_for_pseudo(*pseudo).first() { - return resolve_for_fragment(fragment, None); - } - } - - fragment_tree - .and_then(|fragment_tree| { - fragment_tree.find(|fragment, _, containing_block| { - if Some(tag_to_find) == fragment.tag() { - Some(resolve_for_fragment(fragment, Some(containing_block))) - } else { - None + // https://drafts.csswg.org/css-grid/#resolved-track-list + // > The grid-template-rows and grid-template-columns properties are + // > resolved value special case properties. + // + // > When an element generates a grid container box... + if display.inside() == DisplayInside::Grid { + if let Some(SpecificLayoutInfo::Grid(info)) = specific_layout_info { + if let Some(value) = resolve_grid_template(&info, style, longhand_id) { + return value; } - }) - }) + } + } + + // https://drafts.csswg.org/cssom/#resolved-value-special-case-property-like-height + // > If the property applies to the element or pseudo-element and the resolved value of the + // > display property is not none or contents, then the resolved value is the used value. + // > Otherwise the resolved value is the computed value. + // + // However, all browsers ignore that for margin and padding properties, and resolve to a length + // even if the property doesn't apply: https://github.com/w3c/csswg-drafts/issues/10391 + match longhand_id { + LonghandId::Width if resolved_size_should_be_used_value(fragment) => { + content_rect.size.width + }, + LonghandId::Height if resolved_size_should_be_used_value(fragment) => { + content_rect.size.height + }, + LonghandId::MarginBottom => margins.bottom, + LonghandId::MarginTop => margins.top, + LonghandId::MarginLeft => margins.left, + LonghandId::MarginRight => margins.right, + LonghandId::PaddingBottom => padding.bottom, + LonghandId::PaddingTop => padding.top, + LonghandId::PaddingLeft => padding.left, + LonghandId::PaddingRight => padding.right, + _ => return computed_style(Some(fragment)), + } + .to_css_string() + }; + + node.fragments_for_pseudo(*pseudo) + .first() + .map(resolve_for_fragment) .unwrap_or_else(|| computed_style(None)) } @@ -383,9 +362,9 @@ fn resolve_grid_template( } } -pub fn process_resolved_style_request_for_unstyled_node<'dom>( +pub fn process_resolved_style_request_for_unstyled_node( context: &SharedStyleContext, - node: impl LayoutNode<'dom>, + node: ServoLayoutNode<'_>, pseudo: &Option, property: &PropertyId, ) -> String { @@ -450,233 +429,155 @@ fn shorthand_to_css_string( } } -pub fn process_offset_parent_query( - node: OpaqueNode, - fragment_tree: Option>, -) -> OffsetParentResponse { - process_offset_parent_query_inner(node, fragment_tree).unwrap_or_default() +struct OffsetParentFragments { + parent: ArcRefCell, + grandparent: Option, +} + +/// +fn offset_parent_fragments(node: ServoLayoutNode<'_>) -> Option { + // 1. If any of the following holds true return null and terminate this algorithm: + // * The element does not have an associated CSS layout box. + // * The element is the root element. + // * The element is the HTML body element. + // * The element’s computed value of the position property is fixed. + let fragment = node.fragments_for_pseudo(None).first().cloned()?; + let flags = fragment.base()?.flags; + if flags.intersects( + FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT, + ) { + return None; + } + if matches!( + fragment, Fragment::Box(fragment) if fragment.borrow().style.get_box().position == Position::Fixed + ) { + return None; + } + + // 2. Return the nearest ancestor element of the element for which at least one of + // the following is true and terminate this algorithm if such an ancestor is found: + // * The computed value of the position property is not static. + // * It is the HTML body element. + // * The computed value of the position property of the element is static and the + // ancestor is one of the following HTML elements: td, th, or table. + let mut maybe_parent_node = node.parent_node(); + while let Some(parent_node) = maybe_parent_node { + maybe_parent_node = parent_node.parent_node(); + + if let Some(parent_fragment) = parent_node.fragments_for_pseudo(None).first() { + let parent_fragment = match parent_fragment { + Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => box_fragment, + _ => continue, + }; + + let grandparent_fragment = + maybe_parent_node.and_then(|node| node.fragments_for_pseudo(None).first().cloned()); + + if parent_fragment.borrow().style.get_box().position != Position::Static { + return Some(OffsetParentFragments { + parent: parent_fragment.clone(), + grandparent: grandparent_fragment, + }); + } + + let flags = parent_fragment.borrow().base.flags; + if flags.intersects( + FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT | + FragmentFlags::IS_TABLE_TH_OR_TD_ELEMENT, + ) { + return Some(OffsetParentFragments { + parent: parent_fragment.clone(), + grandparent: grandparent_fragment, + }); + } + } + } + + None } #[inline] -fn process_offset_parent_query_inner( - node: OpaqueNode, - fragment_tree: Option>, -) -> Option { - let fragment_tree = fragment_tree?; +pub fn process_offset_parent_query(node: ServoLayoutNode<'_>) -> Option { + // Only consider the first fragment of the node found as per a + // possible interpretation of the specification: "[...] return the + // y-coordinate of the top border edge of the first CSS layout box + // associated with the element [...]" + // + // FIXME: Browsers implement this all differently (e.g., [1]) - + // Firefox does returns the union of all layout elements of some + // sort. Chrome returns the first fragment for a block element (the + // same as ours) or the union of all associated fragments in the + // first containing block fragment for an inline element. We could + // implement Chrome's behavior, but our fragment tree currently + // provides insufficient information. + // + // [1]: https://github.com/w3c/csswg-drafts/issues/4541 + // > 1. If the element is the HTML body element or does not have any associated CSS + // layout box return zero and terminate this algorithm. + let fragment = node.fragments_for_pseudo(None).first().cloned()?; + let mut border_box = fragment.cumulative_border_box_rect()?; - struct NodeOffsetBoxInfo { - border_box: Rect, - offset_parent_node_address: Option, - is_static_body_element: bool, - } - - // https://www.w3.org/TR/2016/WD-cssom-view-1-20160317/#extensions-to-the-htmlelement-interface - let mut parent_node_addresses: Vec> = Vec::new(); - let tag_to_find = Tag::new(node); - let node_offset_box = fragment_tree.find(|fragment, level, containing_block| { - let base = fragment.base()?; - let is_body_element = base - .flags - .contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT); - - if fragment.tag() == Some(tag_to_find) { - // Only consider the first fragment of the node found as per a - // possible interpretation of the specification: "[...] return the - // y-coordinate of the top border edge of the first CSS layout box - // associated with the element [...]" - // - // FIXME: Browsers implement this all differently (e.g., [1]) - - // Firefox does returns the union of all layout elements of some - // sort. Chrome returns the first fragment for a block element (the - // same as ours) or the union of all associated fragments in the - // first containing block fragment for an inline element. We could - // implement Chrome's behavior, but our fragment tree currently - // provides insufficient information. - // - // [1]: https://github.com/w3c/csswg-drafts/issues/4541 - let fragment_relative_rect = match fragment { - Fragment::Box(fragment) | Fragment::Float(fragment) => fragment.borrow().border_rect(), - Fragment::Text(fragment) => fragment.borrow().rect, - Fragment::Positioning(fragment) => fragment.borrow().rect, - Fragment::AbsoluteOrFixedPositioned(_) | - Fragment::Image(_) | - Fragment::IFrame(_) => unreachable!(), - }; - - let mut border_box = fragment_relative_rect.translate(containing_block.origin.to_vector()).to_untyped(); - - // "If any of the following holds true return null and terminate - // this algorithm: [...] The element’s computed value of the - // `position` property is `fixed`." - let is_fixed = matches!( - fragment, Fragment::Box(fragment) if fragment.borrow().style.get_box().position == Position::Fixed - ); - - if is_body_element { - // "If the element is the HTML body element or [...] return zero - // and terminate this algorithm." - border_box.origin = Point2D::zero(); - } - - let offset_parent_node = if is_fixed { - None - } else { - // Find the nearest ancestor element eligible as `offsetParent`. - parent_node_addresses[..level] - .iter() - .rev() - .cloned() - .find_map(std::convert::identity) - }; - - Some(NodeOffsetBoxInfo { - border_box, - offset_parent_node_address: offset_parent_node.map(|node| node.0), - is_static_body_element: offset_parent_node.is_some_and(|node| node.1), - }) - } else { - // Record the paths of the nodes being traversed. - let parent_node_address = match fragment { - Fragment::Box(fragment) | Fragment::Float(fragment) => { - let fragment = &*fragment.borrow(); - let is_eligible_parent = is_eligible_parent(fragment); - let is_static_body_element = is_body_element && - fragment.style.get_box().position == Position::Static; - match base.tag { - Some(tag) if is_eligible_parent && !tag.is_pseudo() => { - Some((tag.node, is_static_body_element)) - }, - _ => None, - } - }, - Fragment::AbsoluteOrFixedPositioned(_) | - Fragment::IFrame(_) | - Fragment::Image(_) | - Fragment::Positioning(_) | - Fragment::Text(_) => None, - }; - - while parent_node_addresses.len() <= level { - parent_node_addresses.push(None); - } - parent_node_addresses[level] = parent_node_address; - None - } - }); - - // Bail out if the element doesn't have an associated fragment. - // "If any of the following holds true return null and terminate this - // algorithm: [...] The element does not have an associated CSS layout box." - // (`offsetParent`) "If the element is the HTML body element [...] return - // zero and terminate this algorithm." (others) - let node_offset_box = node_offset_box?; - - let offset_parent_padding_box_corner = if let Some(offset_parent_node_address) = - node_offset_box.offset_parent_node_address - { - // The spec (https://www.w3.org/TR/cssom-view-1/#extensions-to-the-htmlelement-interface) - // says that offsetTop/offsetLeft are always relative to the padding box of the offsetParent. - // However, in practice this is not true in major browsers in the case that the offsetParent is the body - // element and the body element is position:static. In that case offsetLeft/offsetTop are computed - // relative to the root node's border box. - if node_offset_box.is_static_body_element { - fn extract_box_fragment( - fragment: &Fragment, - containing_block: &PhysicalRect, - ) -> PhysicalVec { - let (Fragment::Box(fragment) | Fragment::Float(fragment)) = fragment else { - unreachable!(); - }; - // Again, take the *first* associated CSS layout box. - fragment.borrow().border_rect().origin.to_vector() + - containing_block.origin.to_vector() - } - - let containing_block = &fragment_tree.initial_containing_block; - let fragment = &fragment_tree.root_fragments[0]; - if let Fragment::AbsoluteOrFixedPositioned(shared_fragment) = fragment { - let shared_fragment = &*shared_fragment.borrow(); - let fragment = shared_fragment.fragment.as_ref().unwrap(); - extract_box_fragment(fragment, containing_block) - } else { - extract_box_fragment(fragment, containing_block) - } - } else { - // Find the top and left padding edges of "the first CSS layout box - // associated with the `offsetParent` of the element". - // - // Since we saw `offset_parent_node_address` once, we should be able - // to find it again. - let offset_parent_node_tag = Tag::new(offset_parent_node_address); - fragment_tree - .find(|fragment, _, containing_block| { - match fragment { - Fragment::Box(fragment) | Fragment::Float(fragment) => { - let fragment = fragment.borrow(); - if fragment.base.tag == Some(offset_parent_node_tag) { - // Again, take the *first* associated CSS layout box. - let padding_box_corner = fragment.padding_rect().origin.to_vector() - + containing_block.origin.to_vector(); - Some(padding_box_corner) - } else { - None - } - }, - Fragment::AbsoluteOrFixedPositioned(_) - | Fragment::Text(_) - | Fragment::Image(_) - | Fragment::IFrame(_) - | Fragment::Positioning(_) => None, - } - }) - .unwrap() - } - } else { - // "If the offsetParent of the element is null," subtract zero in the - // following step. - Vector2D::zero() + // 2. If the offsetParent of the element is null return the x-coordinate of the left + // border edge of the first CSS layout box associated with the element, relative to + // the initial containing block origin, ignoring any transforms that apply to the + // element and its ancestors, and terminate this algorithm. + let Some(offset_parent_fragment) = offset_parent_fragments(node) else { + return Some(OffsetParentResponse { + node_address: None, + rect: border_box.to_untyped(), + }); }; + let parent_fragment = offset_parent_fragment.parent.borrow(); + let parent_is_static_body_element = parent_fragment + .base + .flags + .contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) && + parent_fragment.style.get_box().position == Position::Static; + + // For `offsetLeft`: + // 3. Return the result of subtracting the y-coordinate of the top padding edge of the + // first CSS layout box associated with the offsetParent of the element from the + // y-coordinate of the top border edge of the first CSS layout box associated with the + // element, relative to the initial containing block origin, ignoring any transforms + // that apply to the element and its ancestors. + // + // We generalize this for `offsetRight` as described in the specification. + let grandparent_box_fragment = || match offset_parent_fragment.grandparent { + Some(Fragment::Box(box_fragment)) | Some(Fragment::Float(box_fragment)) => { + Some(box_fragment) + }, + _ => None, + }; + + // The spec (https://www.w3.org/TR/cssom-view-1/#extensions-to-the-htmlelement-interface) + // says that offsetTop/offsetLeft are always relative to the padding box of the offsetParent. + // However, in practice this is not true in major browsers in the case that the offsetParent is the body + // element and the body element is position:static. In that case offsetLeft/offsetTop are computed + // relative to the root node's border box. + // + // See . + let parent_offset_rect = if parent_is_static_body_element { + if let Some(grandparent_fragment) = grandparent_box_fragment() { + let grandparent_fragment = grandparent_fragment.borrow(); + grandparent_fragment.offset_by_containing_block(&grandparent_fragment.border_rect()) + } else { + parent_fragment.offset_by_containing_block(&parent_fragment.padding_rect()) + } + } else { + parent_fragment.offset_by_containing_block(&parent_fragment.padding_rect()) + }; + + border_box = border_box.translate(-parent_offset_rect.origin.to_vector()); + Some(OffsetParentResponse { - node_address: node_offset_box.offset_parent_node_address.map(Into::into), - // "Return the result of subtracting the x-coordinate of the left - // padding edge of the first CSS layout box associated with the - // `offsetParent` of the element from the x-coordinate of the left - // border edge of the first CSS layout box associated with the element, - // relative to the initial containing block origin, ignoring any - // transforms that apply to the element and its ancestors." (and vice - // versa for the top border edge) - rect: node_offset_box - .border_box - .translate(-offset_parent_padding_box_corner.to_untyped()), + node_address: parent_fragment.base.tag.map(|tag| tag.node.into()), + rect: border_box.to_untyped(), }) } -/// Returns whether or not the element with the given style and body element determination -/// is eligible to be a parent element for offset* queries. -/// -/// From : -/// -/// > Return the nearest ancestor element of the element for which at least one of the following is -/// > true and terminate this algorithm if such an ancestor is found: -/// > 1. The computed value of the position property is not static. -/// > 2. It is the HTML body element. -/// > 3. The computed value of the position property of the element is static and the ancestor is -/// > one of the following HTML elements: td, th, or table. -fn is_eligible_parent(fragment: &BoxFragment) -> bool { - fragment - .base - .flags - .contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) || - fragment.style.get_box().position != Position::Static || - fragment - .base - .flags - .contains(FragmentFlags::IS_TABLE_TH_OR_TD_ELEMENT) -} - /// -pub fn get_the_text_steps<'dom>(node: impl LayoutNode<'dom>) -> String { +pub fn get_the_text_steps(node: ServoLayoutNode<'_>) -> String { // Step 1: If element is not being rendered or if the user agent is a non-CSS user agent, then // return element's descendant text content. // This is taken care of in HTMLElemnent code @@ -764,8 +665,8 @@ impl Default for RenderedTextCollectionState { } /// -fn rendered_text_collection_steps<'dom>( - node: impl LayoutNode<'dom>, +fn rendered_text_collection_steps( + node: ServoLayoutNode<'_>, state: &mut RenderedTextCollectionState, ) -> Vec { // Step 1. Let items be the result of running the rendered text collection diff --git a/components/layout/replaced.rs b/components/layout/replaced.rs index 6a6b1979ff9..0cc95c1a87d 100644 --- a/components/layout/replaced.rs +++ b/components/layout/replaced.rs @@ -3,7 +3,6 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cell::LazyCell; -use std::fmt; use std::sync::Arc; use app_units::Au; @@ -14,10 +13,12 @@ use euclid::{Scale, Size2D}; use malloc_size_of_derive::MallocSizeOf; use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder}; use pixels::Image; +use script::layout_dom::ServoLayoutNode; use script_layout_interface::IFrameSize; use servo_arc::Arc as ServoArc; use style::Zero; use style::computed_values::object_fit::T as ObjectFit; +use style::dom::TNode; use style::logical_geometry::{Direction, WritingMode}; use style::properties::ComputedValues; use style::servo::url::ComputedUrl; @@ -96,33 +97,9 @@ impl NaturalSizes { } } -#[derive(MallocSizeOf)] -pub(crate) enum CanvasSource { - WebGL(ImageKey), - Image(ImageKey), - WebGPU(ImageKey), - /// transparent black - Empty, -} - -impl fmt::Debug for CanvasSource { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{}", - match *self { - CanvasSource::WebGL(_) => "WebGL", - CanvasSource::Image(_) => "Image", - CanvasSource::WebGPU(_) => "WebGPU", - CanvasSource::Empty => "Empty", - } - ) - } -} - #[derive(Debug, MallocSizeOf)] pub(crate) struct CanvasInfo { - pub source: CanvasSource, + pub source: Option, } #[derive(Debug, MallocSizeOf)] @@ -145,7 +122,7 @@ pub(crate) enum ReplacedContentKind { } impl ReplacedContents { - pub fn for_element<'dom>(element: impl NodeExt<'dom>, context: &LayoutContext) -> Option { + pub fn for_element(element: ServoLayoutNode<'_>, context: &LayoutContext) -> Option { if let Some(ref data_attribute_string) = element.as_typeless_object_with_data_attribute() { if let Some(url) = try_to_parse_image_data_url(data_attribute_string) { return Self::from_image_url( @@ -209,8 +186,8 @@ impl ReplacedContents { }) } - pub fn from_image_url<'dom>( - element: impl NodeExt<'dom>, + pub fn from_image_url( + element: ServoLayoutNode<'_>, context: &LayoutContext, image_url: &ComputedUrl, ) -> Option { @@ -220,13 +197,13 @@ impl ReplacedContents { image_url.clone().into(), UsePlaceholder::No, ) { - Some(ImageOrMetadataAvailable::ImageAvailable { image, .. }) => { + Ok(ImageOrMetadataAvailable::ImageAvailable { image, .. }) => { (Some(image.clone()), image.width as f32, image.height as f32) }, - Some(ImageOrMetadataAvailable::MetadataAvailable(metadata, _id)) => { + Ok(ImageOrMetadataAvailable::MetadataAvailable(metadata, _id)) => { (None, metadata.width as f32, metadata.height as f32) }, - None => return None, + Err(_) => return None, }; return Some(Self { @@ -238,8 +215,8 @@ impl ReplacedContents { None } - pub fn from_image<'dom>( - element: impl NodeExt<'dom>, + pub fn from_image( + element: ServoLayoutNode<'_>, context: &LayoutContext, image: &ComputedImage, ) -> Option { @@ -388,12 +365,10 @@ impl ReplacedContents { return vec![]; } - let image_key = match canvas_info.source { - CanvasSource::WebGL(image_key) => image_key, - CanvasSource::WebGPU(image_key) => image_key, - CanvasSource::Image(image_key) => image_key, - CanvasSource::Empty => return vec![], + let Some(image_key) = canvas_info.source else { + return vec![]; }; + vec![Fragment::Image(ArcRefCell::new(ImageFragment { base: self.base_fragment_info.into(), style: style.clone(), diff --git a/components/layout/style_ext.rs b/components/layout/style_ext.rs index c28511766b2..354da54d39b 100644 --- a/components/layout/style_ext.rs +++ b/components/layout/style_ext.rs @@ -12,7 +12,7 @@ use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode; use style::computed_values::position::T as ComputedPosition; use style::computed_values::transform_style::T as ComputedTransformStyle; use style::computed_values::unicode_bidi::T as UnicodeBidi; -use style::logical_geometry::{Direction as AxisDirection, WritingMode}; +use style::logical_geometry::{Direction as AxisDirection, PhysicalSide, WritingMode}; use style::properties::ComputedValues; use style::properties::longhands::backface_visibility::computed_value::T as BackfaceVisiblity; use style::properties::longhands::box_sizing::computed_value::T as BoxSizing; @@ -280,6 +280,16 @@ impl Default for BorderStyleColor { } } +/// +/// > A scrolling box of a viewport or element has two overflow directions, +/// > which are the block-end and inline-end directions for that viewport or element. +pub(crate) struct OverflowDirection { + /// Whether block-end or inline-end direction is [PhysicalSide::Right]. + pub rightward: bool, + /// Whether block-end or inline-end direction is [PhysicalSide::Bottom]. + pub downward: bool, +} + pub(crate) trait ComputedValuesExt { fn physical_box_offsets(&self) -> PhysicalSides>; fn box_offsets(&self, writing_mode: WritingMode) -> LogicalSides>; @@ -320,7 +330,8 @@ pub(crate) trait ComputedValuesExt { containing_block_writing_mode: WritingMode, ) -> LogicalSides>; fn is_transformable(&self, fragment_flags: FragmentFlags) -> bool; - fn has_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool; + fn has_transform_or_perspective_style(&self) -> bool; + fn has_effective_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool; fn z_index_applies(&self, fragment_flags: FragmentFlags) -> bool; fn effective_z_index(&self, fragment_flags: FragmentFlags) -> i32; fn effective_overflow(&self, fragment_flags: FragmentFlags) -> AxesOverflow; @@ -353,6 +364,7 @@ pub(crate) trait ComputedValuesExt { writing_mode: WritingMode, ) -> bool; fn is_inline_box(&self, fragment_flags: FragmentFlags) -> bool; + fn overflow_direction(&self) -> OverflowDirection; } impl ComputedValuesExt for ComputedValues { @@ -363,6 +375,9 @@ impl ComputedValuesExt for ComputedValues { Inset::Auto => LengthPercentageOrAuto::Auto, Inset::AnchorFunction(_) => unreachable!("anchor() should be disabled"), Inset::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"), + Inset::AnchorContainingCalcFunction(_) => { + unreachable!("anchor() and anchor-size() should be disabled") + }, } } let position = self.get_position(); @@ -483,7 +498,9 @@ impl ComputedValuesExt for ComputedValues { match inset { Margin::LengthPercentage(v) => LengthPercentageOrAuto::LengthPercentage(v), Margin::Auto => LengthPercentageOrAuto::Auto, - Margin::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"), + Margin::AnchorSizeFunction(_) | Margin::AnchorContainingCalcFunction(_) => { + unreachable!("anchor-size() should be disabled") + }, } } let margin = self.get_margin(); @@ -522,15 +539,20 @@ impl ComputedValuesExt for ComputedValues { !self.is_inline_box(fragment_flags) } - /// Returns true if this style has a transform, or perspective property set and + /// Returns true if this style has a transform or perspective property set. + fn has_transform_or_perspective_style(&self) -> bool { + !self.get_box().transform.0.is_empty() || + self.get_box().scale != GenericScale::None || + self.get_box().rotate != GenericRotate::None || + self.get_box().translate != GenericTranslate::None || + self.get_box().perspective != Perspective::None + } + + /// Returns true if this style has a transform or perspective property set, and /// it applies to this element. - fn has_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool { - self.is_transformable(fragment_flags) && - (!self.get_box().transform.0.is_empty() || - self.get_box().scale != GenericScale::None || - self.get_box().rotate != GenericRotate::None || - self.get_box().translate != GenericTranslate::None || - self.get_box().perspective != Perspective::None) + #[inline] + fn has_effective_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool { + self.is_transformable(fragment_flags) && self.has_transform_or_perspective_style() } /// Whether the `z-index` property applies to this fragment. @@ -695,17 +717,20 @@ impl ComputedValuesExt for ComputedValues { // From // > For elements whose layout is governed by the CSS box model, any value other than // > `none` for the `transform` property results in the creation of a stacking context. + // + // From + // > all other values […] create a stacking context and containing block for all + // > descendants, per usual for transforms. + // + // From + // > any value other than none establishes a stacking context. + // // From // > A computed value of `preserve-3d` for `transform-style` on a transformable element // > establishes both a stacking context and a containing block for all descendants. - // From - // > any value other than none establishes a stacking context. - // TODO: handle individual transform properties (`translate`, `scale` and `rotate`). - // if self.is_transformable(fragment_flags) && - (!self.get_box().transform.0.is_empty() || + (self.has_transform_or_perspective_style() || self.get_box().transform_style == ComputedTransformStyle::Preserve3d || - self.get_box().perspective != Perspective::None || will_change_bits .intersects(WillChangeBits::TRANSFORM | WillChangeBits::PERSPECTIVE)) { @@ -795,29 +820,43 @@ impl ComputedValuesExt for ComputedValues { &self, fragment_flags: FragmentFlags, ) -> bool { - if self.has_transform_or_perspective(fragment_flags) { - return true; - } - - if !self.get_effects().filter.0.is_empty() { - return true; - } - - // See . - if self.is_transformable(fragment_flags) && - self.get_box().transform_style == ComputedTransformStyle::Preserve3d - { - return true; - } // From : // > If any non-initial value of a property would cause the element to generate a // > containing block for fixed positioned elements, specifying that property in will-change // > must cause the element to generate a containing block for fixed positioned elements. let will_change_bits = self.clone_will_change().bits; - if will_change_bits.intersects(WillChangeBits::FIXPOS_CB_NON_SVG) || - (will_change_bits - .intersects(WillChangeBits::TRANSFORM | WillChangeBits::PERSPECTIVE) && - self.is_transformable(fragment_flags)) + + // From : + // > any value other than `none` for the `transform` property also causes the element + // > to establish a containing block for all descendants. + // + // From + // > all other values […] create a stacking context and containing block for all + // > descendants, per usual for transforms. + // + // From : + // > The use of this property with any value other than `none` […] establishes a + // > containing block for all descendants, just like the `transform` property does. + // + // From : + // > A computed value of `preserve-3d` for `transform-style` on a transformable element + // > establishes both a stacking context and a containing block for all descendants. + if self.is_transformable(fragment_flags) && + (self.has_transform_or_perspective_style() || + self.get_box().transform_style == ComputedTransformStyle::Preserve3d || + will_change_bits + .intersects(WillChangeBits::TRANSFORM | WillChangeBits::PERSPECTIVE)) + { + return true; + } + + // From : + // > A value other than none for the filter property results in the creation of a containing + // > block for absolute and fixed positioned descendants unless the element it applies to is + // > a document root element in the current browsing context. + if !fragment_flags.contains(FragmentFlags::IS_ROOT_ELEMENT) && + (!self.get_effects().filter.0.is_empty() || + will_change_bits.intersects(WillChangeBits::FIXPOS_CB_NON_SVG)) { return true; } @@ -961,6 +1000,23 @@ impl ComputedValuesExt for ComputedValues { }; has_percentage(box_offsets.block_start) || has_percentage(box_offsets.block_end) } + + // + fn overflow_direction(&self) -> OverflowDirection { + let inline_end_direction = self.writing_mode.inline_end_physical_side(); + let block_end_direction = self.writing_mode.block_end_physical_side(); + + let rightward = inline_end_direction == PhysicalSide::Right || + block_end_direction == PhysicalSide::Right; + let downward = inline_end_direction == PhysicalSide::Bottom || + block_end_direction == PhysicalSide::Bottom; + + // TODO(stevennovaryo): We should consider the flex-container's CSS (e.g. flow-direction: column-reverse). + OverflowDirection { + rightward, + downward, + } + } } pub(crate) enum LayoutStyle<'a> { diff --git a/resources/presentational-hints.css b/components/layout/stylesheets/presentational-hints.css similarity index 100% rename from resources/presentational-hints.css rename to components/layout/stylesheets/presentational-hints.css diff --git a/resources/quirks-mode.css b/components/layout/stylesheets/quirks-mode.css similarity index 100% rename from resources/quirks-mode.css rename to components/layout/stylesheets/quirks-mode.css diff --git a/resources/servo.css b/components/layout/stylesheets/servo.css similarity index 72% rename from resources/servo.css rename to components/layout/stylesheets/servo.css index 9800d67e3b9..c025b19f364 100644 --- a/resources/servo.css +++ b/components/layout/stylesheets/servo.css @@ -87,6 +87,15 @@ input[type="file"] { border-style: none; } +input[type="color"] { + padding: 6px; + width: 64px; + height: 32px; + border-radius: 2px; + background: lightgrey; + border: 1px solid gray; +} + td[align="left"] { text-align: left; } td[align="center"] { text-align: center; } td[align="right"] { text-align: right; } @@ -141,64 +150,12 @@ svg > * { display: none; } -/* - * For most (but not all) anon-boxes, we inherit all values from the - * parent. - * - * Anonymous table flows shouldn't inherit their parents properties in order - * to avoid doubling up styles such as transformations. Same for text and such. - * - * For tables, we do want style to inherit, because TableWrapper is - * responsible for handling clipping and scrolling, while Table is - * responsible for creating stacking contexts. - * - * StackingContextCollectionFlags makes sure this is processed - * properly. - * - * FIXME(emilio): inheriting all is a very strong hammer, and it's likely - * broken for stuff like table backgrounds and such. Gecko explicitly inherits - * what it wants, which seems a bit better off-hand. - */ -*|*::-servo-legacy-anonymous-table, -*|*::-servo-legacy-anonymous-table-wrapper, -*|*::-servo-legacy-table-wrapper, -*|*::-servo-legacy-anonymous-block, -*|*::-servo-legacy-inline-block-wrapper, -*|*::-servo-legacy-inline-absolute { - all: inherit; -} - *|*::-servo-anonymous-box { unicode-bidi: inherit; direction: inherit; writing-mode: inherit; } -*|*::-servo-legacy-table-wrapper { - display: table; - border: none; -} - -*|*::-servo-legacy-anonymous-table-wrapper { - position: static; - margin: 0; - counter-increment: none; - - /* We don't want anonymous table parts to inherit hidden overflow, because - * they will create extra unnecessary ClipScrollNodes which also throws - * off assignment of contained flows. */ - overflow: visible; -} - -*|*::-servo-legacy-anonymous-table { - display: table; - position: static; - border: none; - padding: 0; - counter-increment: none; - overflow: visible; -} - *|*::-servo-anonymous-table { display: table; } @@ -249,32 +206,6 @@ svg > * { display: table; } -*|*::-servo-legacy-anonymous-block { - display: block; - position: static; - border: none; - padding: 0; - margin: 0; - width: auto; - height: auto; -} - -/* The outer fragment wrapper of an inline-block. */ -*|*::-servo-legacy-inline-block-wrapper { - position: static; - border: none; - padding: 0; - margin: 0; -} - -/* The outer fragment wrapper of an inline absolute hypothetical fragment. */ -*|*::-servo-legacy-inline-absolute { - clip: auto; - border: none; - padding: 0; - margin: 0; -} - meter { display: inline-block; width: 100px; diff --git a/resources/user-agent.css b/components/layout/stylesheets/user-agent.css similarity index 100% rename from resources/user-agent.css rename to components/layout/stylesheets/user-agent.css diff --git a/components/layout/table/construct.rs b/components/layout/table/construct.rs index f20360d3b56..0b22ea1c13a 100644 --- a/components/layout/table/construct.rs +++ b/components/layout/table/construct.rs @@ -8,7 +8,7 @@ use std::iter::repeat; use atomic_refcell::AtomicRef; use log::warn; -use script_layout_interface::wrapper_traits::ThreadSafeLayoutNode; +use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode}; use servo_arc::Arc; use style::properties::ComputedValues; use style::properties::style_structs::Font; @@ -19,10 +19,9 @@ use super::{ Table, TableCaption, TableLevelBox, TableSlot, TableSlotCell, TableSlotCoordinates, TableSlotOffset, TableTrack, TableTrackGroup, TableTrackGroupType, }; -use crate::PropagatedBoxTreeData; use crate::cell::ArcRefCell; use crate::context::LayoutContext; -use crate::dom::{BoxSlot, LayoutBox, NodeExt}; +use crate::dom::{BoxSlot, LayoutBox}; use crate::dom_traversal::{Contents, NodeAndStyleInfo, NonReplacedContents, TraversalHandler}; use crate::flow::{BlockContainerBuilder, BlockFormattingContext}; use crate::formatting_contexts::{ @@ -32,6 +31,7 @@ use crate::formatting_contexts::{ use crate::fragment_tree::BaseFragmentInfo; use crate::layout_box_base::LayoutBoxBase; use crate::style_ext::{DisplayGeneratingBox, DisplayLayoutInternal}; +use crate::{PropagatedBoxTreeData, SharedStyle}; /// A reference to a slot and its coordinates in the table #[derive(Debug)] @@ -50,17 +50,17 @@ impl ResolvedSlotAndLocation<'_> { } } -pub(crate) enum AnonymousTableContent<'dom, Node> { - Text(NodeAndStyleInfo, Cow<'dom, str>), +pub(crate) enum AnonymousTableContent<'dom> { + Text(NodeAndStyleInfo<'dom>, Cow<'dom, str>), Element { - info: NodeAndStyleInfo, + info: NodeAndStyleInfo<'dom>, display: DisplayGeneratingBox, contents: Contents, box_slot: BoxSlot<'dom>, }, } -impl AnonymousTableContent<'_, Node> { +impl AnonymousTableContent<'_> { fn is_whitespace_only(&self) -> bool { match self { Self::Element { .. } => false, @@ -74,32 +74,24 @@ impl AnonymousTableContent<'_, Node> { } impl Table { - pub(crate) fn construct<'dom>( + pub(crate) fn construct( context: &LayoutContext, - info: &NodeAndStyleInfo>, + info: &NodeAndStyleInfo, grid_style: Arc, contents: NonReplacedContents, propagated_data: PropagatedBoxTreeData, ) -> Self { - let mut traversal = TableBuilderTraversal::new( - context, - info, - grid_style, - propagated_data.union(&info.style), - ); + let mut traversal = TableBuilderTraversal::new(context, info, grid_style, propagated_data); contents.traverse(context, info, &mut traversal); traversal.finish() } - pub(crate) fn construct_anonymous<'dom, Node>( + pub(crate) fn construct_anonymous<'dom>( context: &LayoutContext, - parent_info: &NodeAndStyleInfo, - contents: Vec>, + parent_info: &NodeAndStyleInfo<'dom>, + contents: Vec>, propagated_data: PropagatedBoxTreeData, - ) -> (NodeAndStyleInfo, IndependentFormattingContext) - where - Node: crate::dom::NodeExt<'dom>, - { + ) -> (NodeAndStyleInfo<'dom>, IndependentFormattingContext) { let table_info = parent_info .pseudo(context, PseudoElement::ServoAnonymousTable) .expect("Should never fail to create anonymous table info."); @@ -645,9 +637,9 @@ impl TableBuilder { } } -pub(crate) struct TableBuilderTraversal<'style, 'dom, Node> { +pub(crate) struct TableBuilderTraversal<'style, 'dom> { context: &'style LayoutContext<'style>, - info: &'style NodeAndStyleInfo, + info: &'style NodeAndStyleInfo<'dom>, /// The value of the [`PropagatedBoxTreeData`] to use, either for the row group /// if processing one or for the table itself if outside a row group. @@ -657,19 +649,16 @@ pub(crate) struct TableBuilderTraversal<'style, 'dom, Node> { /// into another struct so that we can write unit tests against the builder. builder: TableBuilder, - current_anonymous_row_content: Vec>, + current_anonymous_row_content: Vec>, /// The index of the current row group, if there is one. current_row_group_index: Option, } -impl<'style, 'dom, Node> TableBuilderTraversal<'style, 'dom, Node> -where - Node: NodeExt<'dom>, -{ +impl<'style, 'dom> TableBuilderTraversal<'style, 'dom> { pub(crate) fn new( context: &'style LayoutContext<'style>, - info: &'style NodeAndStyleInfo, + info: &'style NodeAndStyleInfo<'dom>, grid_style: Arc, propagated_data: PropagatedBoxTreeData, ) -> Self { @@ -728,9 +717,10 @@ where let style = anonymous_info.style.clone(); self.push_table_row(ArcRefCell::new(TableTrack { - base: LayoutBoxBase::new((&anonymous_info).into(), style), + base: LayoutBoxBase::new((&anonymous_info).into(), style.clone()), group_index: self.current_row_group_index, is_anonymous: true, + shared_background_style: SharedStyle::new(style), })); } @@ -745,11 +735,8 @@ where } } -impl<'dom, Node: 'dom> TraversalHandler<'dom, Node> for TableBuilderTraversal<'_, 'dom, Node> -where - Node: NodeExt<'dom>, -{ - fn handle_text(&mut self, info: &NodeAndStyleInfo, text: Cow<'dom, str>) { +impl<'dom> TraversalHandler<'dom> for TableBuilderTraversal<'_, 'dom> { + fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: Cow<'dom, str>) { self.current_anonymous_row_content .push(AnonymousTableContent::Text(info.clone(), text)); } @@ -757,7 +744,7 @@ where /// fn handle_element( &mut self, - info: &NodeAndStyleInfo, + info: &NodeAndStyleInfo<'dom>, display: DisplayGeneratingBox, contents: Contents, box_slot: BoxSlot<'dom>, @@ -775,12 +762,10 @@ where base: LayoutBoxBase::new(info.into(), info.style.clone()), group_type: internal.into(), track_range: next_row_index..next_row_index, + shared_background_style: SharedStyle::new(info.style.clone()), }); self.builder.table.row_groups.push(row_group.clone()); - let previous_propagated_data = self.current_propagated_data; - self.current_propagated_data = self.current_propagated_data.union(&info.style); - let new_row_group_index = self.builder.table.row_groups.len() - 1; self.current_row_group_index = Some(new_row_group_index); @@ -792,7 +777,6 @@ where self.finish_anonymous_row_if_needed(); self.current_row_group_index = None; - self.current_propagated_data = previous_propagated_data; self.builder.incoming_rowspans.clear(); box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::TrackGroup( @@ -817,6 +801,7 @@ where base: LayoutBoxBase::new(info.into(), info.style.clone()), group_index: self.current_row_group_index, is_anonymous: false, + shared_background_style: SharedStyle::new(info.style.clone()), }); self.push_table_row(row.clone()); box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::Track(row))); @@ -862,6 +847,7 @@ where base: LayoutBoxBase::new(info.into(), info.style.clone()), group_type: internal.into(), track_range: first_column..self.builder.table.columns.len(), + shared_background_style: SharedStyle::new(info.style.clone()), }); self.builder.table.column_groups.push(column_group.clone()); box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::TrackGroup( @@ -916,26 +902,23 @@ where } } -struct TableRowBuilder<'style, 'builder, 'dom, 'a, Node> { - table_traversal: &'builder mut TableBuilderTraversal<'style, 'dom, Node>, +struct TableRowBuilder<'style, 'builder, 'dom, 'a> { + table_traversal: &'builder mut TableBuilderTraversal<'style, 'dom>, /// The [`NodeAndStyleInfo`] of this table row, which we use to /// construct anonymous table cells. - info: &'a NodeAndStyleInfo, + info: &'a NodeAndStyleInfo<'dom>, - current_anonymous_cell_content: Vec>, + current_anonymous_cell_content: Vec>, /// The [`PropagatedBoxTreeData`] to use for all children of this row. propagated_data: PropagatedBoxTreeData, } -impl<'style, 'builder, 'dom, 'a, Node: 'dom> TableRowBuilder<'style, 'builder, 'dom, 'a, Node> -where - Node: NodeExt<'dom>, -{ +impl<'style, 'builder, 'dom, 'a> TableRowBuilder<'style, 'builder, 'dom, 'a> { fn new( - table_traversal: &'builder mut TableBuilderTraversal<'style, 'dom, Node>, - info: &'a NodeAndStyleInfo, + table_traversal: &'builder mut TableBuilderTraversal<'style, 'dom>, + info: &'a NodeAndStyleInfo<'dom>, propagated_data: PropagatedBoxTreeData, ) -> Self { table_traversal.builder.start_row(); @@ -944,7 +927,7 @@ where table_traversal, info, current_anonymous_cell_content: Vec::new(), - propagated_data: propagated_data.union(&info.style), + propagated_data, } } @@ -996,11 +979,8 @@ where } } -impl<'dom, Node: 'dom> TraversalHandler<'dom, Node> for TableRowBuilder<'_, '_, 'dom, '_, Node> -where - Node: NodeExt<'dom>, -{ - fn handle_text(&mut self, info: &NodeAndStyleInfo, text: Cow<'dom, str>) { +impl<'dom> TraversalHandler<'dom> for TableRowBuilder<'_, '_, 'dom, '_> { + fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: Cow<'dom, str>) { self.current_anonymous_cell_content .push(AnonymousTableContent::Text(info.clone(), text)); } @@ -1008,7 +988,7 @@ where /// fn handle_element( &mut self, - info: &NodeAndStyleInfo, + info: &NodeAndStyleInfo<'dom>, display: DisplayGeneratingBox, contents: Contents, box_slot: BoxSlot<'dom>, @@ -1019,15 +999,16 @@ where DisplayLayoutInternal::TableCell => { // This value will already have filtered out rowspan=0 // in quirks mode, so we don't have to worry about that. - // - // The HTML specification limits the parsed value of `rowspan` to - // 65534 and `colspan` to 1000, so we also enforce the same limits - // when dealing with arbitrary DOM elements (perhaps created via - // script). let (rowspan, colspan) = if info.pseudo_element_type.is_none() { let node = info.node.to_threadsafe(); - let rowspan = node.get_rowspan().unwrap_or(1).min(65534) as usize; - let colspan = node.get_colspan().unwrap_or(1).min(1000) as usize; + let rowspan = node.get_rowspan().unwrap_or(1) as usize; + let colspan = node.get_colspan().unwrap_or(1) as usize; + + // The HTML specification clamps value of `rowspan` to [0, 65534] and + // `colspan` to [1, 1000]. + assert!((1..=1000).contains(&colspan)); + assert!((0..=65534).contains(&rowspan)); + (rowspan, colspan) } else { (1, 1) @@ -1090,14 +1071,11 @@ struct TableColumnGroupBuilder { columns: Vec>, } -impl<'dom, Node: 'dom> TraversalHandler<'dom, Node> for TableColumnGroupBuilder -where - Node: NodeExt<'dom>, -{ - fn handle_text(&mut self, _info: &NodeAndStyleInfo, _text: Cow<'dom, str>) {} +impl<'dom> TraversalHandler<'dom> for TableColumnGroupBuilder { + fn handle_text(&mut self, _info: &NodeAndStyleInfo<'dom>, _text: Cow<'dom, str>) {} fn handle_element( &mut self, - info: &NodeAndStyleInfo, + info: &NodeAndStyleInfo<'dom>, display: DisplayGeneratingBox, _contents: Contents, box_slot: BoxSlot<'dom>, @@ -1133,28 +1111,27 @@ impl From for TableTrackGroupType { } } -fn add_column<'dom, Node: NodeExt<'dom>>( +fn add_column( collection: &mut Vec>, - column_info: &NodeAndStyleInfo, + column_info: &NodeAndStyleInfo, group_index: Option, is_anonymous: bool, ) -> ArcRefCell { let span = if column_info.pseudo_element_type.is_none() { - column_info - .node - .to_threadsafe() - .get_span() - .unwrap_or(1) - .min(1000) as usize + column_info.node.to_threadsafe().get_span().unwrap_or(1) } else { 1 }; + // The HTML specification clamps value of `span` for `` to [1, 1000]. + assert!((1..=1000).contains(&span)); + let column = ArcRefCell::new(TableTrack { base: LayoutBoxBase::new(column_info.into(), column_info.style.clone()), group_index, is_anonymous, + shared_background_style: SharedStyle::new(column_info.style.clone()), }); - collection.extend(repeat(column.clone()).take(span)); + collection.extend(repeat(column.clone()).take(span as usize)); column } diff --git a/components/layout/table/layout.rs b/components/layout/table/layout.rs index 57b48ae0bca..5b7e79d7fb0 100644 --- a/components/layout/table/layout.rs +++ b/components/layout/table/layout.rs @@ -1068,7 +1068,6 @@ impl<'a> TableLayout<'a> { &mut self, layout_context: &LayoutContext, containing_block_for_table: &ContainingBlock, - parent_positioning_context: &mut PositioningContext, ) { self.cells_laid_out = self .table @@ -1076,30 +1075,6 @@ impl<'a> TableLayout<'a> { .par_iter() .enumerate() .map(|(row_index, row_slots)| { - // When building the PositioningContext for this cell, we want it to have the same - // configuration for whatever PositioningContext the contents are ultimately added to. - let collect_for_nearest_positioned_ancestor = parent_positioning_context - .collects_for_nearest_positioned_ancestor() || - self.table.rows.get(row_index).is_some_and(|row| { - let row = row.borrow(); - let row_group_collects_for_nearest_positioned_ancestor = - row.group_index.is_some_and(|group_index| { - self.table.row_groups[group_index] - .borrow() - .base - .style - .establishes_containing_block_for_absolute_descendants( - FragmentFlags::empty(), - ) - }); - row_group_collects_for_nearest_positioned_ancestor || - row.base - .style - .establishes_containing_block_for_absolute_descendants( - FragmentFlags::empty(), - ) - }); - row_slots .par_iter() .enumerate() @@ -1141,10 +1116,7 @@ impl<'a> TableLayout<'a> { style: &cell.base.style, }; - let mut positioning_context = PositioningContext::new_for_subtree( - collect_for_nearest_positioned_ancestor, - ); - + let mut positioning_context = PositioningContext::default(); let layout = cell.contents.layout( layout_context, &mut positioning_context, @@ -1503,7 +1475,6 @@ impl<'a> TableLayout<'a> { layout_context: &LayoutContext, parent_positioning_context: &mut PositioningContext, ) -> BoxFragment { - let mut positioning_context = PositioningContext::new_for_style(caption.context.style()); let containing_block = &ContainingBlock { size: ContainingBlockSize { inline: self.table_width + self.pbm.padding_border_sums.inline, @@ -1517,6 +1488,8 @@ impl<'a> TableLayout<'a> { // stretch block size. https://drafts.csswg.org/css-sizing-4/#stretch-fit-sizing let ignore_block_margins_for_stretch = LogicalSides1D::new(false, false); + let mut positioning_context = + PositioningContext::new_for_layout_box_base(&caption.context.base); let mut box_fragment = caption.context.layout_in_flow_block_level( layout_context, positioning_context @@ -1769,11 +1742,7 @@ impl<'a> TableLayout<'a> { ) -> BoxFragment { self.distributed_column_widths = Self::distribute_width_to_columns(self.assignable_width, &self.columns); - self.layout_cells_in_row( - layout_context, - containing_block_for_children, - positioning_context, - ); + self.layout_cells_in_row(layout_context, containing_block_for_children); let table_writing_mode = containing_block_for_children.style.writing_mode; let first_layout_row_heights = self.do_first_row_layout(table_writing_mode); self.compute_table_height_and_final_row_heights( @@ -2094,7 +2063,7 @@ impl<'a> TableLayout<'a> { let column_group = column_group.borrow(); let rect = make_relative_to_row_start(dimensions.get_column_group_rect(&column_group)); fragment.add_extra_background(ExtraBackground { - style: column_group.base.style.clone(), + style: column_group.shared_background_style.clone(), rect, }) } @@ -2103,7 +2072,7 @@ impl<'a> TableLayout<'a> { if !column.is_anonymous { let rect = make_relative_to_row_start(dimensions.get_column_rect(column_index)); fragment.add_extra_background(ExtraBackground { - style: column.base.style.clone(), + style: column.shared_background_style.clone(), rect, }) } @@ -2116,7 +2085,7 @@ impl<'a> TableLayout<'a> { let rect = make_relative_to_row_start(dimensions.get_row_group_rect(&row_group.borrow())); fragment.add_extra_background(ExtraBackground { - style: row_group.borrow().base.style.clone(), + style: row_group.borrow().shared_background_style.clone(), rect, }) } @@ -2124,7 +2093,7 @@ impl<'a> TableLayout<'a> { let row = row.borrow(); let rect = make_relative_to_row_start(row_fragment_layout.rect); fragment.add_extra_background(ExtraBackground { - style: row.base.style.clone(), + style: row.shared_background_style.clone(), rect, }) } @@ -2142,23 +2111,27 @@ impl<'a> TableLayout<'a> { for column_group in self.table.column_groups.iter() { let column_group = column_group.borrow(); if !column_group.is_empty() { - fragments.push(Fragment::Positioning(PositioningFragment::new_empty( + let fragment = Fragment::Positioning(PositioningFragment::new_empty( column_group.base.base_fragment_info, dimensions .get_column_group_rect(&column_group) .as_physical(None), column_group.base.style.clone(), - ))); + )); + column_group.base.set_fragment(fragment.clone()); + fragments.push(fragment); } } for (column_index, column) in self.table.columns.iter().enumerate() { let column = column.borrow(); - fragments.push(Fragment::Positioning(PositioningFragment::new_empty( + let fragment = Fragment::Positioning(PositioningFragment::new_empty( column.base.base_fragment_info, dimensions.get_column_rect(column_index).as_physical(None), column.base.style.clone(), - ))); + )); + column.base.set_fragment(fragment.clone()); + fragments.push(fragment); } } @@ -2321,7 +2294,7 @@ impl<'a> RowFragmentLayout<'a> { Self { row: table_row, rect, - positioning_context: PositioningContext::new_for_style(&table_row.base.style), + positioning_context: PositioningContext::new_for_layout_box_base(&table_row.base), containing_block, fragments: Vec::new(), } @@ -2375,11 +2348,11 @@ impl<'a> RowFragmentLayout<'a> { if let Some(mut row_positioning_context) = self.positioning_context.take() { row_positioning_context.layout_collected_children(layout_context, &mut row_fragment); - let positioning_context = row_group_fragment_layout + let parent_positioning_context = row_group_fragment_layout .as_mut() .and_then(|layout| layout.positioning_context.as_mut()) .unwrap_or(table_positioning_context); - positioning_context.append(row_positioning_context); + parent_positioning_context.append(row_positioning_context); } let fragment = Fragment::Box(ArcRefCell::new(row_fragment)); @@ -2406,7 +2379,7 @@ impl RowGroupFragmentLayout { let row_group = row_group.borrow(); ( dimensions.get_row_group_rect(&row_group), - PositioningContext::new_for_style(&row_group.base.style), + PositioningContext::new_for_layout_box_base(&row_group.base), ) }; Self { @@ -2894,6 +2867,7 @@ impl TableSlotCell { block: vertical_align_offset, }; let vertical_align_fragment = PositioningFragment::new_anonymous( + self.base.style.clone(), vertical_align_fragment_rect.as_physical(None), layout.layout.fragments, ); diff --git a/components/layout/table/mod.rs b/components/layout/table/mod.rs index 120270fc7cf..78884c377e9 100644 --- a/components/layout/table/mod.rs +++ b/components/layout/table/mod.rs @@ -76,12 +76,16 @@ pub(crate) use construct::AnonymousTableContent; pub use construct::TableBuilder; use euclid::{Point2D, Size2D, UnknownUnit, Vector2D}; use malloc_size_of_derive::MallocSizeOf; +use script::layout_dom::{ServoLayoutElement, ServoLayoutNode}; use servo_arc::Arc; +use style::context::SharedStyleContext; use style::properties::ComputedValues; use style::properties::style_structs::Font; +use style::selector_parser::PseudoElement; use style_traits::dom::OpaqueNode; use super::flow::BlockFormattingContext; +use crate::SharedStyle; use crate::cell::ArcRefCell; use crate::flow::BlockContainer; use crate::formatting_contexts::IndependentFormattingContext; @@ -98,12 +102,10 @@ pub struct Table { /// The style of this table. These are the properties that apply to the "wrapper" ie the element /// that contains both the grid and the captions. Not all properties are actually used on the /// wrapper though, such as background and borders, which apply to the grid. - #[conditional_malloc_size_of] style: Arc, /// The style of this table's grid. This is an anonymous style based on the table's style, but /// eliminating all the properties handled by the "wrapper." - #[conditional_malloc_size_of] grid_style: Arc, /// The [`BaseFragmentInfo`] for this table's grid. This is necessary so that when the @@ -192,6 +194,19 @@ impl Table { ), } } + + pub(crate) fn repair_style( + &mut self, + context: &SharedStyleContext, + new_style: &Arc, + ) { + self.style = new_style.clone(); + self.grid_style = context.stylist.style_for_anonymous::( + &context.guards, + &PseudoElement::ServoTableGrid, + new_style, + ); + } } type TableSlotCoordinates = Point2D; @@ -233,6 +248,10 @@ impl TableSlotCell { pub fn node_id(&self) -> usize { self.base.base_fragment_info.tag.map_or(0, |tag| tag.node.0) } + + fn repair_style(&mut self, new_style: &Arc) { + self.base.repair_style(new_style); + } } /// A single table slot. It may be an actual cell, or a reference @@ -288,6 +307,18 @@ pub struct TableTrack { /// Whether or not this [`TableTrack`] was anonymous, for instance created due to /// a `span` attribute set on a parent ``. is_anonymous: bool, + + /// A shared container for this track's style, used to share the style for the purposes + /// of drawing backgrounds in individual cells. This allows updating the style in a + /// single place and having it affect all cell `Fragment`s. + shared_background_style: SharedStyle, +} + +impl TableTrack { + fn repair_style(&mut self, new_style: &Arc) { + self.base.repair_style(new_style); + self.shared_background_style = SharedStyle::new(new_style.clone()); + } } #[derive(Debug, MallocSizeOf, PartialEq)] @@ -308,12 +339,22 @@ pub struct TableTrackGroup { /// The range of tracks in this [`TableTrackGroup`]. track_range: Range, + + /// A shared container for this track's style, used to share the style for the purposes + /// of drawing backgrounds in individual cells. This allows updating the style in a + /// single place and having it affect all cell `Fragment`s. + shared_background_style: SharedStyle, } impl TableTrackGroup { pub(super) fn is_empty(&self) -> bool { self.track_range.is_empty() } + + fn repair_style(&mut self, new_style: &Arc) { + self.base.repair_style(new_style); + self.shared_background_style = SharedStyle::new(new_style.clone()); + } } #[derive(Debug, MallocSizeOf)] @@ -346,6 +387,7 @@ pub(crate) struct TableLayoutStyle<'a> { /// Table parts that are stored in the DOM. This is used in order to map from /// the DOM to the box tree and will eventually be important for incremental /// layout. +#[derive(MallocSizeOf)] pub(crate) enum TableLevelBox { Caption(ArcRefCell), Cell(ArcRefCell), @@ -379,4 +421,23 @@ impl TableLevelBox { TableLevelBox::Track(track) => track.borrow().base.fragments(), } } + + pub(crate) fn repair_style( + &self, + context: &SharedStyleContext<'_>, + node: &ServoLayoutNode, + new_style: &Arc, + ) { + match self { + TableLevelBox::Caption(caption) => caption + .borrow_mut() + .context + .repair_style(context, node, new_style), + TableLevelBox::Cell(cell) => cell.borrow_mut().repair_style(new_style), + TableLevelBox::TrackGroup(track_group) => { + track_group.borrow_mut().repair_style(new_style); + }, + TableLevelBox::Track(track) => track.borrow_mut().repair_style(new_style), + } + } } diff --git a/components/layout/taffy/layout.rs b/components/layout/taffy/layout.rs index a7581136bf2..61c4a0508e9 100644 --- a/components/layout/taffy/layout.rs +++ b/components/layout/taffy/layout.rs @@ -23,13 +23,13 @@ use crate::fragment_tree::{ BoxFragment, CollapsedBlockMargins, Fragment, FragmentFlags, SpecificLayoutInfo, }; use crate::geom::{ - LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, Size, SizeConstraint, - Sizes, + LazySize, LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, Size, + SizeConstraint, Sizes, }; use crate::layout_box_base::CacheableLayoutResult; use crate::positioned::{AbsolutelyPositionedBox, PositioningContext, PositioningContextLength}; use crate::sizing::{ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult}; -use crate::style_ext::{ComputedValuesExt, LayoutStyle}; +use crate::style_ext::LayoutStyle; use crate::{ConstraintSpace, ContainingBlock, ContainingBlockSize}; const DUMMY_NODE_ID: taffy::NodeId = taffy::NodeId::new(u64::MAX); @@ -250,34 +250,30 @@ impl taffy::LayoutPartialTree for TaffyContainerContext<'_> { }, style, }; - let layout = { - let mut child_positioning_context = - PositioningContext::new_for_style(style).unwrap_or_else(|| { - PositioningContext::new_for_subtree( - self.positioning_context - .collects_for_nearest_positioned_ancestor(), - ) - }); - let layout = non_replaced.layout_without_caching( - self.layout_context, - &mut child_positioning_context, - &content_box_size_override, - containing_block, - false, /* depends_on_block_constraints */ - ); - - // Store layout data on child for later access - child.positioning_context = child_positioning_context; - - layout + let lazy_block_size = match content_box_known_dimensions.height { + // FIXME: use the correct min/max sizes. + None => LazySize::intrinsic(), + Some(height) => Au::from_f32_px(height).into(), }; + child.positioning_context = PositioningContext::default(); + let layout = non_replaced.layout_without_caching( + self.layout_context, + &mut child.positioning_context, + &content_box_size_override, + containing_block, + false, /* depends_on_block_constraints */ + &lazy_block_size, + ); + child.child_fragments = layout.fragments; self.child_specific_layout_infos[usize::from(node_id)] = layout.specific_layout_info; - let block_size = layout.content_block_size.to_f32_px(); + let block_size = lazy_block_size + .resolve(|| layout.content_block_size) + .to_f32_px(); let computed_size = taffy::Size { width: inline_size + pb_sum.inline, @@ -372,8 +368,7 @@ impl ComputeInlineContentSizes for TaffyContainer { let mut grid_context = TaffyContainerContext { layout_context, - positioning_context: - &mut PositioningContext::new_for_containing_block_for_all_descendants(), + positioning_context: &mut PositioningContext::default(), content_box_size_override: containing_block, style, source_child_nodes: &self.children, @@ -539,17 +534,6 @@ impl TaffyContainer { let child_specific_layout_info: Option = std::mem::take(&mut container_ctx.child_specific_layout_infos[child_id]); - let establishes_containing_block_for_absolute_descendants = - if let TaffyItemBoxInner::InFlowBox(independent_box) = &child.taffy_level_box { - child - .style - .establishes_containing_block_for_absolute_descendants( - independent_box.base_fragment_info().flags, - ) - } else { - false - }; - let fragment = match &mut child.taffy_level_box { TaffyItemBoxInner::InFlowBox(independent_box) => { let mut fragment_info = independent_box.base_fragment_info(); @@ -572,29 +556,21 @@ impl TaffyContainer { }) .with_specific_layout_info(child_specific_layout_info); - if establishes_containing_block_for_absolute_descendants { - child.positioning_context.layout_collected_children( - container_ctx.layout_context, - &mut box_fragment, - ); - } - - let fragment = Fragment::Box(ArcRefCell::new(box_fragment)); - + child.positioning_context.layout_collected_children( + container_ctx.layout_context, + &mut box_fragment, + ); child .positioning_context - .adjust_static_position_of_hoisted_fragments( - &fragment, + .adjust_static_position_of_hoisted_fragments_with_offset( + &box_fragment.content_rect.origin.to_vector(), PositioningContextLength::zero(), ); - let child_positioning_context = std::mem::replace( - &mut child.positioning_context, - PositioningContext::new_for_containing_block_for_all_descendants(), - ); container_ctx .positioning_context - .append(child_positioning_context); - fragment + .append(std::mem::take(&mut child.positioning_context)); + + Fragment::Box(ArcRefCell::new(box_fragment)) }, TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(abs_pos_box) => { fn resolve_alignment(value: AlignFlags, auto: AlignFlags) -> AlignFlags { diff --git a/components/layout/taffy/mod.rs b/components/layout/taffy/mod.rs index 55a678cd89a..05fc09c5511 100644 --- a/components/layout/taffy/mod.rs +++ b/components/layout/taffy/mod.rs @@ -7,7 +7,9 @@ use std::fmt; use app_units::Au; use malloc_size_of_derive::MallocSizeOf; +use script::layout_dom::ServoLayoutNode; use servo_arc::Arc; +use style::context::SharedStyleContext; use style::properties::ComputedValues; use stylo_taffy::TaffyStyloStyle; @@ -15,7 +17,7 @@ use crate::PropagatedBoxTreeData; use crate::cell::ArcRefCell; use crate::construct_modern::{ModernContainerBuilder, ModernItemKind}; use crate::context::LayoutContext; -use crate::dom::{LayoutBox, NodeExt}; +use crate::dom::LayoutBox; use crate::dom_traversal::{NodeAndStyleInfo, NonReplacedContents}; use crate::formatting_contexts::IndependentFormattingContext; use crate::fragment_tree::Fragment; @@ -24,19 +26,17 @@ use crate::positioned::{AbsolutelyPositionedBox, PositioningContext}; #[derive(Debug, MallocSizeOf)] pub(crate) struct TaffyContainer { children: Vec>, - #[conditional_malloc_size_of] style: Arc, } impl TaffyContainer { - pub fn construct<'dom>( + pub fn construct( context: &LayoutContext, - info: &NodeAndStyleInfo>, + info: &NodeAndStyleInfo, contents: NonReplacedContents, propagated_data: PropagatedBoxTreeData, ) -> Self { - let mut builder = - ModernContainerBuilder::new(context, info, propagated_data.union(&info.style)); + let mut builder = ModernContainerBuilder::new(context, info, propagated_data); contents.traverse(context, info, &mut builder); let items = builder.finish(); @@ -69,6 +69,10 @@ impl TaffyContainer { style: info.style.clone(), } } + + pub(crate) fn repair_style(&mut self, new_style: &Arc) { + self.style = new_style.clone(); + } } #[derive(MallocSizeOf)] @@ -76,7 +80,6 @@ pub(crate) struct TaffyItemBox { pub(crate) taffy_layout: taffy::Layout, pub(crate) child_fragments: Vec, pub(crate) positioning_context: PositioningContext, - #[conditional_malloc_size_of] pub(crate) style: Arc, pub(crate) taffy_level_box: TaffyItemBoxInner, } @@ -110,7 +113,7 @@ impl TaffyItemBox { Self { taffy_layout: Default::default(), child_fragments: Vec::new(), - positioning_context: PositioningContext::new_for_containing_block_for_all_descendants(), + positioning_context: PositioningContext::default(), style, taffy_level_box: inner, } @@ -118,8 +121,7 @@ impl TaffyItemBox { pub(crate) fn invalidate_cached_fragment(&mut self) { self.taffy_layout = Default::default(); - self.positioning_context = - PositioningContext::new_for_containing_block_for_all_descendants(); + self.positioning_context = PositioningContext::default(); match self.taffy_level_box { TaffyItemBoxInner::InFlowBox(ref independent_formatting_context) => { independent_formatting_context @@ -146,6 +148,24 @@ impl TaffyItemBox { }, } } + + pub(crate) fn repair_style( + &mut self, + context: &SharedStyleContext, + node: &ServoLayoutNode, + new_style: &Arc, + ) { + self.style = new_style.clone(); + match &mut self.taffy_level_box { + TaffyItemBoxInner::InFlowBox(independent_formatting_context) => { + independent_formatting_context.repair_style(context, node, new_style) + }, + TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(positioned_box) => positioned_box + .borrow_mut() + .context + .repair_style(context, node, new_style), + } + } } /// Details from Taffy grid layout that will be stored diff --git a/components/layout/taffy/stylo_taffy/convert.rs b/components/layout/taffy/stylo_taffy/convert.rs index 03fbec292a4..5780be17c82 100644 --- a/components/layout/taffy/stylo_taffy/convert.rs +++ b/components/layout/taffy/stylo_taffy/convert.rs @@ -59,6 +59,7 @@ pub fn dimension(val: &stylo::Size) -> taffy::Dimension { // Anchor positioning will be flagged off for time being stylo::Size::AnchorSizeFunction(_) => unreachable!(), + stylo::Size::AnchorContainingCalcFunction(_) => unreachable!(), } } @@ -77,6 +78,7 @@ pub fn max_size_dimension(val: &stylo::MaxSize) -> taffy::Dimension { // Anchor positioning will be flagged off for time being stylo::MaxSize::AnchorSizeFunction(_) => unreachable!(), + stylo::MaxSize::AnchorContainingCalcFunction(_) => unreachable!(), } } @@ -88,6 +90,7 @@ pub fn margin(val: &stylo::MarginVal) -> taffy::LengthPercentageAuto { // Anchor positioning will be flagged off for time being stylo::MarginVal::AnchorSizeFunction(_) => unreachable!(), + stylo::MarginVal::AnchorContainingCalcFunction(_) => unreachable!(), } } @@ -100,6 +103,7 @@ pub fn inset(val: &stylo::InsetVal) -> taffy::LengthPercentageAuto { // Anchor positioning will be flagged off for time being stylo::InsetVal::AnchorSizeFunction(_) => unreachable!(), stylo::InsetVal::AnchorFunction(_) => unreachable!(), + stylo::InsetVal::AnchorContainingCalcFunction(_) => unreachable!(), } } diff --git a/components/layout/traversal.rs b/components/layout/traversal.rs index 40281b640c9..faf25dc170d 100644 --- a/components/layout/traversal.rs +++ b/components/layout/traversal.rs @@ -2,29 +2,29 @@ * 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 script::layout_dom::ServoLayoutNode; use script_layout_interface::wrapper_traits::LayoutNode; use style::context::{SharedStyleContext, StyleContext}; use style::data::ElementData; use style::dom::{NodeInfo, TElement, TNode}; +use style::selector_parser::RestyleDamage; use style::traversal::{DomTraversal, PerLevelTraversalData, recalc_style_at}; +use style::values::computed::Display; use crate::context::LayoutContext; -use crate::dom::DOMLayoutData; +use crate::dom::{DOMLayoutData, NodeExt}; +use crate::dom_traversal::iter_child_nodes; pub struct RecalcStyle<'a> { - context: LayoutContext<'a>, + context: &'a LayoutContext<'a>, } impl<'a> RecalcStyle<'a> { - pub fn new(context: LayoutContext<'a>) -> Self { + pub fn new(context: &'a LayoutContext<'a>) -> Self { RecalcStyle { context } } pub fn context(&self) -> &LayoutContext<'a> { - &self.context - } - - pub fn destroy(self) -> LayoutContext<'a> { self.context } } @@ -44,14 +44,33 @@ where ) where F: FnMut(E::ConcreteNode), { + if node.is_text_node() { + return; + } + + let had_style_data = node.style_data().is_some(); unsafe { node.initialize_style_and_layout_data::(); - if !node.is_text_node() { - let el = node.as_element().unwrap(); - let mut data = el.mutate_data().unwrap(); - recalc_style_at(self, traversal_data, context, el, &mut data, note_child); - el.unset_dirty_descendants(); - } + } + + let element = node.as_element().unwrap(); + let mut element_data = element.mutate_data().unwrap(); + + if !had_style_data { + element_data.damage = RestyleDamage::reconstruct(); + } + + recalc_style_at( + self, + traversal_data, + context, + element, + &mut element_data, + note_child, + ); + + unsafe { + element.unset_dirty_descendants(); } } @@ -72,3 +91,49 @@ where &self.context.style_context } } + +pub(crate) fn compute_damage_and_repair_style( + context: &SharedStyleContext, + node: ServoLayoutNode<'_>, +) -> RestyleDamage { + compute_damage_and_repair_style_inner(context, node, RestyleDamage::empty()) +} + +pub(crate) fn compute_damage_and_repair_style_inner( + context: &SharedStyleContext, + node: ServoLayoutNode<'_>, + parent_restyle_damage: RestyleDamage, +) -> RestyleDamage { + let original_damage; + let damage; + + { + let mut element_data = node + .style_data() + .expect("Should not run `compute_damage` before styling.") + .element_data + .borrow_mut(); + + original_damage = std::mem::take(&mut element_data.damage); + damage = original_damage | parent_restyle_damage; + + if let Some(ref style) = element_data.styles.primary { + if style.get_box().display == Display::None { + return damage; + } + } + } + + let mut propagated_damage = damage; + for child in iter_child_nodes(node) { + if child.is_element() { + propagated_damage |= compute_damage_and_repair_style_inner(context, child, damage); + } + } + + if propagated_damage == RestyleDamage::REPAINT && original_damage == RestyleDamage::REPAINT { + node.repair_style(context); + } + + propagated_damage +} diff --git a/components/malloc_size_of/Cargo.toml b/components/malloc_size_of/Cargo.toml index f6f25075ed6..a24bc70a211 100644 --- a/components/malloc_size_of/Cargo.toml +++ b/components/malloc_size_of/Cargo.toml @@ -35,6 +35,7 @@ tokio = { workspace = true, features = ["sync"] } unicode-bidi = { workspace = true } unicode-script = { workspace = true } url = { workspace = true } +urlpattern = { workspace = true } uuid = { workspace = true } webrender_api = { workspace = true } wr_malloc_size_of = { workspace = true } diff --git a/components/malloc_size_of/lib.rs b/components/malloc_size_of/lib.rs index b7f677f8044..ae951da97e5 100644 --- a/components/malloc_size_of/lib.rs +++ b/components/malloc_size_of/lib.rs @@ -50,8 +50,10 @@ use std::cell::OnceCell; use std::collections::BinaryHeap; use std::hash::{BuildHasher, Hash}; use std::ops::Range; +use std::rc::Rc; use std::sync::Arc; +use style::properties::ComputedValues; use style::values::generics::length::GenericLengthPercentageOrAuto; pub use stylo_malloc_size_of::MallocSizeOfOps; use uuid::Uuid; @@ -577,6 +579,28 @@ impl MallocConditionalSizeOf for Arc { } } +impl MallocUnconditionalShallowSizeOf for Rc { + fn unconditional_shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + unsafe { ops.malloc_size_of(Rc::as_ptr(self)) } + } +} + +impl MallocUnconditionalSizeOf for Rc { + fn unconditional_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.unconditional_shallow_size_of(ops) + (**self).size_of(ops) + } +} + +impl MallocConditionalSizeOf for Rc { + fn conditional_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + if ops.have_seen_ptr(Rc::as_ptr(self)) { + 0 + } else { + self.unconditional_size_of(ops) + } + } +} + /// If a mutex is stored directly as a member of a data type that is being measured, /// it is the unique owner of its contents and deserves to be measured. /// @@ -709,6 +733,12 @@ impl MallocSizeOf for ipc_channel::ipc::IpcSender { } } +impl MallocSizeOf for ipc_channel::ipc::IpcReceiver { + fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { + 0 + } +} + impl MallocSizeOf for ipc_channel::ipc::IpcSharedMemory { fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { self.len() @@ -721,6 +751,12 @@ impl MallocSizeOf for accountable_refcell::RefCell { } } +impl MallocSizeOf for servo_arc::Arc { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.conditional_size_of(ops) + } +} + malloc_size_of_hash_map!(indexmap::IndexMap); malloc_size_of_hash_set!(indexmap::IndexSet); @@ -746,12 +782,17 @@ malloc_size_of_is_0!(std::sync::atomic::AtomicUsize); malloc_size_of_is_0!(std::time::Duration); malloc_size_of_is_0!(std::time::Instant); malloc_size_of_is_0!(std::time::SystemTime); +malloc_size_of_is_0!(style::data::ElementData); malloc_size_of_is_0!(style::font_face::SourceList); malloc_size_of_is_0!(style::properties::ComputedValues); +malloc_size_of_is_0!(style::properties::declaration_block::PropertyDeclarationBlock); malloc_size_of_is_0!(style::queries::values::PrefersColorScheme); +malloc_size_of_is_0!(style::stylesheets::Stylesheet); +malloc_size_of_is_0!(style::values::specified::source_size_list::SourceSizeList); malloc_size_of_is_0!(taffy::Layout); malloc_size_of_is_0!(unicode_bidi::Level); malloc_size_of_is_0!(unicode_script::Script); +malloc_size_of_is_0!(urlpattern::UrlPattern); macro_rules! malloc_size_of_is_webrender_malloc_size_of( ($($ty:ty),+) => ( @@ -771,6 +812,7 @@ malloc_size_of_is_webrender_malloc_size_of!(webrender_api::BorderStyle); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::BoxShadowClipMode); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::ColorF); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::ExtendMode); +malloc_size_of_is_webrender_malloc_size_of!(webrender_api::FontKey); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::FontInstanceKey); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::GlyphInstance); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::GradientStop); @@ -815,6 +857,14 @@ where } } +impl MallocSizeOf for style::shared_lock::Locked { + fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { + // TODO: fix this implementation when Locked derives MallocSizeOf. + 0 + // as stylo_malloc_size_of::MallocSizeOf>::size_of(self, ops) + } +} + impl MallocSizeOf for atomic_refcell::AtomicRefCell { fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { self.borrow().size_of(ops) diff --git a/components/metrics/Cargo.toml b/components/metrics/Cargo.toml index 3888b41bb29..a62ff8c98dc 100644 --- a/components/metrics/Cargo.toml +++ b/components/metrics/Cargo.toml @@ -13,7 +13,6 @@ path = "lib.rs" [dependencies] base = { workspace = true } -constellation_traits = { workspace = true } ipc-channel = { workspace = true } log = { workspace = true } malloc_size_of = { workspace = true } diff --git a/components/net/Cargo.toml b/components/net/Cargo.toml index 04ef7ac190c..33d5826978b 100644 --- a/components/net/Cargo.toml +++ b/components/net/Cargo.toml @@ -29,6 +29,7 @@ crossbeam-channel = { workspace = true } data-url = { workspace = true } devtools_traits = { workspace = true } embedder_traits = { workspace = true } +fst = "0.4" futures = { version = "0.3", package = "futures" } futures-core = { version = "0.3.30", default-features = false } futures-util = { version = "0.3.30", default-features = false } @@ -57,7 +58,6 @@ rustls-pemfile = { workspace = true } rustls-pki-types = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -servo_allocator = { path = "../allocator" } servo_arc = { workspace = true } servo_config = { path = "../config" } servo_url = { path = "../url" } @@ -75,7 +75,9 @@ webpki-roots = { workspace = true } webrender_api = { workspace = true } [dev-dependencies] +embedder_traits = { workspace = true, features = ["baked-default-resources"] } flate2 = "1" +fst = "0.4" futures = { version = "0.3", features = ["compat"] } hyper = { workspace = true, features = ["full"] } hyper-util = { workspace = true, features = ["server-graceful"] } diff --git a/components/net/async_runtime.rs b/components/net/async_runtime.rs index c99068b1076..909bdef8fb0 100644 --- a/components/net/async_runtime.rs +++ b/components/net/async_runtime.rs @@ -2,31 +2,27 @@ * 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::cmp::Ord; +use std::sync::LazyLock; use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::{LazyLock, Mutex}; use std::thread; use tokio::runtime::{Builder, Runtime}; -pub static HANDLE: LazyLock>> = LazyLock::new(|| { - Mutex::new(Some( - Builder::new_multi_thread() - .thread_name_fn(|| { - static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0); - let id = ATOMIC_ID.fetch_add(1, Ordering::Relaxed); - format!("tokio-runtime-{}", id) - }) - .worker_threads( - thread::available_parallelism() - .map(|i| i.get()) - .unwrap_or(servo_config::pref!(threadpools_fallback_worker_num) as usize) - .min( - servo_config::pref!(threadpools_async_runtime_workers_max).max(1) as usize, - ), - ) - .enable_io() - .enable_time() - .build() - .unwrap(), - )) +pub static HANDLE: LazyLock = LazyLock::new(|| { + Builder::new_multi_thread() + .thread_name_fn(|| { + static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0); + let id = ATOMIC_ID.fetch_add(1, Ordering::Relaxed); + format!("tokio-runtime-{}", id) + }) + .worker_threads( + thread::available_parallelism() + .map(|i| i.get()) + .unwrap_or(servo_config::pref!(threadpools_fallback_worker_num) as usize) + .min(servo_config::pref!(threadpools_async_runtime_workers_max).max(1) as usize), + ) + .enable_io() + .enable_time() + .build() + .expect("Unable to build tokio-runtime runtime") }); diff --git a/components/net/connector.rs b/components/net/connector.rs index 12d0638d84d..e02ff8971e3 100644 --- a/components/net/connector.rs +++ b/components/net/connector.rs @@ -165,7 +165,7 @@ where F: Future + 'static + std::marker::Send, { fn execute(&self, fut: F) { - HANDLE.lock().unwrap().as_ref().unwrap().spawn(fut); + HANDLE.spawn(fut); } } diff --git a/components/net/fetch/methods.rs b/components/net/fetch/methods.rs index ba4498c54ae..a6377c03879 100644 --- a/components/net/fetch/methods.rs +++ b/components/net/fetch/methods.rs @@ -18,13 +18,14 @@ use http::{HeaderValue, Method, StatusCode}; use ipc_channel::ipc; use log::{debug, trace, warn}; use mime::{self, Mime}; +use net_traits::fetch::headers::extract_mime_type_as_mime; use net_traits::filemanager_thread::{FileTokenCheck, RelativePos}; use net_traits::http_status::HttpStatus; use net_traits::policy_container::{PolicyContainer, RequestPolicyContainer}; use net_traits::request::{ BodyChunkRequest, BodyChunkResponse, CredentialsMode, Destination, Initiator, - InsecureRequestsPolicy, Origin, RedirectMode, Referrer, Request, RequestMode, ResponseTainting, - Window, is_cors_safelisted_method, is_cors_safelisted_request_header, + InsecureRequestsPolicy, Origin, ParserMetadata, RedirectMode, Referrer, Request, RequestMode, + ResponseTainting, Window, is_cors_safelisted_method, is_cors_safelisted_request_header, }; use net_traits::response::{Response, ResponseBody, ResponseType}; use net_traits::{ @@ -42,7 +43,7 @@ use crate::fetch::cors_cache::CorsCache; use crate::fetch::headers::determine_nosniff; use crate::filemanager_thread::FileManager; use crate::http_loader::{HttpState, determine_requests_referrer, http_fetch, set_default_accept}; -use crate::protocols::ProtocolRegistry; +use crate::protocols::{ProtocolRegistry, is_url_potentially_trustworthy}; use crate::request_interceptor::RequestInterceptor; use crate::subresource_integrity::is_response_integrity_valid; @@ -170,6 +171,29 @@ pub async fn fetch_with_cors_cache( // TODO: We don't implement fetchParams as defined in the spec } +fn convert_request_to_csp_request(request: &Request, origin: &ImmutableOrigin) -> csp::Request { + csp::Request { + url: request.url().into_url(), + origin: origin.clone().into_url_origin(), + redirect_count: request.redirect_count, + destination: request.destination, + initiator: match request.initiator { + Initiator::Download => csp::Initiator::Download, + Initiator::ImageSet => csp::Initiator::ImageSet, + Initiator::Manifest => csp::Initiator::Manifest, + Initiator::Prefetch => csp::Initiator::Prefetch, + _ => csp::Initiator::None, + }, + nonce: request.cryptographic_nonce_metadata.clone(), + integrity_metadata: request.integrity_metadata.clone(), + parser_metadata: match request.parser_metadata { + ParserMetadata::ParserInserted => csp::ParserMetadata::ParserInserted, + ParserMetadata::NotParserInserted => csp::ParserMetadata::NotParserInserted, + ParserMetadata::Default => csp::ParserMetadata::None, + }, + } +} + /// pub fn should_request_be_blocked_by_csp( request: &Request, @@ -179,17 +203,7 @@ pub fn should_request_be_blocked_by_csp( Origin::Client => return (csp::CheckResult::Allowed, Vec::new()), Origin::Origin(origin) => origin, }; - - let csp_request = csp::Request { - url: request.url().into_url(), - origin: origin.clone().into_url_origin(), - redirect_count: request.redirect_count, - destination: request.destination, - initiator: csp::Initiator::None, - nonce: request.cryptographic_nonce_metadata.clone(), - integrity_metadata: request.integrity_metadata.clone(), - parser_metadata: csp::ParserMetadata::None, - }; + let csp_request = convert_request_to_csp_request(request, origin); policy_container .csp_list @@ -198,6 +212,24 @@ pub fn should_request_be_blocked_by_csp( .unwrap_or((csp::CheckResult::Allowed, Vec::new())) } +/// +pub fn report_violations_for_request_by_csp( + request: &Request, + policy_container: &PolicyContainer, +) -> Vec { + let origin = match &request.origin { + Origin::Client => return Vec::new(), + Origin::Origin(origin) => origin, + }; + let csp_request = convert_request_to_csp_request(request, origin); + + policy_container + .csp_list + .as_ref() + .map(|c| c.report_violations_for_request(&csp_request)) + .unwrap_or_default() +} + /// [Main fetch](https://fetch.spec.whatwg.org/#concept-main-fetch) pub async fn main_fetch( fetch_params: &mut FetchParams, @@ -231,9 +263,6 @@ pub async fn main_fetch( response = Some(Response::network_error(NetworkError::UnsupportedScheme)); } - // Step 2.2. - // TODO: Report violations. - // The request should have a valid policy_container associated with it. // TODO: This should not be `Client` here let policy_container = match &request.policy_container { @@ -241,12 +270,19 @@ pub async fn main_fetch( RequestPolicyContainer::PolicyContainer(container) => container.to_owned(), }; + // Step 2.2. + let violations = report_violations_for_request_by_csp(request, &policy_container); + + if !violations.is_empty() { + target.process_csp_violations(request, violations); + } + // Step 3. // TODO: handle request abort. // Step 4. Upgrade request to a potentially trustworthy URL, if appropriate. if should_upgrade_request_to_potentially_trustworty(request, context) || - should_upgrade_mixed_content_request(request) + should_upgrade_mixed_content_request(request, &context.protocols) { trace!( "upgrading {} targeting {:?}", @@ -352,13 +388,16 @@ pub async fn main_fetch( if (same_origin && request.response_tainting == ResponseTainting::Basic) || // request's current URL's scheme is "data" current_scheme == "data" || + // Note: Although it is not part of the specification, we make an exception here + // for custom protocols that are explicitly marked as active for fetch. + context.protocols.is_fetchable(current_scheme) || // request's mode is "navigate" or "websocket" matches!( request.mode, RequestMode::Navigate | RequestMode::WebSocket { .. } ) { - // Substep 1. Set request’s response tainting to "basic". + // Substep 1. Set request's response tainting to "basic". request.response_tainting = ResponseTainting::Basic; // Substep 2. Return the result of running scheme fetch given fetchParams. @@ -366,11 +405,11 @@ pub async fn main_fetch( } else if request.mode == RequestMode::SameOrigin { Response::network_error(NetworkError::CrossOriginResponse) } else if request.mode == RequestMode::NoCors { - // Substep 1. If request’s redirect mode is not "follow", then return a network error. + // Substep 1. If request's redirect mode is not "follow", then return a network error. if request.redirect_mode != RedirectMode::Follow { Response::network_error(NetworkError::RedirectError) } else { - // Substep 2. Set request’s response tainting to "opaque". + // Substep 2. Set request's response tainting to "opaque". request.response_tainting = ResponseTainting::Opaque; // Substep 3. Return the result of running scheme fetch given fetchParams. @@ -481,7 +520,7 @@ pub async fn main_fetch( let should_replace_with_mime_type_error = !response_is_network_error && should_be_blocked_due_to_mime_type(request.destination, &response.headers); let should_replace_with_mixed_content = !response_is_network_error && - should_response_be_blocked_as_mixed_content(request, &response); + should_response_be_blocked_as_mixed_content(request, &response, &context.protocols); // Step 15. let mut network_error_response = response @@ -683,7 +722,7 @@ impl RangeRequestBounds { RangeRequestBounds::Final(pos) => { if let Some(len) = len { if pos.start <= len as i64 { - return Ok(pos.clone()); + return Ok(*pos); } } Err("Tried to process RangeRequestBounds::Final without len") @@ -832,7 +871,7 @@ pub fn should_be_blocked_due_to_nosniff( // Step 2 // Note: an invalid MIME type will produce a `None`. - let content_type_header = response_headers.typed_get::(); + let mime_type = extract_mime_type_as_mime(response_headers); /// #[inline] @@ -861,16 +900,12 @@ pub fn should_be_blocked_due_to_nosniff( .any(|mime| mime.type_() == mime_type.type_() && mime.subtype() == mime_type.subtype()) } - match content_type_header { + match mime_type { // Step 4 - Some(ref ct) if destination.is_script_like() => { - !is_javascript_mime_type(&ct.clone().into()) - }, - + Some(ref mime_type) if destination.is_script_like() => !is_javascript_mime_type(mime_type), // Step 5 - Some(ref ct) if destination == Destination::Style => { - let m: mime::Mime = ct.clone().into(); - m.type_() != mime::TEXT && m.subtype() != mime::CSS + Some(ref mime_type) if destination == Destination::Style => { + mime_type.type_() != mime::TEXT && mime_type.subtype() != mime::CSS }, None if destination == Destination::Style || destination.is_script_like() => true, @@ -884,18 +919,22 @@ fn should_be_blocked_due_to_mime_type( destination: Destination, response_headers: &HeaderMap, ) -> bool { - // Step 1 - let mime_type: mime::Mime = match response_headers.typed_get::() { - Some(header) => header.into(), + // Step 1: Let mimeType be the result of extracting a MIME type from response’s header list. + let mime_type: mime::Mime = match extract_mime_type_as_mime(response_headers) { + Some(mime_type) => mime_type, + // Step 2: If mimeType is failure, then return allowed. None => return false, }; - // Step 2-3 + // Step 3: Let destination be request’s destination. + // Step 4: If destination is script-like and one of the following is true, then return blocked: + // - mimeType’s essence starts with "audio/", "image/", or "video/". + // - mimeType’s essence is "text/csv". + // Step 5: Return allowed. destination.is_script_like() && match mime_type.type_() { mime::AUDIO | mime::VIDEO | mime::IMAGE => true, mime::TEXT if mime_type.subtype() == mime::CSV => true, - // Step 4 _ => false, } } @@ -917,7 +956,10 @@ pub fn should_request_be_blocked_due_to_a_bad_port(url: &ServoUrl) -> bool { } /// -pub fn should_request_be_blocked_as_mixed_content(request: &Request) -> bool { +pub fn should_request_be_blocked_as_mixed_content( + request: &Request, + protocol_registry: &ProtocolRegistry, +) -> bool { // Step 1. Return allowed if one or more of the following conditions are met: // 1.1. Does settings prohibit mixed security contexts? // returns "Does Not Restrict Mixed Security Contexts" when applied to request’s client. @@ -928,7 +970,7 @@ pub fn should_request_be_blocked_as_mixed_content(request: &Request) -> bool { } // 1.2. request’s URL is a potentially trustworthy URL. - if request.url().is_potentially_trustworthy() { + if is_url_potentially_trustworthy(protocol_registry, &request.url()) { return false; } @@ -945,7 +987,11 @@ pub fn should_request_be_blocked_as_mixed_content(request: &Request) -> bool { } /// -pub fn should_response_be_blocked_as_mixed_content(request: &Request, response: &Response) -> bool { +pub fn should_response_be_blocked_as_mixed_content( + request: &Request, + response: &Response, + protocol_registry: &ProtocolRegistry, +) -> bool { // Step 1. Return allowed if one or more of the following conditions are met: // 1.1. Does settings prohibit mixed security contexts? returns Does Not Restrict Mixed Content // when applied to request’s client. @@ -959,7 +1005,7 @@ pub fn should_response_be_blocked_as_mixed_content(request: &Request, response: if response .actual_response() .url() - .is_some_and(|response_url| response_url.is_potentially_trustworthy()) + .is_some_and(|response_url| is_url_potentially_trustworthy(protocol_registry, response_url)) { return false; } @@ -1025,7 +1071,7 @@ fn should_upgrade_request_to_potentially_trustworty( // request’s header list if any of the following criteria are met: // * request’s URL is not a potentially trustworthy URL // * request’s URL's host is not a preloadable HSTS host - if !request.current_url().is_potentially_trustworthy() || + if !is_url_potentially_trustworthy(&context.protocols, &request.current_url()) || !request.current_url().host_str().is_some_and(|host| { !context.state.hsts_list.read().unwrap().is_host_secure(host) }) @@ -1078,10 +1124,13 @@ fn do_settings_prohibit_mixed_security_contexts(request: &Request) -> MixedSecur } /// -fn should_upgrade_mixed_content_request(request: &Request) -> bool { +fn should_upgrade_mixed_content_request( + request: &Request, + protocol_registry: &ProtocolRegistry, +) -> bool { let url = request.url(); // Step 1.1 : request’s URL is a potentially trustworthy URL. - if url.is_potentially_trustworthy() { + if is_url_potentially_trustworthy(protocol_registry, &url) { return false; } diff --git a/components/net/hsts.rs b/components/net/hsts.rs index d74794ce60a..c50f3304142 100644 --- a/components/net/hsts.rs +++ b/components/net/hsts.rs @@ -4,26 +4,39 @@ use std::collections::HashMap; use std::net::{Ipv4Addr, Ipv6Addr}; +use std::num::NonZeroU64; +use std::sync::LazyLock; use std::time::Duration; -use base::cross_process_instant::CrossProcessInstant; use embedder_traits::resources::{self, Resource}; +use fst::{Map, MapBuilder}; use headers::{HeaderMapExt, StrictTransportSecurity}; use http::HeaderMap; -use log::{error, info}; +use log::{debug, error, info}; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use malloc_size_of_derive::MallocSizeOf; use net_traits::IncludeSubdomains; use net_traits::pub_domains::reg_suffix; use serde::{Deserialize, Serialize}; use servo_config::pref; use servo_url::{Host, ServoUrl}; +use time::UtcDateTime; #[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] pub struct HstsEntry { pub host: String, pub include_subdomains: bool, - pub max_age: Option, - pub timestamp: Option, + // Nonzero to allow for memory optimization + pub expires_at: Option, +} + +// Zero and negative times are all expired +fn unix_timestamp_to_nonzerou64(timestamp: i64) -> NonZeroU64 { + if timestamp <= 0 { + NonZeroU64::new(1).unwrap() + } else { + NonZeroU64::new(timestamp.try_into().unwrap()).unwrap() + } } impl HstsEntry { @@ -32,73 +45,117 @@ impl HstsEntry { subdomains: IncludeSubdomains, max_age: Option, ) -> Option { + let expires_at = max_age.map(|duration| { + unix_timestamp_to_nonzerou64((UtcDateTime::now() + duration).unix_timestamp()) + }); if host.parse::().is_ok() || host.parse::().is_ok() { None } else { Some(HstsEntry { host, include_subdomains: (subdomains == IncludeSubdomains::Included), - max_age, - timestamp: Some(CrossProcessInstant::now()), + expires_at, }) } } pub fn is_expired(&self) -> bool { - match (self.max_age, self.timestamp) { - (Some(max_age), Some(timestamp)) => CrossProcessInstant::now() - timestamp >= max_age, - + match self.expires_at { + Some(timestamp) => { + unix_timestamp_to_nonzerou64(UtcDateTime::now().unix_timestamp()) >= timestamp + }, _ => false, } } fn matches_domain(&self, host: &str) -> bool { - !self.is_expired() && self.host == host + self.host == host } fn matches_subdomain(&self, host: &str) -> bool { - !self.is_expired() && host.ends_with(&format!(".{}", self.host)) + host.ends_with(&format!(".{}", self.host)) } } #[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, Serialize)] pub struct HstsList { + // Map from base domains to a list of entries that are subdomains of base domain pub entries_map: HashMap>, } -impl HstsList { +/// Represents the portion of the HSTS list that comes from the preload list +/// it is split out to allow sharing between the private and public http state +/// as well as potentially swpaping out the underlying type to something immutable +/// and more efficient like FSTs or DAFSA/DAWGs. +/// To generate a new version of the FST map file run `./mach update-hsts-preload` +#[derive(Clone, Debug)] +pub struct HstsPreloadList(pub fst::Map>); + +impl MallocSizeOf for HstsPreloadList { + #[allow(unsafe_code)] + fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize { + unsafe { ops.malloc_size_of(self.0.as_fst().as_inner().as_ptr()) } + } +} + +static PRELOAD_LIST_ENTRIES: LazyLock = + LazyLock::new(HstsPreloadList::from_servo_preload); + +pub fn hsts_preload_size_of(ops: &mut MallocSizeOfOps) -> usize { + PRELOAD_LIST_ENTRIES.size_of(ops) +} + +impl HstsPreloadList { /// Create an `HstsList` from the bytes of a JSON preload file. - pub fn from_preload(preload_content: &str) -> Option { - #[derive(Deserialize)] - struct HstsEntries { - entries: Vec, - } - - let hsts_entries: Option = serde_json::from_str(preload_content).ok(); - - hsts_entries.map(|hsts_entries| { - let mut hsts_list: HstsList = HstsList::default(); - - for hsts_entry in hsts_entries.entries { - hsts_list.push(hsts_entry); - } - - hsts_list - }) + pub fn from_preload(preload_content: Vec) -> Option { + Map::new(preload_content).map(HstsPreloadList).ok() } - pub fn from_servo_preload() -> HstsList { - let list = resources::read_string(Resource::HstsPreloadList); - HstsList::from_preload(&list).unwrap_or_else(|| { + pub fn from_servo_preload() -> HstsPreloadList { + debug!("Intializing HSTS Preload list"); + let map_bytes = resources::read_bytes(Resource::HstsPreloadList); + HstsPreloadList::from_preload(map_bytes).unwrap_or_else(|| { error!("HSTS preload file is invalid. Setting HSTS list to default values"); - HstsList::default() + HstsPreloadList(MapBuilder::memory().into_map()) }) } pub fn is_host_secure(&self, host: &str) -> bool { + let base_domain = reg_suffix(host); + let parts = host[..host.len() - base_domain.len()].rsplit_terminator('.'); + let mut domain_to_test = base_domain.to_owned(); + + if self.0.get(&domain_to_test).is_some_and(|id| { + // The FST map ids were constructed such that the parity represents the includeSubdomain flag + id % 2 == 1 || domain_to_test == host + }) { + return true; + } + + // Check all further subdomains up to the passed host + for part in parts { + domain_to_test = format!("{}.{}", part, domain_to_test); + if self.0.get(&domain_to_test).is_some_and(|id| { + // The FST map ids were constructed such that the parity represents the includeSubdomain flag + id % 2 == 1 || domain_to_test == host + }) { + return true; + } + } + false + } +} + +impl HstsList { + pub fn is_host_secure(&self, host: &str) -> bool { + if PRELOAD_LIST_ENTRIES.is_host_secure(host) { + info!("{host} is in the preload list"); + return true; + } + let base_domain = reg_suffix(host); self.entries_map.get(base_domain).is_some_and(|entries| { - entries.iter().any(|e| { + entries.iter().filter(|e| !e.is_expired()).any(|e| { if e.include_subdomains { e.matches_subdomain(host) || e.matches_domain(host) } else { @@ -115,9 +172,11 @@ impl HstsList { } fn has_subdomain(&self, host: &str, base_domain: &str) -> bool { - self.entries_map - .get(base_domain) - .is_some_and(|entries| entries.iter().any(|e| e.matches_subdomain(host))) + self.entries_map.get(base_domain).is_some_and(|entries| { + entries + .iter() + .any(|e| e.include_subdomains && e.matches_subdomain(host)) + }) } pub fn push(&mut self, entry: HstsEntry) { @@ -130,13 +189,14 @@ impl HstsList { if !have_domain && !have_subdomain { entries.push(entry); } else if !have_subdomain { - for e in entries { + for e in entries.iter_mut() { if e.matches_domain(&entry.host) { e.include_subdomains = entry.include_subdomains; - e.max_age = entry.max_age; + e.expires_at = entry.expires_at; } } } + entries.retain(|e| !e.is_expired()); } /// Step 2.9 of . diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index 0a0ab4d696b..ac9d120ee80 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -37,7 +37,7 @@ use hyper::ext::ReasonPhrase; use hyper::header::{HeaderName, TRANSFER_ENCODING}; use hyper_serde::Serde; use hyper_util::client::legacy::Client; -use ipc_channel::ipc::{self, IpcSender}; +use ipc_channel::ipc::{self, IpcSender, IpcSharedMemory}; use ipc_channel::router::ROUTER; use log::{debug, error, info, log_enabled, warn}; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; @@ -462,7 +462,7 @@ fn auth_from_cache( /// used to fill the body with bytes coming-in over IPC. enum BodyChunk { /// A chunk of bytes. - Chunk(Vec), + Chunk(IpcSharedMemory), /// Body is done. Done, } @@ -489,12 +489,14 @@ enum BodySink { } impl BodySink { - fn transmit_bytes(&self, bytes: Vec) { + fn transmit_bytes(&self, bytes: IpcSharedMemory) { match self { BodySink::Chunked(sender) => { let sender = sender.clone(); - HANDLE.lock().unwrap().as_mut().unwrap().spawn(async move { - let _ = sender.send(Ok(Frame::data(bytes.into()))).await; + HANDLE.spawn(async move { + let _ = sender + .send(Ok(Frame::data(Bytes::copy_from_slice(&bytes)))) + .await; }); }, BodySink::Buffered(sender) => { @@ -577,7 +579,7 @@ async fn obtain_response( body_port, Box::new(move |message| { info!("Received message"); - let bytes: Vec = match message.unwrap() { + let bytes = match message.unwrap() { BodyChunkResponse::Chunk(bytes) => bytes, BodyChunkResponse::Done => { // Step 3, abort these parallel steps. @@ -622,8 +624,8 @@ async fn obtain_response( let mut body = vec![]; loop { match receiver.recv().await { - Some(BodyChunk::Chunk(mut bytes)) => { - body.append(&mut bytes); + Some(BodyChunk::Chunk(bytes)) => { + body.extend_from_slice(&bytes); }, Some(BodyChunk::Done) => break, None => warn!("Failed to read all chunks from request body."), @@ -2002,7 +2004,7 @@ async fn http_network_fetch( let url1 = request.url(); let url2 = url1.clone(); - HANDLE.lock().unwrap().as_ref().unwrap().spawn( + HANDLE.spawn( res.into_body() .map_err(|e| { warn!("Error streaming response body: {:?}", e); diff --git a/components/net/image_cache.rs b/components/net/image_cache.rs index eab2f9c08fe..8276baa07e7 100644 --- a/components/net/image_cache.rs +++ b/components/net/image_cache.rs @@ -7,7 +7,7 @@ use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::sync::{Arc, Mutex}; use std::{mem, thread}; -use compositing_traits::{CrossProcessCompositorApi, SerializableImageData}; +use compositing_traits::{CrossProcessCompositorApi, ImageUpdate, SerializableImageData}; use imsz::imsz_from_reader; use ipc_channel::ipc::IpcSharedMemory; use log::{debug, warn}; @@ -66,10 +66,10 @@ fn set_webrender_image_key(compositor_api: &CrossProcessCompositorApi, image: &m return; } let mut bytes = Vec::new(); - let frame_bytes = image.bytes(); + let frame_bytes = image.first_frame().bytes; let is_opaque = match image.format { PixelFormat::BGRA8 => { - bytes.extend_from_slice(&frame_bytes); + bytes.extend_from_slice(frame_bytes); pixels::rgba8_premultiply_inplace(bytes.as_mut_slice()) }, PixelFormat::RGB8 => { @@ -431,7 +431,7 @@ pub struct ImageCacheImpl { store: Arc>, /// Thread pool for image decoding - thread_pool: CoreResourceThreadPool, + thread_pool: Arc, } impl ImageCache for ImageCacheImpl { @@ -454,7 +454,10 @@ impl ImageCache for ImageCacheImpl { placeholder_url: ServoUrl::parse("chrome://resources/rippy.png").unwrap(), compositor_api, })), - thread_pool: CoreResourceThreadPool::new(thread_count, "ImageCache".to_string()), + thread_pool: Arc::new(CoreResourceThreadPool::new( + thread_count, + "ImageCache".to_string(), + )), } } @@ -651,6 +654,39 @@ impl ImageCache for ImageCacheImpl { }, } } + + fn create_new_image_cache( + &self, + compositor_api: CrossProcessCompositorApi, + ) -> Arc { + let store = self.store.lock().unwrap(); + let placeholder_image = store.placeholder_image.clone(); + let placeholder_url = store.placeholder_url.clone(); + Arc::new(ImageCacheImpl { + store: Arc::new(Mutex::new(ImageCacheStore { + pending_loads: AllPendingLoads::new(), + completed_loads: HashMap::new(), + placeholder_image, + placeholder_url, + compositor_api, + })), + thread_pool: self.thread_pool.clone(), + }) + } +} + +impl Drop for ImageCacheStore { + fn drop(&mut self) { + let image_updates = self + .completed_loads + .values() + .filter_map(|load| match &load.image_response { + ImageResponse::Loaded(image, _) => image.id.map(ImageUpdate::DeleteImage), + _ => None, + }) + .collect(); + self.compositor_api.update_images(image_updates); + } } impl ImageCacheImpl { diff --git a/components/net/protocols/mod.rs b/components/net/protocols/mod.rs index f8b989b9623..6dc58ceab64 100644 --- a/components/net/protocols/mod.rs +++ b/components/net/protocols/mod.rs @@ -14,6 +14,7 @@ use log::error; use net_traits::filemanager_thread::RelativePos; use net_traits::request::Request; use net_traits::response::Response; +use servo_url::ServoUrl; use crate::fetch::methods::{DoneChannel, FetchContext, RangeRequestBounds}; @@ -47,6 +48,15 @@ pub trait ProtocolHandler: Send + Sync { fn is_fetchable(&self) -> bool { false } + + /// Specify if this custom protocol can be used in a [secure context] + /// + /// Note: this only works for bypassing mixed content checks right now + /// + /// [secure context]: https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts + fn is_secure(&self) -> bool { + false + } } #[derive(Default)] @@ -54,27 +64,45 @@ pub struct ProtocolRegistry { pub(crate) handlers: HashMap>, // Maps scheme -> handler } +#[derive(Clone, Copy, Debug)] +pub enum ProtocolRegisterError { + ForbiddenScheme, + SchemeAlreadyRegistered, +} + impl ProtocolRegistry { pub fn with_internal_protocols() -> Self { let mut registry = Self::default(); - registry.register("data", DataProtocolHander::default()); - registry.register("blob", BlobProtocolHander::default()); - registry.register("file", FileProtocolHander::default()); + // We just created a new registry, and know that we aren't using + // any forbidden schemes, so this should never panic. + registry + .register("data", DataProtocolHander::default()) + .expect("Infallible"); + registry + .register("blob", BlobProtocolHander::default()) + .expect("Infallible"); + registry + .register("file", FileProtocolHander::default()) + .expect("Infallible"); registry } - pub fn register(&mut self, scheme: &str, handler: impl ProtocolHandler + 'static) -> bool { + pub fn register( + &mut self, + scheme: &str, + handler: impl ProtocolHandler + 'static, + ) -> Result<(), ProtocolRegisterError> { if FORBIDDEN_SCHEMES.contains(&scheme) { error!("Protocol handler for '{scheme}' is not allowed to be registered."); - return false; + return Err(ProtocolRegisterError::ForbiddenScheme); } if let Entry::Vacant(entry) = self.handlers.entry(scheme.into()) { entry.insert(Box::new(handler)); - true + Ok(()) } else { error!("Protocol handler for '{scheme}' is already registered."); - false + Err(ProtocolRegisterError::SchemeAlreadyRegistered) } } @@ -96,9 +124,22 @@ impl ProtocolRegistry { pub fn is_fetchable(&self, scheme: &str) -> bool { self.handlers .get(scheme) - .map(|handler| handler.is_fetchable()) - .unwrap_or(false) + .is_some_and(|handler| handler.is_fetchable()) } + + pub fn is_secure(&self, scheme: &str) -> bool { + self.handlers + .get(scheme) + .is_some_and(|handler| handler.is_secure()) + } +} + +/// Test if the URL is potentially trustworthy or the custom protocol is registered as secure +pub fn is_url_potentially_trustworthy( + protocol_registry: &ProtocolRegistry, + url: &ServoUrl, +) -> bool { + url.is_potentially_trustworthy() || protocol_registry.is_secure(url.scheme()) } pub fn range_not_satisfiable_error(response: &mut Response) { diff --git a/components/net/resource_thread.rs b/components/net/resource_thread.rs index b6f885f29b7..94592d19bed 100644 --- a/components/net/resource_thread.rs +++ b/components/net/resource_thread.rs @@ -23,6 +23,7 @@ use ipc_channel::ipc::{self, IpcReceiver, IpcReceiverSet, IpcSender}; use log::{debug, trace, warn}; use net_traits::blob_url_store::parse_blob_url; use net_traits::filemanager_thread::FileTokenCheck; +use net_traits::pub_domains::public_suffix_list_size_of; use net_traits::request::{Destination, RequestBuilder, RequestId}; use net_traits::response::{Response, ResponseInit}; use net_traits::storage_thread::StorageThreadMsg; @@ -32,8 +33,10 @@ use net_traits::{ WebSocketDomAction, WebSocketNetworkEvent, }; use profile_traits::mem::{ - ProcessReports, ProfilerChan as MemProfilerChan, ReportsChan, perform_memory_report, + ProcessReports, ProfilerChan as MemProfilerChan, Report, ReportKind, ReportsChan, + perform_memory_report, }; +use profile_traits::path; use profile_traits::time::ProfilerChan; use rustls::RootCertStore; use serde::{Deserialize, Serialize}; @@ -50,7 +53,7 @@ use crate::fetch::cors_cache::CorsCache; use crate::fetch::fetch_params::FetchParams; use crate::fetch::methods::{CancellationListener, FetchContext, fetch}; use crate::filemanager_thread::FileManager; -use crate::hsts::HstsList; +use crate::hsts::{self, HstsList}; use crate::http_cache::HttpCache; use crate::http_loader::{HttpState, http_redirect_fetch}; use crate::protocols::ProtocolRegistry; @@ -94,14 +97,15 @@ pub fn new_resource_threads( let (public_core, private_core) = new_core_resource_thread( devtools_sender, time_profiler_chan, - mem_profiler_chan, + mem_profiler_chan.clone(), embedder_proxy, config_dir.clone(), ca_certificates, ignore_certificate_errors, protocols, ); - let storage: IpcSender = StorageThreadFactory::new(config_dir); + let storage: IpcSender = + StorageThreadFactory::new(config_dir, mem_profiler_chan); ( ResourceThreads::new(public_core, storage.clone()), ResourceThreads::new(private_core, storage), @@ -176,7 +180,7 @@ fn create_http_states( ignore_certificate_errors: bool, embedder_proxy: EmbedderProxy, ) -> (Arc, Arc) { - let mut hsts_list = HstsList::from_servo_preload(); + let mut hsts_list = HstsList::default(); let mut auth_cache = AuthCache::default(); let http_cache = HttpCache::default(); let mut cookie_jar = CookieStorage::new(150); @@ -205,7 +209,7 @@ fn create_http_states( let override_manager = CertificateErrorOverrideManager::new(); let private_http_state = HttpState { - hsts_list: RwLock::new(HstsList::from_servo_preload()), + hsts_list: RwLock::new(HstsList::default()), cookie_jar: RwLock::new(CookieStorage::new(150)), auth_cache: RwLock::new(AuthCache::default()), history_states: RwLock::new(HashMap::new()), @@ -284,6 +288,18 @@ impl ResourceChannelManager { perform_memory_report(|ops| { let mut reports = public_http_state.memory_reports("public", ops); reports.extend(private_http_state.memory_reports("private", ops)); + reports.extend(vec![ + Report { + path: path!["hsts-preload-list"], + kind: ReportKind::ExplicitJemallocHeapSize, + size: hsts::hsts_preload_size_of(ops), + }, + Report { + path: path!["public-suffix-list"], + kind: ReportKind::ExplicitJemallocHeapSize, + size: public_suffix_list_size_of(ops), + }, + ]); msg.send(ProcessReports::new(reports)); }) } @@ -437,9 +453,6 @@ impl ResourceChannelManager { history_states.remove(&history_state); } }, - CoreResourceMsg::Synchronize(sender) => { - let _ = sender.send(()); - }, CoreResourceMsg::ClearCache => { http_state.http_cache.write().unwrap().clear(); }, @@ -771,7 +784,7 @@ impl CoreResourceManager { _ => (FileTokenCheck::NotRequired, None), }; - HANDLE.lock().unwrap().as_ref().unwrap().spawn(async move { + HANDLE.spawn(async move { // XXXManishearth: Check origin against pipeline id (also ensure that the mode is allowed) // todo load context / mimesniff in fetch // todo referrer policy? diff --git a/components/net/storage_thread.rs b/components/net/storage_thread.rs index dd058f17170..899214a450c 100644 --- a/components/net/storage_thread.rs +++ b/components/net/storage_thread.rs @@ -8,7 +8,12 @@ use std::path::PathBuf; use std::thread; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; +use malloc_size_of::MallocSizeOf; use net_traits::storage_thread::{StorageThreadMsg, StorageType}; +use profile_traits::mem::{ + ProcessReports, ProfilerChan as MemProfilerChan, Report, ReportKind, perform_memory_report, +}; +use profile_traits::path; use servo_url::ServoUrl; use crate::resource_thread; @@ -16,17 +21,26 @@ use crate::resource_thread; const QUOTA_SIZE_LIMIT: usize = 5 * 1024 * 1024; pub trait StorageThreadFactory { - fn new(config_dir: Option) -> Self; + fn new(config_dir: Option, mem_profiler_chan: MemProfilerChan) -> Self; } impl StorageThreadFactory for IpcSender { /// Create a storage thread - fn new(config_dir: Option) -> IpcSender { + fn new( + config_dir: Option, + mem_profiler_chan: MemProfilerChan, + ) -> IpcSender { let (chan, port) = ipc::channel().unwrap(); + let chan2 = chan.clone(); thread::Builder::new() .name("StorageManager".to_owned()) .spawn(move || { - StorageManager::new(port, config_dir).start(); + mem_profiler_chan.run_with_memory_reporting( + || StorageManager::new(port, config_dir).start(), + String::from("storage-reporter"), + chan2, + StorageThreadMsg::CollectMemoryReport, + ); }) .expect("Thread spawning failed"); chan @@ -83,6 +97,10 @@ impl StorageManager { self.clear(sender, url, storage_type); self.save_state() }, + StorageThreadMsg::CollectMemoryReport(sender) => { + let reports = self.collect_memory_reports(); + sender.send(ProcessReports::new(reports)); + }, StorageThreadMsg::Exit(sender) => { // Nothing to do since we save localstorage set eagerly. let _ = sender.send(()); @@ -92,6 +110,24 @@ impl StorageManager { } } + fn collect_memory_reports(&self) -> Vec { + let mut reports = vec![]; + perform_memory_report(|ops| { + reports.push(Report { + path: path!["storage", "local"], + kind: ReportKind::ExplicitJemallocHeapSize, + size: self.local_data.size_of(ops), + }); + + reports.push(Report { + path: path!["storage", "session"], + kind: ReportKind::ExplicitJemallocHeapSize, + size: self.session_data.size_of(ops), + }); + }); + reports + } + fn save_state(&self) { if let Some(ref config_dir) = self.config_dir { resource_thread::write_json_to_file(&self.local_data, config_dir, "local_data.json"); diff --git a/components/net/tests/fetch.rs b/components/net/tests/fetch.rs index 2e1f5f7c8ff..574ec9fbeea 100644 --- a/components/net/tests/fetch.rs +++ b/components/net/tests/fetch.rs @@ -222,7 +222,7 @@ fn test_fetch_blob() { #[test] fn test_file() { - let path = Path::new("../../resources/servo.css") + let path = Path::new("../../resources/ahem.css") .canonicalize() .unwrap(); let url = ServoUrl::from_file_path(path.clone()).unwrap(); diff --git a/components/net/tests/hsts.rs b/components/net/tests/hsts.rs index 863cbc56fe1..3598b232111 100644 --- a/components/net/tests/hsts.rs +++ b/components/net/tests/hsts.rs @@ -3,32 +3,19 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::collections::HashMap; +use std::num::NonZeroU64; use std::time::Duration as StdDuration; -use base::cross_process_instant::CrossProcessInstant; -use net::hsts::{HstsEntry, HstsList}; +use base64::Engine; +use net::hsts::{HstsEntry, HstsList, HstsPreloadList}; use net_traits::IncludeSubdomains; -use time::Duration; #[test] -fn test_hsts_entry_is_not_expired_when_it_has_no_timestamp() { +fn test_hsts_entry_is_not_expired_when_it_has_no_expires_at() { let entry = HstsEntry { - host: "mozilla.org".to_owned(), + host: "example.com".to_owned(), include_subdomains: false, - max_age: Some(StdDuration::from_secs(20)), - timestamp: None, - }; - - assert!(!entry.is_expired()); -} - -#[test] -fn test_hsts_entry_is_not_expired_when_it_has_no_max_age() { - let entry = HstsEntry { - host: "mozilla.org".to_owned(), - include_subdomains: false, - max_age: None, - timestamp: Some(CrossProcessInstant::now()), + expires_at: None, }; assert!(!entry.is_expired()); @@ -37,10 +24,9 @@ fn test_hsts_entry_is_not_expired_when_it_has_no_max_age() { #[test] fn test_hsts_entry_is_expired_when_it_has_reached_its_max_age() { let entry = HstsEntry { - host: "mozilla.org".to_owned(), + host: "example.com".to_owned(), include_subdomains: false, - max_age: Some(StdDuration::from_secs(10)), - timestamp: Some(CrossProcessInstant::now() - Duration::seconds(20)), + expires_at: Some(NonZeroU64::new(1).unwrap()), }; assert!(entry.is_expired()); @@ -74,7 +60,7 @@ fn test_base_domain_in_entries_map() { list.push( HstsEntry::new( - "servo.mozilla.org".to_owned(), + "servo.example.com".to_owned(), IncludeSubdomains::NotIncluded, None, ) @@ -82,7 +68,7 @@ fn test_base_domain_in_entries_map() { ); list.push( HstsEntry::new( - "firefox.mozilla.org".to_owned(), + "firefox.example.com".to_owned(), IncludeSubdomains::NotIncluded, None, ) @@ -90,7 +76,7 @@ fn test_base_domain_in_entries_map() { ); list.push( HstsEntry::new( - "bugzilla.org".to_owned(), + "example.org".to_owned(), IncludeSubdomains::NotIncluded, None, ) @@ -98,17 +84,17 @@ fn test_base_domain_in_entries_map() { ); assert_eq!(list.entries_map.len(), 2); - assert_eq!(list.entries_map.get("mozilla.org").unwrap().len(), 2); + assert_eq!(list.entries_map.get("example.com").unwrap().len(), 2); } #[test] -fn test_push_entry_with_0_max_age_evicts_entry_from_list() { +fn test_push_entry_with_0_max_age_is_not_secure() { let mut entries_map = HashMap::new(); entries_map.insert( - "mozilla.org".to_owned(), + "example.com".to_owned(), vec![ HstsEntry::new( - "mozilla.org".to_owned(), + "example.com".to_owned(), IncludeSubdomains::NotIncluded, Some(StdDuration::from_secs(500000)), ) @@ -121,22 +107,52 @@ fn test_push_entry_with_0_max_age_evicts_entry_from_list() { list.push( HstsEntry::new( - "mozilla.org".to_owned(), + "example.com".to_owned(), IncludeSubdomains::NotIncluded, Some(StdDuration::ZERO), ) .unwrap(), ); - assert_eq!(list.is_host_secure("mozilla.org"), false) + assert_eq!(list.is_host_secure("example.com"), false) +} + +fn test_push_entry_with_0_max_age_evicts_entry_from_list() { + let mut entries_map = HashMap::new(); + entries_map.insert( + "example.com".to_owned(), + vec![ + HstsEntry::new( + "example.com".to_owned(), + IncludeSubdomains::NotIncluded, + Some(StdDuration::from_secs(500000)), + ) + .unwrap(), + ], + ); + let mut list = HstsList { + entries_map: entries_map, + }; + + assert_eq!(list.entries_map.get("example.com").unwrap().len(), 1); + + list.push( + HstsEntry::new( + "example.com".to_owned(), + IncludeSubdomains::NotIncluded, + Some(StdDuration::ZERO), + ) + .unwrap(), + ); + assert_eq!(list.entries_map.get("example.com").unwrap().len(), 0); } #[test] fn test_push_entry_to_hsts_list_should_not_add_subdomains_whose_superdomain_is_already_matched() { let mut entries_map = HashMap::new(); entries_map.insert( - "mozilla.org".to_owned(), - vec![HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()], + "example.com".to_owned(), + vec![HstsEntry::new("example.com".to_owned(), IncludeSubdomains::Included, None).unwrap()], ); let mut list = HstsList { entries_map: entries_map, @@ -144,49 +160,24 @@ fn test_push_entry_to_hsts_list_should_not_add_subdomains_whose_superdomain_is_a list.push( HstsEntry::new( - "servo.mozilla.org".to_owned(), + "servo.example.com".to_owned(), IncludeSubdomains::NotIncluded, None, ) .unwrap(), ); - assert_eq!(list.entries_map.get("mozilla.org").unwrap().len(), 1) + assert_eq!(list.entries_map.get("example.com").unwrap().len(), 1) } #[test] -fn test_push_entry_to_hsts_list_should_update_existing_domain_entrys_include_subdomains() { +fn test_push_entry_to_hsts_list_should_add_subdomains_whose_superdomain_doesnt_include() { let mut entries_map = HashMap::new(); entries_map.insert( - "mozilla.org".to_owned(), - vec![HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()], - ); - let mut list = HstsList { - entries_map: entries_map, - }; - - assert!(list.is_host_secure("servo.mozilla.org")); - - list.push( - HstsEntry::new( - "mozilla.org".to_owned(), - IncludeSubdomains::NotIncluded, - None, - ) - .unwrap(), - ); - - assert!(!list.is_host_secure("servo.mozilla.org")) -} - -#[test] -fn test_push_entry_to_hsts_list_should_not_create_duplicate_entry() { - let mut entries_map = HashMap::new(); - entries_map.insert( - "mozilla.org".to_owned(), + "example.com".to_owned(), vec![ HstsEntry::new( - "mozilla.org".to_owned(), + "example.com".to_owned(), IncludeSubdomains::NotIncluded, None, ) @@ -199,14 +190,69 @@ fn test_push_entry_to_hsts_list_should_not_create_duplicate_entry() { list.push( HstsEntry::new( - "mozilla.org".to_owned(), + "servo.example.com".to_owned(), IncludeSubdomains::NotIncluded, None, ) .unwrap(), ); - assert_eq!(list.entries_map.get("mozilla.org").unwrap().len(), 1) + assert_eq!(list.entries_map.get("example.com").unwrap().len(), 2) +} + +#[test] +fn test_push_entry_to_hsts_list_should_update_existing_domain_entrys_include_subdomains() { + let mut entries_map = HashMap::new(); + entries_map.insert( + "example.com".to_owned(), + vec![HstsEntry::new("example.com".to_owned(), IncludeSubdomains::Included, None).unwrap()], + ); + let mut list = HstsList { + entries_map: entries_map, + }; + + assert!(list.is_host_secure("servo.example.com")); + + list.push( + HstsEntry::new( + "example.com".to_owned(), + IncludeSubdomains::NotIncluded, + None, + ) + .unwrap(), + ); + + assert!(!list.is_host_secure("servo.example.com")) +} + +#[test] +fn test_push_entry_to_hsts_list_should_not_create_duplicate_entry() { + let mut entries_map = HashMap::new(); + entries_map.insert( + "example.com".to_owned(), + vec![ + HstsEntry::new( + "example.com".to_owned(), + IncludeSubdomains::NotIncluded, + None, + ) + .unwrap(), + ], + ); + let mut list = HstsList { + entries_map: entries_map, + }; + + list.push( + HstsEntry::new( + "example.com".to_owned(), + IncludeSubdomains::NotIncluded, + None, + ) + .unwrap(), + ); + + assert_eq!(list.entries_map.get("example.com").unwrap().len(), 1) } #[test] @@ -215,16 +261,14 @@ fn test_push_multiple_entrie_to_hsts_list_should_add_them_all() { entries_map: HashMap::new(), }; - assert!(!list.is_host_secure("mozilla.org")); - assert!(!list.is_host_secure("bugzilla.org")); + assert!(!list.is_host_secure("example.com")); + assert!(!list.is_host_secure("example.org")); - list.push(HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()); - list.push( - HstsEntry::new("bugzilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap(), - ); + list.push(HstsEntry::new("example.com".to_owned(), IncludeSubdomains::Included, None).unwrap()); + list.push(HstsEntry::new("example.org".to_owned(), IncludeSubdomains::Included, None).unwrap()); - assert!(list.is_host_secure("mozilla.org")); - assert!(list.is_host_secure("bugzilla.org")); + assert!(list.is_host_secure("example.com")); + assert!(list.is_host_secure("example.org")); } #[test] @@ -233,47 +277,35 @@ fn test_push_entry_to_hsts_list_should_add_an_entry() { entries_map: HashMap::new(), }; - assert!(!list.is_host_secure("mozilla.org")); + assert!(!list.is_host_secure("example.com")); - list.push(HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()); + list.push(HstsEntry::new("example.com".to_owned(), IncludeSubdomains::Included, None).unwrap()); - assert!(list.is_host_secure("mozilla.org")); + assert!(list.is_host_secure("example.com")); } #[test] fn test_parse_hsts_preload_should_return_none_when_json_invalid() { - let mock_preload_content = "derp"; + let mock_preload_content = "derp".as_bytes().to_vec(); assert!( - HstsList::from_preload(mock_preload_content).is_none(), - "invalid preload list should not have parsed" - ) -} - -#[test] -fn test_parse_hsts_preload_should_return_none_when_json_contains_no_entries_map_key() { - let mock_preload_content = "{\"nothing\": \"to see here\"}"; - assert!( - HstsList::from_preload(mock_preload_content).is_none(), + HstsPreloadList::from_preload(mock_preload_content).is_none(), "invalid preload list should not have parsed" ) } #[test] fn test_parse_hsts_preload_should_decode_host_and_includes_subdomains() { - let mock_preload_content = "{\ - \"entries\": [\ - {\"host\": \"mozilla.org\",\ - \"include_subdomains\": false}\ - ]\ - }"; - let hsts_list = HstsList::from_preload(mock_preload_content); - let entries_map = hsts_list.unwrap().entries_map; + // Generated with `fst map --sorted` on a csv of "example.com,0\nexample.org,3" + let mock_preload_content = base64::engine::general_purpose::STANDARD + .decode("AwAAAAAAAAAAAAAAAAAAAAAQkMQAEJfHAwABBW9jEQLNws/J0MXqwgIAAAAAAAAAJwAAAAAAAADVOFe6") + .unwrap(); + let hsts_list = HstsPreloadList::from_preload(mock_preload_content).unwrap(); - assert_eq!( - entries_map.get("mozilla.org").unwrap()[0].host, - "mozilla.org" - ); - assert!(!entries_map.get("mozilla.org").unwrap()[0].include_subdomains); + assert_eq!(hsts_list.is_host_secure("derp"), false); + assert_eq!(hsts_list.is_host_secure("example.com"), true); + assert_eq!(hsts_list.is_host_secure("servo.example.com"), false); + assert_eq!(hsts_list.is_host_secure("example.org"), true); + assert_eq!(hsts_list.is_host_secure("servo.example.org"), true); } #[test] @@ -282,17 +314,17 @@ fn test_hsts_list_with_no_entries_map_does_not_is_host_secure() { entries_map: HashMap::new(), }; - assert!(!hsts_list.is_host_secure("mozilla.org")); + assert!(!hsts_list.is_host_secure("example.com")); } #[test] fn test_hsts_list_with_exact_domain_entry_is_is_host_secure() { let mut entries_map = HashMap::new(); entries_map.insert( - "mozilla.org".to_owned(), + "example.com".to_owned(), vec![ HstsEntry::new( - "mozilla.org".to_owned(), + "example.com".to_owned(), IncludeSubdomains::NotIncluded, None, ) @@ -304,31 +336,31 @@ fn test_hsts_list_with_exact_domain_entry_is_is_host_secure() { entries_map: entries_map, }; - assert!(hsts_list.is_host_secure("mozilla.org")); + assert!(hsts_list.is_host_secure("example.com")); } #[test] fn test_hsts_list_with_subdomain_when_include_subdomains_is_true_is_is_host_secure() { let mut entries_map = HashMap::new(); entries_map.insert( - "mozilla.org".to_owned(), - vec![HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()], + "example.com".to_owned(), + vec![HstsEntry::new("example.com".to_owned(), IncludeSubdomains::Included, None).unwrap()], ); let hsts_list = HstsList { entries_map: entries_map, }; - assert!(hsts_list.is_host_secure("servo.mozilla.org")); + assert!(hsts_list.is_host_secure("servo.example.com")); } #[test] fn test_hsts_list_with_subdomain_when_include_subdomains_is_false_is_not_is_host_secure() { let mut entries_map = HashMap::new(); entries_map.insert( - "mozilla.org".to_owned(), + "example.com".to_owned(), vec![ HstsEntry::new( - "mozilla.org".to_owned(), + "example.com".to_owned(), IncludeSubdomains::NotIncluded, None, ) @@ -339,58 +371,57 @@ fn test_hsts_list_with_subdomain_when_include_subdomains_is_false_is_not_is_host entries_map: entries_map, }; - assert!(!hsts_list.is_host_secure("servo.mozilla.org")); + assert!(!hsts_list.is_host_secure("servo.example.com")); } #[test] fn test_hsts_list_with_subdomain_when_host_is_not_a_subdomain_is_not_is_host_secure() { let mut entries_map = HashMap::new(); entries_map.insert( - "mozilla.org".to_owned(), - vec![HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()], + "example.com".to_owned(), + vec![HstsEntry::new("example.com".to_owned(), IncludeSubdomains::Included, None).unwrap()], ); let hsts_list = HstsList { entries_map: entries_map, }; - assert!(!hsts_list.is_host_secure("servo-mozilla.org")); + assert!(!hsts_list.is_host_secure("servo-example.com")); } #[test] fn test_hsts_list_with_subdomain_when_host_is_exact_match_is_is_host_secure() { let mut entries_map = HashMap::new(); entries_map.insert( - "mozilla.org".to_owned(), - vec![HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()], + "example.com".to_owned(), + vec![HstsEntry::new("example.com".to_owned(), IncludeSubdomains::Included, None).unwrap()], ); let hsts_list = HstsList { entries_map: entries_map, }; - assert!(hsts_list.is_host_secure("mozilla.org")); + assert!(hsts_list.is_host_secure("example.com")); } #[test] fn test_hsts_list_with_expired_entry_is_not_is_host_secure() { let mut entries_map = HashMap::new(); entries_map.insert( - "mozilla.org".to_owned(), + "example.com".to_owned(), vec![HstsEntry { - host: "mozilla.org".to_owned(), + host: "example.com".to_owned(), include_subdomains: false, - max_age: Some(StdDuration::from_secs(20)), - timestamp: Some(CrossProcessInstant::now() - Duration::seconds(100)), + expires_at: Some(NonZeroU64::new(1).unwrap()), }], ); let hsts_list = HstsList { entries_map: entries_map, }; - assert!(!hsts_list.is_host_secure("mozilla.org")); + assert!(!hsts_list.is_host_secure("example.com")); } #[test] fn test_preload_hsts_domains_well_formed() { - let hsts_list = HstsList::from_servo_preload(); - assert!(!hsts_list.entries_map.is_empty()); + let hsts_list = HstsPreloadList::from_servo_preload(); + assert_ne!(hsts_list.0.len(), 0); } diff --git a/components/net/tests/http_loader.rs b/components/net/tests/http_loader.rs index 9950c3531e6..698b6381c5e 100644 --- a/components/net/tests/http_loader.rs +++ b/components/net/tests/http_loader.rs @@ -31,7 +31,7 @@ use http::{HeaderName, Method, StatusCode}; use http_body_util::combinators::BoxBody; use hyper::body::{Body, Bytes, Incoming}; use hyper::{Request as HyperRequest, Response as HyperResponse}; -use ipc_channel::ipc; +use ipc_channel::ipc::{self, IpcSharedMemory}; use ipc_channel::router::ROUTER; use net::cookie::ServoCookie; use net::cookie_storage::CookieStorage; @@ -100,7 +100,7 @@ pub fn expect_devtools_http_response( } } -fn create_request_body_with_content(content: Vec) -> RequestBody { +fn create_request_body_with_content(content: IpcSharedMemory) -> RequestBody { let content_len = content.len(); let (chunk_request_sender, chunk_request_receiver) = ipc::channel().unwrap(); @@ -592,7 +592,7 @@ fn test_load_doesnt_send_request_body_on_any_redirect() { let (pre_server, pre_url) = make_server(pre_handler); let content = b"Body on POST!"; - let request_body = create_request_body_with_content(content.to_vec()); + let request_body = create_request_body_with_content(IpcSharedMemory::from_bytes(content)); let request = RequestBuilder::new(None, pre_url.clone(), Referrer::NoReferrer) .body(Some(request_body)) @@ -904,7 +904,7 @@ fn test_load_sets_content_length_to_length_of_request_body() { }; let (server, url) = make_server(handler); - let request_body = create_request_body_with_content(content.to_vec()); + let request_body = create_request_body_with_content(IpcSharedMemory::from_bytes(content)); let request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer) .method(Method::POST) diff --git a/components/net/websocket_loader.rs b/components/net/websocket_loader.rs index 95f66558482..128436ac47c 100644 --- a/components/net/websocket_loader.rs +++ b/components/net/websocket_loader.rs @@ -418,24 +418,21 @@ fn connect( tls_config.alpn_protocols = vec!["http/1.1".to_string().into()]; let resource_event_sender2 = resource_event_sender.clone(); - match HANDLE.lock().unwrap().as_mut() { - Some(handle) => handle.spawn( - start_websocket( - http_state, - req_url.clone(), - resource_event_sender, - protocols, - client, - tls_config, - dom_action_receiver, - ) - .map_err(move |e| { - warn!("Failed to establish a WebSocket connection: {:?}", e); - let _ = resource_event_sender2.send(WebSocketNetworkEvent::Fail); - }), - ), - None => return Err("No runtime available".to_string()), - }; + HANDLE.spawn( + start_websocket( + http_state, + req_url.clone(), + resource_event_sender, + protocols, + client, + tls_config, + dom_action_receiver, + ) + .map_err(move |e| { + warn!("Failed to establish a WebSocket connection: {:?}", e); + let _ = resource_event_sender2.send(WebSocketNetworkEvent::Fail); + }), + ); Ok(()) } diff --git a/components/pixels/lib.rs b/components/pixels/lib.rs index 7cba060ec74..35b32c92414 100644 --- a/components/pixels/lib.rs +++ b/components/pixels/lib.rs @@ -4,6 +4,7 @@ use std::borrow::Cow; use std::io::Cursor; +use std::ops::Range; use std::time::Duration; use std::{cmp, fmt, vec}; @@ -84,6 +85,7 @@ pub fn rgba8_premultiply_inplace(pixels: &mut [u8]) -> bool { is_opaque } +#[inline(always)] pub fn multiply_u8_color(a: u8, b: u8) -> u8 { (a as u32 * b as u32 / 255) as u8 } @@ -125,13 +127,24 @@ pub struct Image { pub format: PixelFormat, pub id: Option, pub cors_status: CorsStatus, + pub bytes: IpcSharedMemory, pub frames: Vec, } #[derive(Clone, Deserialize, MallocSizeOf, Serialize)] pub struct ImageFrame { pub delay: Option, - pub bytes: IpcSharedMemory, + /// References a range of the `bytes` field from the image that this + /// frame belongs to. + pub byte_range: Range, + pub width: u32, + pub height: u32, +} + +/// A non-owning reference to the data of an [ImageFrame] +pub struct ImageFrameView<'a> { + pub delay: Option, + pub bytes: &'a [u8], pub width: u32, pub height: u32, } @@ -141,12 +154,19 @@ impl Image { self.frames.len() > 1 } - pub fn bytes(&self) -> IpcSharedMemory { - self.frames - .first() - .expect("Should have at least one frame") - .bytes - .clone() + pub fn frames(&self) -> impl Iterator { + self.frames.iter().map(|frame| ImageFrameView { + delay: frame.delay, + bytes: self.bytes.get(frame.byte_range.clone()).unwrap(), + width: frame.width, + height: frame.height, + }) + } + + pub fn first_frame(&self) -> ImageFrameView { + self.frames() + .next() + .expect("All images should have at least one frame") } } @@ -188,7 +208,7 @@ pub fn load_from_memory(buffer: &[u8], cors_status: CorsStatus) -> Option rgba8_byte_swap_colors_inplace(&mut rgba); let frame = ImageFrame { delay: None, - bytes: IpcSharedMemory::from_bytes(&rgba), + byte_range: 0..rgba.len(), width: rgba.width(), height: rgba.height(), }; @@ -197,6 +217,7 @@ pub fn load_from_memory(buffer: &[u8], cors_status: CorsStatus) -> Option height: rgba.height(), format: PixelFormat::BGRA8, frames: vec![frame], + bytes: IpcSharedMemory::from_bytes(&rgba), id: None, cors_status, }) @@ -254,6 +275,70 @@ pub fn unmultiply_inplace(pixels: &mut [u8]) { } } +#[repr(u8)] +pub enum Multiply { + None = 0, + PreMultiply = 1, + UnMultiply = 2, +} + +pub fn transform_inplace(pixels: &mut [u8], multiply: Multiply, swap_rb: bool, clear_alpha: bool) { + match (multiply, swap_rb, clear_alpha) { + (Multiply::None, true, true) => generic_transform_inplace::<0, true, true>(pixels), + (Multiply::None, true, false) => generic_transform_inplace::<0, true, false>(pixels), + (Multiply::None, false, true) => generic_transform_inplace::<0, false, true>(pixels), + (Multiply::None, false, false) => generic_transform_inplace::<0, false, false>(pixels), + (Multiply::PreMultiply, true, true) => generic_transform_inplace::<1, true, true>(pixels), + (Multiply::PreMultiply, true, false) => generic_transform_inplace::<1, true, false>(pixels), + (Multiply::PreMultiply, false, true) => generic_transform_inplace::<1, false, true>(pixels), + (Multiply::PreMultiply, false, false) => { + generic_transform_inplace::<1, false, false>(pixels) + }, + (Multiply::UnMultiply, true, true) => generic_transform_inplace::<2, true, true>(pixels), + (Multiply::UnMultiply, true, false) => generic_transform_inplace::<2, true, false>(pixels), + (Multiply::UnMultiply, false, true) => generic_transform_inplace::<2, false, true>(pixels), + (Multiply::UnMultiply, false, false) => { + generic_transform_inplace::<2, false, false>(pixels) + }, + } +} + +pub fn generic_transform_inplace< + const MULTIPLY: u8, // 1 premultiply, 2 unmultiply + const SWAP_RB: bool, + const CLEAR_ALPHA: bool, +>( + pixels: &mut [u8], +) { + for rgba in pixels.chunks_mut(4) { + match MULTIPLY { + 1 => { + let a = rgba[3]; + + rgba[0] = multiply_u8_color(rgba[0], a); + rgba[1] = multiply_u8_color(rgba[1], a); + rgba[2] = multiply_u8_color(rgba[2], a); + }, + 2 => { + let a = rgba[3] as u32; + + if a > 0 { + rgba[0] = (rgba[0] as u32 * 255 / a) as u8; + rgba[1] = (rgba[1] as u32 * 255 / a) as u8; + rgba[2] = (rgba[2] as u32 * 255 / a) as u8; + } + }, + _ => {}, + } + if SWAP_RB { + rgba.swap(0, 2); + } + if CLEAR_ALPHA { + rgba[3] = u8::MAX; + } + } +} + fn is_gif(buffer: &[u8]) -> bool { buffer.starts_with(b"GIF87a") || buffer.starts_with(b"GIF89a") } @@ -299,45 +384,61 @@ fn decode_gif(buffer: &[u8], cors_status: CorsStatus) -> Option { // This uses `map_while`, because the first non-decodable frame seems to // send the frame iterator into an infinite loop. See // . + let mut frame_data = vec![]; + let mut total_number_of_bytes = 0; let frames: Vec = decoded_gif .into_frames() .map_while(|decoded_frame| { - let mut frame = match decoded_frame { + let mut gif_frame = match decoded_frame { Ok(decoded_frame) => decoded_frame, Err(error) => { debug!("decode GIF frame error: {error}"); return None; }, }; - rgba8_byte_swap_colors_inplace(frame.buffer_mut()); - - let frame = ImageFrame { - bytes: IpcSharedMemory::from_bytes(frame.buffer()), - delay: Some(Duration::from(frame.delay())), - width: frame.buffer().width(), - height: frame.buffer().height(), - }; + rgba8_byte_swap_colors_inplace(gif_frame.buffer_mut()); + let frame_start = total_number_of_bytes; + total_number_of_bytes += gif_frame.buffer().len(); // The image size should be at least as large as the largest frame. - width = cmp::max(width, frame.width); - height = cmp::max(height, frame.height); + let frame_width = gif_frame.buffer().width(); + let frame_height = gif_frame.buffer().height(); + width = cmp::max(width, frame_width); + height = cmp::max(height, frame_height); + + let frame = ImageFrame { + byte_range: frame_start..total_number_of_bytes, + delay: Some(Duration::from(gif_frame.delay())), + width: frame_width, + height: frame_height, + }; + + frame_data.push(gif_frame); + Some(frame) }) .collect(); if frames.is_empty() { debug!("Animated Image decoding error"); - None - } else { - Some(Image { - width, - height, - cors_status, - frames, - id: None, - format: PixelFormat::BGRA8, - }) + return None; } + + // Coalesce the frame data into one single shared memory region. + let mut bytes = Vec::with_capacity(total_number_of_bytes); + for frame in frame_data { + bytes.extend_from_slice(frame.buffer()); + } + + Some(Image { + width, + height, + cors_status, + frames, + id: None, + format: PixelFormat::BGRA8, + bytes: IpcSharedMemory::from_bytes(&bytes), + }) } #[cfg(test)] diff --git a/components/profile/Cargo.toml b/components/profile/Cargo.toml index 8a8d0775711..73c7924d863 100644 --- a/components/profile/Cargo.toml +++ b/components/profile/Cargo.toml @@ -15,7 +15,6 @@ path = "lib.rs" base = { workspace = true } ipc-channel = { workspace = true } log = { workspace = true } -parking_lot = { workspace = true } profile_traits = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/components/profile/time.rs b/components/profile/time.rs index ce6e6eb90b3..c2a056cfe9c 100644 --- a/components/profile/time.rs +++ b/components/profile/time.rs @@ -69,27 +69,6 @@ impl Formattable for Option { } } -impl Formattable for ProfilerCategory { - // some categories are subcategories of LayoutPerformCategory - // and should be printed to indicate this - fn format(&self, _output: &Option) -> String { - let padding = match *self { - ProfilerCategory::LayoutStyleRecalc | - ProfilerCategory::LayoutRestyleDamagePropagation | - ProfilerCategory::LayoutGeneratedContent | - ProfilerCategory::LayoutFloatPlacementSpeculation | - ProfilerCategory::LayoutMain | - ProfilerCategory::LayoutStoreOverflow | - ProfilerCategory::LayoutDispListBuild | - ProfilerCategory::LayoutParallelWarmup | - ProfilerCategory::LayoutTextShaping => "| + ", - _ => "", - }; - let name: &'static str = self.into(); - format!("{padding}{name}") - } -} - type ProfilerBuckets = BTreeMap<(ProfilerCategory, Option), Vec>; // back end of the profiler that handles data aggregation and performance metrics @@ -276,7 +255,7 @@ impl Profiler { writeln!( file, "{}\t{}\t{:15.4}\t{:15.4}\t{:15.4}\t{:15.4}\t{:15}", - category.format(&self.output), + category.variant_name(), meta.format(&self.output), mean.as_seconds_f64() * 1000., median.as_seconds_f64() * 1000., @@ -319,7 +298,7 @@ impl Profiler { writeln!( &mut lock, "{:-35}{} {:15.4} {:15.4} {:15.4} {:15.4} {:15}", - category.format(&self.output), + category.variant_name(), meta.format(&self.output), mean.as_seconds_f64() * 1000., median.as_seconds_f64() * 1000., diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index 37e80d0e748..09dab3471df 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -18,6 +18,7 @@ crown = ['js/crown'] debugmozjs = ['js/debugmozjs'] jitspew = ['js/jitspew'] profilemozjs = ['js/profilemozjs'] +testbinding = ["script_bindings/testbinding"] tracing = ["dep:tracing", "script_bindings/tracing"] webgl_backtrace = ["canvas_traits/webgl_backtrace"] js_backtrace = [] @@ -93,13 +94,11 @@ net_traits = { workspace = true } nom = "7.1.3" num-traits = { workspace = true } num_cpus = { workspace = true } -parking_lot = { workspace = true } percent-encoding = { workspace = true } phf = "0.11" pixels = { path = "../pixels" } profile_traits = { workspace = true } range = { path = "../range" } -ref_filter_map = "1.0.1" regex = { workspace = true } script_bindings = { path = "../script_bindings" } script_layout_interface = { workspace = true } @@ -114,6 +113,7 @@ servo_config = { path = "../config" } servo_geometry = { path = "../geometry" } servo_rand = { path = "../rand" } servo_url = { path = "../url" } +snapshot = { workspace = true } smallvec = { workspace = true, features = ["union"] } strum = { workspace = true } strum_macros = { workspace = true } @@ -131,6 +131,7 @@ tracing = { workspace = true, optional = true } unicode-bidi = { workspace = true } unicode-segmentation = { workspace = true } url = { workspace = true } +urlpattern = { workspace = true } utf-8 = "0.7" uuid = { workspace = true, features = ["serde"] } webdriver = { workspace = true } diff --git a/components/script/animations.rs b/components/script/animations.rs index 693c3ae978f..6119de7c1dd 100644 --- a/components/script/animations.rs +++ b/components/script/animations.rs @@ -76,6 +76,10 @@ impl Animations { self.pending_events.borrow_mut().clear(); } + pub(crate) fn animations_present(&self) -> bool { + self.has_running_animations.get() || !self.pending_events.borrow().is_empty() + } + // Mark all animations dirty, if they haven't been marked dirty since the // specified `current_timeline_value`. Returns true if animations were marked // dirty or false otherwise. diff --git a/components/script/body.rs b/components/script/body.rs index 113f3ac7adb..cc7870a0845 100644 --- a/components/script/body.rs +++ b/components/script/body.rs @@ -7,7 +7,7 @@ use std::{ptr, slice, str}; use constellation_traits::BlobImpl; use encoding_rs::{Encoding, UTF_8}; -use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; +use ipc_channel::ipc::{self, IpcReceiver, IpcSender, IpcSharedMemory}; use ipc_channel::router::ROUTER; use js::jsapi::{Heap, JS_ClearPendingException, JSObject, Value as JSValue}; use js::jsval::{JSVal, UndefinedValue}; @@ -73,7 +73,7 @@ struct TransmitBodyConnectHandler { task_source: SendableTaskSource, bytes_sender: Option>, control_sender: IpcSender, - in_memory: Option>, + in_memory: Option, in_memory_done: bool, source: BodySource, } @@ -83,7 +83,7 @@ impl TransmitBodyConnectHandler { stream: Trusted, task_source: SendableTaskSource, control_sender: IpcSender, - in_memory: Option>, + in_memory: Option, source: BodySource, ) -> TransmitBodyConnectHandler { TransmitBodyConnectHandler { @@ -160,7 +160,7 @@ impl TransmitBodyConnectHandler { .bytes_sender .as_ref() .expect("No bytes sender to transmit source.") - .send(BodyChunkResponse::Chunk(bytes.clone())); + .send(BodyChunkResponse::Chunk(bytes)); return; } warn!("Re-directs for file-based Blobs not supported yet."); @@ -310,7 +310,11 @@ impl Callback for TransmitBodyPromiseHandler { // Step 5.1 and 5.2, transmit chunk. // Send the chunk to the body transmitter in net::http_loader::obtain_response. // TODO: queue a fetch task on request to process request body for request. - let _ = self.bytes_sender.send(BodyChunkResponse::Chunk(chunk)); + let _ = self + .bytes_sender + .send(BodyChunkResponse::Chunk(IpcSharedMemory::from_bytes( + &chunk, + ))); } } diff --git a/components/script/canvas_context.rs b/components/script/canvas_context.rs index 8bf188a5aa9..ec388e039f1 100644 --- a/components/script/canvas_context.rs +++ b/components/script/canvas_context.rs @@ -5,16 +5,25 @@ //! Common interfaces for Canvas Contexts use euclid::default::Size2D; -use ipc_channel::ipc::IpcSharedMemory; -use script_layout_interface::{HTMLCanvasData, HTMLCanvasDataSource}; +use script_bindings::root::Dom; +use script_layout_interface::HTMLCanvasData; +use snapshot::Snapshot; +use webrender_api::ImageKey; use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas; use crate::dom::bindings::inheritance::Castable; use crate::dom::htmlcanvaselement::HTMLCanvasElement; use crate::dom::node::{Node, NodeDamage}; +#[cfg(feature = "webgpu")] +use crate::dom::types::GPUCanvasContext; +use crate::dom::types::{ + CanvasRenderingContext2D, OffscreenCanvas, OffscreenCanvasRenderingContext2D, + WebGL2RenderingContext, WebGLRenderingContext, +}; pub(crate) trait LayoutCanvasRenderingContextHelpers { - fn canvas_data_source(self) -> HTMLCanvasDataSource; + /// `None` is rendered as transparent black (cleared canvas) + fn canvas_data_source(self) -> Option; } pub(crate) trait LayoutHTMLCanvasElementHelpers { @@ -30,11 +39,10 @@ pub(crate) trait CanvasContext { fn resize(&self); - fn get_image_data_as_shared_memory(&self) -> Option; - - fn get_image_data(&self) -> Option> { - self.get_image_data_as_shared_memory().map(|sm| sm.to_vec()) - } + /// Returns none if area of canvas is zero. + /// + /// In case of other errors it returns cleared snapshot + fn get_image_data(&self) -> Option; fn origin_is_clean(&self) -> bool { true @@ -86,3 +94,184 @@ impl CanvasHelpers for HTMLCanvasElementOrOffscreenCanvas { } } } + +/// Non rooted variant of [`crate::dom::bindings::codegen::Bindings::HTMLCanvasElementBinding::RenderingContext`] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +#[derive(Clone, JSTraceable, MallocSizeOf)] +pub(crate) enum RenderingContext { + Placeholder(Dom), + Context2d(Dom), + WebGL(Dom), + WebGL2(Dom), + #[cfg(feature = "webgpu")] + WebGPU(Dom), +} + +impl CanvasContext for RenderingContext { + type ID = (); + + fn context_id(&self) -> Self::ID {} + + fn canvas(&self) -> HTMLCanvasElementOrOffscreenCanvas { + match self { + RenderingContext::Placeholder(context) => (*context.context().unwrap()).canvas(), + RenderingContext::Context2d(context) => context.canvas(), + RenderingContext::WebGL(context) => context.canvas(), + RenderingContext::WebGL2(context) => context.canvas(), + #[cfg(feature = "webgpu")] + RenderingContext::WebGPU(context) => context.canvas(), + } + } + + fn resize(&self) { + match self { + RenderingContext::Placeholder(offscreen_canvas) => { + if let Some(context) = offscreen_canvas.context() { + context.resize() + } + }, + RenderingContext::Context2d(context) => context.resize(), + RenderingContext::WebGL(context) => context.resize(), + RenderingContext::WebGL2(context) => context.resize(), + #[cfg(feature = "webgpu")] + RenderingContext::WebGPU(context) => context.resize(), + } + } + + fn get_image_data(&self) -> Option { + match self { + RenderingContext::Placeholder(context) => { + (*context.context().unwrap()).get_image_data() + }, + RenderingContext::Context2d(context) => context.get_image_data(), + RenderingContext::WebGL(context) => context.get_image_data(), + RenderingContext::WebGL2(context) => context.get_image_data(), + #[cfg(feature = "webgpu")] + RenderingContext::WebGPU(context) => context.get_image_data(), + } + } + + fn origin_is_clean(&self) -> bool { + match self { + RenderingContext::Placeholder(context) => { + (*context.context().unwrap()).origin_is_clean() + }, + RenderingContext::Context2d(context) => context.origin_is_clean(), + RenderingContext::WebGL(context) => context.origin_is_clean(), + RenderingContext::WebGL2(context) => context.origin_is_clean(), + #[cfg(feature = "webgpu")] + RenderingContext::WebGPU(context) => context.origin_is_clean(), + } + } + + fn size(&self) -> Size2D { + match self { + RenderingContext::Placeholder(context) => (*context.context().unwrap()).size(), + RenderingContext::Context2d(context) => context.size(), + RenderingContext::WebGL(context) => context.size(), + RenderingContext::WebGL2(context) => context.size(), + #[cfg(feature = "webgpu")] + RenderingContext::WebGPU(context) => context.size(), + } + } + + fn mark_as_dirty(&self) { + match self { + RenderingContext::Placeholder(context) => (*context.context().unwrap()).mark_as_dirty(), + RenderingContext::Context2d(context) => context.mark_as_dirty(), + RenderingContext::WebGL(context) => context.mark_as_dirty(), + RenderingContext::WebGL2(context) => context.mark_as_dirty(), + #[cfg(feature = "webgpu")] + RenderingContext::WebGPU(context) => context.mark_as_dirty(), + } + } + + fn update_rendering(&self) { + match self { + RenderingContext::Placeholder(context) => { + (*context.context().unwrap()).update_rendering() + }, + RenderingContext::Context2d(context) => context.update_rendering(), + RenderingContext::WebGL(context) => context.update_rendering(), + RenderingContext::WebGL2(context) => context.update_rendering(), + #[cfg(feature = "webgpu")] + RenderingContext::WebGPU(context) => context.update_rendering(), + } + } + + fn onscreen(&self) -> bool { + match self { + RenderingContext::Placeholder(context) => (*context.context().unwrap()).onscreen(), + RenderingContext::Context2d(context) => context.onscreen(), + RenderingContext::WebGL(context) => context.onscreen(), + RenderingContext::WebGL2(context) => context.onscreen(), + #[cfg(feature = "webgpu")] + RenderingContext::WebGPU(context) => context.onscreen(), + } + } +} + +/// Non rooted variant of [`crate::dom::bindings::codegen::Bindings::OffscreenCanvasBinding::OffscreenRenderingContext`] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +#[derive(Clone, JSTraceable, MallocSizeOf)] +pub(crate) enum OffscreenRenderingContext { + Context2d(Dom), + //WebGL(Dom), + //WebGL2(Dom), + //#[cfg(feature = "webgpu")] + //WebGPU(Dom), +} + +impl CanvasContext for OffscreenRenderingContext { + type ID = (); + + fn context_id(&self) -> Self::ID {} + + fn canvas(&self) -> HTMLCanvasElementOrOffscreenCanvas { + match self { + OffscreenRenderingContext::Context2d(context) => context.canvas(), + } + } + + fn resize(&self) { + match self { + OffscreenRenderingContext::Context2d(context) => context.resize(), + } + } + + fn get_image_data(&self) -> Option { + match self { + OffscreenRenderingContext::Context2d(context) => context.get_image_data(), + } + } + + fn origin_is_clean(&self) -> bool { + match self { + OffscreenRenderingContext::Context2d(context) => context.origin_is_clean(), + } + } + + fn size(&self) -> Size2D { + match self { + OffscreenRenderingContext::Context2d(context) => context.size(), + } + } + + fn mark_as_dirty(&self) { + match self { + OffscreenRenderingContext::Context2d(context) => context.mark_as_dirty(), + } + } + + fn update_rendering(&self) { + match self { + OffscreenRenderingContext::Context2d(context) => context.update_rendering(), + } + } + + fn onscreen(&self) -> bool { + match self { + OffscreenRenderingContext::Context2d(context) => context.onscreen(), + } + } +} diff --git a/components/script/canvas_state.rs b/components/script/canvas_state.rs index aea3012b365..d4840a5b470 100644 --- a/components/script/canvas_state.rs +++ b/components/script/canvas_state.rs @@ -36,6 +36,7 @@ use style_traits::{CssWriter, ParsingMode}; use url::Url; use webrender_api::ImageKey; +use crate::canvas_context::{OffscreenRenderingContext, RenderingContext}; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::{ CanvasDirection, CanvasFillRule, CanvasImageSource, CanvasLineCap, CanvasLineJoin, @@ -52,10 +53,10 @@ use crate::dom::canvaspattern::CanvasPattern; use crate::dom::dommatrix::DOMMatrix; use crate::dom::element::{Element, cors_setting_for_element}; use crate::dom::globalscope::GlobalScope; -use crate::dom::htmlcanvaselement::{CanvasContext, HTMLCanvasElement}; +use crate::dom::htmlcanvaselement::HTMLCanvasElement; use crate::dom::imagedata::ImageData; use crate::dom::node::{Node, NodeTraits}; -use crate::dom::offscreencanvas::{OffscreenCanvas, OffscreenCanvasContext}; +use crate::dom::offscreencanvas::OffscreenCanvas; use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope; use crate::dom::textmetrics::TextMetrics; use crate::script_runtime::CanGc; @@ -152,6 +153,8 @@ pub(crate) struct CanvasState { canvas_id: CanvasId, #[no_trace] image_key: ImageKey, + #[no_trace] + size: Cell>, state: DomRefCell, origin_clean: Cell, #[ignore_malloc_size_of = "Arc"] @@ -176,6 +179,7 @@ impl CanvasState { profiled_ipc::channel(global.time_profiler_chan().clone()).unwrap(); let script_to_constellation_chan = global.script_to_constellation_chan(); debug!("Asking constellation to create new canvas thread."); + let size = adjust_canvas_size(size); script_to_constellation_chan .send(ScriptToConstellationMessage::CreateCanvasPaintThread( size, sender, @@ -194,6 +198,7 @@ impl CanvasState { CanvasState { ipc_renderer, canvas_id, + size: Cell::new(size), state: DomRefCell::new(CanvasContextState::new()), origin_clean: Cell::new(true), image_cache: global.image_cache(), @@ -221,7 +226,15 @@ impl CanvasState { self.canvas_id } + pub(crate) fn is_paintable(&self) -> bool { + !self.size.get().is_empty() + } + pub(crate) fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) { + if !self.is_paintable() { + return; + } + self.ipc_renderer .send(CanvasMsg::Canvas2d(msg, self.get_canvas_id())) .unwrap() @@ -229,6 +242,10 @@ impl CanvasState { /// Updates WR image and blocks on completion pub(crate) fn update_rendering(&self) { + if !self.is_paintable() { + return; + } + let (sender, receiver) = ipc::channel().unwrap(); self.ipc_renderer .send(CanvasMsg::Canvas2d( @@ -239,16 +256,27 @@ impl CanvasState { receiver.recv().unwrap(); } - // https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions + /// pub(crate) fn set_bitmap_dimensions(&self, size: Size2D) { self.reset_to_initial_state(); + + self.size.replace(adjust_canvas_size(size)); + self.ipc_renderer - .send(CanvasMsg::Recreate(Some(size), self.get_canvas_id())) + .send(CanvasMsg::Recreate( + Some(self.size.get()), + self.get_canvas_id(), + )) .unwrap(); } pub(crate) fn reset(&self) { self.reset_to_initial_state(); + + if !self.is_paintable() { + return; + } + self.ipc_renderer .send(CanvasMsg::Recreate(None, self.get_canvas_id())) .unwrap(); @@ -298,7 +326,7 @@ impl CanvasState { &self, url: ServoUrl, cors_setting: Option, - ) -> Option<(IpcSharedMemory, Size2D)> { + ) -> Option { let img = match self.request_image_from_cache(url, cors_setting) { ImageResponse::Loaded(img, _) => img, ImageResponse::PlaceholderLoaded(_, _) | @@ -308,13 +336,22 @@ impl CanvasState { }, }; - let image_size = Size2D::new(img.width, img.height); - let image_data = match img.format { - PixelFormat::BGRA8 => img.bytes(), + let size = Size2D::new(img.width, img.height); + let format = match img.format { + PixelFormat::BGRA8 => snapshot::PixelFormat::BGRA, + PixelFormat::RGBA8 => snapshot::PixelFormat::RGBA, pixel_format => unimplemented!("unsupported pixel format ({:?})", pixel_format), }; + let alpha_mode = snapshot::AlphaMode::Transparent { + premultiplied: false, + }; - Some((image_data, image_size)) + Some(snapshot::Snapshot::from_shared_memory( + size.cast(), + format, + alpha_mode, + IpcSharedMemory::from_bytes(img.first_frame().bytes), + )) } fn request_image_from_cache( @@ -338,16 +375,18 @@ impl CanvasState { pub(crate) fn get_rect(&self, canvas_size: Size2D, rect: Rect) -> Vec { assert!(self.origin_is_clean()); - assert!(Rect::from_size(canvas_size).contains_rect(&rect)); - let (sender, receiver) = ipc::bytes_channel().unwrap(); + let (sender, receiver) = ipc::channel().unwrap(); self.send_canvas_2d_msg(Canvas2dMsg::GetImageData(rect, canvas_size, sender)); - let mut pixels = receiver.recv().unwrap().to_vec(); - - pixels::unmultiply_inplace::(&mut pixels); - - pixels + let mut snapshot = receiver.recv().unwrap().to_owned(); + snapshot.transform( + snapshot::AlphaMode::Transparent { + premultiplied: false, + }, + snapshot::PixelFormat::RGBA, + ); + snapshot.to_vec() } /// @@ -386,18 +425,22 @@ impl CanvasState { dw: Option, dh: Option, ) -> ErrorResult { + if !self.is_paintable() { + return Ok(()); + } + let result = match image { CanvasImageSource::HTMLCanvasElement(ref canvas) => { - // https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument - if !canvas.is_valid() { + // + if canvas.get_size().is_empty() { return Err(Error::InvalidState); } self.draw_html_canvas_element(canvas, htmlcanvas, sx, sy, sw, sh, dx, dy, dw, dh) }, CanvasImageSource::OffscreenCanvas(ref canvas) => { - // https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument - if !canvas.is_valid() { + // + if canvas.get_size().is_empty() { return Err(Error::InvalidState); } @@ -480,7 +523,7 @@ impl CanvasState { if let Some(context) = canvas.context() { match *context { - OffscreenCanvasContext::OffscreenContext2d(ref context) => { + OffscreenRenderingContext::Context2d(ref context) => { context.send_canvas_2d_msg(Canvas2dMsg::DrawImageInOther( self.get_canvas_id(), image_size, @@ -516,11 +559,6 @@ impl CanvasState { dw: Option, dh: Option, ) -> ErrorResult { - // 1. Check the usability of the image argument - if !canvas.is_valid() { - return Err(Error::InvalidState); - } - let canvas_size = canvas.get_size(); let dw = dw.unwrap_or(canvas_size.width as f64); let dh = dh.unwrap_or(canvas_size.height as f64); @@ -540,7 +578,7 @@ impl CanvasState { if let Some(context) = canvas.context() { match *context { - CanvasContext::Context2d(ref context) => { + RenderingContext::Context2d(ref context) => { context.send_canvas_2d_msg(Canvas2dMsg::DrawImageInOther( self.get_canvas_id(), image_size, @@ -549,12 +587,12 @@ impl CanvasState { smoothing_enabled, )); }, - CanvasContext::Placeholder(ref context) => { + RenderingContext::Placeholder(ref context) => { let Some(context) = context.context() else { return Err(Error::InvalidState); }; match *context { - OffscreenCanvasContext::OffscreenContext2d(ref context) => context + OffscreenRenderingContext::Context2d(ref context) => context .send_canvas_2d_msg(Canvas2dMsg::DrawImageInOther( self.get_canvas_id(), image_size, @@ -594,10 +632,10 @@ impl CanvasState { dh: Option, ) -> ErrorResult { debug!("Fetching image {}.", url); - let (image_data, image_size) = self + let snapshot = self .fetch_image_data(url, cors_setting) .ok_or(Error::InvalidState)?; - let image_size = image_size.to_f64(); + let image_size = snapshot.size().to_f64(); let dw = dw.unwrap_or(image_size.width); let dh = dh.unwrap_or(image_size.height); @@ -614,8 +652,7 @@ impl CanvasState { let smoothing_enabled = self.state.borrow().image_smoothing_enabled; self.send_canvas_2d_msg(Canvas2dMsg::DrawImage( - image_data, - image_size, + snapshot.as_ipc(), dest_rect, source_rect, smoothing_enabled, @@ -929,7 +966,7 @@ impl CanvasState { mut repetition: DOMString, can_gc: CanGc, ) -> Fallible>> { - let (image_data, image_size) = match image { + let snapshot = match image { CanvasImageSource::HTMLImageElement(ref image) => { // https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument if !image.is_usable()? { @@ -941,27 +978,17 @@ impl CanvasState { .and_then(|url| { self.fetch_image_data(url, cors_setting_for_element(image.upcast())) }) - .map(|data| (data.0.to_vec(), data.1)) .ok_or(Error::InvalidState)? }, CanvasImageSource::HTMLCanvasElement(ref canvas) => { - let (data, size) = canvas.fetch_all_data().ok_or(Error::InvalidState)?; - let data = data - .map(|data| data.to_vec()) - .unwrap_or_else(|| vec![0; size.area() as usize * 4]); - (data, size) + canvas.get_image_data().ok_or(Error::InvalidState)? }, CanvasImageSource::OffscreenCanvas(ref canvas) => { - let (data, size) = canvas.fetch_all_data().ok_or(Error::InvalidState)?; - let data = data - .map(|data| data.to_vec()) - .unwrap_or_else(|| vec![0; size.area() as usize * 4]); - (data, size) + canvas.get_image_data().ok_or(Error::InvalidState)? }, CanvasImageSource::CSSStyleValue(ref value) => value .get_url(self.base_url.clone()) .and_then(|url| self.fetch_image_data(url, None)) - .map(|data| (data.0.to_vec(), data.1)) .ok_or(Error::InvalidState)?, }; @@ -970,10 +997,11 @@ impl CanvasState { } if let Ok(rep) = RepetitionStyle::from_str(&repetition) { + let size = snapshot.size(); Ok(Some(CanvasPattern::new( global, - image_data, - image_size, + snapshot.to_vec(), + size.cast(), rep, self.is_origin_clean(image), can_gc, @@ -1401,13 +1429,13 @@ impl CanvasState { }, }; - ImageData::new( - global, - size.width, - size.height, - Some(self.get_rect(canvas_size, read_rect)), - can_gc, - ) + let data = if self.is_paintable() { + Some(self.get_rect(canvas_size, read_rect)) + } else { + None + }; + + ImageData::new(global, size.width, size.height, data, can_gc) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata @@ -1443,6 +1471,10 @@ impl CanvasState { dirty_width: i32, dirty_height: i32, ) { + if !self.is_paintable() { + return; + } + // FIXME(nox): There are many arithmetic operations here that can // overflow or underflow, this should probably be audited. @@ -2011,3 +2043,23 @@ where style.font_family.to_css_string() ) } + +fn adjust_canvas_size(size: Size2D) -> Size2D { + // Firefox limits width/height to 32767 pixels and Chromium to 65535 pixels, + // but slows down dramatically before it reaches that limit. + // We limit by area instead, giving us larger maximum dimensions, + // in exchange for a smaller maximum canvas size. + const MAX_CANVAS_AREA: u64 = 32768 * 8192; + // Max width/height to 65535 in CSS pixels. + const MAX_CANVAS_SIZE: u64 = 65535; + + if !size.is_empty() && + size.greater_than(Size2D::new(MAX_CANVAS_SIZE, MAX_CANVAS_SIZE)) + .none() && + size.area() < MAX_CANVAS_AREA + { + size + } else { + Size2D::zero() + } +} diff --git a/components/script/devtools.rs b/components/script/devtools.rs index c9d7c3094db..93212887dc8 100644 --- a/components/script/devtools.rs +++ b/components/script/devtools.rs @@ -538,3 +538,21 @@ pub(crate) fn handle_get_css_database(reply: IpcSender, +) { + let node = node_id.and_then(|node_id| { + let node = find_node_by_unique_id(documents, id, &node_id); + if node.is_none() { + log::warn!("Node id {node_id} for pipeline id {id} is not found",); + } + node + }); + + if let Some(window) = documents.find_window(id) { + window.Document().highlight_dom_node(node.as_deref()); + } +} diff --git a/components/script/dom/attr.rs b/components/script/dom/attr.rs index 52d0ca7e20c..9f1520bd085 100644 --- a/components/script/dom/attr.rs +++ b/components/script/dom/attr.rs @@ -8,7 +8,7 @@ use std::mem; use devtools_traits::AttrInfo; use dom_struct::dom_struct; -use html5ever::{LocalName, Namespace, Prefix, ns}; +use html5ever::{LocalName, Namespace, Prefix, local_name, ns}; use style::attr::{AttrIdentifier, AttrValue}; use style::values::GenericAtomIdent; use stylo_atoms::Atom; @@ -179,7 +179,7 @@ impl Attr { assert_eq!(Some(owner), self.owner().as_deref()); owner.will_mutate_attr(self); self.swap_value(&mut value); - if *self.namespace() == ns!() { + if is_relevant_attribute(self.namespace(), self.local_name()) { vtable_for(owner.upcast()).attribute_mutated( self, AttributeMutation::Set(Some(&value)), @@ -283,3 +283,9 @@ impl<'dom> AttrHelpersForLayout<'dom> for LayoutDom<'dom, Attr> { &self.unsafe_get().identifier.namespace.0 } } + +/// A helper function to check if attribute is relevant. +pub(crate) fn is_relevant_attribute(namespace: &Namespace, local_name: &LocalName) -> bool { + // + namespace == &ns!() || (namespace == &ns!(xlink) && local_name == &local_name!("href")) +} diff --git a/components/script/dom/bindings/buffer_source.rs b/components/script/dom/bindings/buffer_source.rs index dd6984e1eab..14a71532e9d 100644 --- a/components/script/dom/bindings/buffer_source.rs +++ b/components/script/dom/bindings/buffer_source.rs @@ -37,7 +37,7 @@ use js::rust::{ #[cfg(feature = "webgpu")] use js::typedarray::{ArrayBuffer, HeapArrayBuffer}; use js::typedarray::{ - ArrayBufferU8, ArrayBufferView, ArrayBufferViewU8, CreateWith, TypedArray, TypedArrayElement, + ArrayBufferU8, ArrayBufferViewU8, CreateWith, TypedArray, TypedArrayElement, TypedArrayElementCreator, }; @@ -63,36 +63,25 @@ pub(crate) enum BufferSource { ArrayBuffer(Box>), } -pub(crate) fn new_initialized_heap_buffer_source( - init: HeapTypedArrayInit, +pub(crate) fn create_heap_buffer_source_with_length( + cx: JSContext, + len: u32, can_gc: CanGc, -) -> Result, ()> +) -> Fallible> where T: TypedArrayElement + TypedArrayElementCreator, T::Element: Clone + Copy, { - let heap_buffer_source = match init { - HeapTypedArrayInit::Buffer(buffer_source) => HeapBufferSource { - buffer_source, - phantom: PhantomData, - }, - HeapTypedArrayInit::Info { len, cx } => { - rooted!(in (*cx) let mut array = ptr::null_mut::()); - let typed_array_result = - create_buffer_source_with_length::(cx, len as usize, array.handle_mut(), can_gc); - if typed_array_result.is_err() { - return Err(()); - } + rooted!(in (*cx) let mut array = ptr::null_mut::()); + let typed_array_result = + create_buffer_source_with_length::(cx, len as usize, array.handle_mut(), can_gc); + if typed_array_result.is_err() { + return Err(Error::JSFailed); + } - HeapBufferSource::::new(BufferSource::ArrayBufferView(Heap::boxed(*array.handle()))) - }, - }; - Ok(heap_buffer_source) -} - -pub(crate) enum HeapTypedArrayInit { - Buffer(BufferSource), - Info { len: u32, cx: JSContext }, + Ok(HeapBufferSource::::new(BufferSource::ArrayBufferView( + Heap::boxed(*array.handle()), + ))) } pub(crate) struct HeapBufferSource { @@ -131,11 +120,11 @@ where } pub(crate) fn from_view( - chunk: CustomAutoRooterGuard, - ) -> HeapBufferSource { - HeapBufferSource::::new(BufferSource::ArrayBufferView(Heap::boxed( - unsafe { *chunk.underlying_object() }, - ))) + chunk: CustomAutoRooterGuard>, + ) -> HeapBufferSource { + HeapBufferSource::::new(BufferSource::ArrayBufferView(Heap::boxed(unsafe { + *chunk.underlying_object() + }))) } pub(crate) fn default() -> Self { diff --git a/components/script/dom/bindings/cell.rs b/components/script/dom/bindings/cell.rs index 6c987270911..805ab28224f 100644 --- a/components/script/dom/bindings/cell.rs +++ b/components/script/dom/bindings/cell.rs @@ -9,9 +9,8 @@ use std::cell::{BorrowError, BorrowMutError}; pub(crate) use std::cell::{Ref, RefCell, RefMut}; #[cfg(feature = "refcell_backtrace")] -pub(crate) use accountable_refcell::{Ref, RefCell, RefMut, ref_filter_map}; -#[cfg(not(feature = "refcell_backtrace"))] -pub(crate) use ref_filter_map::ref_filter_map; +pub(crate) use accountable_refcell::{Ref, RefCell, RefMut}; +use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOfOps}; use crate::dom::bindings::root::{assert_in_layout, assert_in_script}; @@ -24,6 +23,12 @@ pub(crate) struct DomRefCell { value: RefCell, } +impl MallocConditionalSizeOf for DomRefCell { + fn conditional_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.value.borrow().conditional_size_of(ops) + } +} + // Functionality specific to Servo's `DomRefCell` type // =================================================== diff --git a/components/script/dom/bindings/error.rs b/components/script/dom/bindings/error.rs index b0fd301df6a..f5bd03cd8d7 100644 --- a/components/script/dom/bindings/error.rs +++ b/components/script/dom/bindings/error.rs @@ -93,6 +93,7 @@ pub(crate) fn throw_dom_exception( Error::NotReadable => DOMErrorName::NotReadableError, Error::Data => DOMErrorName::DataError, Error::Operation => DOMErrorName::OperationError, + Error::NotAllowed => DOMErrorName::NotAllowedError, Error::Type(message) => unsafe { assert!(!JS_IsExceptionPending(*cx)); throw_type_error(*cx, &message); diff --git a/components/script/dom/bindings/serializable.rs b/components/script/dom/bindings/serializable.rs index 4aa1a94a0a4..d0e851b4799 100644 --- a/components/script/dom/bindings/serializable.rs +++ b/components/script/dom/bindings/serializable.rs @@ -11,7 +11,7 @@ use base::id::{Index, NamespaceIndex, PipelineNamespaceId}; use crate::dom::bindings::reflector::DomObject; use crate::dom::bindings::root::DomRoot; -use crate::dom::bindings::structuredclone::{StructuredData, StructuredDataReader}; +use crate::dom::bindings::structuredclone::StructuredData; use crate::dom::globalscope::GlobalScope; use crate::script_runtime::CanGc; @@ -65,13 +65,7 @@ where /// Returns the field of [StructuredDataReader]/[StructuredDataWriter] that /// should be used to read/store serialized instances of this type. - fn serialized_storage( - data: StructuredData<'_>, - ) -> &mut Option, Self::Data>>; - - /// Returns the field of [StructuredDataReader] that should be used to store - /// deserialized instances of this type. - fn deserialized_storage( - reader: &mut StructuredDataReader, - ) -> &mut Option>>; + fn serialized_storage<'a>( + data: StructuredData<'a, '_>, + ) -> &'a mut Option, Self::Data>>; } diff --git a/components/script/dom/bindings/structuredclone.rs b/components/script/dom/bindings/structuredclone.rs index 915a4951bb5..c23156817cb 100644 --- a/components/script/dom/bindings/structuredclone.rs +++ b/components/script/dom/bindings/structuredclone.rs @@ -14,15 +14,16 @@ use base::id::{ }; use constellation_traits::{ BlobImpl, DomException, DomPoint, MessagePortImpl, Serializable as SerializableInterface, - StructuredSerializedData, Transferrable as TransferrableInterface, + StructuredSerializedData, Transferrable as TransferrableInterface, TransformStreamData, }; +use js::gc::RootedVec; use js::glue::{ CopyJSStructuredCloneData, DeleteJSAutoStructuredCloneBuffer, GetLengthOfJSStructuredCloneData, NewJSAutoStructuredCloneBuffer, WriteBytesToJSStructuredCloneData, }; use js::jsapi::{ - CloneDataPolicy, HandleObject as RawHandleObject, JS_ClearPendingException, JS_ReadUint32Pair, - JS_STRUCTURED_CLONE_VERSION, JS_WriteUint32Pair, JSContext, JSObject, + CloneDataPolicy, HandleObject as RawHandleObject, Heap, JS_ClearPendingException, + JS_ReadUint32Pair, JS_STRUCTURED_CLONE_VERSION, JS_WriteUint32Pair, JSContext, JSObject, JSStructuredCloneCallbacks, JSStructuredCloneReader, JSStructuredCloneWriter, MutableHandleObject as RawMutableHandleObject, StructuredCloneScope, TransferableOwnership, }; @@ -43,7 +44,7 @@ use crate::dom::dompointreadonly::DOMPointReadOnly; use crate::dom::globalscope::GlobalScope; use crate::dom::messageport::MessagePort; use crate::dom::readablestream::ReadableStream; -use crate::dom::types::DOMException; +use crate::dom::types::{DOMException, TransformStream}; use crate::dom::writablestream::WritableStream; use crate::realms::{AlreadyInRealm, InRealm, enter_realm}; use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; @@ -64,6 +65,7 @@ pub(super) enum StructuredCloneTags { ReadableStream = 0xFFFF8006, DomException = 0xFFFF8007, WritableStream = 0xFFFF8008, + TransformStream = 0xFFFF8009, Max = 0xFFFFFFFF, } @@ -84,6 +86,7 @@ impl From for StructuredCloneTags { TransferrableInterface::MessagePort => StructuredCloneTags::MessagePort, TransferrableInterface::ReadableStream => StructuredCloneTags::ReadableStream, TransferrableInterface::WritableStream => StructuredCloneTags::WritableStream, + TransferrableInterface::TransformStream => StructuredCloneTags::TransformStream, } } } @@ -93,7 +96,7 @@ fn reader_for_type( ) -> unsafe fn( &GlobalScope, *mut JSStructuredCloneReader, - &mut StructuredDataReader, + &mut StructuredDataReader<'_>, CanGc, ) -> *mut JSObject { match val { @@ -107,7 +110,7 @@ fn reader_for_type( unsafe fn read_object( owner: &GlobalScope, r: *mut JSStructuredCloneReader, - sc_reader: &mut StructuredDataReader, + sc_reader: &mut StructuredDataReader<'_>, can_gc: CanGc, ) -> *mut JSObject { let mut name_space: u32 = 0; @@ -136,9 +139,8 @@ unsafe fn read_object( } if let Ok(obj) = T::deserialize(owner, serialized, can_gc) { - let destination = T::deserialized_storage(sc_reader).get_or_insert_with(HashMap::new); let reflector = obj.reflector().get_jsobject().get(); - destination.insert(storage_key, obj); + sc_reader.roots.push(Heap::boxed(reflector)); return reflector; } warn!("Reading structured data failed in {:?}.", owner.get_url()); @@ -191,7 +193,7 @@ unsafe extern "C" fn read_callback( "tag should be higher than StructuredCloneTags::Min" ); - let sc_reader = &mut *(closure as *mut StructuredDataReader); + let sc_reader = &mut *(closure as *mut StructuredDataReader<'_>); let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx)); let global = GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof)); for serializable in SerializableInterface::iter() { @@ -259,17 +261,19 @@ unsafe extern "C" fn write_callback( fn receiver_for_type( val: TransferrableInterface, -) -> fn(&GlobalScope, &mut StructuredDataReader, u64, RawMutableHandleObject) -> Result<(), ()> { +) -> fn(&GlobalScope, &mut StructuredDataReader<'_>, u64, RawMutableHandleObject) -> Result<(), ()> +{ match val { TransferrableInterface::MessagePort => receive_object::, TransferrableInterface::ReadableStream => receive_object::, TransferrableInterface::WritableStream => receive_object::, + TransferrableInterface::TransformStream => receive_object::, } } fn receive_object( owner: &GlobalScope, - sc_reader: &mut StructuredDataReader, + sc_reader: &mut StructuredDataReader<'_>, extra_data: u64, return_object: RawMutableHandleObject, ) -> Result<(), ()> { @@ -305,13 +309,12 @@ fn receive_object( ); }; - if let Ok(received) = T::transfer_receive(owner, id, serialized) { - return_object.set(received.reflector().rootable().get()); - let storage = T::deserialized_storage(sc_reader).get_or_insert_with(Vec::new); - storage.push(received); - return Ok(()); - } - Err(()) + let Ok(received) = T::transfer_receive(owner, id, serialized) else { + return Err(()); + }; + return_object.set(received.reflector().rootable().get()); + sc_reader.roots.push(Heap::boxed(return_object.get())); + Ok(()) } unsafe extern "C" fn read_transfer_callback( @@ -324,7 +327,7 @@ unsafe extern "C" fn read_transfer_callback( closure: *mut raw::c_void, return_object: RawMutableHandleObject, ) -> bool { - let sc_reader = &mut *(closure as *mut StructuredDataReader); + let sc_reader = &mut *(closure as *mut StructuredDataReader<'_>); let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx)); let owner = GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof)); @@ -390,6 +393,7 @@ fn transfer_for_type(val: TransferrableInterface) -> TransferOperation { TransferrableInterface::MessagePort => try_transfer::, TransferrableInterface::ReadableStream => try_transfer::, TransferrableInterface::WritableStream => try_transfer::, + TransferrableInterface::TransformStream => try_transfer::, } } @@ -438,6 +442,7 @@ unsafe fn can_transfer_for_type( TransferrableInterface::MessagePort => can_transfer::(obj, cx), TransferrableInterface::ReadableStream => can_transfer::(obj, cx), TransferrableInterface::WritableStream => can_transfer::(obj, cx), + TransferrableInterface::TransformStream => can_transfer::(obj, cx), } } @@ -489,8 +494,8 @@ static STRUCTURED_CLONE_CALLBACKS: JSStructuredCloneCallbacks = JSStructuredClon sabCloned: Some(sab_cloned_callback), }; -pub(crate) enum StructuredData<'a> { - Reader(&'a mut StructuredDataReader), +pub(crate) enum StructuredData<'a, 'b> { + Reader(&'a mut StructuredDataReader<'b>), Writer(&'a mut StructuredDataWriter), } @@ -503,23 +508,17 @@ pub(crate) struct DOMErrorRecord { /// Reader and writer structs for results from, and inputs to, structured-data read/write operations. /// #[repr(C)] -pub(crate) struct StructuredDataReader { +pub(crate) struct StructuredDataReader<'a> { /// A struct of error message. - pub(crate) errors: DOMErrorRecord, - /// A map of deserialized blobs, stored temporarily here to keep them rooted. - pub(crate) blobs: Option>>, - /// A map of deserialized points, stored temporarily here to keep them rooted. - pub(crate) points_read_only: Option>>, - pub(crate) dom_points: Option>>, - /// A map of deserialized exceptions, stored temporarily here to keep them rooted. - pub(crate) dom_exceptions: Option>>, - /// A vec of transfer-received DOM ports, - /// to be made available to script through a message event. - pub(crate) message_ports: Option>>, + errors: DOMErrorRecord, + /// Rooted copies of every deserialized object to ensure they are not garbage collected. + roots: RootedVec<'a, Box>>, /// A map of port implementations, /// used as part of the "transfer-receiving" steps of ports, /// to produce the DOM ports stored in `message_ports` above. pub(crate) port_impls: Option>, + /// A map of transform stream implementations, + pub(crate) transform_streams_port_impls: Option>, /// A map of blob implementations, /// used as part of the "deserialize" steps of blobs, /// to produce the DOM blobs stored in `blobs` above. @@ -528,12 +527,6 @@ pub(crate) struct StructuredDataReader { pub(crate) points: Option>, /// A map of serialized exceptions. pub(crate) exceptions: Option>, - - /// - pub(crate) readable_streams: Option>>, - - /// - pub(crate) writable_streams: Option>>, } /// A data holder for transferred and serialized objects. @@ -544,6 +537,8 @@ pub(crate) struct StructuredDataWriter { pub(crate) errors: DOMErrorRecord, /// Transferred ports. pub(crate) ports: Option>, + /// Transferred transform streams. + pub(crate) transform_streams_port: Option>, /// Serialized points. pub(crate) points: Option>, /// Serialized exceptions. @@ -600,6 +595,7 @@ pub(crate) fn write( let data = StructuredSerializedData { serialized: data, ports: sc_writer.ports.take(), + transform_streams: sc_writer.transform_streams_port.take(), points: sc_writer.points.take(), exceptions: sc_writer.exceptions.take(), blobs: sc_writer.blobs.take(), @@ -618,19 +614,15 @@ pub(crate) fn read( ) -> Fallible>> { let cx = GlobalScope::get_cx(); let _ac = enter_realm(global); + rooted_vec!(let mut roots); let mut sc_reader = StructuredDataReader { - blobs: None, - message_ports: None, - points_read_only: None, - dom_points: None, - dom_exceptions: None, + roots, port_impls: data.ports.take(), + transform_streams_port_impls: data.transform_streams.take(), blob_impls: data.blobs.take(), points: data.points.take(), exceptions: data.exceptions.take(), errors: DOMErrorRecord { message: None }, - readable_streams: None, - writable_streams: None, }; let sc_reader_ptr = &mut sc_reader as *mut _; unsafe { @@ -666,8 +658,15 @@ pub(crate) fn read( DeleteJSAutoStructuredCloneBuffer(scbuf); + let mut message_ports = vec![]; + for reflector in sc_reader.roots.iter() { + let Ok(message_port) = root_from_object::(reflector.get(), *cx) else { + continue; + }; + message_ports.push(message_port); + } // Any transfer-received port-impls should have been taken out. assert!(sc_reader.port_impls.is_none()); - Ok(sc_reader.message_ports.take().unwrap_or_default()) + Ok(message_ports) } } diff --git a/components/script/dom/bindings/transferable.rs b/components/script/dom/bindings/transferable.rs index b720c05ae37..e6b2f000f3a 100644 --- a/components/script/dom/bindings/transferable.rs +++ b/components/script/dom/bindings/transferable.rs @@ -12,7 +12,7 @@ use base::id::NamespaceIndex; use crate::dom::bindings::reflector::DomObject; use crate::dom::bindings::root::DomRoot; -use crate::dom::bindings::structuredclone::{StructuredData, StructuredDataReader}; +use crate::dom::bindings::structuredclone::StructuredData; use crate::dom::globalscope::GlobalScope; pub(crate) trait Transferable: DomObject where @@ -32,8 +32,7 @@ where serialized: Self::Data, ) -> Result, ()>; - fn serialized_storage( - data: StructuredData<'_>, - ) -> &mut Option, Self::Data>>; - fn deserialized_storage(reader: &mut StructuredDataReader) -> &mut Option>>; + fn serialized_storage<'a>( + data: StructuredData<'a, '_>, + ) -> &'a mut Option, Self::Data>>; } diff --git a/components/script/dom/blob.rs b/components/script/dom/blob.rs index df2afafd698..18e968aaa70 100644 --- a/components/script/dom/blob.rs +++ b/components/script/dom/blob.rs @@ -7,16 +7,15 @@ use std::ptr; use std::rc::Rc; use base::id::{BlobId, BlobIndex}; -use constellation_traits::BlobImpl; +use constellation_traits::{BlobData, BlobImpl}; use dom_struct::dom_struct; use encoding_rs::UTF_8; use js::jsapi::JSObject; use js::rust::HandleObject; -use js::typedarray::Uint8; +use js::typedarray::{ArrayBufferU8, Uint8}; use net_traits::filemanager_thread::RelativePos; use uuid::Uuid; -use crate::body::{FetchedData, run_array_buffer_data_algorithm}; use crate::dom::bindings::buffer_source::create_buffer_source; use crate::dom::bindings::codegen::Bindings::BlobBinding; use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods; @@ -24,16 +23,16 @@ use crate::dom::bindings::codegen::UnionTypes::ArrayBufferOrArrayBufferViewOrBlo use crate::dom::bindings::error::{Error, Fallible}; use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto}; use crate::dom::bindings::root::DomRoot; -use crate::dom::bindings::serializable::{Serializable, StorageKey}; +use crate::dom::bindings::serializable::Serializable; use crate::dom::bindings::str::DOMString; -use crate::dom::bindings::structuredclone::{StructuredData, StructuredDataReader}; +use crate::dom::bindings::structuredclone::StructuredData; use crate::dom::globalscope::GlobalScope; use crate::dom::promise::Promise; use crate::dom::readablestream::ReadableStream; use crate::realms::{AlreadyInRealm, InRealm}; use crate::script_runtime::CanGc; -// https://w3c.github.io/FileAPI/#blob +/// #[dom_struct] pub(crate) struct Blob { reflector_: Reflector, @@ -119,18 +118,14 @@ impl Serializable for Blob { Ok(deserialized_blob) } - fn serialized_storage(reader: StructuredData<'_>) -> &mut Option> { + fn serialized_storage<'a>( + reader: StructuredData<'a, '_>, + ) -> &'a mut Option> { match reader { StructuredData::Reader(r) => &mut r.blob_impls, StructuredData::Writer(w) => &mut w.blobs, } } - - fn deserialized_storage( - data: &mut StructuredDataReader, - ) -> &mut Option>> { - &mut data.blobs - } } /// Extract bytes from BlobParts, used by Blob and File constructor @@ -202,7 +197,7 @@ impl BlobMethods for Blob { self.get_stream(can_gc) } - // https://w3c.github.io/FileAPI/#slice-method-algo + /// fn Slice( &self, start: Option, @@ -210,14 +205,27 @@ impl BlobMethods for Blob { content_type: Option, can_gc: CanGc, ) -> DomRoot { - let type_string = - normalize_type_string(content_type.unwrap_or(DOMString::from("")).as_ref()); - let rel_pos = RelativePos::from_opts(start, end); - let blob_impl = BlobImpl::new_sliced(rel_pos, self.blob_id, type_string); - Blob::new(&self.global(), blob_impl, can_gc) + let global = self.global(); + let type_string = normalize_type_string(&content_type.unwrap_or_default()); + + // If our parent is already a sliced blob then we reference the data from the grandparent instead, + // to keep the blob ancestry chain short. + let (parent, range) = match *global.get_blob_data(&self.blob_id) { + BlobData::Sliced(grandparent, parent_range) => { + let range = RelativePos { + start: parent_range.start + start.unwrap_or_default(), + end: end.map(|end| end + parent_range.start).or(parent_range.end), + }; + (grandparent, range) + }, + _ => (self.blob_id, RelativePos::from_opts(start, end)), + }; + + let blob_impl = BlobImpl::new_sliced(range, parent, type_string); + Blob::new(&global, blob_impl, can_gc) } - // https://w3c.github.io/FileAPI/#text-method-algo + /// fn Text(&self, can_gc: CanGc) -> Rc { let global = self.global(); let in_realm_proof = AlreadyInRealm::assert::(); @@ -241,35 +249,51 @@ impl BlobMethods for Blob { } // https://w3c.github.io/FileAPI/#arraybuffer-method-algo - fn ArrayBuffer(&self, can_gc: CanGc) -> Rc { - let global = self.global(); - let in_realm_proof = AlreadyInRealm::assert::(); - let p = Promise::new_in_current_realm(InRealm::Already(&in_realm_proof), can_gc); + fn ArrayBuffer(&self, in_realm: InRealm, can_gc: CanGc) -> Rc { + let cx = GlobalScope::get_cx(); + let global = GlobalScope::from_safe_context(cx, in_realm); + let promise = Promise::new_in_current_realm(in_realm, can_gc); - let id = self.get_blob_url_id(); + // 1. Let stream be the result of calling get stream on this. + let stream = self.get_stream(can_gc); - global.read_file_async( - id, - p.clone(), - Box::new(|promise, bytes| { - match bytes { - Ok(b) => { - let cx = GlobalScope::get_cx(); - let result = run_array_buffer_data_algorithm(cx, b, CanGc::note()); + // 2. Let reader be the result of getting a reader from stream. + // If that threw an exception, return a new promise rejected with that exception. + let reader = match stream.and_then(|s| s.acquire_default_reader(can_gc)) { + Ok(reader) => reader, + Err(error) => { + promise.reject_error(error, can_gc); + return promise; + }, + }; - match result { - Ok(FetchedData::ArrayBuffer(a)) => { - promise.resolve_native(&a, CanGc::note()) - }, - Err(e) => promise.reject_error(e, CanGc::note()), - _ => panic!("Unexpected result from run_array_buffer_data_algorithm"), - } - }, - Err(e) => promise.reject_error(e, CanGc::note()), - }; + // 3. Let promise be the result of reading all bytes from stream with reader. + let success_promise = promise.clone(); + let failure_promise = promise.clone(); + reader.read_all_bytes( + cx, + &global, + Rc::new(move |bytes| { + rooted!(in(*cx) let mut js_object = ptr::null_mut::()); + // 4. Return the result of transforming promise by a fulfillment handler that returns a new + // [ArrayBuffer] + let array_buffer = create_buffer_source::( + cx, + bytes, + js_object.handle_mut(), + can_gc, + ) + .expect("Converting input to ArrayBufferU8 should never fail"); + success_promise.resolve_native(&array_buffer, can_gc); }), + Rc::new(move |cx, value| { + failure_promise.reject(cx, value, can_gc); + }), + in_realm, + can_gc, ); - p + + promise } /// diff --git a/components/script/dom/canvasrenderingcontext2d.rs b/components/script/dom/canvasrenderingcontext2d.rs index f6bf432de69..046d478e49d 100644 --- a/components/script/dom/canvasrenderingcontext2d.rs +++ b/components/script/dom/canvasrenderingcontext2d.rs @@ -4,12 +4,12 @@ use canvas_traits::canvas::{Canvas2dMsg, CanvasId, CanvasMsg, FromScriptMsg}; use dom_struct::dom_struct; -use euclid::default::{Point2D, Rect, Size2D}; -use ipc_channel::ipc::IpcSharedMemory; +use euclid::default::Size2D; use profile_traits::ipc; use script_bindings::inheritance::Castable; -use script_layout_interface::HTMLCanvasDataSource; use servo_url::ServoUrl; +use snapshot::Snapshot; +use webrender_api::ImageKey; use crate::canvas_context::{CanvasContext, CanvasHelpers, LayoutCanvasRenderingContextHelpers}; use crate::canvas_state::CanvasState; @@ -74,23 +74,12 @@ impl CanvasRenderingContext2D { reflect_dom_object(boxed, global, can_gc) } - // https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions - pub(crate) fn set_bitmap_dimensions(&self, size: Size2D) { - self.reset_to_initial_state(); - self.canvas_state - .get_ipc_renderer() - .send(CanvasMsg::Recreate( - Some(size.to_u64()), - self.canvas_state.get_canvas_id(), - )) - .unwrap(); - } - // https://html.spec.whatwg.org/multipage/#reset-the-rendering-context-to-its-default-state fn reset_to_initial_state(&self) { self.canvas_state.reset_to_initial_state(); } + /// pub(crate) fn set_canvas_bitmap_dimensions(&self, size: Size2D) { self.canvas_state.set_bitmap_dimensions(size); } @@ -106,20 +95,17 @@ impl CanvasRenderingContext2D { pub(crate) fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) { self.canvas_state.send_canvas_2d_msg(msg) } - - pub(crate) fn get_rect(&self, rect: Rect) -> Vec { - let rect = Rect::new( - Point2D::new(rect.origin.x as u64, rect.origin.y as u64), - Size2D::new(rect.size.width as u64, rect.size.height as u64), - ); - self.canvas_state.get_rect(self.canvas.size(), rect) - } } impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, CanvasRenderingContext2D> { - fn canvas_data_source(self) -> HTMLCanvasDataSource { + fn canvas_data_source(self) -> Option { let canvas_state = &self.unsafe_get().canvas_state; - HTMLCanvasDataSource::Image(canvas_state.image_key()) + + if canvas_state.is_paintable() { + Some(canvas_state.image_key()) + } else { + None + } } } @@ -139,19 +125,19 @@ impl CanvasContext for CanvasRenderingContext2D { } fn resize(&self) { - self.set_bitmap_dimensions(self.size().cast()) + self.set_canvas_bitmap_dimensions(self.size().cast()) } - fn get_image_data_as_shared_memory(&self) -> Option { + fn get_image_data(&self) -> Option { + if !self.canvas_state.is_paintable() { + return None; + } + let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap(); let msg = CanvasMsg::FromScript(FromScriptMsg::SendPixels(sender), self.get_canvas_id()); self.canvas_state.get_ipc_renderer().send(msg).unwrap(); - Some(receiver.recv().unwrap()) - } - - fn get_image_data(&self) -> Option> { - Some(self.get_rect(Rect::from_size(self.size().cast()))) + Some(receiver.recv().unwrap().to_owned()) } fn origin_is_clean(&self) -> bool { diff --git a/components/script/dom/clipboard.rs b/components/script/dom/clipboard.rs index 94c8b3c0f19..25878a1b29b 100644 --- a/components/script/dom/clipboard.rs +++ b/components/script/dom/clipboard.rs @@ -3,24 +3,75 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::rc::Rc; +use std::str::FromStr; use constellation_traits::BlobImpl; +use data_url::mime::Mime; use dom_struct::dom_struct; use embedder_traits::EmbedderMsg; +use js::rust::HandleValue as SafeHandleValue; use crate::dom::bindings::codegen::Bindings::ClipboardBinding::{ ClipboardMethods, PresentationStyle, }; +use crate::dom::bindings::error::Error; use crate::dom::bindings::refcounted::TrustedPromise; use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object}; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; use crate::dom::blob::Blob; +use crate::dom::clipboarditem::Representation; use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; use crate::dom::promise::Promise; +use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler}; use crate::dom::window::Window; -use crate::script_runtime::CanGc; +use crate::realms::{InRealm, enter_realm}; +use crate::routed_promise::{RoutedPromiseListener, route_promise}; +use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; + +/// The fulfillment handler for the reacting to representationDataPromise part of +/// . +#[derive(Clone, JSTraceable, MallocSizeOf)] +struct RepresentationDataPromiseFulfillmentHandler { + #[ignore_malloc_size_of = "Rc are hard"] + promise: Rc, +} + +impl Callback for RepresentationDataPromiseFulfillmentHandler { + /// The fulfillment case of Step 3.4.1.1.4.3 of + /// . + fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // If v is a DOMString, then follow the below steps: + // Resolve p with v. + // Return p. + self.promise.resolve(cx, v, can_gc); + + // NOTE: Since we ask text from arboard, v can't be a Blob + // If v is a Blob, then follow the below steps: + // Let string be the result of UTF-8 decoding v’s underlying byte sequence. + // Resolve p with string. + // Return p. + } +} + +/// The rejection handler for the reacting to representationDataPromise part of +/// . +#[derive(Clone, JSTraceable, MallocSizeOf)] +struct RepresentationDataPromiseRejectionHandler { + #[ignore_malloc_size_of = "Rc are hard"] + promise: Rc, +} + +impl Callback for RepresentationDataPromiseRejectionHandler { + /// The rejection case of Step 3.4.1.1.4.3 of + /// . + fn callback(&self, _cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // Reject p with "NotFoundError" DOMException in realm. + // Return p. + self.promise.reject_error(Error::NotFound, can_gc); + } +} #[dom_struct] pub(crate) struct Clipboard { @@ -40,6 +91,34 @@ impl Clipboard { } impl ClipboardMethods for Clipboard { + /// + fn ReadText(&self, can_gc: CanGc) -> Rc { + // Step 1 Let realm be this's relevant realm. + let global = self.global(); + + // Step 2 Let p be a new promise in realm. + let p = Promise::new(&global, can_gc); + + // Step 3 Run the following steps in parallel: + + // TODO Step 3.1 Let r be the result of running check clipboard read permission. + // Step 3.2 If r is false, then: + // Step 3.2.1 Queue a global task on the permission task source, given realm’s global object, + // to reject p with "NotAllowedError" DOMException in realm. + // Step 3.2.2 Abort these steps. + + // Step 3.3 Let data be a copy of the system clipboard data. + let window = global.as_window(); + let sender = route_promise(&p, self, global.task_manager().clipboard_task_source()); + window.send_to_embedder(EmbedderMsg::GetClipboardText(window.webview_id(), sender)); + + // Step 3.4 Queue a global task on the clipboard task source, + // given realm’s global object, to perform the below steps: + // NOTE: We queue the task inside route_promise and perform the steps inside handle_response + + p + } + /// fn WriteText(&self, data: DOMString, can_gc: CanGc) -> Rc { // Step 1 Let realm be this's relevant realm. @@ -95,6 +174,71 @@ impl ClipboardMethods for Clipboard { } } +impl RoutedPromiseListener> for Clipboard { + fn handle_response( + &self, + response: Result, + promise: &Rc, + can_gc: CanGc, + ) { + let global = self.global(); + let text = response.unwrap_or_default(); + + // Step 3.4.1 For each systemClipboardItem in data: + // Step 3.4.1.1 For each systemClipboardRepresentation in systemClipboardItem: + // TODO: Arboard provide the first item that has a String representation + + // Step 3.4.1.1.1 Let mimeType be the result of running the + // well-known mime type from os specific format algorithm given systemClipboardRepresentation’s name. + // Note: This is done by arboard, so we just convert the format to a MIME + let mime_type = Mime::from_str("text/plain").unwrap(); + + // Step 3.4.1.1.2 If mimeType is null, continue this loop. + // Note: Since the previous step is infallible, we don't need to handle this case + + // Step 3.4.1.1.3 Let representation be a new representation. + let representation = Representation { + mime_type, + is_custom: false, + data: Promise::new_resolved( + &global, + GlobalScope::get_cx(), + DOMString::from(text), + can_gc, + ), + }; + + // Step 3.4.1.1.4 If representation’s MIME type essence is "text/plain", then: + + // Step 3.4.1.1.4.1 Set representation’s MIME type to mimeType. + // Note: Done when creating a new representation + + // Step 3.4.1.1.4.2 Let representationDataPromise be the representation’s data. + // Step 3.4.1.1.4.3 React to representationDataPromise: + let fulfillment_handler = Box::new(RepresentationDataPromiseFulfillmentHandler { + promise: promise.clone(), + }); + let rejection_handler = Box::new(RepresentationDataPromiseRejectionHandler { + promise: promise.clone(), + }); + let handler = PromiseNativeHandler::new( + &global, + Some(fulfillment_handler), + Some(rejection_handler), + can_gc, + ); + let realm = enter_realm(&*global); + let comp = InRealm::Entered(&realm); + representation + .data + .append_native_handler(&handler, comp, can_gc); + + // Step 3.4.2 Reject p with "NotFoundError" DOMException in realm. + // Step 3.4.3 Return p. + // NOTE: We follow the same behaviour of Gecko by doing nothing if no text is available instead of rejecting p + } +} + /// fn write_blobs_and_option_to_the_clipboard( window: &Window, diff --git a/components/script/dom/clipboarditem.rs b/components/script/dom/clipboarditem.rs index c1c66a403b3..129beb686c1 100644 --- a/components/script/dom/clipboarditem.rs +++ b/components/script/dom/clipboarditem.rs @@ -29,13 +29,13 @@ const CUSTOM_FORMAT_PREFIX: &str = "web "; /// #[derive(JSTraceable, MallocSizeOf)] -struct Representation { +pub(super) struct Representation { #[no_trace] #[ignore_malloc_size_of = "Extern type"] - mime_type: Mime, - is_custom: bool, + pub mime_type: Mime, + pub is_custom: bool, #[ignore_malloc_size_of = "Rc is hard"] - data: Rc, + pub data: Rc, } #[dom_struct] diff --git a/components/script/dom/create.rs b/components/script/dom/create.rs index 5722dc4f6ac..2e7c4cf8def 100644 --- a/components/script/dom/create.rs +++ b/components/script/dom/create.rs @@ -85,6 +85,7 @@ use crate::dom::htmlulistelement::HTMLUListElement; use crate::dom::htmlunknownelement::HTMLUnknownElement; use crate::dom::htmlvideoelement::HTMLVideoElement; use crate::dom::svgelement::SVGElement; +use crate::dom::svgimageelement::SVGImageElement; use crate::dom::svgsvgelement::SVGSVGElement; use crate::realms::{InRealm, enter_realm}; use crate::script_runtime::CanGc; @@ -114,6 +115,7 @@ fn create_svg_element( } match name.local { + local_name!("image") => make!(SVGImageElement), local_name!("svg") => make!(SVGSVGElement), _ => make!(SVGElement), } diff --git a/components/script/dom/cssstylesheet.rs b/components/script/dom/cssstylesheet.rs index 421e8f45523..a367c9943de 100644 --- a/components/script/dom/cssstylesheet.rs +++ b/components/script/dom/cssstylesheet.rs @@ -24,7 +24,7 @@ use crate::dom::bindings::reflector::{ DomGlobal, reflect_dom_object, reflect_dom_object_with_proto, }; use crate::dom::bindings::root::{DomRoot, MutNullableDom}; -use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::cssrulelist::{CSSRuleList, RulesSource}; use crate::dom::element::Element; use crate::dom::medialist::MediaList; @@ -290,4 +290,32 @@ impl CSSStyleSheetMethods for CSSStyleSheet { // > 8. Return -1. Ok(-1) } + + /// + fn ReplaceSync(&self, text: USVString) -> Result<(), Error> { + // Step 1. If the constructed flag is not set throw a NotAllowedError + if !self.is_constructed { + return Err(Error::NotAllowed); + } + + // Step 2. Let rules be the result of running parse a stylesheet’s contents from text. + let global = self.global(); + let window = global.as_window(); + + StyleStyleSheet::update_from_str( + &self.style_stylesheet, + &text, + UrlExtraData(window.get_url().get_arc()), + None, + window.css_error_reporter(), + AllowImportRules::No, // Step 3.If rules contains one or more @import rules, remove those rules from rules. + ); + + // Step 4. Set sheet’s CSS rules to rules. + // We reset our rule list, which will be initialized properly + // at the next getter access. + self.rulelist.set(None); + + Ok(()) + } } diff --git a/components/script/dom/defaultteereadrequest.rs b/components/script/dom/defaultteereadrequest.rs index debc084e068..94e285da72b 100644 --- a/components/script/dom/defaultteereadrequest.rs +++ b/components/script/dom/defaultteereadrequest.rs @@ -21,7 +21,7 @@ use crate::dom::globalscope::GlobalScope; use crate::dom::promise::Promise; use crate::dom::readablestream::ReadableStream; use crate::microtask::Microtask; -use crate::script_runtime::CanGc; +use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; #[derive(JSTraceable, MallocSizeOf)] #[cfg_attr(crown, allow(crown::unrooted_must_root))] @@ -32,8 +32,8 @@ pub(crate) struct DefaultTeeReadRequestMicrotask { } impl DefaultTeeReadRequestMicrotask { - pub(crate) fn microtask_chunk_steps(&self, can_gc: CanGc) { - self.tee_read_request.chunk_steps(&self.chunk, can_gc) + pub(crate) fn microtask_chunk_steps(&self, cx: SafeJSContext, can_gc: CanGc) { + self.tee_read_request.chunk_steps(cx, &self.chunk, can_gc) } } @@ -94,8 +94,14 @@ impl DefaultTeeReadRequest { } /// Call into cancel of the stream, /// - pub(crate) fn stream_cancel(&self, reason: SafeHandleValue, can_gc: CanGc) { - self.stream.cancel(reason, can_gc); + pub(crate) fn stream_cancel( + &self, + cx: SafeJSContext, + global: &GlobalScope, + reason: SafeHandleValue, + can_gc: CanGc, + ) { + self.stream.cancel(cx, global, reason, can_gc); } /// Enqueue a microtask to perform the chunk steps /// @@ -115,13 +121,13 @@ impl DefaultTeeReadRequest { } /// #[allow(clippy::borrowed_box)] - pub(crate) fn chunk_steps(&self, chunk: &Box>, can_gc: CanGc) { + pub(crate) fn chunk_steps(&self, cx: SafeJSContext, chunk: &Box>, can_gc: CanGc) { + let global = &self.stream.global(); // Set readAgain to false. self.read_again.set(false); // Let chunk1 and chunk2 be chunk. let chunk1 = chunk; let chunk2 = chunk; - let cx = GlobalScope::get_cx(); rooted!(in(*cx) let chunk1_value = chunk1.get()); rooted!(in(*cx) let chunk2_value = chunk2.get()); @@ -131,9 +137,7 @@ impl DefaultTeeReadRequest { rooted!(in(*cx) let mut clone_result = UndefinedValue()); let data = structuredclone::write(cx, chunk2_value.handle(), None).unwrap(); // If cloneResult is an abrupt completion, - if structuredclone::read(&self.stream.global(), data, clone_result.handle_mut()) - .is_err() - { + if structuredclone::read(global, data, clone_result.handle_mut()).is_err() { // Perform ! ReadableStreamDefaultControllerError(branch_1.[[controller]], cloneResult.[[Value]]). self.readable_stream_default_controller_error( &self.branch_1, @@ -148,7 +152,7 @@ impl DefaultTeeReadRequest { can_gc, ); // Resolve cancelPromise with ! ReadableStreamCancel(stream, cloneResult.[[Value]]). - self.stream_cancel(clone_result.handle(), can_gc); + self.stream_cancel(cx, global, clone_result.handle(), can_gc); // Return. return; } else { diff --git a/components/script/dom/defaultteeunderlyingsource.rs b/components/script/dom/defaultteeunderlyingsource.rs index 5895297d982..7935c388842 100644 --- a/components/script/dom/defaultteeunderlyingsource.rs +++ b/components/script/dom/defaultteeunderlyingsource.rs @@ -19,7 +19,7 @@ use crate::dom::defaultteereadrequest::DefaultTeeReadRequest; use crate::dom::globalscope::GlobalScope; use crate::dom::promise::Promise; use crate::dom::readablestreamdefaultreader::ReadRequest; -use crate::script_runtime::CanGc; +use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; #[derive(JSTraceable, MallocSizeOf)] pub(crate) enum TeeCancelAlgorithm { @@ -156,6 +156,8 @@ impl DefaultTeeUnderlyingSource { #[allow(unsafe_code)] pub(crate) fn cancel_algorithm( &self, + cx: SafeJSContext, + global: &GlobalScope, reason: SafeHandleValue, can_gc: CanGc, ) -> Option, Error>> { @@ -169,7 +171,7 @@ impl DefaultTeeUnderlyingSource { // If canceled_2 is true, if self.canceled_2.get() { - self.resolve_cancel_promise(can_gc); + self.resolve_cancel_promise(cx, global, can_gc); } // Return cancelPromise. Some(Ok(self.cancel_promise.clone())) @@ -183,7 +185,7 @@ impl DefaultTeeUnderlyingSource { // If canceled_1 is true, if self.canceled_1.get() { - self.resolve_cancel_promise(can_gc); + self.resolve_cancel_promise(cx, global, can_gc); } // Return cancelPromise. Some(Ok(self.cancel_promise.clone())) @@ -192,9 +194,8 @@ impl DefaultTeeUnderlyingSource { } #[allow(unsafe_code)] - fn resolve_cancel_promise(&self, can_gc: CanGc) { + fn resolve_cancel_promise(&self, cx: SafeJSContext, global: &GlobalScope, can_gc: CanGc) { // Let compositeReason be ! CreateArrayFromList(« reason_1, reason_2 »). - let cx = GlobalScope::get_cx(); rooted_vec!(let mut reasons_values); reasons_values.push(self.reason_1.get()); reasons_values.push(self.reason_2.get()); @@ -204,7 +205,9 @@ impl DefaultTeeUnderlyingSource { rooted!(in(*cx) let reasons_value = ObjectValue(reasons.get())); // Let cancelResult be ! ReadableStreamCancel(stream, compositeReason). - let cancel_result = self.stream.cancel(reasons_value.handle(), can_gc); + let cancel_result = self + .stream + .cancel(cx, global, reasons_value.handle(), can_gc); // Resolve cancelPromise with cancelResult. self.cancel_promise.resolve_native(&cancel_result, can_gc); diff --git a/components/script/dom/dissimilaroriginwindow.rs b/components/script/dom/dissimilaroriginwindow.rs index b7fbe0855fe..70c384db822 100644 --- a/components/script/dom/dissimilaroriginwindow.rs +++ b/components/script/dom/dissimilaroriginwindow.rs @@ -181,12 +181,13 @@ impl DissimilarOriginWindowMethods for DissimilarOriginWin // https://html.spec.whatwg.org/multipage/#dom-window-blur fn Blur(&self) { - // TODO: Implement x-origin blur + // > User agents are encouraged to ignore calls to this `blur()` method + // > entirely. } - // https://html.spec.whatwg.org/multipage/#dom-focus + // https://html.spec.whatwg.org/multipage/#dom-window-focus fn Focus(&self) { - // TODO: Implement x-origin focus + self.window_proxy().focus(); } // https://html.spec.whatwg.org/multipage/#dom-location diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 852a12fc7c5..6056f1f1e5a 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -21,9 +21,7 @@ use base::id::WebViewId; use canvas_traits::canvas::CanvasId; use canvas_traits::webgl::{self, WebGLContextId, WebGLMsg}; use chrono::Local; -use constellation_traits::{ - AnimationTickType, NavigationHistoryBehavior, ScriptToConstellationMessage, -}; +use constellation_traits::{NavigationHistoryBehavior, ScriptToConstellationMessage}; use content_security_policy::{self as csp, CspList, PolicyDisposition}; use cookie::Cookie; use cssparser::match_ignore_ascii_case; @@ -32,8 +30,8 @@ use devtools_traits::ScriptToDevtoolsControlMsg; use dom_struct::dom_struct; use embedder_traits::{ AllowOrDeny, AnimationState, CompositorHitTestResult, ContextMenuResult, EditingActionEvent, - EmbedderMsg, ImeEvent, InputEvent, LoadStatus, MouseButton, MouseButtonAction, - MouseButtonEvent, TouchEvent, TouchEventType, TouchId, WheelEvent, + EmbedderMsg, FocusSequenceNumber, ImeEvent, InputEvent, LoadStatus, MouseButton, + MouseButtonAction, MouseButtonEvent, TouchEvent, TouchEventType, TouchId, WheelEvent, }; use encoding_rs::{Encoding, UTF_8}; use euclid::default::{Point2D, Rect, Size2D}; @@ -105,7 +103,9 @@ use crate::dom::bindings::codegen::Bindings::WindowBinding::{ }; use crate::dom::bindings::codegen::Bindings::XPathEvaluatorBinding::XPathEvaluatorMethods; use crate::dom::bindings::codegen::Bindings::XPathNSResolverBinding::XPathNSResolver; -use crate::dom::bindings::codegen::UnionTypes::{NodeOrString, StringOrElementCreationOptions}; +use crate::dom::bindings::codegen::UnionTypes::{ + NodeOrString, StringOrElementCreationOptions, TrustedHTMLOrString, +}; use crate::dom::bindings::error::{Error, ErrorInfo, ErrorResult, Fallible}; use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; use crate::dom::bindings::num::Finite; @@ -186,6 +186,7 @@ use crate::dom::touch::Touch; use crate::dom::touchevent::TouchEvent as DomTouchEvent; use crate::dom::touchlist::TouchList; use crate::dom::treewalker::TreeWalker; +use crate::dom::trustedhtml::TrustedHTML; use crate::dom::types::VisibilityStateEntry; use crate::dom::uievent::UIEvent; use crate::dom::virtualmethods::vtable_for; @@ -272,12 +273,11 @@ pub(crate) enum IsHTMLDocument { #[derive(JSTraceable, MallocSizeOf)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] -enum FocusTransaction { - /// No focus operation is in effect. - NotInTransaction, - /// A focus operation is in effect. - /// Contains the element that has most recently requested focus for itself. - InTransaction(Option>), +struct FocusTransaction { + /// The focused element of this document. + element: Option>, + /// See [`Document::has_focus`]. + has_focus: bool, } /// Information about a declarative refresh @@ -343,9 +343,16 @@ pub(crate) struct Document { /// Whether the DOMContentLoaded event has already been dispatched. domcontentloaded_dispatched: Cell, /// The state of this document's focus transaction. - focus_transaction: DomRefCell, + focus_transaction: DomRefCell>, /// The element that currently has the document focus context. focused: MutNullableDom, + /// The last sequence number sent to the constellation. + #[no_trace] + focus_sequence: Cell, + /// Indicates whether the container is included in the top-level browsing + /// context's focus chain (not considering system focus). Permanently `true` + /// for a top-level document. + has_focus: Cell, /// The script element that is currently executing. current_script: MutNullableDom, /// @@ -516,10 +523,6 @@ pub(crate) struct Document { pending_input_events: DomRefCell>, /// The index of the last mouse move event in the pending compositor events queue. mouse_move_event_index: DomRefCell>, - /// Pending animation ticks, to be handled at the next rendering opportunity. - #[no_trace] - #[ignore_malloc_size_of = "AnimationTickType contains data from an outside crate"] - pending_animation_ticks: DomRefCell, /// /// /// Note: we are storing, but never removing, resize observers. @@ -562,6 +565,8 @@ pub(crate) struct Document { /// The active keyboard modifiers for the WebView. This is updated when receiving any input event. #[no_trace] active_keyboard_modifiers: Cell, + /// The node that is currently highlighted by the devtools + highlighted_dom_node: MutNullableDom, } #[allow(non_snake_case)] @@ -1114,128 +1119,330 @@ impl Document { self.scripting_enabled } + /// Return whether scripting is enabled or not + /// + pub(crate) fn scripting_enabled(&self) -> bool { + self.has_browsing_context() + } + /// Return the element that currently has focus. // https://w3c.github.io/uievents/#events-focusevent-doc-focus pub(crate) fn get_focused_element(&self) -> Option> { self.focused.get() } + /// Get the last sequence number sent to the constellation. + /// + /// Received focus-related messages with sequence numbers less than the one + /// returned by this method must be discarded. + pub fn get_focus_sequence(&self) -> FocusSequenceNumber { + self.focus_sequence.get() + } + + /// Generate the next sequence number for focus-related messages. + fn increment_fetch_focus_sequence(&self) -> FocusSequenceNumber { + self.focus_sequence.set(FocusSequenceNumber( + self.focus_sequence + .get() + .0 + .checked_add(1) + .expect("too many focus messages have been sent"), + )); + self.focus_sequence.get() + } + /// Initiate a new round of checking for elements requesting focus. The last element to call /// `request_focus` before `commit_focus_transaction` is called will receive focus. fn begin_focus_transaction(&self) { - *self.focus_transaction.borrow_mut() = FocusTransaction::InTransaction(Default::default()); + // Initialize it with the current state + *self.focus_transaction.borrow_mut() = Some(FocusTransaction { + element: self.focused.get().as_deref().map(Dom::from_ref), + has_focus: self.has_focus.get(), + }); } /// pub(crate) fn perform_focus_fixup_rule(&self, not_focusable: &Element, can_gc: CanGc) { + // Return if `not_focusable` is not the designated focused area of the + // `Document`. if Some(not_focusable) != self.focused.get().as_deref() { return; } - self.request_focus( - self.GetBody().as_ref().map(|e| e.upcast()), - FocusType::Element, - can_gc, - ) - } - /// Request that the given element receive focus once the current transaction is complete. - /// If None is passed, then whatever element is currently focused will no longer be focused - /// once the transaction is complete. - pub(crate) fn request_focus( - &self, - elem: Option<&Element>, - focus_type: FocusType, - can_gc: CanGc, - ) { - let implicit_transaction = matches!( - *self.focus_transaction.borrow(), - FocusTransaction::NotInTransaction - ); + let implicit_transaction = self.focus_transaction.borrow().is_none(); + if implicit_transaction { self.begin_focus_transaction(); } - if elem.is_none_or(|e| e.is_focusable_area()) { - *self.focus_transaction.borrow_mut() = - FocusTransaction::InTransaction(elem.map(Dom::from_ref)); + + // Designate the viewport as the new focused area of the `Document`, but + // do not run the focusing steps. + { + let mut focus_transaction = self.focus_transaction.borrow_mut(); + focus_transaction.as_mut().unwrap().element = None; } + if implicit_transaction { - self.commit_focus_transaction(focus_type, can_gc); + self.commit_focus_transaction(FocusInitiator::Local, can_gc); } } - /// Reassign the focus context to the element that last requested focus during this - /// transaction, or none if no elements requested it. - fn commit_focus_transaction(&self, focus_type: FocusType, can_gc: CanGc) { - let possibly_focused = match *self.focus_transaction.borrow() { - FocusTransaction::NotInTransaction => unreachable!(), - FocusTransaction::InTransaction(ref elem) => { - elem.as_ref().map(|e| DomRoot::from_ref(&**e)) - }, - }; - *self.focus_transaction.borrow_mut() = FocusTransaction::NotInTransaction; - if self.focused == possibly_focused.as_deref() { + /// Request that the given element receive focus once the current + /// transaction is complete. `None` specifies to focus the document. + /// + /// If there's no ongoing transaction, this method automatically starts and + /// commits an implicit transaction. + pub(crate) fn request_focus( + &self, + elem: Option<&Element>, + focus_initiator: FocusInitiator, + can_gc: CanGc, + ) { + // If an element is specified, and it's non-focusable, ignore the + // request. + if elem.is_some_and(|e| !e.is_focusable_area()) { return; } - if let Some(ref elem) = self.focused.get() { - let node = elem.upcast::(); - elem.set_focus_state(false); - // FIXME: pass appropriate relatedTarget - self.fire_focus_event(FocusEventType::Blur, node, None, can_gc); - // Notify the embedder to hide the input method. - if elem.input_method_type().is_some() { - self.send_to_embedder(EmbedderMsg::HideIME(self.webview_id())); + let implicit_transaction = self.focus_transaction.borrow().is_none(); + + if implicit_transaction { + self.begin_focus_transaction(); + } + + { + let mut focus_transaction = self.focus_transaction.borrow_mut(); + let focus_transaction = focus_transaction.as_mut().unwrap(); + focus_transaction.element = elem.map(Dom::from_ref); + focus_transaction.has_focus = true; + } + + if implicit_transaction { + self.commit_focus_transaction(focus_initiator, can_gc); + } + } + + /// Update the local focus state accordingly after being notified that the + /// document's container is removed from the top-level browsing context's + /// focus chain (not considering system focus). + pub(crate) fn handle_container_unfocus(&self, can_gc: CanGc) { + assert!( + self.window().parent_info().is_some(), + "top-level document cannot be unfocused", + ); + + // Since this method is called from an event loop, there mustn't be + // an in-progress focus transaction + assert!( + self.focus_transaction.borrow().is_none(), + "there mustn't be an in-progress focus transaction at this point" + ); + + // Start an implicit focus transaction + self.begin_focus_transaction(); + + // Update the transaction + { + let mut focus_transaction = self.focus_transaction.borrow_mut(); + focus_transaction.as_mut().unwrap().has_focus = false; + } + + // Commit the implicit focus transaction + self.commit_focus_transaction(FocusInitiator::Remote, can_gc); + } + + /// Reassign the focus context to the element that last requested focus during this + /// transaction, or the document if no elements requested it. + fn commit_focus_transaction(&self, focus_initiator: FocusInitiator, can_gc: CanGc) { + let (mut new_focused, new_focus_state) = { + let focus_transaction = self.focus_transaction.borrow(); + let focus_transaction = focus_transaction + .as_ref() + .expect("no focus transaction in progress"); + ( + focus_transaction + .element + .as_ref() + .map(|e| DomRoot::from_ref(&**e)), + focus_transaction.has_focus, + ) + }; + *self.focus_transaction.borrow_mut() = None; + + if !new_focus_state { + // In many browsers, a document forgets its focused area when the + // document is removed from the top-level BC's focus chain + if new_focused.take().is_some() { + trace!( + "Forgetting the document's focused area because the \ + document's container was removed from the top-level BC's \ + focus chain" + ); } } - self.focused.set(possibly_focused.as_deref()); + let old_focused = self.focused.get(); + let old_focus_state = self.has_focus.get(); - if let Some(ref elem) = self.focused.get() { - elem.set_focus_state(true); - let node = elem.upcast::(); - // FIXME: pass appropriate relatedTarget - self.fire_focus_event(FocusEventType::Focus, node, None, can_gc); - // Update the focus state for all elements in the focus chain. - // https://html.spec.whatwg.org/multipage/#focus-chain - if focus_type == FocusType::Element { - self.window() - .send_to_constellation(ScriptToConstellationMessage::Focus); + debug!( + "Committing focus transaction: {:?} → {:?}", + (&old_focused, old_focus_state), + (&new_focused, new_focus_state), + ); + + // `*_focused_filtered` indicates the local element (if any) included in + // the top-level BC's focus chain. + let old_focused_filtered = old_focused.as_ref().filter(|_| old_focus_state); + let new_focused_filtered = new_focused.as_ref().filter(|_| new_focus_state); + + let trace_focus_chain = |name, element, doc| { + trace!( + "{} local focus chain: {}", + name, + match (element, doc) { + (Some(e), _) => format!("[{:?}, document]", e), + (None, true) => "[document]".to_owned(), + (None, false) => "[]".to_owned(), + } + ); + }; + + trace_focus_chain("Old", old_focused_filtered, old_focus_state); + trace_focus_chain("New", new_focused_filtered, new_focus_state); + + if old_focused_filtered != new_focused_filtered { + if let Some(elem) = &old_focused_filtered { + let node = elem.upcast::(); + elem.set_focus_state(false); + // FIXME: pass appropriate relatedTarget + if node.is_connected() { + self.fire_focus_event(FocusEventType::Blur, node.upcast(), None, can_gc); + } + + // Notify the embedder to hide the input method. + if elem.input_method_type().is_some() { + self.send_to_embedder(EmbedderMsg::HideIME(self.webview_id())); + } } + } - // Notify the embedder to display an input method. - if let Some(kind) = elem.input_method_type() { - let rect = elem.upcast::().bounding_content_box_or_zero(can_gc); - let rect = Rect::new( - Point2D::new(rect.origin.x.to_px(), rect.origin.y.to_px()), - Size2D::new(rect.size.width.to_px(), rect.size.height.to_px()), + if old_focus_state != new_focus_state && !new_focus_state { + self.fire_focus_event(FocusEventType::Blur, self.global().upcast(), None, can_gc); + } + + self.focused.set(new_focused.as_deref()); + self.has_focus.set(new_focus_state); + + if old_focus_state != new_focus_state && new_focus_state { + self.fire_focus_event(FocusEventType::Focus, self.global().upcast(), None, can_gc); + } + + if old_focused_filtered != new_focused_filtered { + if let Some(elem) = &new_focused_filtered { + elem.set_focus_state(true); + let node = elem.upcast::(); + // FIXME: pass appropriate relatedTarget + self.fire_focus_event(FocusEventType::Focus, node.upcast(), None, can_gc); + + // Notify the embedder to display an input method. + if let Some(kind) = elem.input_method_type() { + let rect = elem.upcast::().bounding_content_box_or_zero(can_gc); + let rect = Rect::new( + Point2D::new(rect.origin.x.to_px(), rect.origin.y.to_px()), + Size2D::new(rect.size.width.to_px(), rect.size.height.to_px()), + ); + let (text, multiline) = if let Some(input) = elem.downcast::() + { + ( + Some(( + (input.Value()).to_string(), + input.GetSelectionEnd().unwrap_or(0) as i32, + )), + false, + ) + } else if let Some(textarea) = elem.downcast::() { + ( + Some(( + (textarea.Value()).to_string(), + textarea.GetSelectionEnd().unwrap_or(0) as i32, + )), + true, + ) + } else { + (None, false) + }; + self.send_to_embedder(EmbedderMsg::ShowIME( + self.webview_id(), + kind, + text, + multiline, + DeviceIntRect::from_untyped(&rect.to_box2d()), + )); + } + } + } + + if focus_initiator != FocusInitiator::Local { + return; + } + + // We are the initiator of the focus operation, so we must broadcast + // the change we intend to make. + match (old_focus_state, new_focus_state) { + (_, true) => { + // Advertise the change in the focus chain. + // + // + // + // If the top-level BC doesn't have system focus, this won't + // have an immediate effect, but it will when we gain system + // focus again. Therefore we still have to send `ScriptMsg:: + // Focus`. + // + // When a container with a non-null nested browsing context is + // focused, its active document becomes the focused area of the + // top-level browsing context instead. Therefore we need to let + // the constellation know if such a container is focused. + // + // > The focusing steps for an object `new focus target` [...] + // > + // > 3. If `new focus target` is a browsing context container + // > with non-null nested browsing context, then set + // > `new focus target` to the nested browsing context's + // > active document. + let child_browsing_context_id = new_focused + .as_ref() + .and_then(|elem| elem.downcast::()) + .and_then(|iframe| iframe.browsing_context_id()); + + let sequence = self.increment_fetch_focus_sequence(); + + debug!( + "Advertising the focus request to the constellation \ + with sequence number {} and child BC ID {}", + sequence, + child_browsing_context_id + .as_ref() + .map(|id| id as &dyn std::fmt::Display) + .unwrap_or(&"(none)"), ); - let (text, multiline) = if let Some(input) = elem.downcast::() { - ( - Some(( - input.Value().to_string(), - input.GetSelectionEnd().unwrap_or(0) as i32, - )), - false, - ) - } else if let Some(textarea) = elem.downcast::() { - ( - Some(( - textarea.Value().to_string(), - textarea.GetSelectionEnd().unwrap_or(0) as i32, - )), - true, - ) - } else { - (None, false) - }; - self.send_to_embedder(EmbedderMsg::ShowIME( - self.webview_id(), - kind, - text, - multiline, - DeviceIntRect::from_untyped(&rect.to_box2d()), - )); - } + + self.window() + .send_to_constellation(ScriptToConstellationMessage::Focus( + child_browsing_context_id, + sequence, + )); + }, + (false, false) => { + // Our `Document` doesn't have focus, and we intend to keep it + // this way. + }, + (true, false) => { + unreachable!( + "Can't lose the document's focus without specifying \ + another one to focus" + ); + }, } } @@ -1350,7 +1557,10 @@ impl Document { } self.begin_focus_transaction(); - self.request_focus(Some(&*el), FocusType::Element, can_gc); + // Try to focus `el`. If it's not focusable, focus the document + // instead. + self.request_focus(None, FocusInitiator::Local, can_gc); + self.request_focus(Some(&*el), FocusInitiator::Local, can_gc); } let dom_event = DomRoot::upcast::(MouseEvent::for_platform_mouse_event( @@ -1388,7 +1598,9 @@ impl Document { } if let MouseButtonAction::Click = event.action { - self.commit_focus_transaction(FocusType::Element, can_gc); + if self.focus_transaction.borrow().is_some() { + self.commit_focus_transaction(FocusInitiator::Local, can_gc); + } self.maybe_fire_dblclick( hit_test_result.point_in_viewport, node, @@ -2215,7 +2427,7 @@ impl Document { ImeEvent::Dismissed => { self.request_focus( self.GetBody().as_ref().map(|e| e.upcast()), - FocusType::Element, + FocusInitiator::Local, can_gc, ); return; @@ -2397,10 +2609,6 @@ impl Document { pub(crate) fn run_the_animation_frame_callbacks(&self, can_gc: CanGc) { let _realm = enter_realm(self); - self.pending_animation_ticks - .borrow_mut() - .remove(AnimationTickType::REQUEST_ANIMATION_FRAME); - self.running_animation_callbacks.set(true); let was_faking_animation_frames = self.is_faking_animation_frames(); let timing = self.global().performance().Now(); @@ -3198,7 +3406,7 @@ impl Document { fn fire_focus_event( &self, focus_event_type: FocusEventType, - node: &Node, + event_target: &EventTarget, related_target: Option<&EventTarget>, can_gc: CanGc, ) { @@ -3218,8 +3426,7 @@ impl Document { ); let event = event.upcast::(); event.set_trusted(true); - let target = node.upcast(); - event.fire(target, can_gc); + event.fire(event_target, can_gc); } /// @@ -3616,6 +3823,90 @@ impl Document { .Performance() .queue_entry(entry.upcast::(), can_gc); } + + /// + fn write( + &self, + text: Vec, + line_feed: bool, + containing_class: &str, + field: &str, + can_gc: CanGc, + ) -> ErrorResult { + // Step 1: Let string be the empty string. + let mut strings: Vec = Vec::with_capacity(text.len()); + // Step 2: Let isTrusted be false if text contains a string; otherwise true. + let mut is_trusted = true; + // Step 3: For each value of text: + for value in text { + match value { + // Step 3.1: If value is a TrustedHTML object, then append value's associated data to string. + TrustedHTMLOrString::TrustedHTML(trusted_html) => { + strings.push(trusted_html.to_string().to_owned()); + }, + TrustedHTMLOrString::String(str_) => { + // Step 2: Let isTrusted be false if text contains a string; otherwise true. + is_trusted = false; + // Step 3.2: Otherwise, append value to string. + strings.push(str_.into()); + }, + }; + } + let mut string = itertools::join(strings, ""); + // Step 4: If isTrusted is false, set string to the result of invoking the + // Get Trusted Type compliant string algorithm with TrustedHTML, + // this's relevant global object, string, sink, and "script". + if !is_trusted { + string = TrustedHTML::get_trusted_script_compliant_string( + &self.global(), + TrustedHTMLOrString::String(string.into()), + containing_class, + field, + can_gc, + )?; + } + // Step 5: If lineFeed is true, append U+000A LINE FEED to string. + if line_feed { + string.push('\n'); + } + // Step 6: If document is an XML document, then throw an "InvalidStateError" DOMException. + if !self.is_html_document() { + return Err(Error::InvalidState); + } + + // Step 7: If document's throw-on-dynamic-markup-insertion counter is greater than 0, + // then throw an "InvalidStateError" DOMException. + if self.throw_on_dynamic_markup_insertion_counter.get() > 0 { + return Err(Error::InvalidState); + } + + // Step 8: If document's active parser was aborted is true, then return. + if !self.is_active() || self.active_parser_was_aborted.get() { + return Ok(()); + } + + let parser = match self.get_current_parser() { + Some(ref parser) if parser.can_write() => DomRoot::from_ref(&**parser), + // Step 9: If the insertion point is undefined, then: + _ => { + // Step 9.1: If document's unload counter is greater than 0 or + // document's ignore-destructive-writes counter is greater than 0, then return. + if self.is_prompting_or_unloading() || + self.ignore_destructive_writes_counter.get() > 0 + { + return Ok(()); + } + // Step 9.2: Run the document open steps with document. + self.Open(None, None, can_gc)?; + self.get_current_parser().unwrap() + }, + }; + + // Steps 10-11. + parser.write(string.into(), can_gc); + + Ok(()) + } } fn is_character_value_key(key: &Key) -> bool { @@ -3799,6 +4090,8 @@ impl Document { .and_then(|charset| Encoding::for_label(charset.as_bytes())) .unwrap_or(UTF_8); + let has_focus = window.parent_info().is_none(); + let has_browsing_context = has_browsing_context == HasBrowsingContext::Yes; Document { @@ -3846,8 +4139,10 @@ impl Document { stylesheet_list: MutNullableDom::new(None), ready_state: Cell::new(ready_state), domcontentloaded_dispatched: Cell::new(domcontentloaded_dispatched), - focus_transaction: DomRefCell::new(FocusTransaction::NotInTransaction), + focus_transaction: DomRefCell::new(None), focused: Default::default(), + focus_sequence: Cell::new(FocusSequenceNumber::default()), + has_focus: Cell::new(has_focus), current_script: Default::default(), pending_parsing_blocking_script: Default::default(), script_blocking_stylesheets_count: Cell::new(0u32), @@ -3916,7 +4211,6 @@ impl Document { image_animation_manager: DomRefCell::new(ImageAnimationManager::new()), dirty_root: Default::default(), declarative_refresh: Default::default(), - pending_animation_ticks: Default::default(), pending_input_events: Default::default(), mouse_move_event_index: Default::default(), resize_observers: Default::default(), @@ -3930,6 +4224,7 @@ impl Document { intersection_observer_task_queued: Cell::new(false), intersection_observers: Default::default(), active_keyboard_modifiers: Cell::new(Modifiers::empty()), + highlighted_dom_node: Default::default(), } } @@ -4012,21 +4307,19 @@ impl Document { type_: csp::InlineCheckType, source: &str, ) -> csp::CheckResult { - let element = csp::Element { - nonce: el - .get_attribute(&ns!(), &local_name!("nonce")) - .map(|attr| Cow::Owned(attr.value().to_string())), - }; let (result, violations) = match self.get_csp_list() { None => { return csp::CheckResult::Allowed; }, Some(csp_list) => { + let element = csp::Element { + nonce: el.nonce_value_if_nonceable().map(Cow::Owned), + }; csp_list.should_elements_inline_type_behavior_be_blocked(&element, type_, source) }, }; - self.global().report_csp_violations(violations); + self.global().report_csp_violations(violations, Some(el)); result } @@ -4689,18 +4982,6 @@ impl Document { .collect() } - /// Note a pending animation tick, to be processed at the next `update_the_rendering` task. - pub(crate) fn note_pending_animation_tick(&self, tick_type: AnimationTickType) { - self.pending_animation_ticks.borrow_mut().extend(tick_type); - } - - /// Whether this document has received an animation tick for rafs. - pub(crate) fn has_received_raf_tick(&self) -> bool { - self.pending_animation_ticks - .borrow() - .contains(AnimationTickType::REQUEST_ANIMATION_FRAME) - } - pub(crate) fn advance_animation_timeline_for_testing(&self, delta: f64) { self.animation_timeline.borrow_mut().advance_specific(delta); let current_timeline_value = self.current_animation_timeline_value(); @@ -4770,6 +5051,35 @@ impl Document { self.image_animation_manager.borrow_mut() } + pub(crate) fn update_animating_images(&self) { + let mut image_animation_manager = self.image_animation_manager.borrow_mut(); + if !image_animation_manager.image_animations_present() { + return; + } + image_animation_manager + .update_active_frames(&self.window, self.current_animation_timeline_value()); + + if !self.animations().animations_present() { + let next_scheduled_time = + image_animation_manager.next_schedule_time(self.current_animation_timeline_value()); + // TODO: Once we have refresh signal from the compositor, + // we should get rid of timer for animated image update. + if let Some(next_scheduled_time) = next_scheduled_time { + self.schedule_image_animation_update(next_scheduled_time); + } + } + } + + fn schedule_image_animation_update(&self, next_scheduled_time: f64) { + let callback = ImageAnimationUpdateCallback { + document: Trusted::new(self), + }; + self.global().schedule_callback( + OneshotTimerCallback::ImageAnimationUpdate(callback), + Duration::from_secs_f64(next_scheduled_time), + ); + } + /// pub(crate) fn shared_declarative_refresh_steps(&self, content: &[u8]) { // 1. If document's will declaratively refresh is true, then return. @@ -4933,6 +5243,14 @@ impl Document { self.has_trustworthy_ancestor_origin.get() || self.origin().immutable().is_potentially_trustworthy() } + pub(crate) fn highlight_dom_node(&self, node: Option<&Node>) { + self.highlighted_dom_node.set(node); + self.set_needs_paint(true); + } + + pub(crate) fn highlighted_dom_node(&self) -> Option> { + self.highlighted_dom_node.get() + } } #[allow(non_snake_case)] @@ -5006,12 +5324,34 @@ impl DocumentMethods for Document { // https://html.spec.whatwg.org/multipage/#dom-document-hasfocus fn HasFocus(&self) -> bool { - // Step 1-2. - if self.window().parent_info().is_none() && self.is_fully_active() { - return true; + // + // + // > The has focus steps, given a `Document` object `target`, are as + // > follows: + // > + // > 1. If `target`'s browsing context's top-level browsing context does + // > not have system focus, then return false. + + // > 2. Let `candidate` be `target`'s browsing context's top-level + // > browsing context's active document. + // > + // > 3. While true: + // > + // > 3.1. If `candidate` is target, then return true. + // > + // > 3.2. If the focused area of `candidate` is a browsing context + // > container with a non-null nested browsing context, then set + // > `candidate` to the active document of that browsing context + // > container's nested browsing context. + // > + // > 3.3. Otherwise, return false. + if self.window().parent_info().is_none() { + // 2 → 3 → (3.1 || ⋯ → 3.3) + self.is_fully_active() + } else { + // 2 → 3 → 3.2 → (⋯ → 3.1 || ⋯ → 3.3) + self.is_fully_active() && self.has_focus.get() } - // TODO Step 3. - false } // https://html.spec.whatwg.org/multipage/#dom-document-domain @@ -6056,6 +6396,30 @@ impl DocumentMethods for Document { ) } + /// + fn GetScrollingElement(&self, can_gc: CanGc) -> Option> { + // Step 1. If the Document is in quirks mode, follow these steps: + if self.quirks_mode() == QuirksMode::Quirks { + // Step 1.1. If the body element exists, + if let Some(ref body) = self.GetBody() { + let e = body.upcast::(); + // and it is not potentially scrollable, return the body element and abort these steps. + // For this purpose, a value of overflow:clip on the the body element’s parent element + // must be treated as overflow:hidden. + if !e.is_potentially_scrollable_body_for_scrolling_element(can_gc) { + return Some(DomRoot::from_ref(e)); + } + } + + // Step 1.2. Return null and abort these steps. + return None; + } + + // Step 2. If there is a root element, return the root element and abort these steps. + // Step 3. Return null. + self.GetDocumentElement() + } + // https://html.spec.whatwg.org/multipage/#dom-document-open fn Open( &self, @@ -6193,54 +6557,17 @@ impl DocumentMethods for Document { } // https://html.spec.whatwg.org/multipage/#dom-document-write - fn Write(&self, text: Vec, can_gc: CanGc) -> ErrorResult { - if !self.is_html_document() { - // Step 1. - return Err(Error::InvalidState); - } - - // Step 2. - if self.throw_on_dynamic_markup_insertion_counter.get() > 0 { - return Err(Error::InvalidState); - } - - // Step 3 - what specifies the is_active() part here? - if !self.is_active() || self.active_parser_was_aborted.get() { - return Ok(()); - } - - let parser = match self.get_current_parser() { - Some(ref parser) if parser.can_write() => DomRoot::from_ref(&**parser), - _ => { - // Either there is no parser, which means the parsing ended; - // or script nesting level is 0, which means the method was - // called from outside a parser-executed script. - if self.is_prompting_or_unloading() || - self.ignore_destructive_writes_counter.get() > 0 - { - // Step 4. - return Ok(()); - } - // Step 5. - self.Open(None, None, can_gc)?; - self.get_current_parser().unwrap() - }, - }; - - // Step 7. - // TODO: handle reload override buffer. - - // Steps 6-8. - parser.write(text, can_gc); - - // Step 9. - Ok(()) + fn Write(&self, text: Vec, can_gc: CanGc) -> ErrorResult { + // The document.write(...text) method steps are to run the document write steps + // with this, text, false, and "Document write". + self.write(text, false, "Document", "write", can_gc) } // https://html.spec.whatwg.org/multipage/#dom-document-writeln - fn Writeln(&self, mut text: Vec, can_gc: CanGc) -> ErrorResult { - text.push("\n".into()); - self.Write(text, can_gc) + fn Writeln(&self, text: Vec, can_gc: CanGc) -> ErrorResult { + // The document.writeln(...text) method steps are to run the document write steps + // with this, text, true, and "Document writeln". + self.write(text, true, "Document", "writeln", can_gc) } // https://html.spec.whatwg.org/multipage/#dom-document-close @@ -6269,9 +6596,6 @@ impl DocumentMethods for Document { Ok(()) } - // https://html.spec.whatwg.org/multipage/#documentandelementeventhandlers - document_and_element_event_handlers!(); - // https://fullscreen.spec.whatwg.org/#handler-document-onfullscreenerror event_handler!(fullscreenerror, GetOnfullscreenerror, SetOnfullscreenerror); @@ -6414,6 +6738,17 @@ pub(crate) enum FocusType { Parent, // Focusing a parent element (an iframe) } +/// Specifies the initiator of a focus operation. +#[derive(Clone, Copy, PartialEq)] +pub enum FocusInitiator { + /// The operation is initiated by this document and to be broadcasted + /// through the constellation. + Local, + /// The operation is initiated somewhere else, and we are updating our + /// internal state accordingly. + Remote, +} + /// Focus events pub(crate) enum FocusEventType { Focus, // Element gained focus. Doesn't bubble. @@ -6437,10 +6772,22 @@ impl FakeRequestAnimationFrameCallback { pub(crate) fn invoke(self, can_gc: CanGc) { // TODO: Once there is a more generic mechanism to trigger `update_the_rendering` when // not driven by the compositor, it should be used here. - self.document - .root() - .note_pending_animation_tick(AnimationTickType::REQUEST_ANIMATION_FRAME); - with_script_thread(|script_thread| script_thread.update_the_rendering(false, can_gc)) + with_script_thread(|script_thread| script_thread.update_the_rendering(true, can_gc)) + } +} + +/// This is a temporary workaround to update animated images, +/// we should get rid of this after we have refresh driver #3406 +#[derive(JSTraceable, MallocSizeOf)] +pub(crate) struct ImageAnimationUpdateCallback { + /// The document. + #[ignore_malloc_size_of = "non-owning"] + document: Trusted, +} + +impl ImageAnimationUpdateCallback { + pub(crate) fn invoke(self, can_gc: CanGc) { + with_script_thread(|script_thread| script_thread.update_the_rendering(true, can_gc)) } } diff --git a/components/script/dom/domexception.rs b/components/script/dom/domexception.rs index e63c3ff1f53..fbb807b7a95 100644 --- a/components/script/dom/domexception.rs +++ b/components/script/dom/domexception.rs @@ -17,9 +17,9 @@ use crate::dom::bindings::reflector::{ Reflector, reflect_dom_object, reflect_dom_object_with_proto, }; use crate::dom::bindings::root::DomRoot; -use crate::dom::bindings::serializable::{Serializable, StorageKey}; +use crate::dom::bindings::serializable::Serializable; use crate::dom::bindings::str::DOMString; -use crate::dom::bindings::structuredclone::{StructuredData, StructuredDataReader}; +use crate::dom::bindings::structuredclone::StructuredData; use crate::dom::globalscope::GlobalScope; use crate::script_runtime::CanGc; @@ -53,6 +53,7 @@ pub(crate) enum DOMErrorName { NotReadableError, DataError, OperationError, + NotAllowedError, } impl DOMErrorName { @@ -84,6 +85,7 @@ impl DOMErrorName { "NotReadableError" => Some(DOMErrorName::NotReadableError), "DataError" => Some(DOMErrorName::DataError), "OperationError" => Some(DOMErrorName::OperationError), + "NotAllowedError" => Some(DOMErrorName::NotAllowedError), _ => None, } } @@ -135,6 +137,10 @@ impl DOMException { DOMErrorName::OperationError => { "The operation failed for an operation-specific reason." }, + DOMErrorName::NotAllowedError => { + r#"The request is not allowed by the user agent or the platform in the current context, + possibly because the user denied permission."# + }, }; ( @@ -252,18 +258,12 @@ impl Serializable for DOMException { )) } - fn serialized_storage( - data: StructuredData<'_>, - ) -> &mut Option> { + fn serialized_storage<'a>( + data: StructuredData<'a, '_>, + ) -> &'a mut Option> { match data { StructuredData::Reader(reader) => &mut reader.exceptions, StructuredData::Writer(writer) => &mut writer.exceptions, } } - - fn deserialized_storage( - reader: &mut StructuredDataReader, - ) -> &mut Option>> { - &mut reader.dom_exceptions - } } diff --git a/components/script/dom/dompoint.rs b/components/script/dom/dompoint.rs index 006abb50def..5e848c43c47 100644 --- a/components/script/dom/dompoint.rs +++ b/components/script/dom/dompoint.rs @@ -14,8 +14,8 @@ use crate::dom::bindings::codegen::Bindings::DOMPointReadOnlyBinding::DOMPointRe use crate::dom::bindings::error::Fallible; use crate::dom::bindings::reflector::reflect_dom_object_with_proto; use crate::dom::bindings::root::DomRoot; -use crate::dom::bindings::serializable::{Serializable, StorageKey}; -use crate::dom::bindings::structuredclone::{StructuredData, StructuredDataReader}; +use crate::dom::bindings::serializable::Serializable; +use crate::dom::bindings::structuredclone::StructuredData; use crate::dom::dompointreadonly::{DOMPointReadOnly, DOMPointWriteMethods}; use crate::dom::globalscope::GlobalScope; use crate::script_runtime::CanGc; @@ -163,18 +163,12 @@ impl Serializable for DOMPoint { )) } - fn serialized_storage( - data: StructuredData<'_>, - ) -> &mut Option> { + fn serialized_storage<'a>( + data: StructuredData<'a, '_>, + ) -> &'a mut Option> { match data { StructuredData::Reader(reader) => &mut reader.points, StructuredData::Writer(writer) => &mut writer.points, } } - - fn deserialized_storage( - reader: &mut StructuredDataReader, - ) -> &mut Option>> { - &mut reader.dom_points - } } diff --git a/components/script/dom/dompointreadonly.rs b/components/script/dom/dompointreadonly.rs index 44e4a70b680..eb6bc9b93a9 100644 --- a/components/script/dom/dompointreadonly.rs +++ b/components/script/dom/dompointreadonly.rs @@ -15,8 +15,8 @@ use crate::dom::bindings::codegen::Bindings::DOMPointReadOnlyBinding::DOMPointRe use crate::dom::bindings::error::Fallible; use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto}; use crate::dom::bindings::root::DomRoot; -use crate::dom::bindings::serializable::{Serializable, StorageKey}; -use crate::dom::bindings::structuredclone::{StructuredData, StructuredDataReader}; +use crate::dom::bindings::serializable::Serializable; +use crate::dom::bindings::structuredclone::StructuredData; use crate::dom::globalscope::GlobalScope; use crate::script_runtime::CanGc; @@ -172,18 +172,12 @@ impl Serializable for DOMPointReadOnly { )) } - fn serialized_storage( - data: StructuredData<'_>, - ) -> &mut Option> { + fn serialized_storage<'a>( + data: StructuredData<'a, '_>, + ) -> &'a mut Option> { match data { StructuredData::Reader(r) => &mut r.points, StructuredData::Writer(w) => &mut w.points, } } - - fn deserialized_storage( - reader: &mut StructuredDataReader, - ) -> &mut Option>> { - &mut reader.points_read_only - } } diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 3a8ac8f0cd8..dbf0f14ab68 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -12,6 +12,7 @@ use std::rc::Rc; use std::str::FromStr; use std::{fmt, mem}; +use content_security_policy as csp; use cssparser::match_ignore_ascii_case; use devtools_traits::AttrInfo; use dom_struct::dom_struct; @@ -50,6 +51,7 @@ use style::selector_parser::{ use style::shared_lock::{Locked, SharedRwLock}; use style::stylesheets::layer_rule::LayerOrder; use style::stylesheets::{CssRuleType, UrlExtraData}; +use style::values::computed::Overflow; use style::values::generics::NonNegative; use style::values::generics::position::PreferredRatio; use style::values::generics::ratio::Ratio; @@ -61,9 +63,10 @@ use xml5ever::serialize::TraversalScope::{ ChildrenOnly as XmlChildrenOnly, IncludeNode as XmlIncludeNode, }; +use crate::conversions::Convert; use crate::dom::activation::Activatable; -use crate::dom::attr::{Attr, AttrHelpersForLayout}; -use crate::dom::bindings::cell::{DomRefCell, Ref, RefMut, ref_filter_map}; +use crate::dom::attr::{Attr, AttrHelpersForLayout, is_relevant_attribute}; +use crate::dom::bindings::cell::{DomRefCell, Ref, RefMut}; use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; use crate::dom::bindings::codegen::Bindings::ElementBinding::{ @@ -78,7 +81,9 @@ use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{ use crate::dom::bindings::codegen::Bindings::WindowBinding::{ ScrollBehavior, ScrollToOptions, WindowMethods, }; -use crate::dom::bindings::codegen::UnionTypes::{NodeOrString, TrustedScriptURLOrUSVString}; +use crate::dom::bindings::codegen::UnionTypes::{ + NodeOrString, TrustedHTMLOrNullIsEmptyString, TrustedHTMLOrString, TrustedScriptURLOrUSVString, +}; use crate::dom::bindings::conversions::DerivedFrom; use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; @@ -123,6 +128,7 @@ use crate::dom::htmllinkelement::HTMLLinkElement; use crate::dom::htmlobjectelement::HTMLObjectElement; use crate::dom::htmloptgroupelement::HTMLOptGroupElement; use crate::dom::htmloutputelement::HTMLOutputElement; +use crate::dom::htmlscriptelement::HTMLScriptElement; use crate::dom::htmlselectelement::HTMLSelectElement; use crate::dom::htmlslotelement::{HTMLSlotElement, Slottable}; use crate::dom::htmlstyleelement::HTMLStyleElement; @@ -140,8 +146,8 @@ use crate::dom::intersectionobserver::{IntersectionObserver, IntersectionObserve use crate::dom::mutationobserver::{Mutation, MutationObserver}; use crate::dom::namednodemap::NamedNodeMap; use crate::dom::node::{ - BindContext, ChildrenMutation, LayoutNodeHelpers, Node, NodeDamage, NodeFlags, NodeTraits, - ShadowIncluding, UnbindContext, + BindContext, ChildrenMutation, CloneChildrenFlag, LayoutNodeHelpers, Node, NodeDamage, + NodeFlags, NodeTraits, ShadowIncluding, UnbindContext, }; use crate::dom::nodelist::NodeList; use crate::dom::promise::Promise; @@ -149,7 +155,7 @@ use crate::dom::raredata::ElementRareData; use crate::dom::servoparser::ServoParser; use crate::dom::shadowroot::{IsUserAgentWidget, ShadowRoot}; use crate::dom::text::Text; -use crate::dom::types::TrustedTypePolicyFactory; +use crate::dom::trustedhtml::TrustedHTML; use crate::dom::validation::Validatable; use crate::dom::validitystate::ValidationFlags; use crate::dom::virtualmethods::{VirtualMethods, vtable_for}; @@ -179,7 +185,7 @@ pub struct Element { /// #[no_trace] is: DomRefCell>, - #[ignore_malloc_size_of = "Arc"] + #[conditional_malloc_size_of] #[no_trace] style_attribute: DomRefCell>>>, attr_list: MutNullableDom, @@ -327,7 +333,21 @@ impl Element { ) } - impl_rare_data!(ElementRareData); + fn rare_data(&self) -> Ref>> { + self.rare_data.borrow() + } + + fn rare_data_mut(&self) -> RefMut>> { + self.rare_data.borrow_mut() + } + + fn ensure_rare_data(&self) -> RefMut> { + let mut rare_data = self.rare_data.borrow_mut(); + if rare_data.is_none() { + *rare_data = Some(Default::default()); + } + RefMut::map(rare_data, |rare_data| rare_data.as_mut().unwrap()) + } pub(crate) fn restyle(&self, damage: NodeDamage) { let doc = self.node.owner_doc(); @@ -339,7 +359,7 @@ impl Element { if damage == NodeDamage::OtherNodeDamage { doc.note_node_with_dirty_descendants(self.upcast()); - restyle.damage = RestyleDamage::rebuild_and_reflow(); + restyle.damage = RestyleDamage::reconstruct(); } } @@ -442,8 +462,25 @@ impl Element { .is_some_and(|s| !s.get_box().clone_display().is_none()) } - // https://drafts.csswg.org/cssom-view/#potentially-scrollable - fn is_potentially_scrollable_body(&self, can_gc: CanGc) -> bool { + /// + pub(crate) fn is_potentially_scrollable_body(&self, can_gc: CanGc) -> bool { + self.is_potentially_scrollable_body_shared_logic(false, can_gc) + } + + /// + pub(crate) fn is_potentially_scrollable_body_for_scrolling_element( + &self, + can_gc: CanGc, + ) -> bool { + self.is_potentially_scrollable_body_shared_logic(true, can_gc) + } + + /// + fn is_potentially_scrollable_body_shared_logic( + &self, + treat_overflow_clip_on_parent_as_hidden: bool, + can_gc: CanGc, + ) -> bool { let node = self.upcast::(); debug_assert!( node.owner_doc().GetBody().as_deref() == self.downcast::(), @@ -461,9 +498,21 @@ impl Element { // overflow-y properties is neither visible nor clip." if let Some(parent) = node.GetParentElement() { if let Some(style) = parent.style(can_gc) { - if !style.get_box().clone_overflow_x().is_scrollable() && - !style.get_box().clone_overflow_y().is_scrollable() - { + let mut overflow_x = style.get_box().clone_overflow_x(); + let mut overflow_y = style.get_box().clone_overflow_y(); + + // This fulfills the 'treat parent element overflow:clip as overflow:hidden' stipulation + // from the document.scrollingElement specification. + if treat_overflow_clip_on_parent_as_hidden { + if overflow_x == Overflow::Clip { + overflow_x = Overflow::Hidden; + } + if overflow_y == Overflow::Clip { + overflow_y = Overflow::Hidden; + } + } + + if !overflow_x.is_scrollable() && !overflow_y.is_scrollable() { return false; } }; @@ -1431,7 +1480,7 @@ impl Element { // is "xmlns", and local name is prefix, or if prefix is null and it has an attribute // whose namespace is the XMLNS namespace, namespace prefix is null, and local name is // "xmlns", then return its value if it is not the empty string, and null otherwise." - let attr = ref_filter_map(self.attrs(), |attrs| { + let attr = Ref::filter_map(self.attrs(), |attrs| { attrs.iter().find(|attr| { if attr.namespace() != &ns!(xmlns) { return false; @@ -1444,7 +1493,8 @@ impl Element { _ => false, } }) - }); + }) + .ok(); if let Some(attr) = attr { return (**attr.value()).into(); @@ -1656,7 +1706,7 @@ impl Element { assert!(attr.GetOwnerElement().as_deref() == Some(self)); self.will_mutate_attr(attr); self.attrs.borrow_mut().push(Dom::from_ref(attr)); - if attr.namespace() == &ns!() { + if is_relevant_attribute(attr.namespace(), attr.local_name()) { vtable_for(self.upcast()).attribute_mutated(attr, AttributeMutation::Set(None), can_gc); } } @@ -1798,7 +1848,7 @@ impl Element { local_name: &LocalName, value: DOMString, ) -> AttrValue { - if *namespace == ns!() { + if is_relevant_attribute(namespace, local_name) { vtable_for(self.upcast()).parse_plain_attribute(local_name, value) } else { AttrValue::String(value.into()) @@ -1853,7 +1903,7 @@ impl Element { self.attrs.borrow_mut().remove(idx); attr.set_owner(None); - if attr.namespace() == &ns!() { + if is_relevant_attribute(attr.namespace(), attr.local_name()) { vtable_for(self.upcast()).attribute_mutated( &attr, AttributeMutation::Removed, @@ -1947,33 +1997,13 @@ impl Element { .unwrap_or_else(|_| TrustedScriptURLOrUSVString::USVString(USVString(value.to_owned()))) } - pub(crate) fn set_trusted_type_url_attribute( - &self, - local_name: &LocalName, - value: TrustedScriptURLOrUSVString, - can_gc: CanGc, - ) -> Fallible<()> { + pub(crate) fn get_trusted_html_attribute(&self, local_name: &LocalName) -> TrustedHTMLOrString { assert_eq!(*local_name, local_name.to_ascii_lowercase()); - let value = match value { - TrustedScriptURLOrUSVString::USVString(url) => { - let global = self.owner_global(); - // TODO(36258): Reflectively get the name of the class - let sink = format!("{} {}", "HTMLScriptElement", &local_name); - let result = TrustedTypePolicyFactory::get_trusted_type_compliant_string( - &global, - url.to_string(), - &sink, - "'script'", - can_gc, - ); - result? - }, - // This partially implements - // Step 1: If input is an instance of expectedType, return stringified input and abort these steps. - TrustedScriptURLOrUSVString::TrustedScriptURL(script_url) => script_url.to_string(), + let value = match self.get_attribute(&ns!(), local_name) { + Some(attr) => (&**attr.value()).into(), + None => "".into(), }; - self.set_attribute(local_name, AttrValue::String(value), can_gc); - Ok(()) + TrustedHTMLOrString::String(value) } pub(crate) fn get_string_attribute(&self, local_name: &LocalName) -> DOMString { @@ -2106,6 +2136,130 @@ impl Element { node.owner_doc().element_attr_will_change(self, attr); } + /// + fn update_style_attribute(&self, attr: &Attr, mutation: AttributeMutation) { + let doc = self.upcast::().owner_doc(); + // Modifying the `style` attribute might change style. + *self.style_attribute.borrow_mut() = match mutation { + AttributeMutation::Set(..) => { + // This is the fast path we use from + // CSSStyleDeclaration. + // + // Juggle a bit to keep the borrow checker happy + // while avoiding the extra clone. + let is_declaration = matches!(*attr.value(), AttrValue::Declaration(..)); + + let block = if is_declaration { + let mut value = AttrValue::String(String::new()); + attr.swap_value(&mut value); + let (serialization, block) = match value { + AttrValue::Declaration(s, b) => (s, b), + _ => unreachable!(), + }; + let mut value = AttrValue::String(serialization); + attr.swap_value(&mut value); + block + } else { + let win = self.owner_window(); + let source = &**attr.value(); + // However, if the Should element's inline behavior be blocked by + // Content Security Policy? algorithm returns "Blocked" when executed + // upon the attribute's element, "style attribute", and the attribute's value, + // then the style rules defined in the attribute's value must not be applied to the element. [CSP] + if doc.should_elements_inline_type_behavior_be_blocked( + self, + csp::InlineCheckType::StyleAttribute, + source, + ) == csp::CheckResult::Blocked + { + return; + } + Arc::new(doc.style_shared_lock().wrap(parse_style_attribute( + source, + &UrlExtraData(doc.base_url().get_arc()), + win.css_error_reporter(), + doc.quirks_mode(), + CssRuleType::Style, + ))) + }; + + Some(block) + }, + AttributeMutation::Removed => None, + }; + } + + /// + pub(crate) fn update_nonce_internal_slot(&self, nonce: String) { + self.ensure_rare_data().cryptographic_nonce = nonce; + } + + /// + pub(crate) fn nonce_value(&self) -> String { + match self.rare_data().as_ref() { + None => String::new(), + Some(rare_data) => rare_data.cryptographic_nonce.clone(), + } + } + + /// + pub(crate) fn update_nonce_post_connection(&self) { + // Whenever an element including HTMLOrSVGElement becomes browsing-context connected, + // the user agent must execute the following steps on the element: + if !self.upcast::().is_connected_with_browsing_context() { + return; + } + let global = self.owner_global(); + // Step 1: Let CSP list be element's shadow-including root's policy container's CSP list. + let csp_list = match global.get_csp_list() { + None => return, + Some(csp_list) => csp_list, + }; + // Step 2: If CSP list contains a header-delivered Content Security Policy, + // and element has a nonce content attribute whose value is not the empty string, then: + if !csp_list.contains_a_header_delivered_content_security_policy() || + self.get_string_attribute(&local_name!("nonce")).is_empty() + { + return; + } + // Step 2.1: Let nonce be element's [[CryptographicNonce]]. + let nonce = self.nonce_value(); + // Step 2.2: Set an attribute value for element using "nonce" and the empty string. + self.set_string_attribute(&local_name!("nonce"), "".into(), CanGc::note()); + // Step 2.3: Set element's [[CryptographicNonce]] to nonce. + self.update_nonce_internal_slot(nonce); + } + + /// + pub(crate) fn nonce_value_if_nonceable(&self) -> Option { + // Step 1: If element does not have an attribute named "nonce", return "Not Nonceable". + if !self.has_attribute(&local_name!("nonce")) { + return None; + } + // Step 2: If element is a script element, then for each attribute of element’s attribute list: + if self.downcast::().is_some() { + for attr in self.attrs().iter() { + // Step 2.1: If attribute’s name contains an ASCII case-insensitive match + // for " pub(crate) fn fragment_parsing_context( owner_doc: &Document, element: Option<&Self>, can_gc: CanGc, ) -> DomRoot { + // If context is not an Element or all of the following are true: match element { Some(elem) + // context's node document is an HTML document; + // context's local name is "html"; and + // context's namespace is the HTML namespace, if elem.local_name() != &local_name!("html") || !elem.html_element_in_html_document() => { DomRoot::from_ref(elem) }, + // set context to the result of creating an element + // given this's node document, "body", and the HTML namespace. _ => DomRoot::upcast(HTMLBodyElement::new( local_name!("body"), None, @@ -2349,6 +2510,13 @@ impl Element { Dom::from_ref(&*ElementInternals::new(elem, can_gc)) })) } + + pub(crate) fn outer_html(&self, can_gc: CanGc) -> Fallible { + match self.GetOuterHTML(can_gc)? { + TrustedHTMLOrNullIsEmptyString::NullIsEmptyString(str) => Ok(str), + TrustedHTMLOrNullIsEmptyString::TrustedHTML(_) => unreachable!(), + } + } } impl ElementMethods for Element { @@ -2607,7 +2775,7 @@ impl ElementMethods for Element { attr.set_owner(Some(self)); self.attrs.borrow_mut()[position] = Dom::from_ref(attr); old_attr.set_owner(None); - if attr.namespace() == &ns!() { + if is_relevant_attribute(attr.namespace(), attr.local_name()) { vtable.attribute_mutated( attr, AttributeMutation::Set(Some(&old_attr.value())), @@ -3003,7 +3171,17 @@ impl ElementMethods for Element { } /// - fn SetHTMLUnsafe(&self, html: DOMString, can_gc: CanGc) { + fn SetHTMLUnsafe(&self, html: TrustedHTMLOrString, can_gc: CanGc) -> ErrorResult { + // Step 1. Let compliantHTML be the result of invoking the + // Get Trusted Type compliant string algorithm with TrustedHTML, + // this's relevant global object, html, "Element setHTMLUnsafe", and "script". + let html = DOMString::from(TrustedHTML::get_trusted_script_compliant_string( + &self.owner_global(), + html, + "Element", + "setHTMLUnsafe", + can_gc, + )?); // Step 2. Let target be this's template contents if this is a template element; otherwise this. let target = if let Some(template) = self.downcast::() { DomRoot::upcast(template.Content(can_gc)) @@ -3011,28 +3189,9 @@ impl ElementMethods for Element { DomRoot::from_ref(self.upcast()) }; - // Step 3. Unsafely set HTML given target, this, and compliantHTML. - // Let newChildren be the result of the HTML fragment parsing algorithm. - let new_children = ServoParser::parse_html_fragment(self, html, true, can_gc); - - let context_document = { - if let Some(template) = self.downcast::() { - template.Content(can_gc).upcast::().owner_doc() - } else { - self.owner_document() - } - }; - - // Let fragment be a new DocumentFragment whose node document is contextElement's node document. - let frag = DocumentFragment::new(&context_document, can_gc); - - // For each node in newChildren, append node to fragment. - for child in new_children { - frag.upcast::().AppendChild(&child, can_gc).unwrap(); - } - - // Replace all with fragment within target. - Node::replace_all(Some(frag.upcast()), &target, can_gc); + // Step 3. Unsafely set HTML given target, this, and compliantHTML + Node::unsafely_set_html(&target, self, html, can_gc); + Ok(()) } /// @@ -3048,7 +3207,7 @@ impl ElementMethods for Element { } /// - fn GetInnerHTML(&self, can_gc: CanGc) -> Fallible { + fn GetInnerHTML(&self, can_gc: CanGc) -> Fallible { let qname = QualName::new( self.prefix().clone(), self.namespace().clone(), @@ -3065,16 +3224,28 @@ impl ElementMethods for Element { .xml_serialize(XmlChildrenOnly(Some(qname))) }; - Ok(result) + Ok(TrustedHTMLOrNullIsEmptyString::NullIsEmptyString(result)) } /// - fn SetInnerHTML(&self, value: DOMString, can_gc: CanGc) -> ErrorResult { - // Step 2. + fn SetInnerHTML(&self, value: TrustedHTMLOrNullIsEmptyString, can_gc: CanGc) -> ErrorResult { + // Step 1: Let compliantString be the result of invoking the + // Get Trusted Type compliant string algorithm with TrustedHTML, + // this's relevant global object, the given value, "Element innerHTML", and "script". + let value = DOMString::from(TrustedHTML::get_trusted_script_compliant_string( + &self.owner_global(), + value.convert(), + "Element", + "innerHTML", + can_gc, + )?); // https://github.com/w3c/DOM-Parsing/issues/1 let target = if let Some(template) = self.downcast::() { + // Step 4: If context is a template element, then set context to + // the template element's template contents (a DocumentFragment). DomRoot::upcast(template.Content(can_gc)) } else { + // Step 2: Let context be this. DomRoot::from_ref(self.upcast()) }; @@ -3091,15 +3262,17 @@ impl ElementMethods for Element { return Ok(()); } - // Step 1. + // Step 3: Let fragment be the result of invoking the fragment parsing algorithm steps + // with context and compliantString. let frag = self.parse_fragment(value, can_gc)?; + // Step 5: Replace all with fragment within context. Node::replace_all(Some(frag.upcast()), &target, can_gc); Ok(()) } /// - fn GetOuterHTML(&self, can_gc: CanGc) -> Fallible { + fn GetOuterHTML(&self, can_gc: CanGc) -> Fallible { // FIXME: This should use the fragment serialization algorithm, which takes // care of distinguishing between html/xml documents let result = if self.owner_document().is_html_document() { @@ -3109,27 +3282,39 @@ impl ElementMethods for Element { self.upcast::().xml_serialize(XmlIncludeNode) }; - Ok(result) + Ok(TrustedHTMLOrNullIsEmptyString::NullIsEmptyString(result)) } /// - fn SetOuterHTML(&self, value: DOMString, can_gc: CanGc) -> ErrorResult { + fn SetOuterHTML(&self, value: TrustedHTMLOrNullIsEmptyString, can_gc: CanGc) -> ErrorResult { + // Step 1: Let compliantString be the result of invoking the + // Get Trusted Type compliant string algorithm with TrustedHTML, + // this's relevant global object, the given value, "Element outerHTML", and "script". + let value = DOMString::from(TrustedHTML::get_trusted_script_compliant_string( + &self.owner_global(), + value.convert(), + "Element", + "outerHTML", + can_gc, + )?); let context_document = self.owner_document(); let context_node = self.upcast::(); - // Step 1. + // Step 2: Let parent be this's parent. let context_parent = match context_node.GetParentNode() { None => { - // Step 2. + // Step 3: If parent is null, return. There would be no way to + // obtain a reference to the nodes created even if the remaining steps were run. return Ok(()); }, Some(parent) => parent, }; let parent = match context_parent.type_id() { - // Step 3. + // Step 4: If parent is a Document, throw a "NoModificationAllowedError" DOMException. NodeTypeId::Document(_) => return Err(Error::NoModificationAllowed), - // Step 4. + // Step 5: If parent is a DocumentFragment, set parent to the result of + // creating an element given this's node document, "body", and the HTML namespace. NodeTypeId::DocumentFragment(_) => { let body_elem = Element::create( QualName::new(None, ns!(html), local_name!("body")), @@ -3145,9 +3330,10 @@ impl ElementMethods for Element { _ => context_node.GetParentElement().unwrap(), }; - // Step 5. + // Step 6: Let fragment be the result of invoking the + // fragment parsing algorithm steps given parent and compliantString. let frag = parent.parse_fragment(value, can_gc)?; - // Step 6. + // Step 7: Replace this with fragment within this's parent. context_parent.ReplaceChild(frag.upcast(), context_node, can_gc)?; Ok(()) } @@ -3314,38 +3500,57 @@ impl ElementMethods for Element { fn InsertAdjacentHTML( &self, position: DOMString, - text: DOMString, + text: TrustedHTMLOrString, can_gc: CanGc, ) -> ErrorResult { - // Step 1. + // Step 1: Let compliantString be the result of invoking the + // Get Trusted Type compliant string algorithm with TrustedHTML, + // this's relevant global object, string, "Element insertAdjacentHTML", and "script". + let text = DOMString::from(TrustedHTML::get_trusted_script_compliant_string( + &self.owner_global(), + text, + "Element", + "insertAdjacentHTML", + can_gc, + )?); let position = position.parse::()?; + // Step 2: Let context be null. + // Step 3: Use the first matching item from this list: let context = match position { + // If position is an ASCII case-insensitive match for the string "beforebegin" + // If position is an ASCII case-insensitive match for the string "afterend" AdjacentPosition::BeforeBegin | AdjacentPosition::AfterEnd => { match self.upcast::().GetParentNode() { + // Step 3.2: If context is null or a Document, throw a "NoModificationAllowedError" DOMException. Some(ref node) if node.is::() => { return Err(Error::NoModificationAllowed); }, None => return Err(Error::NoModificationAllowed), + // Step 3.1: Set context to this's parent. Some(node) => node, } }, + // If position is an ASCII case-insensitive match for the string "afterbegin" + // If position is an ASCII case-insensitive match for the string "beforeend" AdjacentPosition::AfterBegin | AdjacentPosition::BeforeEnd => { + // Set context to this. DomRoot::from_ref(self.upcast::()) }, }; - // Step 2. + // Step 4. let context = Element::fragment_parsing_context( &context.owner_doc(), context.downcast::(), can_gc, ); - // Step 3. + // Step 5: Let fragment be the result of invoking the + // fragment parsing algorithm steps with context and compliantString. let fragment = context.parse_fragment(text, can_gc)?; - // Step 4. + // Step 6. self.insert_adjacent(position, fragment.upcast(), can_gc) .map(|_| ()) } @@ -3806,43 +4011,7 @@ impl VirtualMethods for Element { &local_name!("tabindex") | &local_name!("draggable") | &local_name!("hidden") => { self.update_sequentially_focusable_status(can_gc) }, - &local_name!("style") => { - // Modifying the `style` attribute might change style. - *self.style_attribute.borrow_mut() = match mutation { - AttributeMutation::Set(..) => { - // This is the fast path we use from - // CSSStyleDeclaration. - // - // Juggle a bit to keep the borrow checker happy - // while avoiding the extra clone. - let is_declaration = matches!(*attr.value(), AttrValue::Declaration(..)); - - let block = if is_declaration { - let mut value = AttrValue::String(String::new()); - attr.swap_value(&mut value); - let (serialization, block) = match value { - AttrValue::Declaration(s, b) => (s, b), - _ => unreachable!(), - }; - let mut value = AttrValue::String(serialization); - attr.swap_value(&mut value); - block - } else { - let win = self.owner_window(); - Arc::new(doc.style_shared_lock().wrap(parse_style_attribute( - &attr.value(), - &UrlExtraData(doc.base_url().get_arc()), - win.css_error_reporter(), - doc.quirks_mode(), - CssRuleType::Style, - ))) - }; - - Some(block) - }, - AttributeMutation::Removed => None, - }; - }, + &local_name!("style") => self.update_style_attribute(attr, mutation), &local_name!("id") => { *self.id_attribute.borrow_mut() = mutation.new_value(attr).and_then(|value| { let value = value.as_atom(); @@ -4072,6 +4241,31 @@ impl VirtualMethods for Element { self.tag_name.clear(); } } + + fn post_connection_steps(&self) { + if let Some(s) = self.super_type() { + s.post_connection_steps(); + } + + self.update_nonce_post_connection(); + } + + /// + fn cloning_steps( + &self, + copy: &Node, + maybe_doc: Option<&Document>, + clone_children: CloneChildrenFlag, + can_gc: CanGc, + ) { + if let Some(s) = self.super_type() { + s.cloning_steps(copy, maybe_doc, clone_children, can_gc); + } + let elem = copy.downcast::().unwrap(); + if let Some(rare_data) = self.rare_data().as_ref() { + elem.update_nonce_internal_slot(rare_data.cryptographic_nonce.clone()); + } + } } #[derive(Clone, PartialEq)] diff --git a/components/script/dom/event.rs b/components/script/dom/event.rs index e199e12f655..743ced42a4b 100644 --- a/components/script/dom/event.rs +++ b/components/script/dom/event.rs @@ -1040,14 +1040,39 @@ impl From for EventCancelable { } impl From for bool { - fn from(bubbles: EventCancelable) -> Self { - match bubbles { + fn from(cancelable: EventCancelable) -> Self { + match cancelable { EventCancelable::Cancelable => true, EventCancelable::NotCancelable => false, } } } +#[derive(Clone, Copy, MallocSizeOf, PartialEq)] +pub(crate) enum EventComposed { + Composed, + NotComposed, +} + +impl From for EventComposed { + fn from(boolean: bool) -> Self { + if boolean { + EventComposed::Composed + } else { + EventComposed::NotComposed + } + } +} + +impl From for bool { + fn from(composed: EventComposed) -> Self { + match composed { + EventComposed::Composed => true, + EventComposed::NotComposed => false, + } + } +} + #[derive(Clone, Copy, Debug, Eq, JSTraceable, PartialEq)] #[repr(u16)] #[derive(MallocSizeOf)] diff --git a/components/script/dom/eventsource.rs b/components/script/dom/eventsource.rs index 428eabc8ad8..7cf7bd6106f 100644 --- a/components/script/dom/eventsource.rs +++ b/components/script/dom/eventsource.rs @@ -11,6 +11,7 @@ use std::time::Duration; use content_security_policy as csp; use dom_struct::dom_struct; use headers::ContentType; +use http::StatusCode; use http::header::{self, HeaderName, HeaderValue}; use ipc_channel::ipc; use ipc_channel::router::ROUTER; @@ -183,7 +184,9 @@ impl EventSourceContext { self.data.push_str(&self.value); self.data.push('\n'); }, - "id" => mem::swap(&mut self.last_event_id, &mut self.value), + "id" if !self.value.contains('\0') => { + mem::swap(&mut self.last_event_id, &mut self.value); + }, "retry" => { if let Ok(time) = u64::from_str(&self.value) { self.event_source @@ -349,6 +352,11 @@ impl FetchResponseListener for EventSourceContext { _ => unsafe_, }, }; + // Step 15.3 if res's status is not 200, or if res's `Content-Type` is not + // `text/event-stream`, then fail the connection. + if meta.status.code() != StatusCode::OK { + return self.fail_the_connection(); + } let mime = match meta.content_type { None => return self.fail_the_connection(), Some(ct) => >::into(ct.into_inner()), @@ -357,12 +365,15 @@ impl FetchResponseListener for EventSourceContext { return self.fail_the_connection(); } self.origin = meta.final_url.origin().ascii_serialization(); + // Step 15.4 announce the connection and interpret res's body line by line. self.announce_the_connection(); }, Err(_) => { - // The spec advises failing here if reconnecting would be - // "futile", with no more specific advice; WPT tests - // consider a non-http(s) scheme to be futile. + // Step 15.2 if res is a network error, then reestablish the connection, unless + // the user agent knows that to be futile, in which case the user agent may + // fail the connection. + + // WPT tests consider a non-http(s) scheme to be futile. match self.event_source.root().url.scheme() { "http" | "https" => self.reestablish_the_connection(), _ => self.fail_the_connection(), @@ -413,12 +424,14 @@ impl FetchResponseListener for EventSourceContext { fn process_response_eof( &mut self, _: RequestId, - _response: Result, + response: Result, ) { if self.incomplete_utf8.take().is_some() { self.parse("\u{FFFD}".chars(), CanGc::note()); } - self.reestablish_the_connection(); + if response.is_ok() { + self.reestablish_the_connection(); + } } fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming { @@ -435,7 +448,7 @@ impl FetchResponseListener for EventSourceContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } @@ -535,29 +548,35 @@ impl EventSourceMethods for EventSource { event_source_init: &EventSourceInit, ) -> Fallible> { // TODO: Step 2 relevant settings object - // Step 3 + // Step 3 Let urlRecord be the result of encoding-parsing a URL given url, + // relative to settings. let base_url = global.api_base_url(); let url_record = match base_url.join(&url) { Ok(u) => u, - // Step 4 + // Step 4 If urlRecord is failure, then throw a "SyntaxError" DOMException. Err(_) => return Err(Error::Syntax), }; - // Step 1, 5 + // Step 1 Let ev be a new EventSource object. let ev = EventSource::new( global, proto, + // Step 5 Set ev's url to urlRecord. url_record.clone(), event_source_init.withCredentials, can_gc, ); global.track_event_source(&ev); - // Steps 6-7 let cors_attribute_state = if event_source_init.withCredentials { + // Step 7 If the value of eventSourceInitDict's withCredentials member is true, + // then set corsAttributeState to Use Credentials and set ev's withCredentials + // attribute to true. CorsSettings::UseCredentials } else { + // Step 6 Let corsAttributeState be Anonymous. CorsSettings::Anonymous }; - // Step 8 + // Step 8 Let request be the result of creating a potential-CORS request + // given urlRecord, the empty string, and corsAttributeState. // TODO: Step 9 set request's client settings let mut request = create_a_potential_cors_request( global.webview_id(), @@ -573,17 +592,18 @@ impl EventSourceMethods for EventSource { .origin(global.origin().immutable().clone()) .pipeline_id(Some(global.pipeline_id())); - // Step 10 + // Step 10 User agents may set (`Accept`, `text/event-stream`) in request's header list. // TODO(eijebong): Replace once typed headers allow it request.headers.insert( header::ACCEPT, HeaderValue::from_static("text/event-stream"), ); - // Step 11 + // Step 11 Set request's cache mode to "no-store". request.cache_mode = CacheMode::NoStore; - // Step 12 + // Step 13 Set ev's request to request. *ev.request.borrow_mut() = Some(request.clone()); - // Step 14 + // Step 14 Let processEventSourceEndOfBody given response res be the following step: + // if res is not a network error, then reestablish the connection. let (action_sender, action_receiver) = ipc::channel().unwrap(); let context = EventSourceContext { incomplete_utf8: None, @@ -620,7 +640,7 @@ impl EventSourceMethods for EventSource { FetchChannels::ResponseMsg(action_sender), )) .unwrap(); - // Step 13 + // Step 16 Return ev. Ok(ev) } diff --git a/components/script/dom/gamepad.rs b/components/script/dom/gamepad.rs index dcafc58bcd9..baf3af7466f 100644 --- a/components/script/dom/gamepad.rs +++ b/components/script/dom/gamepad.rs @@ -13,7 +13,7 @@ use crate::dom::bindings::codegen::Bindings::GamepadBinding::{GamepadHand, Gamep use crate::dom::bindings::codegen::Bindings::GamepadButtonListBinding::GamepadButtonListMethods; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::num::Finite; -use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto}; +use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object}; use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::str::DOMString; use crate::dom::event::Event; @@ -23,6 +23,7 @@ use crate::dom::gamepadevent::{GamepadEvent, GamepadEventType}; use crate::dom::gamepadhapticactuator::GamepadHapticActuator; use crate::dom::gamepadpose::GamepadPose; use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; use crate::script_runtime::{CanGc, JSContext}; // This value is for determining when to consider a gamepad as having a user gesture @@ -88,39 +89,14 @@ impl Gamepad { } } - #[allow(clippy::too_many_arguments)] - pub(crate) fn new( - global: &GlobalScope, - gamepad_id: u32, - id: String, - mapping_type: String, - axis_bounds: (f64, f64), - button_bounds: (f64, f64), - supported_haptic_effects: GamepadSupportedHapticEffects, - xr: bool, - can_gc: CanGc, - ) -> DomRoot { - Self::new_with_proto( - global, - gamepad_id, - id, - mapping_type, - axis_bounds, - button_bounds, - supported_haptic_effects, - xr, - can_gc, - ) - } - /// When we construct a new gamepad, we initialize the number of buttons and /// axes corresponding to the "standard" gamepad mapping. /// The spec says UAs *may* do this for fingerprint mitigation, and it also /// happens to simplify implementation /// #[allow(clippy::too_many_arguments)] - fn new_with_proto( - global: &GlobalScope, + pub(crate) fn new( + window: &Window, gamepad_id: u32, id: String, mapping_type: String, @@ -130,11 +106,11 @@ impl Gamepad { xr: bool, can_gc: CanGc, ) -> DomRoot { - let button_list = GamepadButtonList::init_buttons(global, can_gc); + let button_list = GamepadButtonList::init_buttons(window, can_gc); let vibration_actuator = - GamepadHapticActuator::new(global, gamepad_id, supported_haptic_effects, can_gc); + GamepadHapticActuator::new(window, gamepad_id, supported_haptic_effects, can_gc); let index = if xr { -1 } else { 0 }; - let gamepad = reflect_dom_object_with_proto( + let gamepad = reflect_dom_object( Box::new(Gamepad::new_inherited( gamepad_id, id, @@ -149,8 +125,7 @@ impl Gamepad { button_bounds, &vibration_actuator, )), - global, - None, + window, can_gc, ); gamepad.init_axes(can_gc); diff --git a/components/script/dom/gamepadbutton.rs b/components/script/dom/gamepadbutton.rs index fead990ccd3..3588ba775ca 100644 --- a/components/script/dom/gamepadbutton.rs +++ b/components/script/dom/gamepadbutton.rs @@ -10,7 +10,7 @@ use crate::dom::bindings::codegen::Bindings::GamepadButtonBinding::GamepadButton use crate::dom::bindings::num::Finite; use crate::dom::bindings::reflector::{Reflector, reflect_dom_object}; use crate::dom::bindings::root::DomRoot; -use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; use crate::script_runtime::CanGc; #[dom_struct] @@ -32,14 +32,14 @@ impl GamepadButton { } pub(crate) fn new( - global: &GlobalScope, + window: &Window, pressed: bool, touched: bool, can_gc: CanGc, ) -> DomRoot { reflect_dom_object( Box::new(GamepadButton::new_inherited(pressed, touched)), - global, + window, can_gc, ) } diff --git a/components/script/dom/gamepadbuttonlist.rs b/components/script/dom/gamepadbuttonlist.rs index 50d9c8172bc..7e540ab56bb 100644 --- a/components/script/dom/gamepadbuttonlist.rs +++ b/components/script/dom/gamepadbuttonlist.rs @@ -8,7 +8,7 @@ use crate::dom::bindings::codegen::Bindings::GamepadButtonListBinding::GamepadBu use crate::dom::bindings::reflector::{Reflector, reflect_dom_object}; use crate::dom::bindings::root::{Dom, DomRoot, DomSlice}; use crate::dom::gamepadbutton::GamepadButton; -use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; use crate::script_runtime::CanGc; // https://w3c.github.io/gamepad/#gamepadbutton-interface @@ -28,13 +28,13 @@ impl GamepadButtonList { } pub(crate) fn new( - global: &GlobalScope, + window: &Window, list: &[&GamepadButton], can_gc: CanGc, ) -> DomRoot { reflect_dom_object( Box::new(GamepadButtonList::new_inherited(list)), - global, + window, can_gc, ) } @@ -62,27 +62,27 @@ impl GamepadButtonListMethods for GamepadButtonList { impl GamepadButtonList { /// Initialize the number of buttons in the "standard" gamepad mapping. /// - pub(crate) fn init_buttons(global: &GlobalScope, can_gc: CanGc) -> DomRoot { + pub(crate) fn init_buttons(window: &Window, can_gc: CanGc) -> DomRoot { let standard_buttons = &[ - GamepadButton::new(global, false, false, can_gc), // Bottom button in right cluster - GamepadButton::new(global, false, false, can_gc), // Right button in right cluster - GamepadButton::new(global, false, false, can_gc), // Left button in right cluster - GamepadButton::new(global, false, false, can_gc), // Top button in right cluster - GamepadButton::new(global, false, false, can_gc), // Top left front button - GamepadButton::new(global, false, false, can_gc), // Top right front button - GamepadButton::new(global, false, false, can_gc), // Bottom left front button - GamepadButton::new(global, false, false, can_gc), // Bottom right front button - GamepadButton::new(global, false, false, can_gc), // Left button in center cluster - GamepadButton::new(global, false, false, can_gc), // Right button in center cluster - GamepadButton::new(global, false, false, can_gc), // Left stick pressed button - GamepadButton::new(global, false, false, can_gc), // Right stick pressed button - GamepadButton::new(global, false, false, can_gc), // Top button in left cluster - GamepadButton::new(global, false, false, can_gc), // Bottom button in left cluster - GamepadButton::new(global, false, false, can_gc), // Left button in left cluster - GamepadButton::new(global, false, false, can_gc), // Right button in left cluster - GamepadButton::new(global, false, false, can_gc), // Center button in center cluster + GamepadButton::new(window, false, false, can_gc), // Bottom button in right cluster + GamepadButton::new(window, false, false, can_gc), // Right button in right cluster + GamepadButton::new(window, false, false, can_gc), // Left button in right cluster + GamepadButton::new(window, false, false, can_gc), // Top button in right cluster + GamepadButton::new(window, false, false, can_gc), // Top left front button + GamepadButton::new(window, false, false, can_gc), // Top right front button + GamepadButton::new(window, false, false, can_gc), // Bottom left front button + GamepadButton::new(window, false, false, can_gc), // Bottom right front button + GamepadButton::new(window, false, false, can_gc), // Left button in center cluster + GamepadButton::new(window, false, false, can_gc), // Right button in center cluster + GamepadButton::new(window, false, false, can_gc), // Left stick pressed button + GamepadButton::new(window, false, false, can_gc), // Right stick pressed button + GamepadButton::new(window, false, false, can_gc), // Top button in left cluster + GamepadButton::new(window, false, false, can_gc), // Bottom button in left cluster + GamepadButton::new(window, false, false, can_gc), // Left button in left cluster + GamepadButton::new(window, false, false, can_gc), // Right button in left cluster + GamepadButton::new(window, false, false, can_gc), // Center button in center cluster ]; rooted_vec!(let buttons <- standard_buttons.iter().map(DomRoot::as_traced)); - Self::new(global, buttons.r(), can_gc) + Self::new(window, buttons.r(), can_gc) } } diff --git a/components/script/dom/gamepadhapticactuator.rs b/components/script/dom/gamepadhapticactuator.rs index d19db6d1279..ddea21b97ee 100644 --- a/components/script/dom/gamepadhapticactuator.rs +++ b/components/script/dom/gamepadhapticactuator.rs @@ -18,12 +18,12 @@ use crate::dom::bindings::codegen::Bindings::GamepadHapticActuatorBinding::{ use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods; use crate::dom::bindings::error::Error; use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; -use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto}; +use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object}; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; use crate::dom::bindings::utils::to_frozen_array; -use crate::dom::globalscope::GlobalScope; use crate::dom::promise::Promise; +use crate::dom::window::Window; use crate::realms::InRealm; use crate::script_runtime::{CanGc, JSContext}; use crate::task_source::SendableTaskSource; @@ -98,27 +98,17 @@ impl GamepadHapticActuator { } pub(crate) fn new( - global: &GlobalScope, + window: &Window, gamepad_index: u32, supported_haptic_effects: GamepadSupportedHapticEffects, can_gc: CanGc, ) -> DomRoot { - Self::new_with_proto(global, gamepad_index, supported_haptic_effects, can_gc) - } - - fn new_with_proto( - global: &GlobalScope, - gamepad_index: u32, - supported_haptic_effects: GamepadSupportedHapticEffects, - can_gc: CanGc, - ) -> DomRoot { - reflect_dom_object_with_proto( + reflect_dom_object( Box::new(GamepadHapticActuator::new_inherited( gamepad_index, supported_haptic_effects, )), - global, - None, + window, can_gc, ) } diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index 2658911c795..55db2e4d248 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -2,7 +2,7 @@ * 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::{Cell, OnceCell}; +use std::cell::{Cell, OnceCell, Ref}; use std::collections::hash_map::Entry; use std::collections::{HashMap, VecDeque}; use std::ops::Index; @@ -18,18 +18,17 @@ use base::id::{ ServiceWorkerId, ServiceWorkerRegistrationId, WebViewId, }; use constellation_traits::{ - BlobData, BlobImpl, BroadcastMsg, FileBlob, MessagePortImpl, MessagePortMsg, PortMessageTask, - ScriptToConstellationChan, ScriptToConstellationMessage, + BlobData, BlobImpl, BroadcastMsg, FileBlob, LoadData, LoadOrigin, MessagePortImpl, + MessagePortMsg, PortMessageTask, ScriptToConstellationChan, ScriptToConstellationMessage, }; use content_security_policy::{ - CheckResult, CspList, PolicyDisposition, PolicySource, Violation, ViolationResource, + CheckResult, CspList, Destination, Initiator, NavigationCheckType, ParserMetadata, + PolicyDisposition, PolicySource, Request, Violation, ViolationResource, }; use crossbeam_channel::Sender; use devtools_traits::{PageError, ScriptToDevtoolsControlMsg}; use dom_struct::dom_struct; -use embedder_traits::{ - EmbedderMsg, GamepadEvent, GamepadSupportedHapticEffects, GamepadUpdateType, -}; +use embedder_traits::EmbedderMsg; use http::HeaderMap; use hyper_serde::Serde; use ipc_channel::ipc::{self, IpcSender}; @@ -64,6 +63,7 @@ use profile_traits::{ipc as profile_ipc, mem as profile_mem, time as profile_tim use script_bindings::interfaces::GlobalScopeHelpers; use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl}; use timers::{TimerEventId, TimerEventRequest, TimerSource}; +use url::Origin; use uuid::Uuid; #[cfg(feature = "webgpu")] use webgpu_traits::{DeviceLostReason, WebGPUDevice}; @@ -81,9 +81,7 @@ use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function; use crate::dom::bindings::codegen::Bindings::ImageBitmapBinding::{ ImageBitmapOptions, ImageBitmapSource, }; -use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods; use crate::dom::bindings::codegen::Bindings::NotificationBinding::NotificationPermissionCallback; -use crate::dom::bindings::codegen::Bindings::PerformanceBinding::Performance_Binding::PerformanceMethods; use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{ PermissionName, PermissionState, }; @@ -108,17 +106,17 @@ use crate::dom::crypto::Crypto; use crate::dom::dedicatedworkerglobalscope::{ DedicatedWorkerControlMsg, DedicatedWorkerGlobalScope, }; +use crate::dom::element::Element; use crate::dom::errorevent::ErrorEvent; use crate::dom::event::{Event, EventBubbles, EventCancelable, EventStatus}; use crate::dom::eventsource::EventSource; use crate::dom::eventtarget::EventTarget; use crate::dom::file::File; -use crate::dom::gamepad::{Gamepad, contains_user_gesture}; -use crate::dom::gamepadevent::GamepadEventType; use crate::dom::htmlscriptelement::{ScriptId, SourceCode}; use crate::dom::imagebitmap::ImageBitmap; use crate::dom::messageevent::MessageEvent; use crate::dom::messageport::MessagePort; +use crate::dom::node::Node; use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope; use crate::dom::performance::Performance; use crate::dom::performanceobserver::VALID_ENTRY_TYPES; @@ -457,8 +455,9 @@ pub(crate) struct ManagedMessagePort { /// and only add them, and ask the constellation to complete the transfer, /// in a subsequent task if the port hasn't been re-transfered. pending: bool, - /// Has the port been closed? If closed, it can be dropped and later GC'ed. - closed: bool, + /// Whether the port has been closed by script in this global, + /// so it can be removed. + explicitly_closed: bool, /// Note: it may seem strange to use a pair of options, versus for example an enum. /// But it looks like tranform streams will require both of those in their transfer. /// This will be resolved when we reach that point of the implementation. @@ -546,12 +545,17 @@ impl MessageListener { let mut succeeded = vec![]; let mut failed = HashMap::new(); - for (id, buffer) in ports.into_iter() { + for (id, info) in ports.into_iter() { if global.is_managing_port(&id) { succeeded.push(id); - global.complete_port_transfer(id, buffer); + global.complete_port_transfer( + id, + info.port_message_queue, + info.disentangled, + CanGc::note() + ); } else { - failed.insert(id, buffer); + failed.insert(id, info); } } let _ = global.script_to_constellation_chan().send( @@ -560,13 +564,21 @@ impl MessageListener { }) ); }, - MessagePortMsg::CompletePendingTransfer(port_id, buffer) => { + MessagePortMsg::CompletePendingTransfer(port_id, info) => { let context = self.context.clone(); self.task_source.queue(task!(complete_pending: move || { let global = context.root(); - global.complete_port_transfer(port_id, buffer); + global.complete_port_transfer(port_id, info.port_message_queue, info.disentangled, CanGc::note()); })); }, + MessagePortMsg::CompleteDisentanglement(port_id) => { + let context = self.context.clone(); + self.task_source + .queue(task!(try_complete_disentanglement: move || { + let global = context.root(); + global.try_complete_disentanglement(port_id, CanGc::note()); + })); + }, MessagePortMsg::NewTask(port_id, task) => { let context = self.context.clone(); self.task_source.queue(task!(process_new_task: move || { @@ -574,14 +586,6 @@ impl MessageListener { global.route_task_to_port(port_id, task, CanGc::note()); })); }, - MessagePortMsg::RemoveMessagePort(port_id) => { - let context = self.context.clone(); - self.task_source - .queue(task!(process_remove_message_port: move || { - let global = context.root(); - global.note_entangled_port_removed(&port_id); - })); - }, } } } @@ -871,7 +875,13 @@ impl GlobalScope { } /// Complete the transfer of a message-port. - fn complete_port_transfer(&self, port_id: MessagePortId, tasks: VecDeque) { + fn complete_port_transfer( + &self, + port_id: MessagePortId, + tasks: VecDeque, + disentangled: bool, + can_gc: CanGc, + ) { let should_start = if let MessagePortState::Managed(_id, message_ports) = &mut *self.message_port_state.borrow_mut() { @@ -885,6 +895,10 @@ impl GlobalScope { } if let Some(port_impl) = managed_port.port_impl.as_mut() { port_impl.complete_transfer(tasks); + if disentangled { + port_impl.disentangle(); + managed_port.dom_port.disentangle(); + } port_impl.enabled() } else { panic!("managed-port has no port-impl."); @@ -895,7 +909,45 @@ impl GlobalScope { panic!("complete_port_transfer called for an unknown port."); }; if should_start { - self.start_message_port(&port_id); + self.start_message_port(&port_id, can_gc); + } + } + + /// The closing of `otherPort`, if it is in a different global. + /// + fn try_complete_disentanglement(&self, port_id: MessagePortId, can_gc: CanGc) { + let dom_port = if let MessagePortState::Managed(_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + let dom_port = if let Some(managed_port) = message_ports.get_mut(&port_id) { + if managed_port.pending { + unreachable!("CompleteDisentanglement msg received for a pending port."); + } + let port_impl = managed_port + .port_impl + .as_mut() + .expect("managed-port has no port-impl."); + port_impl.disentangle(); + managed_port.dom_port.as_rooted() + } else { + // Note: this, and the other return below, + // can happen if the port has already been transferred out of this global, + // in which case the disentanglement will complete along with the transfer. + return; + }; + dom_port + } else { + return; + }; + + // Fire an event named close at otherPort. + dom_port.upcast().fire_event(atom!("close"), can_gc); + + let res = self.script_to_constellation_chan().send( + ScriptToConstellationMessage::DisentanglePorts(port_id, None), + ); + if res.is_err() { + warn!("Sending DisentanglePorts failed"); } } @@ -951,8 +1003,64 @@ impl GlobalScope { } /// - pub(crate) fn disentangle_port(&self, _port: &MessagePort) { - // TODO: #36465 + pub(crate) fn disentangle_port(&self, port: &MessagePort, can_gc: CanGc) { + let initiator_port = port.message_port_id(); + // Let otherPort be the MessagePort which initiatorPort was entangled with. + let Some(other_port) = port.disentangle() else { + // Assert: otherPort exists. + // Note: ignoring the assert, + // because the streams spec seems to disentangle ports that are disentangled already. + return; + }; + + // Disentangle initiatorPort and otherPort, so that they are no longer entangled or associated with each other. + // Note: this is done in part here, and in part at the constellation(if otherPort is in another global). + let dom_port = if let MessagePortState::Managed(_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + let mut dom_port = None; + for port_id in &[initiator_port, &other_port] { + match message_ports.get_mut(port_id) { + None => { + continue; + }, + Some(managed_port) => { + let port_impl = managed_port + .port_impl + .as_mut() + .expect("managed-port has no port-impl."); + managed_port.dom_port.disentangle(); + port_impl.disentangle(); + + if **port_id == other_port { + dom_port = Some(managed_port.dom_port.as_rooted()) + } + }, + } + } + dom_port + } else { + panic!("disentangle_port called on a global not managing any ports."); + }; + + // Fire an event named close at `otherPort`. + // Note: done here if the port is managed by the same global as `initialPort`. + if let Some(dom_port) = dom_port { + dom_port.upcast().fire_event(atom!("close"), can_gc); + } + + let chan = self.script_to_constellation_chan().clone(); + let initiator_port = *initiator_port; + self.task_manager() + .port_message_queue() + .queue(task!(post_message: move || { + // Note: we do this in a task to ensure it doesn't affect messages that are still to be routed, + // see the task queueing in `post_messageport_msg`. + let res = chan.send(ScriptToConstellationMessage::DisentanglePorts(initiator_port, Some(other_port))); + if res.is_err() { + warn!("Sending DisentanglePorts failed"); + } + })); } /// @@ -984,18 +1092,6 @@ impl GlobalScope { .send(ScriptToConstellationMessage::EntanglePorts(port1, port2)); } - /// Note that the entangled port of `port_id` has been removed in another global. - pub(crate) fn note_entangled_port_removed(&self, port_id: &MessagePortId) { - // Note: currently this is a no-op, - // as we only use the `close` method to manage the local lifecyle of a port. - // This could be used as part of lifecyle management to determine a port can be GC'ed. - // See https://github.com/servo/servo/issues/25772 - warn!( - "Entangled port of {:?} has been removed in another global", - port_id - ); - } - /// Handle the transfer of a port in the current task. pub(crate) fn mark_port_as_transferred(&self, port_id: &MessagePortId) -> MessagePortImpl { if let MessagePortState::Managed(_id, message_ports) = @@ -1021,20 +1117,21 @@ impl GlobalScope { } /// - pub(crate) fn start_message_port(&self, port_id: &MessagePortId) { - let message_buffer = if let MessagePortState::Managed(_id, message_ports) = + pub(crate) fn start_message_port(&self, port_id: &MessagePortId, can_gc: CanGc) { + let (message_buffer, dom_port) = if let MessagePortState::Managed(_id, message_ports) = &mut *self.message_port_state.borrow_mut() { - match message_ports.get_mut(port_id) { + let (message_buffer, dom_port) = match message_ports.get_mut(port_id) { None => panic!("start_message_port called on a unknown port."), Some(managed_port) => { if let Some(port_impl) = managed_port.port_impl.as_mut() { - port_impl.start() + (port_impl.start(), managed_port.dom_port.as_rooted()) } else { panic!("managed-port has no port-impl."); } }, - } + }; + (message_buffer, dom_port) } else { return warn!("start_message_port called on a global not managing any ports."); }; @@ -1042,6 +1139,18 @@ impl GlobalScope { for task in message_buffer { self.route_task_to_port(*port_id, task, CanGc::note()); } + if dom_port.disentangled() { + // + // Fire an event named close at otherPort. + dom_port.upcast().fire_event(atom!("close"), can_gc); + + let res = self.script_to_constellation_chan().send( + ScriptToConstellationMessage::DisentanglePorts(*port_id, None), + ); + if res.is_err() { + warn!("Sending DisentanglePorts failed"); + } + } } } @@ -1055,7 +1164,7 @@ impl GlobalScope { Some(managed_port) => { if let Some(port_impl) = managed_port.port_impl.as_mut() { port_impl.close(); - managed_port.closed = true; + managed_port.explicitly_closed = true; } else { panic!("managed-port has no port-impl."); } @@ -1436,12 +1545,7 @@ impl GlobalScope { let to_be_removed: Vec = message_ports .iter() .filter_map(|(id, managed_port)| { - if managed_port.closed { - // Let the constellation know to drop this port and the one it is entangled with, - // and to forward this message to the script-process where the entangled is found. - let _ = self - .script_to_constellation_chan() - .send(ScriptToConstellationMessage::RemoveMessagePort(*id)); + if managed_port.explicitly_closed { Some(*id) } else { None @@ -1451,6 +1555,9 @@ impl GlobalScope { for id in to_be_removed { message_ports.remove(&id); } + // Note: ports are only removed throught explicit closure by script in this global. + // TODO: #25772 + // TODO: remove ports when we can be sure their port message queue is empty(via the constellation). message_ports.is_empty() } else { false @@ -1581,7 +1688,7 @@ impl GlobalScope { port_impl: Some(port_impl), dom_port: Dom::from_ref(dom_port), pending: true, - closed: false, + explicitly_closed: false, cross_realm_transform_readable: None, cross_realm_transform_writable: None, }, @@ -1605,7 +1712,7 @@ impl GlobalScope { port_impl: Some(port_impl), dom_port: Dom::from_ref(dom_port), pending: false, - closed: false, + explicitly_closed: false, cross_realm_transform_readable: None, cross_realm_transform_writable: None, }, @@ -1716,12 +1823,8 @@ impl GlobalScope { /// In the case of a File-backed blob, this might incur synchronous read and caching. pub(crate) fn get_blob_bytes(&self, blob_id: &BlobId) -> Result, ()> { let parent = { - let blob_state = self.blob_state.borrow(); - let blob_info = blob_state - .get(blob_id) - .expect("get_blob_bytes for an unknown blob."); - match blob_info.blob_impl.blob_data() { - BlobData::Sliced(parent, rel_pos) => Some((*parent, rel_pos.clone())), + match *self.get_blob_data(blob_id) { + BlobData::Sliced(parent, rel_pos) => Some((parent, rel_pos)), _ => None, } }; @@ -1735,14 +1838,24 @@ impl GlobalScope { } } + /// Retrieve information about a specific blob from the blob store + /// + /// # Panics + /// This function panics if there is no blob with the given ID. + pub(crate) fn get_blob_data<'a>(&'a self, blob_id: &BlobId) -> Ref<'a, BlobData> { + Ref::map(self.blob_state.borrow(), |blob_state| { + blob_state + .get(blob_id) + .expect("get_blob_impl called for a unknown blob") + .blob_impl + .blob_data() + }) + } + /// Get bytes from a non-sliced blob fn get_blob_bytes_non_sliced(&self, blob_id: &BlobId) -> Result, ()> { - let blob_state = self.blob_state.borrow(); - let blob_info = blob_state - .get(blob_id) - .expect("get_blob_bytes_non_sliced called for a unknown blob."); - match blob_info.blob_impl.blob_data() { - BlobData::File(f) => { + match *self.get_blob_data(blob_id) { + BlobData::File(ref f) => { let (buffer, is_new_buffer) = match f.get_cache() { Some(bytes) => (bytes, false), None => { @@ -1758,7 +1871,7 @@ impl GlobalScope { Ok(buffer) }, - BlobData::Memory(s) => Ok(s.clone()), + BlobData::Memory(ref s) => Ok(s.clone()), BlobData::Sliced(_, _) => panic!("This blob doesn't have a parent."), } } @@ -1771,12 +1884,8 @@ impl GlobalScope { /// TODO: merge with `get_blob_bytes` by way of broader integration with blob streams. fn get_blob_bytes_or_file_id(&self, blob_id: &BlobId) -> BlobResult { let parent = { - let blob_state = self.blob_state.borrow(); - let blob_info = blob_state - .get(blob_id) - .expect("get_blob_bytes_or_file_id for an unknown blob."); - match blob_info.blob_impl.blob_data() { - BlobData::Sliced(parent, rel_pos) => Some((*parent, rel_pos.clone())), + match *self.get_blob_data(blob_id) { + BlobData::Sliced(parent, rel_pos) => Some((parent, rel_pos)), _ => None, } }; @@ -1801,16 +1910,12 @@ impl GlobalScope { /// tweaked for integration with streams. /// TODO: merge with `get_blob_bytes` by way of broader integration with blob streams. fn get_blob_bytes_non_sliced_or_file_id(&self, blob_id: &BlobId) -> BlobResult { - let blob_state = self.blob_state.borrow(); - let blob_info = blob_state - .get(blob_id) - .expect("get_blob_bytes_non_sliced_or_file_id called for a unknown blob."); - match blob_info.blob_impl.blob_data() { - BlobData::File(f) => match f.get_cache() { + match *self.get_blob_data(blob_id) { + BlobData::File(ref f) => match f.get_cache() { Some(bytes) => BlobResult::Bytes(bytes.clone()), None => BlobResult::File(f.get_id(), f.get_size() as usize), }, - BlobData::Memory(s) => BlobResult::Bytes(s.clone()), + BlobData::Memory(ref s) => BlobResult::Bytes(s.clone()), BlobData::Sliced(_, _) => panic!("This blob doesn't have a parent."), } } @@ -1826,39 +1931,27 @@ impl GlobalScope { /// pub(crate) fn get_blob_size(&self, blob_id: &BlobId) -> u64 { - let blob_state = self.blob_state.borrow(); let parent = { - let blob_info = blob_state - .get(blob_id) - .expect("get_blob_size called for a unknown blob."); - match blob_info.blob_impl.blob_data() { - BlobData::Sliced(parent, rel_pos) => Some((*parent, rel_pos.clone())), + match *self.get_blob_data(blob_id) { + BlobData::Sliced(parent, rel_pos) => Some((parent, rel_pos)), _ => None, } }; match parent { Some((parent_id, rel_pos)) => { - let parent_info = blob_state - .get(&parent_id) - .expect("Parent of blob whose size is unknown."); - let parent_size = match parent_info.blob_impl.blob_data() { - BlobData::File(f) => f.get_size(), - BlobData::Memory(v) => v.len() as u64, + let parent_size = match *self.get_blob_data(&parent_id) { + BlobData::File(ref f) => f.get_size(), + BlobData::Memory(ref v) => v.len() as u64, BlobData::Sliced(_, _) => panic!("Blob ancestry should be only one level."), }; rel_pos.to_abs_range(parent_size as usize).len() as u64 }, - None => { - let blob_info = blob_state - .get(blob_id) - .expect("Blob whose size is unknown."); - match blob_info.blob_impl.blob_data() { - BlobData::File(f) => f.get_size(), - BlobData::Memory(v) => v.len() as u64, - BlobData::Sliced(_, _) => { - panic!("It was previously checked that this blob does not have a parent.") - }, - } + None => match *self.get_blob_data(blob_id) { + BlobData::File(ref f) => f.get_size(), + BlobData::Memory(ref v) => v.len() as u64, + BlobData::Sliced(_, _) => { + panic!("It was previously checked that this blob does not have a parent.") + }, }, } } @@ -1874,7 +1967,7 @@ impl GlobalScope { blob_info.has_url = true; match blob_info.blob_impl.blob_data() { - BlobData::Sliced(parent, rel_pos) => Some((*parent, rel_pos.clone())), + BlobData::Sliced(parent, rel_pos) => Some((*parent, *rel_pos)), _ => None, } }; @@ -1915,12 +2008,8 @@ impl GlobalScope { let origin = get_blob_origin(&self.get_url()); let (tx, rx) = profile_ipc::channel(self.time_profiler_chan().clone()).unwrap(); - let msg = FileManagerThreadMsg::AddSlicedURLEntry( - *parent_file_id, - rel_pos.clone(), - tx, - origin.clone(), - ); + let msg = + FileManagerThreadMsg::AddSlicedURLEntry(*parent_file_id, *rel_pos, tx, origin.clone()); self.send_to_file_manager(msg); match rx.recv().expect("File manager thread is down.") { Ok(new_id) => { @@ -2422,7 +2511,8 @@ impl GlobalScope { headers: &Option>, ) -> Option { // TODO: Implement step 1 (local scheme special case) - let mut csp = headers.as_ref()?.get_all("content-security-policy").iter(); + let headers = headers.as_ref()?; + let mut csp = headers.get_all("content-security-policy").iter(); // This silently ignores the CSP if it contains invalid Unicode. // We should probably report an error somewhere. let c = csp.next().and_then(|c| c.to_str().ok())?; @@ -2435,6 +2525,19 @@ impl GlobalScope { PolicyDisposition::Enforce, )); } + let csp_report = headers + .get_all("content-security-policy-report-only") + .iter(); + // This silently ignores the CSP if it contains invalid Unicode. + // We should probably report an error somewhere. + for c in csp_report { + let c = c.to_str().ok()?; + csp_list.append(CspList::parse( + c, + PolicySource::Header, + PolicyDisposition::Report, + )); + } Some(csp_list) } @@ -2822,36 +2925,43 @@ impl GlobalScope { })) } - #[allow(unsafe_code)] - pub(crate) fn is_js_evaluation_allowed(&self, cx: SafeJSContext) -> bool { + pub(crate) fn is_js_evaluation_allowed(&self, source: &str) -> bool { let Some(csp_list) = self.get_csp_list() else { return true; }; - let scripted_caller = unsafe { describe_scripted_caller(*cx) }.unwrap_or_default(); - let is_js_evaluation_allowed = csp_list.is_js_evaluation_allowed() == CheckResult::Allowed; + let (is_js_evaluation_allowed, violations) = csp_list.is_js_evaluation_allowed(source); - if !is_js_evaluation_allowed { - // FIXME: Don't fire event if `script-src` and `default-src` - // were not passed. - for policy in csp_list.0 { - let report = CSPViolationReportBuilder::default() - .resource("eval".to_owned()) - .effective_directive("script-src".to_owned()) - .report_only(policy.disposition == PolicyDisposition::Report) - .source_file(scripted_caller.filename.clone()) - .line_number(scripted_caller.line) - .column_number(scripted_caller.col) - .build(self); - let task = CSPViolationReportTask::new(self, report); + self.report_csp_violations(violations, None); - self.task_manager() - .dom_manipulation_task_source() - .queue(task); - } - } + is_js_evaluation_allowed == CheckResult::Allowed + } - is_js_evaluation_allowed + pub(crate) fn should_navigation_request_be_blocked(&self, load_data: &LoadData) -> bool { + let Some(csp_list) = self.get_csp_list() else { + return false; + }; + let request = Request { + url: load_data.url.clone().into_url(), + origin: match &load_data.load_origin { + LoadOrigin::Script(immutable_origin) => immutable_origin.clone().into_url_origin(), + _ => Origin::new_opaque(), + }, + // TODO: populate this field correctly + redirect_count: 0, + destination: Destination::None, + initiator: Initiator::None, + nonce: "".to_owned(), + integrity_metadata: "".to_owned(), + parser_metadata: ParserMetadata::None, + }; + // TODO: set correct navigation check type for form submission if applicable + let (result, violations) = + csp_list.should_navigation_request_be_blocked(&request, NavigationCheckType::Other); + + self.report_csp_violations(violations, None); + + result == CheckResult::Blocked } pub(crate) fn create_image_bitmap( @@ -2880,15 +2990,11 @@ impl GlobalScope { return p; } - if let Some((data, size)) = canvas.fetch_all_data() { - let data = data - .map(|data| data.to_vec()) - .unwrap_or_else(|| vec![0; size.area() as usize * 4]); - + if let Some(snapshot) = canvas.get_image_data() { + let size = snapshot.size().cast(); let image_bitmap = ImageBitmap::new(self, size.width, size.height, can_gc).unwrap(); - - image_bitmap.set_bitmap_data(data); + image_bitmap.set_bitmap_data(snapshot.to_vec()); image_bitmap.set_origin_clean(canvas.origin_is_clean()); p.resolve_native(&(image_bitmap), can_gc); } @@ -2901,14 +3007,11 @@ impl GlobalScope { return p; } - if let Some((data, size)) = canvas.fetch_all_data() { - let data = data - .map(|data| data.to_vec()) - .unwrap_or_else(|| vec![0; size.area() as usize * 4]); - + if let Some(snapshot) = canvas.get_image_data() { + let size = snapshot.size().cast(); let image_bitmap = ImageBitmap::new(self, size.width, size.height, can_gc).unwrap(); - image_bitmap.set_bitmap_data(data); + image_bitmap.set_bitmap_data(snapshot.to_vec()); image_bitmap.set_origin_clean(canvas.origin_is_clean()); p.resolve_native(&(image_bitmap), can_gc); } @@ -3191,134 +3294,6 @@ impl GlobalScope { } } - pub(crate) fn handle_gamepad_event(&self, gamepad_event: GamepadEvent) { - match gamepad_event { - GamepadEvent::Connected(index, name, bounds, supported_haptic_effects) => { - self.handle_gamepad_connect( - index.0, - name, - bounds.axis_bounds, - bounds.button_bounds, - supported_haptic_effects, - ); - }, - GamepadEvent::Disconnected(index) => { - self.handle_gamepad_disconnect(index.0); - }, - GamepadEvent::Updated(index, update_type) => { - self.receive_new_gamepad_button_or_axis(index.0, update_type); - }, - }; - } - - /// - fn handle_gamepad_connect( - &self, - // As the spec actually defines how to set the gamepad index, the GilRs index - // is currently unused, though in practice it will almost always be the same. - // More infra is currently needed to track gamepads across windows. - _index: usize, - name: String, - axis_bounds: (f64, f64), - button_bounds: (f64, f64), - supported_haptic_effects: GamepadSupportedHapticEffects, - ) { - // TODO: 2. If document is not null and is not allowed to use the "gamepad" permission, - // then abort these steps. - let this = Trusted::new(self); - self.task_manager() - .gamepad_task_source() - .queue(task!(gamepad_connected: move || { - let global = this.root(); - - if let Some(window) = global.downcast::() { - let navigator = window.Navigator(); - let selected_index = navigator.select_gamepad_index(); - let gamepad = Gamepad::new( - &global, - selected_index, - name, - "standard".into(), - axis_bounds, - button_bounds, - supported_haptic_effects, - false, - CanGc::note(), - ); - navigator.set_gamepad(selected_index as usize, &gamepad, CanGc::note()); - } - })); - } - - /// - pub(crate) fn handle_gamepad_disconnect(&self, index: usize) { - let this = Trusted::new(self); - self.task_manager() - .gamepad_task_source() - .queue(task!(gamepad_disconnected: move || { - let global = this.root(); - if let Some(window) = global.downcast::() { - let navigator = window.Navigator(); - if let Some(gamepad) = navigator.get_gamepad(index) { - if window.Document().is_fully_active() { - gamepad.update_connected(false, gamepad.exposed(), CanGc::note()); - navigator.remove_gamepad(index); - } - } - } - })); - } - - /// - pub(crate) fn receive_new_gamepad_button_or_axis( - &self, - index: usize, - update_type: GamepadUpdateType, - ) { - let this = Trusted::new(self); - - // - self.task_manager().gamepad_task_source().queue( - task!(update_gamepad_state: move || { - let global = this.root(); - if let Some(window) = global.downcast::() { - let navigator = window.Navigator(); - if let Some(gamepad) = navigator.get_gamepad(index) { - let current_time = global.performance().Now(); - gamepad.update_timestamp(*current_time); - match update_type { - GamepadUpdateType::Axis(index, value) => { - gamepad.map_and_normalize_axes(index, value); - }, - GamepadUpdateType::Button(index, value) => { - gamepad.map_and_normalize_buttons(index, value); - } - }; - if !navigator.has_gamepad_gesture() && contains_user_gesture(update_type) { - navigator.set_has_gamepad_gesture(true); - navigator.GetGamepads() - .iter() - .filter_map(|g| g.as_ref()) - .for_each(|gamepad| { - gamepad.set_exposed(true); - gamepad.update_timestamp(*current_time); - let new_gamepad = Trusted::new(&**gamepad); - if window.Document().is_fully_active() { - global.task_manager().gamepad_task_source().queue( - task!(update_gamepad_connect: move || { - let gamepad = new_gamepad.root(); - gamepad.notify_event(GamepadEventType::Connected, CanGc::note()); - }) - ); - } - }); - } - } - } - }) - ); - } - pub(crate) fn current_group_label(&self) -> Option { self.console_group_stack .borrow() @@ -3471,10 +3446,18 @@ impl GlobalScope { unreachable!(); } - pub(crate) fn report_csp_violations(&self, violations: Vec) { + /// + #[allow(unsafe_code)] + pub(crate) fn report_csp_violations( + &self, + violations: Vec, + element: Option<&Element>, + ) { + let scripted_caller = + unsafe { describe_scripted_caller(*GlobalScope::get_cx()) }.unwrap_or_default(); for violation in violations { let (sample, resource) = match violation.resource { - ViolationResource::Inline { .. } => (None, "inline".to_owned()), + ViolationResource::Inline { sample } => (sample, "inline".to_owned()), ViolationResource::Url(url) => (None, url.into()), ViolationResource::TrustedTypePolicy { sample } => { (Some(sample), "trusted-types-policy".to_owned()) @@ -3482,6 +3465,8 @@ impl GlobalScope { ViolationResource::TrustedTypeSink { sample } => { (Some(sample), "trusted-types-sink".to_owned()) }, + ViolationResource::Eval { sample } => (sample, "eval".to_owned()), + ViolationResource::WasmEval => (None, "wasm-eval".to_owned()), }; let report = CSPViolationReportBuilder::default() .resource(resource) @@ -3489,8 +3474,42 @@ impl GlobalScope { .effective_directive(violation.directive.name) .original_policy(violation.policy.to_string()) .report_only(violation.policy.disposition == PolicyDisposition::Report) + .source_file(scripted_caller.filename.clone()) + .line_number(scripted_caller.line) + .column_number(scripted_caller.col + 1) .build(self); - let task = CSPViolationReportTask::new(self, report); + // Step 1: Let global be violation’s global object. + // We use `self` as `global`; + // Step 2: Let target be violation’s element. + let target = element.and_then(|event_target| { + // Step 3.1: If target is not null, and global is a Window, + // and target’s shadow-including root is not global’s associated Document, set target to null. + if let Some(window) = self.downcast::() { + if !window + .Document() + .upcast::() + .is_shadow_including_inclusive_ancestor_of(event_target.upcast()) + { + return None; + } + } + Some(event_target) + }); + let target = match target { + // Step 3.2: If target is null: + None => { + // Step 3.2.2: If target is a Window, set target to target’s associated Document. + if let Some(window) = self.downcast::() { + Trusted::new(window.Document().upcast()) + } else { + // Step 3.2.1: Set target to violation’s global object. + Trusted::new(self.upcast()) + } + }, + Some(event_target) => Trusted::new(event_target.upcast()), + }; + // Step 3: Queue a task to run the following steps: + let task = CSPViolationReportTask::new(Trusted::new(self), target, report); self.task_manager() .dom_manipulation_task_source() .queue(task); @@ -3543,10 +3562,6 @@ impl GlobalScopeHelpers for GlobalScope { GlobalScope::from_reflector(reflector, realm) } - unsafe fn from_object_maybe_wrapped(obj: *mut JSObject, cx: *mut JSContext) -> DomRoot { - GlobalScope::from_object_maybe_wrapped(obj, cx) - } - fn origin(&self) -> &MutableOrigin { GlobalScope::origin(self) } diff --git a/components/script/dom/gpucanvascontext.rs b/components/script/dom/gpucanvascontext.rs index 5304d0f5d3b..f47e1dfddd1 100644 --- a/components/script/dom/gpucanvascontext.rs +++ b/components/script/dom/gpucanvascontext.rs @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use dom_struct::dom_struct; -use script_layout_interface::HTMLCanvasDataSource; +use webrender_api::ImageKey; use crate::dom::bindings::codegen::Bindings::GPUCanvasContextBinding::GPUCanvasContextMethods; use crate::dom::bindings::codegen::UnionTypes; @@ -31,7 +31,7 @@ impl GPUCanvasContextMethods for GPUCanvasContext { } impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, GPUCanvasContext> { - fn canvas_data_source(self) -> HTMLCanvasDataSource { + fn canvas_data_source(self) -> Option { unimplemented!() } } diff --git a/components/script/dom/headers.rs b/components/script/dom/headers.rs index 10a8be731bf..f195992faa5 100644 --- a/components/script/dom/headers.rs +++ b/components/script/dom/headers.rs @@ -5,14 +5,15 @@ use std::cell::Cell; use std::str::{self, FromStr}; -use data_url::mime::Mime as DataUrlMime; use dom_struct::dom_struct; use http::header::{HeaderMap as HyperHeaders, HeaderName, HeaderValue}; use js::rust::HandleObject; use net_traits::fetch::headers::{ - get_decode_and_split_header_value, get_value_from_header_list, is_forbidden_method, + extract_mime_type, get_decode_and_split_header_value, get_value_from_header_list, + is_forbidden_method, }; use net_traits::request::is_cors_safelisted_request_header; +use net_traits::trim_http_whitespace; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::HeadersBinding::{HeadersInit, HeadersMethods}; @@ -33,7 +34,7 @@ pub(crate) struct Headers { header_list: DomRefCell, } -// https://fetch.spec.whatwg.org/#concept-headers-guard +/// #[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)] pub(crate) enum Guard { Immutable, @@ -66,7 +67,7 @@ impl Headers { } impl HeadersMethods for Headers { - // https://fetch.spec.whatwg.org/#dom-headers + /// fn Constructor( global: &GlobalScope, proto: Option, @@ -78,47 +79,41 @@ impl HeadersMethods for Headers { Ok(dom_headers_new) } - // https://fetch.spec.whatwg.org/#concept-headers-append + /// fn Append(&self, name: ByteString, value: ByteString) -> ErrorResult { - // Step 1 - let value = normalize_value(value); + // 1. Normalize value. + let value = trim_http_whitespace(&value); - // Step 2 - // https://fetch.spec.whatwg.org/#headers-validate - let (mut valid_name, valid_value) = validate_name_and_value(name, value)?; + // 2. If validating (name, value) for headers returns false, then return. + let Some((mut valid_name, valid_value)) = + self.validate_name_and_value(name, ByteString::new(value.into()))? + else { + return Ok(()); + }; valid_name = valid_name.to_lowercase(); - if self.guard.get() == Guard::Immutable { - return Err(Error::Type("Guard is immutable".to_string())); - } - if self.guard.get() == Guard::Request && - is_forbidden_request_header(&valid_name, &valid_value) - { - return Ok(()); - } - if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) { - return Ok(()); - } - - // Step 3 + // 3. If headers’s guard is "request-no-cors": if self.guard.get() == Guard::RequestNoCors { + // 3.1. Let temporaryValue be the result of getting name from headers’s header list. let tmp_value = if let Some(mut value) = get_value_from_header_list(&valid_name, &self.header_list.borrow()) { + // 3.3. Otherwise, set temporaryValue to temporaryValue, followed by 0x2C 0x20, followed by value. value.extend(b", "); - value.extend(valid_value.clone()); + value.extend(valid_value.to_vec()); value } else { - valid_value.clone() + // 3.2. If temporaryValue is null, then set temporaryValue to value. + valid_value.to_vec() }; - + // 3.4. If (name, temporaryValue) is not a no-CORS-safelisted request-header, then return. if !is_cors_safelisted_request_header(&valid_name, &tmp_value) { return Ok(()); } } - // Step 4 + // 4. Append (name, value) to headers’s header list. match HeaderValue::from_bytes(&valid_value) { Ok(value) => { self.header_list @@ -134,7 +129,7 @@ impl HeadersMethods for Headers { }, }; - // Step 5 + // 5. If headers’s guard is "request-no-cors", then remove privileged no-CORS request-headers from headers. if self.guard.get() == Guard::RequestNoCors { self.remove_privileged_no_cors_request_headers(); } @@ -142,50 +137,53 @@ impl HeadersMethods for Headers { Ok(()) } - // https://fetch.spec.whatwg.org/#dom-headers-delete + /// fn Delete(&self, name: ByteString) -> ErrorResult { - // Step 1 - let (mut valid_name, valid_value) = validate_name_and_value(name, ByteString::new(vec![]))?; + // Step 1 If validating (name, ``) for this returns false, then return. + let name_and_value = self.validate_name_and_value(name, ByteString::new(vec![]))?; + let Some((mut valid_name, _valid_value)) = name_and_value else { + return Ok(()); + }; valid_name = valid_name.to_lowercase(); - // Step 2 - if self.guard.get() == Guard::Immutable { - return Err(Error::Type("Guard is immutable".to_string())); - } - // Step 3 - if self.guard.get() == Guard::Request && - is_forbidden_request_header(&valid_name, &valid_value) - { - return Ok(()); - } - // Step 4 + // Step 2 If this’s guard is "request-no-cors", name is not a no-CORS-safelisted request-header name, + // and name is not a privileged no-CORS request-header name, then return. if self.guard.get() == Guard::RequestNoCors && !is_cors_safelisted_request_header(&valid_name, &b"invalid".to_vec()) { return Ok(()); } - // Step 5 - if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) { - return Ok(()); + + // 3. If this’s header list does not contain name, then return. + // 4. Delete name from this’s header list. + self.header_list.borrow_mut().remove(valid_name); + + // 5. If this’s guard is "request-no-cors", then remove privileged no-CORS request-headers from this. + if self.guard.get() == Guard::RequestNoCors { + self.remove_privileged_no_cors_request_headers(); } - // Step 6 - self.header_list.borrow_mut().remove(&valid_name); + Ok(()) } - // https://fetch.spec.whatwg.org/#dom-headers-get + /// fn Get(&self, name: ByteString) -> Fallible> { - // Step 1 + // 1. If name is not a header name, then throw a TypeError. let valid_name = validate_name(name)?; + + // 2. Return the result of getting name from this’s header list. Ok( get_value_from_header_list(&valid_name, &self.header_list.borrow()) .map(ByteString::new), ) } - // https://fetch.spec.whatwg.org/#dom-headers-getsetcookie + /// fn GetSetCookie(&self) -> Vec { + // 1. If this’s header list does not contain `Set-Cookie`, then return « ». + // 2. Return the values of all headers in this’s header list whose name is a + // byte-case-insensitive match for `Set-Cookie`, in order. self.header_list .borrow() .get_all("set-cookie") @@ -194,42 +192,36 @@ impl HeadersMethods for Headers { .collect() } - // https://fetch.spec.whatwg.org/#dom-headers-has + /// fn Has(&self, name: ByteString) -> Fallible { - // Step 1 + // 1. If name is not a header name, then throw a TypeError. let valid_name = validate_name(name)?; - // Step 2 + // 2. Return true if this’s header list contains name; otherwise false. Ok(self.header_list.borrow_mut().get(&valid_name).is_some()) } - // https://fetch.spec.whatwg.org/#dom-headers-set + /// fn Set(&self, name: ByteString, value: ByteString) -> Fallible<()> { - // Step 1 - let value = normalize_value(value); - // Step 2 - let (mut valid_name, valid_value) = validate_name_and_value(name, value)?; + // 1. Normalize value + let value = trim_http_whitespace(&value); + + // 2. If validating (name, value) for this returns false, then return. + let Some((mut valid_name, valid_value)) = + self.validate_name_and_value(name, ByteString::new(value.into()))? + else { + return Ok(()); + }; valid_name = valid_name.to_lowercase(); - // Step 3 - if self.guard.get() == Guard::Immutable { - return Err(Error::Type("Guard is immutable".to_string())); - } - // Step 4 - if self.guard.get() == Guard::Request && - is_forbidden_request_header(&valid_name, &valid_value) - { - return Ok(()); - } - // Step 5 + + // 3. If this’s guard is "request-no-cors" and (name, value) is not a + // no-CORS-safelisted request-header, then return. if self.guard.get() == Guard::RequestNoCors && - !is_cors_safelisted_request_header(&valid_name, &valid_value) + !is_cors_safelisted_request_header(&valid_name, &valid_value.to_vec()) { return Ok(()); } - // Step 6 - if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) { - return Ok(()); - } - // Step 7 + + // 4. Set (name, value) in this’s header list. // https://fetch.spec.whatwg.org/#concept-header-list-set match HeaderValue::from_bytes(&valid_value) { Ok(value) => { @@ -245,6 +237,12 @@ impl HeadersMethods for Headers { ); }, }; + + // 5. If this’s guard is "request-no-cors", then remove privileged no-CORS request-headers from this. + if self.guard.get() == Guard::RequestNoCors { + self.remove_privileged_no_cors_request_headers(); + } + Ok(()) } } @@ -260,7 +258,7 @@ impl Headers { Ok(()) } - // https://fetch.spec.whatwg.org/#concept-headers-fill + /// pub(crate) fn fill(&self, filler: Option) -> ErrorResult { match filler { Some(HeadersInit::ByteStringSequenceSequence(v)) => { @@ -316,12 +314,12 @@ impl Headers { self.header_list.borrow_mut().clone() } - // https://fetch.spec.whatwg.org/#concept-header-extract-mime-type + /// pub(crate) fn extract_mime_type(&self) -> Vec { extract_mime_type(&self.header_list.borrow()).unwrap_or_default() } - // https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine + /// pub(crate) fn sort_and_combine(&self) -> Vec<(String, Vec)> { let borrowed_header_list = self.header_list.borrow(); let mut header_vec = vec![]; @@ -341,11 +339,38 @@ impl Headers { header_vec } - // https://fetch.spec.whatwg.org/#ref-for-privileged-no-cors-request-header-name + /// pub(crate) fn remove_privileged_no_cors_request_headers(&self) { - // https://fetch.spec.whatwg.org/#privileged-no-cors-request-header-name + // self.header_list.borrow_mut().remove("range"); } + + /// + pub(crate) fn validate_name_and_value( + &self, + name: ByteString, + value: ByteString, + ) -> Fallible> { + // 1. If name is not a header name or value is not a header value, then throw a TypeError. + let valid_name = validate_name(name)?; + if !is_legal_header_value(&value) { + return Err(Error::Type("Header value is not valid".to_string())); + } + // 2. If headers’s guard is "immutable", then throw a TypeError. + if self.guard.get() == Guard::Immutable { + return Err(Error::Type("Guard is immutable".to_string())); + } + // 3. If headers’s guard is "request" and (name, value) is a forbidden request-header, then return false. + if self.guard.get() == Guard::Request && is_forbidden_request_header(&valid_name, &value) { + return Ok(None); + } + // 4. If headers’s guard is "response" and name is a forbidden response-header name, then return false. + if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) { + return Ok(None); + } + + Ok(Some((valid_name, value))) + } } impl Iterable for Headers { @@ -391,6 +416,7 @@ pub(crate) fn is_forbidden_request_header(name: &str, value: &[u8]) -> bool { "keep-alive", "origin", "referer", + "set-cookie", "te", "trailer", "transfer-encoding", @@ -448,26 +474,11 @@ pub(crate) fn is_forbidden_request_header(name: &str, value: &[u8]) -> bool { false } -// https://fetch.spec.whatwg.org/#forbidden-response-header-name +/// fn is_forbidden_response_header(name: &str) -> bool { - matches!(name, "set-cookie" | "set-cookie2") -} - -// There is some unresolved confusion over the definition of a name and a value. -// -// As of December 2019, WHATWG has no formal grammar production for value; -// https://fetch.spec.whatg.org/#concept-header-value just says not to have -// newlines, nulls, or leading/trailing whitespace. It even allows -// octets that aren't a valid UTF-8 encoding, and WPT tests reflect this. -// The HeaderValue class does not fully reflect this, so headers -// containing bytes with values 1..31 or 127 can't be created, failing -// WPT tests but probably not affecting anything important on the real Internet. -fn validate_name_and_value(name: ByteString, value: ByteString) -> Fallible<(String, Vec)> { - let valid_name = validate_name(name)?; - if !is_legal_header_value(&value) { - return Err(Error::Type("Header value is not valid".to_string())); - } - Ok((valid_name, value.into())) + // A forbidden response-header name is a header name that is a byte-case-insensitive match for one of + let name = name.to_ascii_lowercase(); + matches!(name.as_str(), "set-cookie" | "set-cookie2") } fn validate_name(name: ByteString) -> Fallible { @@ -480,47 +491,20 @@ fn validate_name(name: ByteString) -> Fallible { } } -// Removes trailing and leading HTTP whitespace bytes. -// https://fetch.spec.whatwg.org/#concept-header-value-normalize -pub fn normalize_value(value: ByteString) -> ByteString { - match ( - index_of_first_non_whitespace(&value), - index_of_last_non_whitespace(&value), - ) { - (Some(begin), Some(end)) => ByteString::new(value[begin..end + 1].to_owned()), - _ => ByteString::new(vec![]), - } -} - -fn is_http_whitespace(byte: u8) -> bool { - byte == b'\t' || byte == b'\n' || byte == b'\r' || byte == b' ' -} - -fn index_of_first_non_whitespace(value: &ByteString) -> Option { - for (index, &byte) in value.iter().enumerate() { - if !is_http_whitespace(byte) { - return Some(index); - } - } - None -} - -fn index_of_last_non_whitespace(value: &ByteString) -> Option { - for (index, &byte) in value.iter().enumerate().rev() { - if !is_http_whitespace(byte) { - return Some(index); - } - } - None -} - -// http://tools.ietf.org/html/rfc7230#section-3.2 +/// fn is_field_name(name: &ByteString) -> bool { is_token(name) } -// https://fetch.spec.whatg.org/#concept-header-value -fn is_legal_header_value(value: &ByteString) -> bool { +// As of December 2019, WHATWG has no formal grammar production for value; +// https://fetch.spec.whatg.org/#concept-header-value just says not to have +// newlines, nulls, or leading/trailing whitespace. It even allows +// octets that aren't a valid UTF-8 encoding, and WPT tests reflect this. +// The HeaderValue class does not fully reflect this, so headers +// containing bytes with values 1..31 or 127 can't be created, failing +// WPT tests but probably not affecting anything important on the real Internet. +/// +fn is_legal_header_value(value: &[u8]) -> bool { let value_len = value.len(); if value_len == 0 { return true; @@ -533,7 +517,7 @@ fn is_legal_header_value(value: &ByteString) -> bool { b' ' | b'\t' => return false, _ => {}, }; - for &ch in &value[..] { + for &ch in value { match ch { b'\0' | b'\n' | b'\r' => return false, _ => {}, @@ -555,81 +539,12 @@ fn is_legal_header_value(value: &ByteString) -> bool { // } } -// https://tools.ietf.org/html/rfc5234#appendix-B.1 +/// pub(crate) fn is_vchar(x: u8) -> bool { matches!(x, 0x21..=0x7E) } -// http://tools.ietf.org/html/rfc7230#section-3.2.6 +/// pub(crate) fn is_obs_text(x: u8) -> bool { matches!(x, 0x80..=0xFF) } - -// https://fetch.spec.whatwg.org/#concept-header-extract-mime-type -// This function uses data_url::Mime to parse the MIME Type because -// mime::Mime does not provide a parser following the Fetch spec -// see https://github.com/hyperium/mime/issues/106 -pub(crate) fn extract_mime_type(headers: &HyperHeaders) -> Option> { - let mut charset: Option = None; - let mut essence: String = "".to_string(); - let mut mime_type: Option = None; - - // Step 4 - let headers_values = headers.get_all(http::header::CONTENT_TYPE).iter(); - - // Step 5 - if headers_values.size_hint() == (0, Some(0)) { - return None; - } - - // Step 6 - for header_value in headers_values { - // Step 6.1 - match DataUrlMime::from_str(header_value.to_str().unwrap_or("")) { - // Step 6.2 - Err(_) => continue, - Ok(temp_mime) => { - let temp_essence = format!("{}/{}", temp_mime.type_, temp_mime.subtype); - - // Step 6.2 - if temp_essence == "*/*" { - continue; - } - - let temp_charset = &temp_mime.get_parameter("charset"); - - // Step 6.3 - mime_type = Some(DataUrlMime { - type_: temp_mime.type_.to_string(), - subtype: temp_mime.subtype.to_string(), - parameters: temp_mime.parameters.clone(), - }); - - // Step 6.4 - if temp_essence != essence { - charset = temp_charset.map(|c| c.to_string()); - temp_essence.clone_into(&mut essence); - } else { - // Step 6.5 - if temp_charset.is_none() && charset.is_some() { - let DataUrlMime { - type_: t, - subtype: st, - parameters: p, - } = mime_type.unwrap(); - let mut params = p; - params.push(("charset".to_string(), charset.clone().unwrap())); - mime_type = Some(DataUrlMime { - type_: t.to_string(), - subtype: st.to_string(), - parameters: params, - }) - } - } - }, - } - } - - // Step 7, 8 - mime_type.map(|m| format!("{}", m).into_bytes()) -} diff --git a/components/script/dom/htmlbodyelement.rs b/components/script/dom/htmlbodyelement.rs index b4efba9bed9..19b0ab4efce 100644 --- a/components/script/dom/htmlbodyelement.rs +++ b/components/script/dom/htmlbodyelement.rs @@ -181,26 +181,31 @@ impl VirtualMethods for HTMLBodyElement { (name, AttributeMutation::Set(_)) if name.starts_with("on") => { let window = self.owner_window(); // https://html.spec.whatwg.org/multipage/ - // #event-handlers-on-elements,-document-objects,-and-window-objects:event-handlers-3 + // #event-handlers-on-elements,-document-objects,-and-window-objects:event-handlers-6 match name { - &local_name!("onfocus") | - &local_name!("onload") | - &local_name!("onscroll") | &local_name!("onafterprint") | &local_name!("onbeforeprint") | &local_name!("onbeforeunload") | + &local_name!("onerror") | + &local_name!("onfocus") | &local_name!("onhashchange") | + &local_name!("onload") | &local_name!("onlanguagechange") | &local_name!("onmessage") | + &local_name!("onmessageerror") | &local_name!("onoffline") | &local_name!("ononline") | &local_name!("onpagehide") | + &local_name!("onpagereveal") | &local_name!("onpageshow") | + &local_name!("onpageswap") | &local_name!("onpopstate") | - &local_name!("onstorage") | + &local_name!("onrejectionhandled") | &local_name!("onresize") | - &local_name!("onunload") | - &local_name!("onerror") => { + &local_name!("onscroll") | + &local_name!("onstorage") | + &local_name!("onunhandledrejection") | + &local_name!("onunload") => { let source = &**attr.value(); let evtarget = window.upcast::(); // forwarded event let source_line = 1; //TODO(#9604) obtain current JS execution line diff --git a/components/script/dom/htmlcanvaselement.rs b/components/script/dom/htmlcanvaselement.rs index 9e20539ceca..499e91c127b 100644 --- a/components/script/dom/htmlcanvaselement.rs +++ b/components/script/dom/htmlcanvaselement.rs @@ -17,24 +17,24 @@ use image::codecs::jpeg::JpegEncoder; use image::codecs::png::PngEncoder; use image::codecs::webp::WebPEncoder; use image::{ColorType, ImageEncoder}; -use ipc_channel::ipc::IpcSharedMemory; #[cfg(feature = "webgpu")] use ipc_channel::ipc::{self as ipcchan}; use js::error::throw_type_error; use js::rust::{HandleObject, HandleValue}; -use script_layout_interface::{HTMLCanvasData, HTMLCanvasDataSource}; +use script_layout_interface::HTMLCanvasData; use servo_media::streams::MediaStreamType; use servo_media::streams::registry::MediaStreamId; +use snapshot::Snapshot; use style::attr::AttrValue; -use crate::canvas_context::CanvasContext as _; +use super::node::NodeDamage; pub(crate) use crate::canvas_context::*; use crate::conversions::Convert; use crate::dom::attr::Attr; use crate::dom::bindings::callback::ExceptionHandling; -use crate::dom::bindings::cell::{DomRefCell, Ref, ref_filter_map}; +use crate::dom::bindings::cell::{DomRefCell, Ref}; use crate::dom::bindings::codegen::Bindings::HTMLCanvasElementBinding::{ - BlobCallback, HTMLCanvasElementMethods, RenderingContext, + BlobCallback, HTMLCanvasElementMethods, RenderingContext as RootedRenderingContext, }; use crate::dom::bindings::codegen::Bindings::MediaStreamBinding::MediaStreamMethods; use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLContextAttributes; @@ -69,6 +69,7 @@ use crate::script_runtime::{CanGc, JSContext}; const DEFAULT_WIDTH: u32 = 300; const DEFAULT_HEIGHT: u32 = 150; +#[derive(PartialEq)] enum EncodedImageType { Png, Jpeg, @@ -103,21 +104,14 @@ impl EncodedImageType { } } -#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] -#[derive(Clone, JSTraceable, MallocSizeOf)] -pub(crate) enum CanvasContext { - Placeholder(Dom), - Context2d(Dom), - WebGL(Dom), - WebGL2(Dom), - #[cfg(feature = "webgpu")] - WebGPU(Dom), -} - +/// #[dom_struct] pub(crate) struct HTMLCanvasElement { htmlelement: HTMLElement, - context: DomRefCell>, + + /// + context_mode: DomRefCell>, + // This id and hashmap are used to keep track of ongoing toBlob() calls. callback_id: Cell, #[ignore_malloc_size_of = "not implemented for webidl callbacks"] @@ -132,7 +126,7 @@ impl HTMLCanvasElement { ) -> HTMLCanvasElement { HTMLCanvasElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), - context: DomRefCell::new(None), + context_mode: DomRefCell::new(None), callback_id: Cell::new(0), blob_callbacks: RefCell::new(HashMap::new()), } @@ -157,15 +151,8 @@ impl HTMLCanvasElement { } fn recreate_contexts_after_resize(&self) { - if let Some(ref context) = *self.context.borrow() { - match *context { - CanvasContext::Context2d(ref context) => context.resize(), - CanvasContext::WebGL(ref context) => context.resize(), - CanvasContext::WebGL2(ref context) => context.resize(), - #[cfg(feature = "webgpu")] - CanvasContext::WebGPU(ref context) => context.resize(), - CanvasContext::Placeholder(ref context) => context.resize(self.get_size().cast()), - } + if let Some(ref context) = *self.context_mode.borrow() { + context.resize() } } @@ -174,24 +161,15 @@ impl HTMLCanvasElement { } pub(crate) fn origin_is_clean(&self) -> bool { - match *self.context.borrow() { - Some(CanvasContext::Context2d(ref context)) => context.origin_is_clean(), + match *self.context_mode.borrow() { + Some(ref context) => context.origin_is_clean(), _ => true, } } pub(crate) fn mark_as_dirty(&self) { - if let Some(ref context) = *self.context.borrow() { - match *context { - CanvasContext::Context2d(ref context) => context.mark_as_dirty(), - CanvasContext::WebGL(ref context) => context.mark_as_dirty(), - CanvasContext::WebGL2(ref context) => context.mark_as_dirty(), - #[cfg(feature = "webgpu")] - CanvasContext::WebGPU(ref context) => context.mark_as_dirty(), - CanvasContext::Placeholder(ref _context) => { - // TODO: Should this be marked as dirty? - }, - } + if let Some(ref context) = *self.context_mode.borrow() { + context.mark_as_dirty() } } @@ -220,13 +198,15 @@ impl LayoutHTMLCanvasElementHelpers for LayoutDom<'_, HTMLCanvasElement> { #[allow(unsafe_code)] fn data(self) -> HTMLCanvasData { let source = unsafe { - match self.unsafe_get().context.borrow_for_layout().as_ref() { - Some(CanvasContext::Context2d(context)) => context.to_layout().canvas_data_source(), - Some(CanvasContext::WebGL(context)) => context.to_layout().canvas_data_source(), - Some(CanvasContext::WebGL2(context)) => context.to_layout().canvas_data_source(), + match self.unsafe_get().context_mode.borrow_for_layout().as_ref() { + Some(RenderingContext::Context2d(context)) => { + context.to_layout().canvas_data_source() + }, + Some(RenderingContext::WebGL(context)) => context.to_layout().canvas_data_source(), + Some(RenderingContext::WebGL2(context)) => context.to_layout().canvas_data_source(), #[cfg(feature = "webgpu")] - Some(CanvasContext::WebGPU(context)) => context.to_layout().canvas_data_source(), - Some(CanvasContext::Placeholder(_)) | None => HTMLCanvasDataSource::Empty, + Some(RenderingContext::WebGPU(context)) => context.to_layout().canvas_data_source(), + Some(RenderingContext::Placeholder(_)) | None => None, } }; @@ -245,14 +225,14 @@ impl LayoutHTMLCanvasElementHelpers for LayoutDom<'_, HTMLCanvasElement> { } impl HTMLCanvasElement { - pub(crate) fn context(&self) -> Option> { - ref_filter_map(self.context.borrow(), |ctx| ctx.as_ref()) + pub(crate) fn context(&self) -> Option> { + Ref::filter_map(self.context_mode.borrow(), |ctx| ctx.as_ref()).ok() } fn get_or_init_2d_context(&self, can_gc: CanGc) -> Option> { if let Some(ctx) = self.context() { return match *ctx { - CanvasContext::Context2d(ref ctx) => Some(DomRoot::from_ref(ctx)), + RenderingContext::Context2d(ref ctx) => Some(DomRoot::from_ref(ctx)), _ => None, }; } @@ -260,7 +240,8 @@ impl HTMLCanvasElement { let window = self.owner_window(); let size = self.get_size(); let context = CanvasRenderingContext2D::new(window.as_global_scope(), self, size, can_gc); - *self.context.borrow_mut() = Some(CanvasContext::Context2d(Dom::from_ref(&*context))); + *self.context_mode.borrow_mut() = + Some(RenderingContext::Context2d(Dom::from_ref(&*context))); Some(context) } @@ -272,7 +253,7 @@ impl HTMLCanvasElement { ) -> Option> { if let Some(ctx) = self.context() { return match *ctx { - CanvasContext::WebGL(ref ctx) => Some(DomRoot::from_ref(ctx)), + RenderingContext::WebGL(ref ctx) => Some(DomRoot::from_ref(ctx)), _ => None, }; } @@ -288,7 +269,7 @@ impl HTMLCanvasElement { attrs, can_gc, )?; - *self.context.borrow_mut() = Some(CanvasContext::WebGL(Dom::from_ref(&*context))); + *self.context_mode.borrow_mut() = Some(RenderingContext::WebGL(Dom::from_ref(&*context))); Some(context) } @@ -304,7 +285,7 @@ impl HTMLCanvasElement { } if let Some(ctx) = self.context() { return match *ctx { - CanvasContext::WebGL2(ref ctx) => Some(DomRoot::from_ref(ctx)), + RenderingContext::WebGL2(ref ctx) => Some(DomRoot::from_ref(ctx)), _ => None, }; } @@ -313,7 +294,7 @@ impl HTMLCanvasElement { let attrs = Self::get_gl_attributes(cx, options)?; let canvas = HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(DomRoot::from_ref(self)); let context = WebGL2RenderingContext::new(&window, &canvas, size, attrs, can_gc)?; - *self.context.borrow_mut() = Some(CanvasContext::WebGL2(Dom::from_ref(&*context))); + *self.context_mode.borrow_mut() = Some(RenderingContext::WebGL2(Dom::from_ref(&*context))); Some(context) } @@ -326,7 +307,7 @@ impl HTMLCanvasElement { fn get_or_init_webgpu_context(&self, can_gc: CanGc) -> Option> { if let Some(ctx) = self.context() { return match *ctx { - CanvasContext::WebGPU(ref ctx) => Some(DomRoot::from_ref(ctx)), + RenderingContext::WebGPU(ref ctx) => Some(DomRoot::from_ref(ctx)), _ => None, }; } @@ -340,16 +321,17 @@ impl HTMLCanvasElement { .expect("Failed to get WebGPU channel") .map(|channel| { let context = GPUCanvasContext::new(&global_scope, self, channel, can_gc); - *self.context.borrow_mut() = Some(CanvasContext::WebGPU(Dom::from_ref(&*context))); + *self.context_mode.borrow_mut() = + Some(RenderingContext::WebGPU(Dom::from_ref(&*context))); context }) } /// Gets the base WebGLRenderingContext for WebGL or WebGL 2, if exists. pub(crate) fn get_base_webgl_context(&self) -> Option> { - match *self.context.borrow() { - Some(CanvasContext::WebGL(ref context)) => Some(DomRoot::from_ref(context)), - Some(CanvasContext::WebGL2(ref context)) => Some(context.base_context()), + match *self.context_mode.borrow() { + Some(RenderingContext::WebGL(ref context)) => Some(DomRoot::from_ref(context)), + Some(RenderingContext::WebGL2(ref context)) => Some(context.base_context()), _ => None, } } @@ -375,42 +357,16 @@ impl HTMLCanvasElement { self.Height() != 0 && self.Width() != 0 } - pub(crate) fn fetch_all_data(&self) -> Option<(Option, Size2D)> { - let size = self.get_size(); - - if size.width == 0 || size.height == 0 { - return None; - } - - let data = match self.context.borrow().as_ref() { - Some(CanvasContext::Context2d(context)) => context.get_image_data_as_shared_memory(), - Some(CanvasContext::WebGL(_context)) => { - // TODO: add a method in WebGLRenderingContext to get the pixels. - return None; - }, - Some(CanvasContext::WebGL2(_context)) => { - // TODO: add a method in WebGL2RenderingContext to get the pixels. - return None; - }, - #[cfg(feature = "webgpu")] - Some(CanvasContext::WebGPU(context)) => context.get_image_data_as_shared_memory(), - Some(CanvasContext::Placeholder(context)) => return context.fetch_all_data(), - None => None, - }; - - Some((data, size)) - } - - fn get_content(&self) -> Option> { - match *self.context.borrow() { - Some(CanvasContext::Context2d(ref context)) => context.get_image_data(), - Some(CanvasContext::WebGL(ref context)) => context.get_image_data(), - Some(CanvasContext::WebGL2(ref context)) => context.get_image_data(), - #[cfg(feature = "webgpu")] - Some(CanvasContext::WebGPU(ref context)) => context.get_image_data(), - Some(CanvasContext::Placeholder(_)) | None => { - // Each pixel is fully-transparent black. - Some(vec![0; (self.Width() * self.Height() * 4) as usize]) + pub(crate) fn get_image_data(&self) -> Option { + match self.context_mode.borrow().as_ref() { + Some(context) => context.get_image_data(), + None => { + let size = self.get_size(); + if size.width == 0 || size.height == 0 { + None + } else { + Some(Snapshot::cleared(size.cast())) + } }, } } @@ -427,15 +383,23 @@ impl HTMLCanvasElement { &self, image_type: &EncodedImageType, quality: Option, - bytes: &[u8], + snapshot: &Snapshot, encoder: &mut W, ) { + // We can't use self.Width() or self.Height() here, since the size of the canvas + // may have changed since the snapshot was created. Truncating the dimensions to a + // u32 can't panic, since the data comes from a canvas which is always smaller than + // u32::MAX. + let canvas_data = snapshot.data(); + let width = snapshot.size().width as u32; + let height = snapshot.size().height as u32; + match image_type { EncodedImageType::Png => { // FIXME(nox): https://github.com/image-rs/image-png/issues/86 // FIXME(nox): https://github.com/image-rs/image-png/issues/87 PngEncoder::new(encoder) - .write_image(bytes, self.Width(), self.Height(), ColorType::Rgba8) + .write_image(canvas_data, width, height, ColorType::Rgba8) .unwrap(); }, EncodedImageType::Jpeg => { @@ -455,14 +419,14 @@ impl HTMLCanvasElement { }; jpeg_encoder - .write_image(bytes, self.Width(), self.Height(), ColorType::Rgba8) + .write_image(canvas_data, width, height, ColorType::Rgba8) .unwrap(); }, EncodedImageType::Webp => { // No quality support because of https://github.com/image-rs/image/issues/1984 WebPEncoder::new_lossless(encoder) - .write_image(bytes, self.Width(), self.Height(), ColorType::Rgba8) + .write_image(canvas_data, width, height, ColorType::Rgba8) .unwrap(); }, } @@ -473,12 +437,12 @@ impl HTMLCanvasElementMethods for HTMLCanvasElement { // https://html.spec.whatwg.org/multipage/#dom-canvas-width make_uint_getter!(Width, "width", DEFAULT_WIDTH); - // https://html.spec.whatwg.org/multipage/#dom-canvas-width - // When setting the value of the width or height attribute, if the context mode of the canvas element - // is set to placeholder, the user agent must throw an "InvalidStateError" DOMException and leave the - // attribute's value unchanged. + /// fn SetWidth(&self, value: u32, can_gc: CanGc) -> Fallible<()> { - if let Some(CanvasContext::Placeholder(_)) = *self.context.borrow() { + // > When setting the value of the width or height attribute, if the context mode of the canvas element + // > is set to placeholder, the user agent must throw an "InvalidStateError" DOMException and leave the + // > attribute's value unchanged. + if let Some(RenderingContext::Placeholder(_)) = *self.context_mode.borrow() { return Err(Error::InvalidState); } @@ -495,9 +459,12 @@ impl HTMLCanvasElementMethods for HTMLCanvasElement { // https://html.spec.whatwg.org/multipage/#dom-canvas-height make_uint_getter!(Height, "height", DEFAULT_HEIGHT); - // https://html.spec.whatwg.org/multipage/#dom-canvas-height + /// fn SetHeight(&self, value: u32, can_gc: CanGc) -> Fallible<()> { - if let Some(CanvasContext::Placeholder(_)) = *self.context.borrow() { + // > When setting the value of the width or height attribute, if the context mode of the canvas element + // > is set to placeholder, the user agent must throw an "InvalidStateError" DOMException and leave the + // > attribute's value unchanged. + if let Some(RenderingContext::Placeholder(_)) = *self.context_mode.borrow() { return Err(Error::InvalidState); } @@ -518,26 +485,26 @@ impl HTMLCanvasElementMethods for HTMLCanvasElement { id: DOMString, options: HandleValue, can_gc: CanGc, - ) -> Fallible> { + ) -> Fallible> { // Always throw an InvalidState exception when the canvas is in Placeholder mode (See table in the spec). - if let Some(CanvasContext::Placeholder(_)) = *self.context.borrow() { + if let Some(RenderingContext::Placeholder(_)) = *self.context_mode.borrow() { return Err(Error::InvalidState); } Ok(match &*id { "2d" => self .get_or_init_2d_context(can_gc) - .map(RenderingContext::CanvasRenderingContext2D), + .map(RootedRenderingContext::CanvasRenderingContext2D), "webgl" | "experimental-webgl" => self .get_or_init_webgl_context(cx, options, can_gc) - .map(RenderingContext::WebGLRenderingContext), + .map(RootedRenderingContext::WebGLRenderingContext), "webgl2" | "experimental-webgl2" => self .get_or_init_webgl2_context(cx, options, can_gc) - .map(RenderingContext::WebGL2RenderingContext), + .map(RootedRenderingContext::WebGL2RenderingContext), #[cfg(feature = "webgpu")] "webgpu" => self .get_or_init_webgpu_context(can_gc) - .map(RenderingContext::GPUCanvasContext), + .map(RootedRenderingContext::GPUCanvasContext), _ => None, }) } @@ -560,11 +527,23 @@ impl HTMLCanvasElementMethods for HTMLCanvasElement { } // Step 3. - let Some(file) = self.get_content() else { + let Some(mut snapshot) = self.get_image_data() else { return Ok(USVString("data:,".into())); }; let image_type = EncodedImageType::from(mime_type); + snapshot.transform( + if image_type == EncodedImageType::Jpeg { + snapshot::AlphaMode::AsOpaque { + premultiplied: true, + } + } else { + snapshot::AlphaMode::Transparent { + premultiplied: false, + } + }, + snapshot::PixelFormat::RGBA, + ); let mut url = format!("data:{};base64,", image_type.as_mime_type()); let mut encoder = base64::write::EncoderStringWriter::from_consumer( @@ -575,7 +554,7 @@ impl HTMLCanvasElementMethods for HTMLCanvasElement { self.encode_for_mime_type( &image_type, Self::maybe_quality(quality), - &file, + &snapshot, &mut encoder, ); encoder.into_inner(); @@ -597,14 +576,14 @@ impl HTMLCanvasElementMethods for HTMLCanvasElement { return Err(Error::Security); } - // Step 2. and 3. - // If this canvas element's bitmap has pixels (i.e., neither its horizontal dimension + // Step 2. Let result be null. + // Step 3. If this canvas element's bitmap has pixels (i.e., neither its horizontal dimension // nor its vertical dimension is zero), // then set result to a copy of this canvas element's bitmap. let result = if self.Width() == 0 || self.Height() == 0 { None } else { - self.get_content() + self.get_image_data() }; let this = Trusted::new(self); @@ -625,18 +604,22 @@ impl HTMLCanvasElementMethods for HTMLCanvasElement { return error!("Expected blob callback, but found none!"); }; - if let Some(bytes) = result { + if let Some(mut snapshot) = result { + snapshot.transform( + snapshot::AlphaMode::Transparent{ premultiplied: false }, + snapshot::PixelFormat::RGBA + ); // Step 4.1 // If result is non-null, then set result to a serialization of result as a file with // type and quality if given. let mut encoded: Vec = vec![]; - this.encode_for_mime_type(&image_type, quality, &bytes, &mut encoded); + this.encode_for_mime_type(&image_type, quality, &snapshot, &mut encoded); let blob_impl = BlobImpl::new_from_bytes(encoded, image_type.as_mime_type()); - // Step 4.2.1 & 4.2.2 - // Set result to a new Blob object, created in the relevant realm of this canvas element - // Invoke callback with « result » and "report". + // Step 4.2.1 Set result to a new Blob object, created in the relevant realm of this canvas element let blob = Blob::new(&this.global(), blob_impl, CanGc::note()); + + // Step 4.2.2 Invoke callback with « result » and "report". let _ = callback.Call__(Some(&blob), ExceptionHandling::Report, CanGc::note()); } else { let _ = callback.Call__(None, ExceptionHandling::Report, CanGc::note()); @@ -648,7 +631,7 @@ impl HTMLCanvasElementMethods for HTMLCanvasElement { /// fn TransferControlToOffscreen(&self, can_gc: CanGc) -> Fallible> { - if self.context.borrow().is_some() { + if self.context_mode.borrow().is_some() { // Step 1. // If this canvas element's context mode is not set to none, throw an "InvalidStateError" DOMException. return Err(Error::InvalidState); @@ -667,8 +650,10 @@ impl HTMLCanvasElementMethods for HTMLCanvasElement { Some(&Dom::from_ref(self)), can_gc, ); + // Step 4. Set this canvas element's context mode to placeholder. - *self.context.borrow_mut() = Some(CanvasContext::Placeholder(offscreen_canvas.as_traced())); + *self.context_mode.borrow_mut() = + Some(RenderingContext::Placeholder(offscreen_canvas.as_traced())); // Step 5. Return offscreenCanvas. Ok(offscreen_canvas) @@ -703,8 +688,11 @@ impl VirtualMethods for HTMLCanvasElement { .unwrap() .attribute_mutated(attr, mutation, can_gc); match attr.local_name() { - &local_name!("width") | &local_name!("height") => self.recreate_contexts_after_resize(), - _ => (), + &local_name!("width") | &local_name!("height") => { + self.recreate_contexts_after_resize(); + self.upcast::().dirty(NodeDamage::OtherNodeDamage); + }, + _ => {}, }; } diff --git a/components/script/dom/htmldetailselement.rs b/components/script/dom/htmldetailselement.rs index a3e2a05af32..1d48b8e7a97 100644 --- a/components/script/dom/htmldetailselement.rs +++ b/components/script/dom/htmldetailselement.rs @@ -178,8 +178,6 @@ impl HTMLDetailsElement { } } shadow_tree.descendants.Assign(slottable_children); - - self.upcast::().dirty(NodeDamage::OtherNodeDamage); } fn update_shadow_tree_styles(&self, can_gc: CanGc) { @@ -214,8 +212,6 @@ impl HTMLDetailsElement { .implicit_summary .upcast::() .set_string_attribute(&local_name!("style"), implicit_summary_style.into(), can_gc); - - self.upcast::().dirty(NodeDamage::OtherNodeDamage); } } diff --git a/components/script/dom/htmlelement.rs b/components/script/dom/htmlelement.rs index 9505d5182c7..f41370386e9 100644 --- a/components/script/dom/htmlelement.rs +++ b/components/script/dom/htmlelement.rs @@ -32,7 +32,7 @@ use crate::dom::bindings::str::DOMString; use crate::dom::characterdata::CharacterData; use crate::dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner}; use crate::dom::customelementregistry::CallbackReaction; -use crate::dom::document::{Document, FocusType}; +use crate::dom::document::{Document, FocusInitiator}; use crate::dom::documentfragment::DocumentFragment; use crate::dom::domstringmap::DOMStringMap; use crate::dom::element::{AttributeMutation, Element}; @@ -116,7 +116,7 @@ impl HTMLElement { /// `.outerText` in JavaScript.` /// /// - fn get_inner_outer_text(&self, can_gc: CanGc) -> DOMString { + pub(crate) fn get_inner_outer_text(&self, can_gc: CanGc) -> DOMString { let node = self.upcast::(); let window = node.owner_window(); let element = self.as_element(); @@ -134,6 +134,16 @@ impl HTMLElement { DOMString::from(text) } + + /// + pub(crate) fn set_inner_text(&self, input: DOMString, can_gc: CanGc) { + // Step 1: Let fragment be the rendered text fragment for value given element's node + // document. + let fragment = self.rendered_text_fragment(input, can_gc); + + // Step 2: Replace all with fragment within element. + Node::replace_all(Some(fragment.upcast()), self.upcast::(), can_gc); + } } impl HTMLElementMethods for HTMLElement { @@ -181,9 +191,6 @@ impl HTMLElementMethods for HTMLElement { // https://html.spec.whatwg.org/multipage/#globaleventhandlers global_event_handlers!(NoOnload); - // https://html.spec.whatwg.org/multipage/#documentandelementeventhandlers - document_and_element_event_handlers!(); - // https://html.spec.whatwg.org/multipage/#dom-dataset fn Dataset(&self, can_gc: CanGc) -> DomRoot { self.dataset.or_init(|| DOMStringMap::new(self, can_gc)) @@ -415,18 +422,19 @@ impl HTMLElementMethods for HTMLElement { // TODO: Mark the element as locked for focus and run the focusing steps. // https://html.spec.whatwg.org/multipage/#focusing-steps let document = self.owner_document(); - document.request_focus(Some(self.upcast()), FocusType::Element, can_gc); + document.request_focus(Some(self.upcast()), FocusInitiator::Local, can_gc); } // https://html.spec.whatwg.org/multipage/#dom-blur fn Blur(&self, can_gc: CanGc) { - // TODO: Run the unfocusing steps. + // TODO: Run the unfocusing steps. Focus the top-level document, not + // the current document. if !self.as_element().focus_state() { return; } // https://html.spec.whatwg.org/multipage/#unfocusing-steps let document = self.owner_document(); - document.request_focus(None, FocusType::Element, can_gc); + document.request_focus(None, FocusInitiator::Local, can_gc); } // https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetparent @@ -493,12 +501,7 @@ impl HTMLElementMethods for HTMLElement { /// fn SetInnerText(&self, input: DOMString, can_gc: CanGc) { - // Step 1: Let fragment be the rendered text fragment for value given element's node - // document. - let fragment = self.rendered_text_fragment(input, can_gc); - - // Step 2: Replace all with fragment within element. - Node::replace_all(Some(fragment.upcast()), self.upcast::(), can_gc); + self.set_inner_text(input, can_gc) } /// @@ -642,13 +645,16 @@ impl HTMLElementMethods for HTMLElement { Ok(internals) } - // FIXME: The nonce should be stored in an internal slot instead of an - // attribute (https://html.spec.whatwg.org/multipage/#cryptographicnonce) // https://html.spec.whatwg.org/multipage/#dom-noncedelement-nonce - make_getter!(Nonce, "nonce"); + fn Nonce(&self) -> DOMString { + self.as_element().nonce_value().into() + } // https://html.spec.whatwg.org/multipage/#dom-noncedelement-nonce - make_setter!(SetNonce, "nonce"); + fn SetNonce(&self, value: DOMString) { + self.as_element() + .update_nonce_internal_slot(value.to_string()) + } // https://html.spec.whatwg.org/multipage/#dom-fe-autofocus fn Autofocus(&self) -> bool { @@ -1135,6 +1141,15 @@ impl VirtualMethods for HTMLElement { }, } }, + (&local_name!("nonce"), mutation) => match mutation { + AttributeMutation::Set(_) => { + let nonce = &**attr.value(); + element.update_nonce_internal_slot(nonce.to_owned()); + }, + AttributeMutation::Removed => { + element.update_nonce_internal_slot("".to_owned()); + }, + }, _ => {}, } } diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index ce6dcca66f3..2421b683bf7 100644 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -1270,8 +1270,14 @@ impl HTMLFormElement { return; } - let controls = self.controls.borrow(); - for child in controls.iter() { + let controls: Vec<_> = self + .controls + .borrow() + .iter() + .map(|c| c.as_rooted()) + .collect(); + + for child in controls { let child = child.upcast::(); match child.type_id() { diff --git a/components/script/dom/htmliframeelement.rs b/components/script/dom/htmliframeelement.rs index c5194c4527f..7de3d4977b1 100644 --- a/components/script/dom/htmliframeelement.rs +++ b/components/script/dom/htmliframeelement.rs @@ -16,6 +16,7 @@ use embedder_traits::ViewportDetails; use html5ever::{LocalName, Prefix, local_name, ns}; use js::rust::HandleObject; use net_traits::ReferrerPolicy; +use net_traits::request::Destination; use profile_traits::ipc as ProfiledIpc; use script_traits::{NewLayoutInfo, UpdatePipelineIdReason}; use servo_url::ServoUrl; @@ -27,6 +28,8 @@ use crate::dom::attr::Attr; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods; +use crate::dom::bindings::codegen::UnionTypes::TrustedHTMLOrString; +use crate::dom::bindings::error::Fallible; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::DomGlobal; use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom}; @@ -40,6 +43,7 @@ use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; use crate::dom::htmlelement::HTMLElement; use crate::dom::node::{Node, NodeDamage, NodeTraits, UnbindContext}; +use crate::dom::trustedhtml::TrustedHTML; use crate::dom::virtualmethods::VirtualMethods; use crate::dom::windowproxy::WindowProxy; use crate::script_runtime::CanGc; @@ -162,8 +166,13 @@ impl HTMLIFrameElement { if load_data.url.scheme() == "javascript" { let window_proxy = self.GetContentWindow(); if let Some(window_proxy) = window_proxy { + if document + .global() + .should_navigation_request_be_blocked(&load_data) + { + return; + } // Important re security. See https://github.com/servo/servo/issues/23373 - // TODO: check according to https://w3c.github.io/webappsec-csp/#should-block-navigation-request if ScriptThread::check_load_origin(&load_data.load_origin, &document.url().origin()) { ScriptThread::eval_js_url(&window_proxy.global(), &mut load_data, can_gc); @@ -274,6 +283,8 @@ impl HTMLIFrameElement { Some(document.insecure_requests_policy()), document.has_trustworthy_ancestor_or_current_origin(), ); + load_data.destination = Destination::IFrame; + load_data.policy_container = Some(window.as_global_scope().policy_container()); let element = self.upcast::(); load_data.srcdoc = String::from(element.get_string_attribute(&local_name!("srcdoc"))); self.navigate_or_reload_child_browsing_context( @@ -356,7 +367,7 @@ impl HTMLIFrameElement { None }; - let load_data = LoadData::new( + let mut load_data = LoadData::new( LoadOrigin::Script(document.origin().immutable().clone()), url, creator_pipeline_id, @@ -366,6 +377,8 @@ impl HTMLIFrameElement { Some(document.insecure_requests_policy()), document.has_trustworthy_ancestor_or_current_origin(), ); + load_data.destination = Destination::IFrame; + load_data.policy_container = Some(window.as_global_scope().policy_container()); let pipeline_id = self.pipeline_id(); // If the initial `about:blank` page is the current page, load with replacement enabled, @@ -402,7 +415,7 @@ impl HTMLIFrameElement { let document = self.owner_document(); let window = self.owner_window(); let pipeline_id = Some(window.pipeline_id()); - let load_data = LoadData::new( + let mut load_data = LoadData::new( LoadOrigin::Script(document.origin().immutable().clone()), url, pipeline_id, @@ -412,6 +425,8 @@ impl HTMLIFrameElement { Some(document.insecure_requests_policy()), document.has_trustworthy_ancestor_or_current_origin(), ); + load_data.destination = Destination::IFrame; + load_data.policy_container = Some(window.as_global_scope().policy_container()); let browsing_context_id = BrowsingContextId::new(); let webview_id = window.window_proxy().webview_id(); self.pipeline_id.set(None); @@ -584,10 +599,29 @@ impl HTMLIFrameElementMethods for HTMLIFrameElement { make_url_setter!(SetSrc, "src"); // https://html.spec.whatwg.org/multipage/#dom-iframe-srcdoc - make_getter!(Srcdoc, "srcdoc"); + fn Srcdoc(&self) -> TrustedHTMLOrString { + let element = self.upcast::(); + element.get_trusted_html_attribute(&local_name!("srcdoc")) + } // https://html.spec.whatwg.org/multipage/#dom-iframe-srcdoc - make_setter!(SetSrcdoc, "srcdoc"); + fn SetSrcdoc(&self, value: TrustedHTMLOrString, can_gc: CanGc) -> Fallible<()> { + // Step 1: Let compliantString be the result of invoking the + // Get Trusted Type compliant string algorithm with TrustedHTML, + // this's relevant global object, the given value, "HTMLIFrameElement srcdoc", and "script". + let element = self.upcast::(); + let local_name = &local_name!("srcdoc"); + let value = TrustedHTML::get_trusted_script_compliant_string( + &element.owner_global(), + value, + "HTMLIFrameElement", + local_name, + can_gc, + )?; + // Step 2: Set an attribute value given this, srcdoc's local name, and compliantString. + element.set_attribute(local_name, AttrValue::String(value), can_gc); + Ok(()) + } // https://html.spec.whatwg.org/multipage/#dom-iframe-sandbox fn Sandbox(&self, can_gc: CanGc) -> DomRoot { diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index adff445ae1c..a79c7f6e463 100644 --- a/components/script/dom/htmlimageelement.rs +++ b/components/script/dom/htmlimageelement.rs @@ -97,6 +97,7 @@ enum ParseState { AfterDescriptor, } +#[derive(MallocSizeOf)] pub(crate) struct SourceSet { image_sources: Vec, source_size: SourceSizeList, @@ -111,13 +112,13 @@ impl SourceSet { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, MallocSizeOf, PartialEq)] pub struct ImageSource { pub url: String, pub descriptor: Descriptor, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, MallocSizeOf, PartialEq)] pub struct Descriptor { pub width: Option, pub density: Option, @@ -145,7 +146,7 @@ struct ImageRequest { parsed_url: Option, source_url: Option, blocker: DomRefCell>, - #[ignore_malloc_size_of = "Arc"] + #[conditional_malloc_size_of] #[no_trace] image: Option>, #[no_trace] @@ -162,7 +163,6 @@ pub(crate) struct HTMLImageElement { pending_request: DomRefCell, form_owner: MutNullableDom, generation: Cell, - #[ignore_malloc_size_of = "SourceSet"] source_set: DomRefCell, last_selected_source: DomRefCell>, #[ignore_malloc_size_of = "promises are hard"] @@ -298,7 +298,7 @@ impl FetchResponseListener for ImageContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 1999c7193ff..c6a3e2227f5 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -12,9 +12,13 @@ use std::str::FromStr; use std::{f64, ptr}; use dom_struct::dom_struct; -use embedder_traits::{FilterPattern, InputMethodType}; +use embedder_traits::{ + EmbedderMsg, FilterPattern, FormControl as EmbedderFormControl, InputMethodType, RgbColor, +}; use encoding_rs::Encoding; +use euclid::{Point2D, Rect, Size2D}; use html5ever::{LocalName, Prefix, local_name, ns}; +use ipc_channel::ipc; use js::jsapi::{ ClippedTime, DateGetMsecSinceEpoch, Handle, JS_ClearPendingException, JSObject, NewDateObject, NewUCRegExpObject, ObjectIsDate, RegExpFlag_UnicodeSets, RegExpFlags, @@ -25,7 +29,9 @@ use js::rust::{HandleObject, MutableHandleObject}; use net_traits::blob_url_store::get_blob_origin; use net_traits::filemanager_thread::FileManagerThreadMsg; use net_traits::{CoreResourceMsg, IpcSend}; -use profile_traits::ipc; +use script_bindings::codegen::GenericBindings::ShadowRootBinding::{ + ShadowRootMode, SlotAssignmentMode, +}; use style::attr::AttrValue; use style::str::{split_commas, str_join}; use stylo_atoms::Atom; @@ -33,12 +39,12 @@ use stylo_dom::ElementState; use time::{Month, OffsetDateTime, Time}; use unicode_bidi::{BidiClass, bidi_class}; use url::Url; +use webrender_api::units::DeviceIntRect; -use super::bindings::str::{FromInputValueString, ToInputValueString}; use crate::clipboard_provider::EmbedderClipboardProvider; use crate::dom::activation::Activatable; use crate::dom::attr::Attr; -use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::cell::{DomRefCell, Ref}; use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; use crate::dom::bindings::codegen::Bindings::FileListBinding::FileListMethods; @@ -48,30 +54,33 @@ use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, N use crate::dom::bindings::error::{Error, ErrorResult}; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::DomGlobal; -use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom}; -use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom}; +use crate::dom::bindings::str::{DOMString, FromInputValueString, ToInputValueString, USVString}; use crate::dom::clipboardevent::ClipboardEvent; use crate::dom::compositionevent::CompositionEvent; use crate::dom::document::Document; -use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers}; +use crate::dom::element::{AttributeMutation, Element, ElementCreator, LayoutElementHelpers}; use crate::dom::event::{Event, EventBubbles, EventCancelable}; use crate::dom::eventtarget::EventTarget; use crate::dom::file::File; use crate::dom::filelist::{FileList, LayoutFileListHelpers}; use crate::dom::globalscope::GlobalScope; use crate::dom::htmldatalistelement::HTMLDataListElement; +use crate::dom::htmldivelement::HTMLDivElement; use crate::dom::htmlelement::HTMLElement; use crate::dom::htmlfieldsetelement::HTMLFieldSetElement; use crate::dom::htmlformelement::{ FormControl, FormDatum, FormDatumValue, FormSubmitterElement, HTMLFormElement, ResetFrom, SubmittedFrom, }; +use crate::dom::htmlstyleelement::HTMLStyleElement; use crate::dom::keyboardevent::KeyboardEvent; use crate::dom::mouseevent::MouseEvent; use crate::dom::node::{ BindContext, CloneChildrenFlag, Node, NodeDamage, NodeTraits, ShadowIncluding, UnbindContext, }; use crate::dom::nodelist::NodeList; +use crate::dom::shadowroot::{IsUserAgentWidget, ShadowRoot}; use crate::dom::textcontrol::{TextControlElement, TextControlSelection}; use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor}; use crate::dom::validitystate::{ValidationFlags, ValidityState}; @@ -92,6 +101,34 @@ const DEFAULT_RESET_VALUE: &str = "Reset"; const PASSWORD_REPLACEMENT_CHAR: char = '●'; const DEFAULT_FILE_INPUT_VALUE: &str = "No file chosen"; +#[derive(Clone, JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +/// Contains references to the elements in the shadow tree for ``. +/// +/// The shadow tree consists of a single div with the currently selected color as +/// the background. +struct InputTypeColorShadowTree { + color_value: Dom, +} + +#[derive(Clone, JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +#[non_exhaustive] +enum ShadowTree { + Color(InputTypeColorShadowTree), + // TODO: Add shadow trees for other input types (range etc) here +} + +const COLOR_TREE_STYLE: &str = " +#color-value { + width: 100%; + height: 100%; + box-sizing: border-box; + border: 1px solid gray; + border-radius: 2px; +} +"; + /// #[derive(Clone, Copy, Default, JSTraceable, PartialEq)] #[allow(dead_code)] @@ -172,8 +209,7 @@ impl InputType { fn is_textual(&self) -> bool { matches!( *self, - InputType::Color | - InputType::Date | + InputType::Date | InputType::DatetimeLocal | InputType::Email | InputType::Hidden | @@ -277,9 +313,16 @@ impl From<&Atom> for InputType { #[derive(Debug, PartialEq)] enum ValueMode { + /// Value, + + /// Default, + + /// DefaultOn, + + /// Filename, } @@ -314,6 +357,7 @@ pub(crate) struct HTMLInputElement { form_owner: MutNullableDom, labels_node_list: MutNullableDom, validity_state: MutNullableDom, + shadow_tree: DomRefCell>, } #[derive(JSTraceable)] @@ -372,6 +416,7 @@ impl HTMLInputElement { form_owner: Default::default(), labels_node_list: MutNullableDom::new(None), validity_state: Default::default(), + shadow_tree: Default::default(), } } @@ -475,6 +520,7 @@ impl HTMLInputElement { let mut value = textinput.single_line_content().clone(); self.sanitize_value(&mut value); textinput.set_content(value); + self.upcast::().dirty(NodeDamage::OtherNodeDamage); } fn does_minmaxlength_apply(&self) -> bool { @@ -803,7 +849,7 @@ impl HTMLInputElement { .map(DomRoot::from_ref) } - // https://html.spec.whatwg.org/multipage/#suffering-from-being-missing + /// fn suffers_from_being_missing(&self, value: &DOMString) -> bool { match self.input_type() { // https://html.spec.whatwg.org/multipage/#checkbox-state-(type%3Dcheckbox)%3Asuffering-from-being-missing @@ -958,9 +1004,9 @@ impl HTMLInputElement { failed_flags } - // https://html.spec.whatwg.org/multipage/#suffering-from-an-underflow - // https://html.spec.whatwg.org/multipage/#suffering-from-an-overflow - // https://html.spec.whatwg.org/multipage/#suffering-from-a-step-mismatch + /// * + /// * + /// * fn suffers_from_range_issues(&self, value: &DOMString) -> ValidationFlags { if value.is_empty() || !self.does_value_as_number_apply() { return ValidationFlags::empty(); @@ -1014,9 +1060,109 @@ impl HTMLInputElement { failed_flags } + + /// Return a reference to the ShadowRoot that this element is a host of, + /// or create one if none exists. + fn shadow_root(&self, can_gc: CanGc) -> DomRoot { + self.upcast::().shadow_root().unwrap_or_else(|| { + self.upcast::() + .attach_shadow( + IsUserAgentWidget::Yes, + ShadowRootMode::Closed, + false, + false, + false, + SlotAssignmentMode::Manual, + can_gc, + ) + .expect("Attaching UA shadow root failed") + }) + } + + fn create_color_shadow_tree(&self, can_gc: CanGc) { + let document = self.owner_document(); + let shadow_root = self.shadow_root(can_gc); + Node::replace_all(None, shadow_root.upcast::(), can_gc); + + let color_value = HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc); + color_value + .upcast::() + .SetId(DOMString::from("color-value"), can_gc); + shadow_root + .upcast::() + .AppendChild(color_value.upcast::(), can_gc) + .unwrap(); + + let style = HTMLStyleElement::new( + local_name!("style"), + None, + &document, + None, + ElementCreator::ScriptCreated, + can_gc, + ); + style + .upcast::() + .SetTextContent(Some(DOMString::from(COLOR_TREE_STYLE)), can_gc); + shadow_root + .upcast::() + .AppendChild(style.upcast::(), can_gc) + .unwrap(); + + let _ = self + .shadow_tree + .borrow_mut() + .insert(ShadowTree::Color(InputTypeColorShadowTree { + color_value: color_value.as_traced(), + })); + } + + /// Get a handle to the shadow tree for this input, assuming it's [InputType] is `Color`. + /// + /// If the input is not currently a shadow host, a new shadow tree will be created. + /// + /// If the input is a shadow host for a different kind of shadow tree then the old + /// tree will be removed and a new one will be created. + fn color_shadow_tree(&self, can_gc: CanGc) -> Ref { + let has_color_shadow_tree = self + .shadow_tree + .borrow() + .as_ref() + .is_some_and(|shadow_tree| matches!(shadow_tree, ShadowTree::Color(_))); + if !has_color_shadow_tree { + self.create_color_shadow_tree(can_gc); + } + + let shadow_tree = self.shadow_tree.borrow(); + Ref::filter_map(shadow_tree, |shadow_tree| { + let shadow_tree = shadow_tree.as_ref()?; + let ShadowTree::Color(color_tree) = shadow_tree; + Some(color_tree) + }) + .ok() + .expect("UA shadow tree was not created") + } + + fn update_shadow_tree_if_needed(&self, can_gc: CanGc) { + if self.input_type() == InputType::Color { + let color_shadow_tree = self.color_shadow_tree(can_gc); + let mut value = self.Value(); + if value.str().is_valid_simple_color_string() { + value.make_ascii_lowercase(); + } else { + value = DOMString::from("#000000"); + } + let style = format!("background-color: {value}"); + color_shadow_tree + .color_value + .upcast::() + .set_string_attribute(&local_name!("style"), style.into(), can_gc); + } + } } pub(crate) trait LayoutHTMLInputElementHelpers<'dom> { + /// Return a string that represents the contents of the element for layout. fn value_for_layout(self) -> Cow<'dom, str>; fn size_for_layout(self) -> u32; fn selection_for_layout(self) -> Option>; @@ -1075,16 +1221,15 @@ impl<'dom> LayoutHTMLInputElementHelpers<'dom> for LayoutDom<'dom, HTMLInputElem Some(filelist) => { let length = filelist.len(); if length == 0 { - return DEFAULT_FILE_INPUT_VALUE.into(); - } - if length == 1 { + DEFAULT_FILE_INPUT_VALUE.into() + } else if length == 1 { match filelist.file_for_layout(0) { - Some(file) => return file.name().to_string().into(), - None => return DEFAULT_FILE_INPUT_VALUE.into(), + Some(file) => file.name().to_string().into(), + None => DEFAULT_FILE_INPUT_VALUE.into(), } + } else { + format!("{} files", length).into() } - - format!("{} files", length).into() }, None => DEFAULT_FILE_INPUT_VALUE.into(), } @@ -1103,6 +1248,9 @@ impl<'dom> LayoutHTMLInputElementHelpers<'dom> for LayoutDom<'dom, HTMLInputElem self.placeholder().into() } }, + InputType::Color => { + unreachable!("Input type color is explicitly not rendered as text"); + }, _ => { let text = self.get_raw_textinput_value(); if !text.is_empty() { @@ -1178,11 +1326,11 @@ impl TextControlElement for HTMLInputElement { InputType::Week | InputType::Time | InputType::DatetimeLocal | - InputType::Number | - InputType::Color => true, + InputType::Number => true, InputType::Button | InputType::Checkbox | + InputType::Color | InputType::File | InputType::Hidden | InputType::Image | @@ -1257,6 +1405,7 @@ impl HTMLInputElementMethods for HTMLInputElement { // https://html.spec.whatwg.org/multipage/#dom-input-checked fn SetChecked(&self, checked: bool) { self.update_checked_state(checked, true); + self.value_changed(CanGc::note()); } // https://html.spec.whatwg.org/multipage/#dom-input-readonly @@ -1348,7 +1497,7 @@ impl HTMLInputElementMethods for HTMLInputElement { }, } - update_related_validity_states(self, can_gc); + self.value_changed(can_gc); self.upcast::().dirty(NodeDamage::OtherNodeDamage); Ok(()) } @@ -1697,7 +1846,7 @@ fn radio_group_iter<'a>( ) -> impl Iterator> + 'a { root.traverse_preorder(ShadowIncluding::No) .filter_map(DomRoot::downcast::) - .filter(move |r| &**r == elem || in_same_group(r, form, group, None)) + .filter(move |r| &**r == elem || in_same_group(r, form, group, Some(root))) } fn broadcast_radio_checked(broadcaster: &HTMLInputElement, group: Option<&Atom>) { @@ -1723,18 +1872,6 @@ fn perform_radio_group_validation(elem: &HTMLInputElement, group: Option<&Atom>, } } -fn update_related_validity_states(elem: &HTMLInputElement, can_gc: CanGc) { - match elem.input_type() { - InputType::Radio => { - perform_radio_group_validation(elem, elem.radio_group_name().as_ref(), can_gc) - }, - _ => { - elem.validity_state() - .perform_validation_and_update(ValidationFlags::all(), can_gc); - }, - } -} - // https://html.spec.whatwg.org/multipage/#radio-button-group fn in_same_group( other: &HTMLInputElement, @@ -1904,7 +2041,7 @@ impl HTMLInputElement { InputType::Radio | InputType::Checkbox => { self.update_checked_state(self.DefaultChecked(), false); self.checked_changed.set(false); - update_related_validity_states(self, can_gc); + self.value_changed(can_gc); }, InputType::Image => (), _ => (), @@ -1948,8 +2085,9 @@ impl HTMLInputElement { .collect() }); - let (chan, recv) = ipc::channel(self.global().time_profiler_chan().clone()) - .expect("Error initializing channel"); + let (chan, recv) = + profile_traits::ipc::channel(self.global().time_profiler_chan().clone()) + .expect("Error initializing channel"); let msg = FileManagerThreadMsg::SelectFiles(webview_id, filter, chan, origin, opt_test_paths); resource_threads @@ -1976,8 +2114,9 @@ impl HTMLInputElement { None => None, }; - let (chan, recv) = ipc::channel(self.global().time_profiler_chan().clone()) - .expect("Error initializing channel"); + let (chan, recv) = + profile_traits::ipc::channel(self.global().time_profiler_chan().clone()) + .expect("Error initializing channel"); let msg = FileManagerThreadMsg::SelectFile(webview_id, filter, chan, origin, opt_test_path); resource_threads @@ -2347,6 +2486,70 @@ impl HTMLInputElement { }, }) } + + fn update_related_validity_states(&self, can_gc: CanGc) { + match self.input_type() { + InputType::Radio => { + perform_radio_group_validation(self, self.radio_group_name().as_ref(), can_gc) + }, + _ => { + self.validity_state() + .perform_validation_and_update(ValidationFlags::all(), can_gc); + }, + } + } + + fn value_changed(&self, can_gc: CanGc) { + self.update_related_validity_states(can_gc); + self.update_shadow_tree_if_needed(can_gc); + } + + /// + fn show_the_picker_if_applicable(&self, can_gc: CanGc) { + // FIXME: Implement most of this algorithm + + // Step 2. If element is not mutable, then return. + if !self.is_mutable() { + return; + } + + // Step 6. Otherwise, the user agent should show the relevant user interface for selecting a value for element, + // in the way it normally would when the user interacts with the control. + if self.input_type() == InputType::Color { + let (ipc_sender, ipc_receiver) = + ipc::channel::>().expect("Failed to create IPC channel!"); + let document = self.owner_document(); + let rect = self.upcast::().bounding_content_box_or_zero(can_gc); + let rect = Rect::new( + Point2D::new(rect.origin.x.to_px(), rect.origin.y.to_px()), + Size2D::new(rect.size.width.to_px(), rect.size.height.to_px()), + ); + let current_value = self.Value(); + let current_color = RgbColor { + red: u8::from_str_radix(¤t_value[1..3], 16).unwrap(), + green: u8::from_str_radix(¤t_value[3..5], 16).unwrap(), + blue: u8::from_str_radix(¤t_value[5..7], 16).unwrap(), + }; + document.send_to_embedder(EmbedderMsg::ShowFormControl( + document.webview_id(), + DeviceIntRect::from_untyped(&rect.to_box2d()), + EmbedderFormControl::ColorPicker(current_color, ipc_sender), + )); + + let Ok(response) = ipc_receiver.recv() else { + log::error!("Failed to receive response"); + return; + }; + + if let Some(selected_color) = response { + let formatted_color = format!( + "#{:0>2x}{:0>2x}{:0>2x}", + selected_color.red, selected_color.green, selected_color.blue + ); + let _ = self.SetValue(formatted_color.into(), can_gc); + } + } + } } impl VirtualMethods for HTMLInputElement { @@ -2358,6 +2561,7 @@ impl VirtualMethods for HTMLInputElement { self.super_type() .unwrap() .attribute_mutated(attr, mutation, can_gc); + match *attr.local_name() { local_name!("disabled") => { let disabled_state = match mutation { @@ -2465,6 +2669,7 @@ impl VirtualMethods for HTMLInputElement { let mut value = textinput.single_line_content().clone(); self.sanitize_value(&mut value); textinput.set_content(value); + self.upcast::().dirty(NodeDamage::OtherNodeDamage); // Steps 7-9 if !previously_selectable && self.selection_api_applies() { @@ -2492,6 +2697,8 @@ impl VirtualMethods for HTMLInputElement { self.sanitize_value(&mut value); self.textinput.borrow_mut().set_content(value); self.update_placeholder_shown_state(); + + self.upcast::().dirty(NodeDamage::OtherNodeDamage); }, local_name!("name") if self.input_type() == InputType::Radio => { self.radio_group_updated( @@ -2552,7 +2759,7 @@ impl VirtualMethods for HTMLInputElement { _ => {}, } - update_related_validity_states(self, can_gc); + self.value_changed(can_gc); } fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { @@ -2580,7 +2787,11 @@ impl VirtualMethods for HTMLInputElement { self.upcast::() .check_ancestors_disabled_state_for_form_control(); - update_related_validity_states(self, can_gc); + if self.input_type() == InputType::Radio { + self.radio_group_updated(self.radio_group_name().as_ref()); + } + + self.value_changed(can_gc); } fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) { @@ -2726,7 +2937,7 @@ impl VirtualMethods for HTMLInputElement { } } - update_related_validity_states(self, can_gc); + self.value_changed(can_gc); } // https://html.spec.whatwg.org/multipage/#the-input-element%3Aconcept-node-clone-ext @@ -2748,7 +2959,7 @@ impl VirtualMethods for HTMLInputElement { elem.textinput .borrow_mut() .set_content(self.textinput.borrow().get_content()); - update_related_validity_states(self, can_gc); + self.value_changed(can_gc); } } @@ -2857,7 +3068,8 @@ impl Activatable for HTMLInputElement { }, // https://html.spec.whatwg.org/multipage/#checkbox-state-(type=checkbox):input-activation-behavior // https://html.spec.whatwg.org/multipage/#radio-button-state-(type=radio):input-activation-behavior - InputType::Checkbox | InputType::Radio => true, + // https://html.spec.whatwg.org/multipage/#color-state-(type=color):input-activation-behavior + InputType::Checkbox | InputType::Radio | InputType::Color => true, _ => false, } } @@ -2903,7 +3115,7 @@ impl Activatable for HTMLInputElement { }; if activation_state.is_some() { - update_related_validity_states(self, can_gc); + self.value_changed(can_gc); } activation_state @@ -2962,7 +3174,7 @@ impl Activatable for HTMLInputElement { _ => (), } - update_related_validity_states(self, can_gc); + self.value_changed(can_gc); } /// @@ -3024,6 +3236,10 @@ impl Activatable for HTMLInputElement { }, // https://html.spec.whatwg.org/multipage/#file-upload-state-(type=file):input-activation-behavior InputType::File => self.select_files(None, can_gc), + // https://html.spec.whatwg.org/multipage/#color-state-(type=color):input-activation-behavior + InputType::Color => { + self.show_the_picker_if_applicable(can_gc); + }, _ => (), } } diff --git a/components/script/dom/htmllinkelement.rs b/components/script/dom/htmllinkelement.rs index db5c14af450..f4e7683cf2a 100644 --- a/components/script/dom/htmllinkelement.rs +++ b/components/script/dom/htmllinkelement.rs @@ -31,7 +31,6 @@ use crate::dom::attr::Attr; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenList_Binding::DOMTokenListMethods; use crate::dom::bindings::codegen::Bindings::HTMLLinkElementBinding::HTMLLinkElementMethods; -use crate::dom::bindings::codegen::GenericBindings::HTMLElementBinding::HTMLElement_Binding::HTMLElementMethods; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::reflector::DomGlobal; @@ -98,7 +97,7 @@ pub(crate) struct HTMLLinkElement { #[no_trace] relations: Cell, - #[ignore_malloc_size_of = "Arc"] + #[conditional_malloc_size_of] #[no_trace] stylesheet: DomRefCell>>, cssom_stylesheet: MutNullableDom, @@ -344,7 +343,7 @@ impl HTMLLinkElement { destination: Some(destination), integrity: String::new(), link_type: String::new(), - cryptographic_nonce_metadata: self.upcast::().Nonce().into(), + cryptographic_nonce_metadata: self.upcast::().nonce_value(), cross_origin: cors_setting_for_element(element), referrer_policy: referrer_policy_for_element(element), policy_container: document.policy_container().to_owned(), @@ -773,7 +772,7 @@ impl FetchResponseListener for PrefetchContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs index c07f866ab41..d1791620592 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -12,7 +12,6 @@ use std::{f64, mem}; use compositing_traits::{CrossProcessCompositorApi, ImageUpdate, SerializableImageData}; use content_security_policy as csp; use dom_struct::dom_struct; -use embedder_traits::resources::{self, Resource as EmbedderResource}; use embedder_traits::{MediaPositionState, MediaSessionEvent, MediaSessionPlaybackState}; use euclid::default::Size2D; use headers::{ContentLength, ContentRange, HeaderMapExt}; @@ -110,6 +109,12 @@ use crate::realms::{InRealm, enter_realm}; use crate::script_runtime::CanGc; use crate::script_thread::ScriptThread; +/// A CSS file to style the media controls. +static MEDIA_CONTROL_CSS: &str = include_str!("../resources/media-controls.css"); + +/// A JS file to control the media controls. +static MEDIA_CONTROL_JS: &str = include_str!("../resources/media-controls.js"); + #[derive(PartialEq)] enum FrameStatus { Locked, @@ -1364,7 +1369,6 @@ impl HTMLMediaElement { .lock() .unwrap() .render_poster_frame(image); - self.upcast::().dirty(NodeDamage::OtherNodeDamage); if pref!(media_testing_enabled) { self.owner_global() @@ -1613,7 +1617,6 @@ impl HTMLMediaElement { // TODO: 6. Abort the overall resource selection algorithm. }, PlayerEvent::VideoFrameUpdated => { - self.upcast::().dirty(NodeDamage::OtherNodeDamage); // Check if the frame was resized if let Some(frame) = self.video_renderer.lock().unwrap().current_frame { self.handle_resize(Some(frame.width as u32), Some(frame.height as u32)); @@ -1949,14 +1952,13 @@ impl HTMLMediaElement { ElementCreator::ScriptCreated, can_gc, ); - let mut media_controls_script = resources::read_string(EmbedderResource::MediaControlsJS); // This is our hacky way to temporarily workaround the lack of a privileged // JS context. // The media controls UI accesses the document.servoGetMediaControls(id) API // to get an instance to the media controls ShadowRoot. // `id` needs to match the internally generated UUID assigned to a media element. let id = document.register_media_controls(&shadow_root); - let media_controls_script = media_controls_script.as_mut_str().replace("@@@id@@@", &id); + let media_controls_script = MEDIA_CONTROL_JS.replace("@@@id@@@", &id); *self.media_controls_id.borrow_mut() = Some(id); script .upcast::() @@ -1969,7 +1971,6 @@ impl HTMLMediaElement { return; } - let media_controls_style = resources::read_string(EmbedderResource::MediaControlsCSS); let style = HTMLStyleElement::new( local_name!("script"), None, @@ -1980,7 +1981,7 @@ impl HTMLMediaElement { ); style .upcast::() - .SetTextContent(Some(DOMString::from(media_controls_style)), can_gc); + .SetTextContent(Some(DOMString::from(MEDIA_CONTROL_CSS)), can_gc); if let Err(e) = shadow_root .upcast::() @@ -2014,12 +2015,12 @@ impl HTMLMediaElement { pub(crate) fn clear_current_frame_data(&self) { self.handle_resize(None, None); self.video_renderer.lock().unwrap().current_frame = None; - self.upcast::().dirty(NodeDamage::OtherNodeDamage); } fn handle_resize(&self, width: Option, height: Option) { if let Some(video_elem) = self.downcast::() { video_elem.resize(width, height); + self.upcast::().dirty(NodeDamage::OtherNodeDamage); } } @@ -2948,7 +2949,7 @@ impl FetchResponseListener for HTMLMediaElementFetchListener { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/dom/htmloptgroupelement.rs b/components/script/dom/htmloptgroupelement.rs index 55ffa92257b..f5256e71b70 100644 --- a/components/script/dom/htmloptgroupelement.rs +++ b/components/script/dom/htmloptgroupelement.rs @@ -135,8 +135,8 @@ impl VirtualMethods for HTMLOptGroupElement { } fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) { - if let Some(s) = self.super_type() { - s.bind_to_tree(context, can_gc); + if let Some(super_type) = self.super_type() { + super_type.bind_to_tree(context, can_gc); } self.update_select_validity(can_gc); diff --git a/components/script/dom/htmloptionelement.rs b/components/script/dom/htmloptionelement.rs index b573388c73a..800e88f0758 100644 --- a/components/script/dom/htmloptionelement.rs +++ b/components/script/dom/htmloptionelement.rs @@ -29,7 +29,7 @@ use crate::dom::htmlformelement::HTMLFormElement; use crate::dom::htmloptgroupelement::HTMLOptGroupElement; use crate::dom::htmlscriptelement::HTMLScriptElement; use crate::dom::htmlselectelement::HTMLSelectElement; -use crate::dom::node::{BindContext, Node, ShadowIncluding, UnbindContext}; +use crate::dom::node::{BindContext, ChildrenMutation, Node, ShadowIncluding, UnbindContext}; use crate::dom::text::Text; use crate::dom::validation::Validatable; use crate::dom::validitystate::ValidationFlags; @@ -380,4 +380,26 @@ impl VirtualMethods for HTMLOptionElement { el.check_disabled_attribute(); } } + + fn children_changed(&self, mutation: &ChildrenMutation) { + if let Some(super_type) = self.super_type() { + super_type.children_changed(mutation); + } + + // Changing the descendants of a selected option can change it's displayed label + // if it does not have a label attribute + if !self + .upcast::() + .has_attribute(&local_name!("label")) + { + if let Some(owner_select) = self.owner_select_element() { + if owner_select + .selected_option() + .is_some_and(|selected_option| self == &*selected_option) + { + owner_select.update_shadow_tree(CanGc::note()); + } + } + } + } } diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index 9452dcb17a6..d1b3cfd3467 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -32,6 +32,7 @@ use net_traits::{ }; use servo_config::pref; use servo_url::{ImmutableOrigin, ServoUrl}; +use style::attr::AttrValue; use style::str::{HTML_SPACE_CHARACTERS, StaticStringVec}; use stylo_atoms::Atom; use uuid::Uuid; @@ -44,7 +45,9 @@ use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; use crate::dom::bindings::codegen::Bindings::HTMLScriptElementBinding::HTMLScriptElementMethods; use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use crate::dom::bindings::codegen::GenericBindings::HTMLElementBinding::HTMLElement_Binding::HTMLElementMethods; -use crate::dom::bindings::codegen::UnionTypes::TrustedScriptURLOrUSVString; +use crate::dom::bindings::codegen::UnionTypes::{ + TrustedScriptOrString, TrustedScriptURLOrUSVString, +}; use crate::dom::bindings::error::Fallible; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::refcounted::Trusted; @@ -64,6 +67,8 @@ use crate::dom::globalscope::GlobalScope; use crate::dom::htmlelement::HTMLElement; use crate::dom::node::{ChildrenMutation, CloneChildrenFlag, Node, NodeTraits}; use crate::dom::performanceresourcetiming::InitiatorType; +use crate::dom::trustedscript::TrustedScript; +use crate::dom::trustedscripturl::TrustedScriptURL; use crate::dom::virtualmethods::VirtualMethods; use crate::fetch::create_a_potential_cors_request; use crate::network_listener::{self, NetworkListener, PreInvoke, ResourceTimingListener}; @@ -276,19 +281,18 @@ pub(crate) enum ScriptType { pub(crate) struct CompiledSourceCode { #[ignore_malloc_size_of = "SM handles JS values"] pub(crate) source_code: Stencil, - #[ignore_malloc_size_of = "Rc is hard"] + #[conditional_malloc_size_of = "Rc is hard"] pub(crate) original_text: Rc, } -#[derive(JSTraceable)] +#[derive(JSTraceable, MallocSizeOf)] pub(crate) enum SourceCode { - Text(Rc), + Text(#[conditional_malloc_size_of] Rc), Compiled(CompiledSourceCode), } #[derive(JSTraceable, MallocSizeOf)] pub(crate) struct ScriptOrigin { - #[ignore_malloc_size_of = "Rc is hard"] code: SourceCode, #[no_trace] url: ServoUrl, @@ -542,7 +546,8 @@ impl FetchResponseListener for ClassicContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + let elem = self.elem.root(); + global.report_csp_violations(violations, Some(elem.upcast::())); } } @@ -667,7 +672,7 @@ impl HTMLScriptElement { // Step 5. Let source text be el's child text content. // Step 6. If el has no src attribute, and source text is the empty string, then return. - let text = self.Text(); + let text = self.text(); if text.is_empty() && !element.has_attribute(&local_name!("src")) { return; } @@ -776,7 +781,7 @@ impl HTMLScriptElement { }; // Step 24. Let cryptographic nonce be el's [[CryptographicNonce]] internal slot's value. - let cryptographic_nonce = self.upcast::().Nonce().into(); + let cryptographic_nonce = self.upcast::().nonce_value(); // Step 25. If el has an integrity attribute, then let integrity metadata be that attribute's value. // Otherwise, let integrity metadata be the empty string. @@ -1008,12 +1013,12 @@ impl HTMLScriptElement { Ok(script) => script, }; - // TODO: we need to handle this for worker if let Some(chan) = self.global().devtools_chan() { let pipeline_id = self.global().pipeline_id(); let source_info = SourceInfo { url: script.url.clone(), external: script.external, + worker_id: None, }; let _ = chan.send(ScriptToDevtoolsControlMsg::ScriptSourceLoaded( pipeline_id, @@ -1272,6 +1277,15 @@ impl HTMLScriptElement { let event = Event::new(window.upcast(), type_, bubbles, cancelable, can_gc); event.fire(self.upcast(), can_gc) } + + fn text(&self) -> DOMString { + match self.Text() { + TrustedScriptOrString::String(value) => value, + TrustedScriptOrString::TrustedScript(trusted_script) => { + DOMString::from(trusted_script.to_string()) + }, + } + } } impl VirtualMethods for HTMLScriptElement { @@ -1286,7 +1300,7 @@ impl VirtualMethods for HTMLScriptElement { if *attr.local_name() == local_name!("src") { if let AttributeMutation::Set(_) = mutation { if !self.parser_inserted.get() && self.upcast::().is_connected() { - self.prepare(CanGc::note()); + self.prepare(can_gc); } } } @@ -1344,10 +1358,25 @@ impl VirtualMethods for HTMLScriptElement { impl HTMLScriptElementMethods for HTMLScriptElement { // https://html.spec.whatwg.org/multipage/#dom-script-src - make_trusted_type_url_getter!(Src, "src"); + fn Src(&self) -> TrustedScriptURLOrUSVString { + let element = self.upcast::(); + element.get_trusted_type_url_attribute(&local_name!("src")) + } - // https://html.spec.whatwg.org/multipage/#dom-script-src - make_trusted_type_url_setter!(SetSrc, "src"); + /// + fn SetSrc(&self, value: TrustedScriptURLOrUSVString, can_gc: CanGc) -> Fallible<()> { + let element = self.upcast::(); + let local_name = &local_name!("src"); + let value = TrustedScriptURL::get_trusted_script_url_compliant_string( + &element.owner_global(), + value, + "HTMLScriptElement", + local_name, + can_gc, + )?; + element.set_attribute(local_name, AttrValue::String(value), can_gc); + Ok(()) + } // https://html.spec.whatwg.org/multipage/#dom-script-type make_getter!(Type, "type"); @@ -1416,14 +1445,77 @@ impl HTMLScriptElementMethods for HTMLScriptElement { // https://html.spec.whatwg.org/multipage/#dom-script-referrerpolicy make_setter!(SetReferrerPolicy, "referrerpolicy"); - // https://html.spec.whatwg.org/multipage/#dom-script-text - fn Text(&self) -> DOMString { - self.upcast::().child_text_content() + /// + fn InnerText(&self, can_gc: CanGc) -> TrustedScriptOrString { + // Step 1: Return the result of running get the text steps with this. + TrustedScriptOrString::String(self.upcast::().get_inner_outer_text(can_gc)) } - // https://html.spec.whatwg.org/multipage/#dom-script-text - fn SetText(&self, value: DOMString, can_gc: CanGc) { - self.upcast::().SetTextContent(Some(value), can_gc) + /// + fn SetInnerText(&self, input: TrustedScriptOrString, can_gc: CanGc) -> Fallible<()> { + // Step 1: Let value be the result of calling Get Trusted Type compliant string with TrustedScript, + // this's relevant global object, the given value, HTMLScriptElement innerText, and script. + let value = TrustedScript::get_trusted_script_compliant_string( + &self.owner_global(), + input, + "HTMLScriptElement", + "innerText", + can_gc, + )?; + // Step 3: Run set the inner text steps with this and value. + self.upcast::() + .set_inner_text(DOMString::from(value), can_gc); + Ok(()) + } + + /// + fn Text(&self) -> TrustedScriptOrString { + TrustedScriptOrString::String(self.upcast::().child_text_content()) + } + + /// + fn SetText(&self, value: TrustedScriptOrString, can_gc: CanGc) -> Fallible<()> { + // Step 1: Let value be the result of calling Get Trusted Type compliant string with TrustedScript, + // this's relevant global object, the given value, HTMLScriptElement text, and script. + let value = TrustedScript::get_trusted_script_compliant_string( + &self.owner_global(), + value, + "HTMLScriptElement", + "text", + can_gc, + )?; + // Step 2: Set this's script text value to the given value. + // TODO: Implement for https://w3c.github.io/trusted-types/dist/spec/#prepare-script-text + // Step 3: String replace all with the given value within this. + Node::string_replace_all(DOMString::from(value), self.upcast::(), can_gc); + Ok(()) + } + + /// + fn GetTextContent(&self) -> Option { + // Step 1: Return the result of running get text content with this. + Some(TrustedScriptOrString::String( + self.upcast::().GetTextContent()?, + )) + } + + /// + fn SetTextContent(&self, value: Option, can_gc: CanGc) -> Fallible<()> { + // Step 1: Let value be the result of calling Get Trusted Type compliant string with TrustedScript, + // this's relevant global object, the given value, HTMLScriptElement textContent, and script. + let value = TrustedScript::get_trusted_script_compliant_string( + &self.owner_global(), + value.unwrap_or(TrustedScriptOrString::String(DOMString::from(""))), + "HTMLScriptElement", + "textContent", + can_gc, + )?; + // Step 2: Set this's script text value to value. + // TODO: Implement for https://w3c.github.io/trusted-types/dist/spec/#prepare-script-text + // Step 3: Run set text content with this and value. + self.upcast::() + .SetTextContent(Some(DOMString::from(value)), can_gc); + Ok(()) } } diff --git a/components/script/dom/htmlselectelement.rs b/components/script/dom/htmlselectelement.rs index f4a62abe8b4..0f48c8e923f 100644 --- a/components/script/dom/htmlselectelement.rs +++ b/components/script/dom/htmlselectelement.rs @@ -14,7 +14,7 @@ use style::attr::AttrValue; use stylo_dom::ElementState; use embedder_traits::{SelectElementOptionOrOptgroup, SelectElementOption}; use euclid::{Size2D, Point2D, Rect}; -use embedder_traits::EmbedderMsg; +use embedder_traits::{FormControl as EmbedderFormControl, EmbedderMsg}; use crate::dom::bindings::codegen::GenericBindings::HTMLOptGroupElementBinding::HTMLOptGroupElement_Binding::HTMLOptGroupElementMethods; use crate::dom::activation::Activatable; @@ -153,7 +153,7 @@ impl HTMLSelectElement { n } - // https://html.spec.whatwg.org/multipage/#concept-select-option-list + /// pub(crate) fn list_of_options( &self, ) -> impl Iterator> + use<'_> { @@ -353,8 +353,10 @@ impl HTMLSelectElement { .fire_bubbling_event(atom!("change"), can_gc); } - fn selected_option(&self) -> Option> { - self.list_of_options().find(|opt_elem| opt_elem.Selected()) + pub(crate) fn selected_option(&self) -> Option> { + self.list_of_options() + .find(|opt_elem| opt_elem.Selected()) + .or_else(|| self.list_of_options().next()) } pub(crate) fn show_menu(&self, can_gc: CanGc) -> Option { @@ -404,12 +406,10 @@ impl HTMLSelectElement { let selected_index = self.list_of_options().position(|option| option.Selected()); let document = self.owner_document(); - document.send_to_embedder(EmbedderMsg::ShowSelectElementMenu( + document.send_to_embedder(EmbedderMsg::ShowFormControl( document.webview_id(), - options, - selected_index, DeviceIntRect::from_untyped(&rect.to_box2d()), - ipc_sender, + EmbedderFormControl::SelectElement(options, selected_index, ipc_sender), )); let Ok(response) = ipc_receiver.recv() else { @@ -539,7 +539,8 @@ impl HTMLSelectElementMethods for HTMLSelectElement { /// fn Value(&self) -> DOMString { - self.selected_option() + self.list_of_options() + .find(|opt_elem| opt_elem.Selected()) .map(|opt_elem| opt_elem.Value()) .unwrap_or_default() } diff --git a/components/script/dom/htmlstyleelement.rs b/components/script/dom/htmlstyleelement.rs index 0deb507f283..aed08b7bcf6 100644 --- a/components/script/dom/htmlstyleelement.rs +++ b/components/script/dom/htmlstyleelement.rs @@ -4,6 +4,7 @@ use std::cell::Cell; +use content_security_policy as csp; use dom_struct::dom_struct; use html5ever::{LocalName, Prefix}; use js::rust::HandleObject; @@ -33,7 +34,7 @@ use crate::stylesheet_loader::{StylesheetLoader, StylesheetOwner}; #[dom_struct] pub(crate) struct HTMLStyleElement { htmlelement: HTMLElement, - #[ignore_malloc_size_of = "Arc"] + #[conditional_malloc_size_of] #[no_trace] stylesheet: DomRefCell>>, cssom_stylesheet: MutNullableDom, @@ -97,8 +98,21 @@ impl HTMLStyleElement { return; } - let window = node.owner_window(); let doc = self.owner_document(); + + // Step 5: If the Should element's inline behavior be blocked by Content Security Policy? algorithm + // returns "Blocked" when executed upon the style element, "style", + // and the style element's child text content, then return. [CSP] + if doc.should_elements_inline_type_behavior_be_blocked( + self.upcast(), + csp::InlineCheckType::Style, + &node.child_text_content(), + ) == csp::CheckResult::Blocked + { + return; + } + + let window = node.owner_window(); let data = node .GetTextContent() .expect("Element.textContent must be a string"); diff --git a/components/script/dom/htmltablecellelement.rs b/components/script/dom/htmltablecellelement.rs index 4f312e928c4..78f00132580 100644 --- a/components/script/dom/htmltablecellelement.rs +++ b/components/script/dom/htmltablecellelement.rs @@ -9,6 +9,9 @@ use style::attr::{AttrValue, LengthOrPercentageOrAuto}; use style::color::AbsoluteColor; use style::context::QuirksMode; +use super::attr::Attr; +use super::element::AttributeMutation; +use super::node::NodeDamage; use crate::dom::bindings::codegen::Bindings::HTMLTableCellElementBinding::HTMLTableCellElementMethods; use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use crate::dom::bindings::inheritance::Castable; @@ -70,13 +73,17 @@ impl HTMLTableCellElementMethods for HTMLTableCellElement make_uint_getter!(ColSpan, "colspan", DEFAULT_COLSPAN); // https://html.spec.whatwg.org/multipage/#dom-tdth-colspan - make_uint_setter!(SetColSpan, "colspan", DEFAULT_COLSPAN); + // > The colSpan IDL attribute must reflect the colspan content attribute. It is clamped to + // > the range [1, 1000], and its default value is 1. + make_clamped_uint_setter!(SetColSpan, "colspan", 1, 1000, 1); // https://html.spec.whatwg.org/multipage/#dom-tdth-rowspan make_uint_getter!(RowSpan, "rowspan", DEFAULT_ROWSPAN); // https://html.spec.whatwg.org/multipage/#dom-tdth-rowspan - make_uint_setter!(SetRowSpan, "rowspan", DEFAULT_ROWSPAN); + // > The rowSpan IDL attribute must reflect the rowspan content attribute. It is clamped to + // > the range [0, 65534], and its default value is 1. + make_clamped_uint_setter!(SetRowSpan, "rowspan", 0, 65534, 1); // https://html.spec.whatwg.org/multipage/#dom-tdth-bgcolor make_getter!(BgColor, "bgcolor"); @@ -170,27 +177,43 @@ impl VirtualMethods for HTMLTableCellElement { Some(self.upcast::() as &dyn VirtualMethods) } + fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) { + if let Some(super_type) = self.super_type() { + super_type.attribute_mutated(attr, mutation, can_gc); + } + + if matches!(*attr.local_name(), local_name!("colspan")) { + self.upcast::().dirty(NodeDamage::OtherNodeDamage); + } + if matches!(*attr.local_name(), local_name!("rowspan")) { + self.upcast::().dirty(NodeDamage::OtherNodeDamage); + } + } + fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue { match *local_name { local_name!("colspan") => { let mut attr = AttrValue::from_u32(value.into(), DEFAULT_COLSPAN); - if let AttrValue::UInt(_, ref mut val) = attr { - if *val == 0 { - *val = 1; - } + if let AttrValue::UInt(_, ref mut value) = attr { + // From : + // > The colSpan IDL attribute must reflect the colspan content attribute. It is clamped to + // > the range [1, 1000], and its default value is 1. + *value = (*value).clamp(1, 1000); } attr }, local_name!("rowspan") => { let mut attr = AttrValue::from_u32(value.into(), DEFAULT_ROWSPAN); - if let AttrValue::UInt(_, ref mut val) = attr { - if *val == 0 { - let node = self.upcast::(); - let doc = node.owner_doc(); - // rowspan = 0 is not supported in quirks mode - if doc.quirks_mode() != QuirksMode::NoQuirks { - *val = 1; - } + if let AttrValue::UInt(_, ref mut value) = attr { + // From : + // > The rowSpan IDL attribute must reflect the rowspan content attribute. It is clamped to + // > the range [0, 65534], and its default value is 1. + // Note that rowspan = 0 is not supported in quirks mode. + let document = self.upcast::().owner_doc(); + if document.quirks_mode() != QuirksMode::NoQuirks { + *value = (*value).clamp(1, 65534); + } else { + *value = (*value).clamp(0, 65534); } } attr diff --git a/components/script/dom/htmltablecolelement.rs b/components/script/dom/htmltablecolelement.rs index c7ad4afd944..70355f274fc 100644 --- a/components/script/dom/htmltablecolelement.rs +++ b/components/script/dom/htmltablecolelement.rs @@ -7,8 +7,10 @@ use html5ever::{LocalName, Prefix, local_name, ns}; use js::rust::HandleObject; use style::attr::{AttrValue, LengthOrPercentageOrAuto}; +use super::attr::Attr; use super::bindings::root::LayoutDom; -use super::element::Element; +use super::element::{AttributeMutation, Element}; +use super::node::NodeDamage; use crate::dom::bindings::codegen::Bindings::HTMLTableColElementBinding::HTMLTableColElementMethods; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::root::DomRoot; @@ -20,8 +22,6 @@ use crate::dom::node::Node; use crate::dom::virtualmethods::VirtualMethods; use crate::script_runtime::CanGc; -const DEFAULT_SPAN: u32 = 1; - #[dom_struct] pub(crate) struct HTMLTableColElement { htmlelement: HTMLElement, @@ -62,9 +62,11 @@ impl HTMLTableColElement { impl HTMLTableColElementMethods for HTMLTableColElement { // - make_uint_getter!(Span, "span", DEFAULT_SPAN); + make_uint_getter!(Span, "span", 1); // - make_uint_setter!(SetSpan, "span", DEFAULT_SPAN); + // > The span IDL attribute must reflect the content attribute of the same name. It is clamped + // > to the range [1, 1000], and its default value is 1. + make_clamped_uint_setter!(SetSpan, "span", 1, 1000, 1); } pub(crate) trait HTMLTableColElementLayoutHelpers<'dom> { @@ -93,14 +95,25 @@ impl VirtualMethods for HTMLTableColElement { Some(self.upcast::() as &dyn VirtualMethods) } + fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) { + if let Some(super_type) = self.super_type() { + super_type.attribute_mutated(attr, mutation, can_gc); + } + + if matches!(*attr.local_name(), local_name!("span")) { + self.upcast::().dirty(NodeDamage::OtherNodeDamage); + } + } + fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue { match *local_name { local_name!("span") => { - let mut attr = AttrValue::from_u32(value.into(), DEFAULT_SPAN); + let mut attr = AttrValue::from_u32(value.into(), 1); if let AttrValue::UInt(_, ref mut val) = attr { - if *val == 0 { - *val = 1; - } + // From : + // > The span IDL attribute must reflect the content attribute of the same name. + // > It is clamped to the range [1, 1000], and its default value is 1. + *val = (*val).clamp(1, 1000); } attr }, diff --git a/components/script/dom/htmlvideoelement.rs b/components/script/dom/htmlvideoelement.rs index 6f27c164d02..c5d21c19d9b 100644 --- a/components/script/dom/htmlvideoelement.rs +++ b/components/script/dom/htmlvideoelement.rs @@ -422,7 +422,7 @@ impl FetchResponseListener for PosterFrameFetchContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/dom/imagedata.rs b/components/script/dom/imagedata.rs index bd45a80fce2..a891064952a 100644 --- a/components/script/dom/imagedata.rs +++ b/components/script/dom/imagedata.rs @@ -9,13 +9,13 @@ use std::vec::Vec; use dom_struct::dom_struct; use euclid::default::{Rect, Size2D}; use ipc_channel::ipc::IpcSharedMemory; -use js::jsapi::{Heap, JSObject}; +use js::gc::CustomAutoRooterGuard; +use js::jsapi::JSObject; use js::rust::HandleObject; use js::typedarray::{ClampedU8, CreateWith, Uint8ClampedArray}; -use super::bindings::buffer_source::{ - BufferSource, HeapBufferSource, HeapTypedArrayInit, new_initialized_heap_buffer_source, -}; +use super::bindings::buffer_source::{HeapBufferSource, create_heap_buffer_source_with_length}; +use crate::dom::bindings::buffer_source::create_buffer_source; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::ImageDataMethods; use crate::dom::bindings::error::{Error, Fallible}; use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto}; @@ -55,31 +55,31 @@ impl ImageData { rooted!(in (*cx) let mut js_object = ptr::null_mut::()); if let Some(ref mut d) = data { d.resize(len as usize, 0); + + let typed_array = + create_buffer_source::(cx, &d[..], js_object.handle_mut(), can_gc) + .map_err(|_| Error::JSFailed)?; + let data = CreateWith::Slice(&d[..]); Uint8ClampedArray::create(*cx, data, js_object.handle_mut()).unwrap(); - Self::new_with_jsobject(global, None, width, Some(height), js_object.get(), can_gc) + auto_root!(in(*cx) let data = typed_array); + Self::new_with_data(global, None, width, Some(height), data, can_gc) } else { - Self::new_without_jsobject(global, None, width, height, can_gc) + Self::new_without_data(global, None, width, height, can_gc) } } } #[allow(unsafe_code)] - fn new_with_jsobject( + fn new_with_data( global: &GlobalScope, proto: Option, width: u32, opt_height: Option, - jsobject: *mut JSObject, + data: CustomAutoRooterGuard, can_gc: CanGc, ) -> Fallible> { - let heap_typed_array = match new_initialized_heap_buffer_source::( - HeapTypedArrayInit::Buffer(BufferSource::ArrayBufferView(Heap::boxed(jsobject))), - can_gc, - ) { - Ok(heap_typed_array) => heap_typed_array, - Err(_) => return Err(Error::JSFailed), - }; + let heap_typed_array = HeapBufferSource::::from_view(data); let typed_array = match heap_typed_array.get_typed_array() { Ok(array) => array, @@ -117,13 +117,14 @@ impl ImageData { )) } - fn new_without_jsobject( + fn new_without_data( global: &GlobalScope, proto: Option, width: u32, height: u32, can_gc: CanGc, ) -> Fallible> { + // If one or both of sw and sh are zero, then throw an "IndexSizeError" DOMException. if width == 0 || height == 0 { return Err(Error::IndexSize); } @@ -139,13 +140,8 @@ impl ImageData { let cx = GlobalScope::get_cx(); - let heap_typed_array = match new_initialized_heap_buffer_source::( - HeapTypedArrayInit::Info { len, cx }, - can_gc, - ) { - Ok(heap_typed_array) => heap_typed_array, - Err(_) => return Err(Error::JSFailed), - }; + let heap_typed_array = create_heap_buffer_source_with_length::(cx, len, can_gc)?; + let imagedata = Box::new(ImageData { reflector_: Reflector::new(), width, @@ -198,20 +194,19 @@ impl ImageDataMethods for ImageData { width: u32, height: u32, ) -> Fallible> { - Self::new_without_jsobject(global, proto, width, height, can_gc) + Self::new_without_data(global, proto, width, height, can_gc) } /// fn Constructor_( - _cx: JSContext, global: &GlobalScope, proto: Option, can_gc: CanGc, - jsobject: *mut JSObject, + data: CustomAutoRooterGuard, width: u32, opt_height: Option, ) -> Fallible> { - Self::new_with_jsobject(global, proto, width, opt_height, jsobject, can_gc) + Self::new_with_data(global, proto, width, opt_height, data, can_gc) } /// diff --git a/components/script/dom/intersectionobserver.rs b/components/script/dom/intersectionobserver.rs index ec98116d3a4..6a6f9ce45eb 100644 --- a/components/script/dom/intersectionobserver.rs +++ b/components/script/dom/intersectionobserver.rs @@ -524,11 +524,10 @@ impl IntersectionObserver { // Step 9 // > Let targetArea be targetRect’s area. - let target_area = target_rect.size.width.0 * target_rect.size.height.0; - // Step 10 // > Let intersectionArea be intersectionRect’s area. - let intersection_area = intersection_rect.size.width.0 * intersection_rect.size.height.0; + // These steps are folded in Step 12, rewriting (w1 * h1) / (w2 * h2) as (w1 / w2) * (h1 / h2) + // to avoid multiplication overflows. // Step 11 // > Let isIntersecting be true if targetRect and rootBounds intersect or are edge-adjacent, @@ -545,9 +544,12 @@ impl IntersectionObserver { // Step 12 // > If targetArea is non-zero, let intersectionRatio be intersectionArea divided by targetArea. // > Otherwise, let intersectionRatio be 1 if isIntersecting is true, or 0 if isIntersecting is false. - let intersection_ratio = match target_area { - 0 => is_intersecting.into(), - _ => (intersection_area as f64) / (target_area as f64), + let intersection_ratio = if target_rect.size.width.0 == 0 || target_rect.size.height.0 == 0 + { + is_intersecting.into() + } else { + (intersection_rect.size.width.0 as f64 / target_rect.size.width.0 as f64) * + (intersection_rect.size.height.0 as f64 / target_rect.size.height.0 as f64) }; // Step 13 diff --git a/components/script/dom/macros.rs b/components/script/dom/macros.rs index b3f222af0da..564fe810db0 100644 --- a/components/script/dom/macros.rs +++ b/components/script/dom/macros.rs @@ -121,32 +121,6 @@ macro_rules! make_url_setter( ); ); -#[macro_export] -macro_rules! make_trusted_type_url_getter( - ( $attr:ident, $htmlname:tt ) => ( - fn $attr(&self) -> TrustedScriptURLOrUSVString { - use $crate::dom::bindings::inheritance::Castable; - use $crate::dom::element::Element; - let element = self.upcast::(); - element.get_trusted_type_url_attribute(&html5ever::local_name!($htmlname)) - } - ); -); - -#[macro_export] -macro_rules! make_trusted_type_url_setter( - ( $attr:ident, $htmlname:tt ) => ( - fn $attr(&self, value: TrustedScriptURLOrUSVString, can_gc: CanGc) -> Fallible<()> { - use $crate::dom::bindings::inheritance::Castable; - use $crate::dom::element::Element; - use $crate::script_runtime::CanGc; - let element = self.upcast::(); - element.set_trusted_type_url_attribute(&html5ever::local_name!($htmlname), - value, can_gc) - } - ); -); - #[macro_export] macro_rules! make_form_action_getter( ( $attr:ident, $htmlname:tt ) => ( @@ -317,6 +291,26 @@ macro_rules! make_uint_setter( }; ); +#[macro_export] +macro_rules! make_clamped_uint_setter( + ($attr:ident, $htmlname:tt, $min:expr, $max:expr, $default:expr) => ( + fn $attr(&self, value: u32) { + use $crate::dom::bindings::inheritance::Castable; + use $crate::dom::element::Element; + use $crate::dom::values::UNSIGNED_LONG_MAX; + use $crate::script_runtime::CanGc; + let value = if value > UNSIGNED_LONG_MAX { + $default + } else { + value.clamp($min, $max) + }; + + let element = self.upcast::(); + element.set_uint_attribute(&html5ever::local_name!($htmlname), value, CanGc::note()) + } + ); +); + #[macro_export] macro_rules! make_limited_uint_setter( ($attr:ident, $htmlname:tt, $default:expr) => ( @@ -529,21 +523,29 @@ macro_rules! global_event_handlers( ); (NoOnload) => ( event_handler!(abort, GetOnabort, SetOnabort); + event_handler!(auxclick, GetOnauxclick, SetOnauxclick); event_handler!(animationend, GetOnanimationend, SetOnanimationend); event_handler!(animationiteration, GetOnanimationiteration, SetOnanimationiteration); + event_handler!(beforeinput, GetOnbeforeinput, SetOnbeforeinput); + event_handler!(beforematch, GetOnbeforematch, SetOnbeforematch); + event_handler!(beforetoggle, GetOnbeforetoggle, SetOnbeforetoggle); event_handler!(cancel, GetOncancel, SetOncancel); event_handler!(canplay, GetOncanplay, SetOncanplay); event_handler!(canplaythrough, GetOncanplaythrough, SetOncanplaythrough); event_handler!(change, GetOnchange, SetOnchange); event_handler!(click, GetOnclick, SetOnclick); event_handler!(close, GetOnclose, SetOnclose); + event_handler!(command, GetOncommand, SetOncommand); + event_handler!(contextlost, GetOncontextlost, SetOncontextlost); event_handler!(contextmenu, GetOncontextmenu, SetOncontextmenu); + event_handler!(contextrestored, GetOncontextrestored, SetOncontextrestored); + event_handler!(copy, GetOncopy, SetOncopy); event_handler!(cuechange, GetOncuechange, SetOncuechange); + event_handler!(cut, GetOncut, SetOncut); event_handler!(dblclick, GetOndblclick, SetOndblclick); event_handler!(drag, GetOndrag, SetOndrag); event_handler!(dragend, GetOndragend, SetOndragend); event_handler!(dragenter, GetOndragenter, SetOndragenter); - event_handler!(dragexit, GetOndragexit, SetOndragexit); event_handler!(dragleave, GetOndragleave, SetOndragleave); event_handler!(dragover, GetOndragover, SetOndragover); event_handler!(dragstart, GetOndragstart, SetOndragstart); @@ -567,20 +569,21 @@ macro_rules! global_event_handlers( event_handler!(mouseout, GetOnmouseout, SetOnmouseout); event_handler!(mouseover, GetOnmouseover, SetOnmouseover); event_handler!(mouseup, GetOnmouseup, SetOnmouseup); - event_handler!(wheel, GetOnwheel, SetOnwheel); + event_handler!(paste, GetOnpaste, SetOnpaste); event_handler!(pause, GetOnpause, SetOnpause); event_handler!(play, GetOnplay, SetOnplay); event_handler!(playing, GetOnplaying, SetOnplaying); event_handler!(progress, GetOnprogress, SetOnprogress); event_handler!(ratechange, GetOnratechange, SetOnratechange); event_handler!(reset, GetOnreset, SetOnreset); + event_handler!(scrollend, GetOnscrollend, SetOnscrollend); event_handler!(securitypolicyviolation, GetOnsecuritypolicyviolation, SetOnsecuritypolicyviolation); event_handler!(seeked, GetOnseeked, SetOnseeked); event_handler!(seeking, GetOnseeking, SetOnseeking); event_handler!(select, GetOnselect, SetOnselect); event_handler!(selectionchange, GetOnselectionchange, SetOnselectionchange); event_handler!(selectstart, GetOnselectstart, SetOnselectstart); - event_handler!(show, GetOnshow, SetOnshow); + event_handler!(slotchange, GetOnslotchange, SetOnslotchange); event_handler!(stalled, GetOnstalled, SetOnstalled); event_handler!(submit, GetOnsubmit, SetOnsubmit); event_handler!(suspend, GetOnsuspend, SetOnsuspend); @@ -591,6 +594,11 @@ macro_rules! global_event_handlers( event_handler!(transitionrun, GetOntransitionrun, SetOntransitionrun); event_handler!(volumechange, GetOnvolumechange, SetOnvolumechange); event_handler!(waiting, GetOnwaiting, SetOnwaiting); + event_handler!(webkitanimationend, GetOnwebkitanimationend, SetOnwebkitanimationend); + event_handler!(webkitanimationiteration, GetOnwebkitanimationiteration, SetOnwebkitanimationiteration); + event_handler!(webkitanimationstart, GetOnwebkitanimationstart, SetOnwebkitanimationstart); + event_handler!(webkittransitionend, GetOnwebkittransitionend, SetOnwebkittransitionend); + event_handler!(wheel, GetOnwheel, SetOnwheel); ) ); @@ -611,7 +619,9 @@ macro_rules! window_event_handlers( event_handler!(offline, GetOnoffline, SetOnoffline); event_handler!(online, GetOnonline, SetOnonline); event_handler!(pagehide, GetOnpagehide, SetOnpagehide); + event_handler!(pagereveal, GetOnpagereveal, SetOnpagereveal); event_handler!(pageshow, GetOnpageshow, SetOnpageshow); + event_handler!(pageswap, GetOnpageswap, SetOnpageswap); event_handler!(popstate, GetOnpopstate, SetOnpopstate); event_handler!(rejectionhandled, GetOnrejectionhandled, SetOnrejectionhandled); @@ -639,7 +649,9 @@ macro_rules! window_event_handlers( window_owned_event_handler!(offline, GetOnoffline, SetOnoffline); window_owned_event_handler!(online, GetOnonline, SetOnonline); window_owned_event_handler!(pagehide, GetOnpagehide, SetOnpagehide); + window_owned_event_handler!(pagereveal, GetOnpagereveal, SetOnpagereveal); window_owned_event_handler!(pageshow, GetOnpageshow, SetOnpageshow); + window_owned_event_handler!(pageswap, GetOnpageswap, SetOnpageswap); window_owned_event_handler!(popstate, GetOnpopstate, SetOnpopstate); window_owned_event_handler!(rejectionhandled, GetOnrejectionhandled, SetOnrejectionhandled); @@ -652,17 +664,6 @@ macro_rules! window_event_handlers( ); ); -// https://html.spec.whatwg.org/multipage/#documentandelementeventhandlers -// see webidls/EventHandler.webidl -// As more methods get added, just update them here. -macro_rules! document_and_element_event_handlers( - () => ( - event_handler!(cut, GetOncut, SetOncut); - event_handler!(copy, GetOncopy, SetOncopy); - event_handler!(paste, GetOnpaste, SetOnpaste); - ) -); - /// DOM struct implementation for simple interfaces inheriting from PerformanceEntry. macro_rules! impl_performance_entry_struct( ($binding:ident, $struct:ident, $type:expr) => ( @@ -719,26 +720,3 @@ macro_rules! handle_potential_webgl_error { handle_potential_webgl_error!($context, $call, ()) }; } - -macro_rules! impl_rare_data ( - ($type:ty) => ( - fn rare_data(&self) -> Ref>> { - self.rare_data.borrow() - } - - #[allow(dead_code)] - fn rare_data_mut(&self) -> RefMut>> { - self.rare_data.borrow_mut() - } - - fn ensure_rare_data(&self) -> RefMut> { - let mut rare_data = self.rare_data.borrow_mut(); - if rare_data.is_none() { - *rare_data = Some(Default::default()); - } - RefMut::map(rare_data, |rare_data| { - rare_data.as_mut().unwrap() - }) - } - ); -); diff --git a/components/script/dom/messageport.rs b/components/script/dom/messageport.rs index fe590052db4..d9cd6c50768 100644 --- a/components/script/dom/messageport.rs +++ b/components/script/dom/messageport.rs @@ -23,7 +23,7 @@ use crate::dom::bindings::error::{Error, ErrorResult, ErrorToJsval}; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object}; use crate::dom::bindings::root::DomRoot; -use crate::dom::bindings::structuredclone::{self, StructuredData, StructuredDataReader}; +use crate::dom::bindings::structuredclone::{self, StructuredData}; use crate::dom::bindings::trace::RootedTraceableBox; use crate::dom::bindings::transferable::Transferable; use crate::dom::bindings::utils::set_dictionary_property; @@ -83,6 +83,20 @@ impl MessagePort { *self.entangled_port.borrow_mut() = Some(other_id); } + /// + pub(crate) fn disentangle(&self) -> Option { + // Disentangle initiatorPort and otherPort, so that they are no longer entangled or associated with each other. + // Note: called from `disentangle_port` in the global, where the rest happens. + self.entangled_port.borrow_mut().take() + } + + /// Has the port been disentangled? + /// Used when starting the port to fire the `close` event, + /// to cover the case of a disentanglement while in transfer. + pub(crate) fn disentangled(&self) -> bool { + self.entangled_port.borrow().is_none() + } + pub(crate) fn message_port_id(&self) -> &MessagePortId { &self.message_port_id } @@ -268,18 +282,14 @@ impl Transferable for MessagePort { Ok(transferred_port) } - fn serialized_storage( - data: StructuredData<'_>, - ) -> &mut Option> { + fn serialized_storage<'a>( + data: StructuredData<'a, '_>, + ) -> &'a mut Option> { match data { StructuredData::Reader(r) => &mut r.port_impls, StructuredData::Writer(w) => &mut w.ports, } } - - fn deserialized_storage(reader: &mut StructuredDataReader) -> &mut Option>> { - &mut reader.message_ports - } } impl MessagePortMethods for MessagePort { @@ -318,20 +328,24 @@ impl MessagePortMethods for MessagePort { } /// - fn Start(&self) { + fn Start(&self, can_gc: CanGc) { if self.detached.get() { return; } - self.global().start_message_port(self.message_port_id()); + self.global() + .start_message_port(self.message_port_id(), can_gc); } /// - fn Close(&self) { - if self.detached.get() { - return; - } + fn Close(&self, can_gc: CanGc) { + // Set this's [[Detached]] internal slot value to true. self.detached.set(true); - self.global().close_message_port(self.message_port_id()); + + let global = self.global(); + global.close_message_port(self.message_port_id()); + + // If this is entangled, disentangle it. + global.disentangle_port(self, can_gc); } /// @@ -344,15 +358,19 @@ impl MessagePortMethods for MessagePort { } /// - fn SetOnmessage(&self, listener: Option>) { + fn SetOnmessage(&self, listener: Option>, can_gc: CanGc) { if self.detached.get() { return; } self.set_onmessage(listener); // Note: we cannot use the event_handler macro, due to the need to start the port. - self.global().start_message_port(self.message_port_id()); + self.global() + .start_message_port(self.message_port_id(), can_gc); } // event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror); + + // + event_handler!(close, GetOnclose, SetOnclose); } diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 4bc272db8dd..388d8cfde2a 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -547,18 +547,31 @@ pub(crate) mod submitevent; pub(crate) mod subtlecrypto; pub(crate) mod svgelement; pub(crate) mod svggraphicselement; +pub(crate) mod svgimageelement; pub(crate) mod svgsvgelement; +#[cfg(feature = "testbinding")] pub(crate) mod testbinding; +#[cfg(feature = "testbinding")] pub(crate) mod testbindingiterable; +#[cfg(feature = "testbinding")] pub(crate) mod testbindingmaplikewithinterface; +#[cfg(feature = "testbinding")] pub(crate) mod testbindingmaplikewithprimitive; +#[cfg(feature = "testbinding")] pub(crate) mod testbindingpairiterable; +#[cfg(feature = "testbinding")] pub(crate) mod testbindingproxy; +#[cfg(feature = "testbinding")] pub(crate) mod testbindingsetlikewithinterface; +#[cfg(feature = "testbinding")] pub(crate) mod testbindingsetlikewithprimitive; +#[cfg(feature = "testbinding")] pub(crate) mod testns; +#[cfg(feature = "testbinding")] pub(crate) mod testutils; +#[cfg(feature = "testbinding")] pub(crate) mod testworklet; +#[cfg(feature = "testbinding")] pub(crate) mod testworkletglobalscope; pub(crate) mod text; pub(crate) mod textcontrol; @@ -631,6 +644,8 @@ pub(crate) mod webgpu; pub(crate) use self::webgpu::*; #[cfg(not(feature = "webgpu"))] pub(crate) mod gpucanvascontext; +pub(crate) mod transformstream; +pub(crate) mod transformstreamdefaultcontroller; pub(crate) mod wheelevent; #[allow(dead_code)] pub(crate) mod window; diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index 2a01370085a..17235543906 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -50,7 +50,6 @@ use style::stylesheets::{Stylesheet, UrlExtraData}; use uuid::Uuid; use xml5ever::serialize as xml_serialize; -use super::globalscope::GlobalScope; use crate::conversions::Convert; use crate::document_loader::DocumentLoader; use crate::dom::attr::Attr; @@ -92,13 +91,14 @@ use crate::dom::documenttype::DocumentType; use crate::dom::element::{CustomElementCreationMode, Element, ElementCreator, SelectorWrapper}; use crate::dom::event::{Event, EventBubbles, EventCancelable}; use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; use crate::dom::htmlbodyelement::HTMLBodyElement; use crate::dom::htmlcanvaselement::{HTMLCanvasElement, LayoutHTMLCanvasElementHelpers}; use crate::dom::htmlcollection::HTMLCollection; use crate::dom::htmlelement::HTMLElement; use crate::dom::htmliframeelement::{HTMLIFrameElement, HTMLIFrameElementLayoutMethods}; use crate::dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers}; -use crate::dom::htmlinputelement::{HTMLInputElement, LayoutHTMLInputElementHelpers}; +use crate::dom::htmlinputelement::{HTMLInputElement, InputType, LayoutHTMLInputElementHelpers}; use crate::dom::htmllinkelement::HTMLLinkElement; use crate::dom::htmlslotelement::{HTMLSlotElement, Slottable}; use crate::dom::htmlstyleelement::HTMLStyleElement; @@ -110,7 +110,7 @@ use crate::dom::pointerevent::{PointerEvent, PointerId}; use crate::dom::processinginstruction::ProcessingInstruction; use crate::dom::range::WeakRangeVec; use crate::dom::raredata::NodeRareData; -use crate::dom::servoparser::serialize_html_fragment; +use crate::dom::servoparser::{ServoParser, serialize_html_fragment}; use crate::dom::shadowroot::{IsUserAgentWidget, LayoutShadowRootHelpers, ShadowRoot}; use crate::dom::stylesheetlist::StyleSheetListOwner; use crate::dom::svgsvgelement::{LayoutSVGSVGElementHelpers, SVGSVGElement}; @@ -167,7 +167,6 @@ pub struct Node { /// Layout data for this node. This is populated during layout and can /// be used for incremental relayout and script queries. - #[ignore_malloc_size_of = "trait object"] #[no_trace] layout_data: DomRefCell>>, } @@ -317,6 +316,34 @@ impl Node { } } + /// Implements the "unsafely set HTML" algorithm as specified in: + /// + pub fn unsafely_set_html( + target: &Node, + context_element: &Element, + html: DOMString, + can_gc: CanGc, + ) { + // Step 1. Let newChildren be the result of the HTML fragment parsing algorithm. + let new_children = ServoParser::parse_html_fragment(context_element, html, true, can_gc); + + // Step 2. Let fragment be a new DocumentFragment whose node document is contextElement's node document. + + let context_document = context_element.owner_document(); + let fragment = DocumentFragment::new(&context_document, can_gc); + + // Step 3. For each node in newChildren, append node to fragment. + for child in new_children { + fragment + .upcast::() + .AppendChild(&child, can_gc) + .unwrap(); + } + + // Step 4. Replace all with fragment within target. + Node::replace_all(Some(fragment.upcast()), target, can_gc); + } + pub(crate) fn clean_up_style_and_layout_data(&self) { self.owner_doc().cancel_animations_for_node(self); self.style_data.borrow_mut().take(); @@ -565,7 +592,17 @@ impl Iterator for QuerySelectorIterator { } impl Node { - impl_rare_data!(NodeRareData); + fn rare_data(&self) -> Ref>> { + self.rare_data.borrow() + } + + fn ensure_rare_data(&self) -> RefMut> { + let mut rare_data = self.rare_data.borrow_mut(); + if rare_data.is_none() { + *rare_data = Some(Default::default()); + } + RefMut::map(rare_data, |rare_data| rare_data.as_mut().unwrap()) + } /// Returns true if this node is before `other` in the same connected DOM /// tree. @@ -1008,24 +1045,25 @@ impl Node { /// pub(crate) fn replace_with(&self, nodes: Vec, can_gc: CanGc) -> ErrorResult { - // Step 1. - let parent = if let Some(parent) = self.GetParentNode() { - parent - } else { - // Step 2. + // Step 1. Let parent be this’s parent. + let Some(parent) = self.GetParentNode() else { + // Step 2. If parent is null, then return. return Ok(()); }; - // Step 3. + + // Step 3. Let viableNextSibling be this’s first following sibling not in nodes; otherwise null. let viable_next_sibling = first_node_not_in(self.following_siblings(), &nodes); - // Step 4. + + // Step 4. Let node be the result of converting nodes into a node, given nodes and this’s node document. let node = self .owner_doc() .node_from_nodes_and_strings(nodes, can_gc)?; + if self.parent_node == Some(&*parent) { - // Step 5. + // Step 5. If this’s parent is parent, replace this with node within parent. parent.ReplaceChild(&node, self, can_gc)?; } else { - // Step 6. + // Step 6. Otherwise, pre-insert node into parent before viableNextSibling. Node::pre_insert(&node, &parent, viable_next_sibling.as_deref(), can_gc)?; } Ok(()) @@ -1273,6 +1311,21 @@ impl Node { is_shadow_host, shadow_root_mode, display, + doctype_name: self + .downcast::() + .map(DocumentType::name) + .cloned() + .map(String::from), + doctype_public_identifier: self + .downcast::() + .map(DocumentType::public_id) + .cloned() + .map(String::from), + doctype_system_identifier: self + .downcast::() + .map(DocumentType::system_id) + .cloned() + .map(String::from), } } @@ -1559,6 +1612,8 @@ pub(crate) trait LayoutNodeHelpers<'dom> { /// attempting to read or modify the opaque layout data of this node. unsafe fn clear_style_and_layout_data(self); + /// Whether this element is a `` rendered as text or a ` + + diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.https.html b/tests/wpt/mozilla/tests/mozilla/interfaces.https.html index 76d746b0663..eee8c799727 100644 --- a/tests/wpt/mozilla/tests/mozilla/interfaces.https.html +++ b/tests/wpt/mozilla/tests/mozilla/interfaces.https.html @@ -315,6 +315,7 @@ test_interfaces([ "SubtleCrypto", "SVGElement", "SVGGraphicsElement", + "SVGImageElement", "SVGRect", "SVGSVGElement", "Text", @@ -371,6 +372,8 @@ test_interfaces([ "WritableStream", "WritableStreamDefaultController", "WritableStreamDefaultWriter", + "TransformStream", + "TransformStreamDefaultController", "WGSLLanguageFeatures", "XMLDocument", "XMLHttpRequest", diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js b/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js index 8d109502622..e86f34f2614 100644 --- a/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js +++ b/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js @@ -137,6 +137,8 @@ test_interfaces([ "WritableStream", "WritableStreamDefaultController", "WritableStreamDefaultWriter", + "TransformStream", + "TransformStreamDefaultController", "WGSLLanguageFeatures", "XMLHttpRequest", "XMLHttpRequestEventTarget", diff --git a/tests/wpt/mozilla/tests/mozilla/path2D_addPath-crash.html b/tests/wpt/mozilla/tests/mozilla/path2D_addPath-crash.html new file mode 100644 index 00000000000..f913e6baf83 --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/path2D_addPath-crash.html @@ -0,0 +1,5 @@ + + diff --git a/tests/wpt/mozilla/tests/mozilla/preferences.html b/tests/wpt/mozilla/tests/mozilla/preferences.html index a1da58a825e..8a65161198c 100644 --- a/tests/wpt/mozilla/tests/mozilla/preferences.html +++ b/tests/wpt/mozilla/tests/mozilla/preferences.html @@ -7,16 +7,16 @@ test(function() { var testBinding = new TestBinding(); assert_equals(typeof testBinding.BooleanMozPreference, "function"); - assert_equals(testBinding.BooleanMozPreference("dom.testbinding.preference_value.falsy"), false); - assert_equals(testBinding.BooleanMozPreference("dom.testbinding.preference_value.truthy"), true); - assert_equals(testBinding.BooleanMozPreference("dom.testbinding.preference_value.string_test"), false); - assert_equals(testBinding.BooleanMozPreference("dom.testbinding.preference_value.string_empty"), false); + assert_equals(testBinding.BooleanMozPreference("dom_testbinding_preference_value_falsy"), false); + assert_equals(testBinding.BooleanMozPreference("dom_testbinding_preference_value_truthy"), true); + assert_equals(testBinding.BooleanMozPreference("dom_testbinding_preference_value_string_test"), false); + assert_equals(testBinding.BooleanMozPreference("dom_testbinding_preference_value_string_empty"), false); assert_equals(typeof testBinding.StringMozPreference, "function"); - assert_equals(testBinding.StringMozPreference("dom.testbinding.preference_value.string_test"), "test"); - assert_equals(testBinding.StringMozPreference("dom.testbinding.preference_value.string_empty"), ""); - assert_equals(testBinding.StringMozPreference("dom.testbinding.preference_value.falsy"), ""); - assert_equals(testBinding.StringMozPreference("dom.testbinding.preference_value.truthy"), ""); - assert_equals(testBinding.StringMozPreference("dom.testbinding.preference_value.space_string_test"), "test1 test2"); - assert_equals(testBinding.StringMozPreference("dom.testbinding.preference_value.space_string_test"), "test1 test2"); + assert_equals(testBinding.StringMozPreference("dom_testbinding_preference_value_string_test"), "test"); + assert_equals(testBinding.StringMozPreference("dom_testbinding_preference_value_string_empty"), ""); + assert_equals(testBinding.StringMozPreference("dom_testbinding_preference_value_falsy"), ""); + assert_equals(testBinding.StringMozPreference("dom_testbinding_preference_value_truthy"), ""); + assert_equals(testBinding.StringMozPreference("dom_testbinding_preference_value_space_string_test"), "test1 test2"); + assert_equals(testBinding.StringMozPreference("dom_testbinding_preference_value_quote_string_test"), "\"test\""); }, "prefs"); diff --git a/tests/wpt/mozilla/tests/mozilla/slice-blob-twice-crash.html b/tests/wpt/mozilla/tests/mozilla/slice-blob-twice-crash.html new file mode 100644 index 00000000000..94227dcc74d --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/slice-blob-twice-crash.html @@ -0,0 +1,5 @@ + + + diff --git a/tests/wpt/mozilla/tests/mozilla/window_resizeTo.html b/tests/wpt/mozilla/tests/mozilla/window_resizeTo.html index 87d60498e80..3f6c5a610d5 100644 --- a/tests/wpt/mozilla/tests/mozilla/window_resizeTo.html +++ b/tests/wpt/mozilla/tests/mozilla/window_resizeTo.html @@ -1,16 +1,21 @@ -Verify that the resize event is fired when the window is resized (particularly in headless mode) +Verify that the resize event is fired when the window is resized (in popup) diff --git a/tests/wpt/mozilla/tests/mozilla/window_resizeTo_not_permitted-crash.html b/tests/wpt/mozilla/tests/mozilla/window_resizeTo_not_permitted-crash.html new file mode 100644 index 00000000000..7b6a67ff814 --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/window_resizeTo_not_permitted-crash.html @@ -0,0 +1,6 @@ + + +Test that calling resizeTo() on normal window does not crash + diff --git a/tests/wpt/tests/.github/workflows/docker.yml b/tests/wpt/tests/.github/workflows/docker.yml index 66794b69aba..5de0cc4bcb1 100644 --- a/tests/wpt/tests/.github/workflows/docker.yml +++ b/tests/wpt/tests/.github/workflows/docker.yml @@ -40,7 +40,7 @@ jobs: latest type=raw,value=${{ inputs.tag }} - name: Build and push the Docker image - uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 + uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0 with: context: ./tools/docker push: true diff --git a/tests/wpt/tests/.github/workflows/documentation.yml b/tests/wpt/tests/.github/workflows/documentation.yml index 6c92e435c69..41cf7c331e1 100644 --- a/tests/wpt/tests/.github/workflows/documentation.yml +++ b/tests/wpt/tests/.github/workflows/documentation.yml @@ -2,7 +2,11 @@ name: documentation on: push: branches: - - ubuntu-24.04 + - master + paths: + - 'docs/**' + - 'resources/**' + - 'tools/**' pull_request: paths: - 'docs/**' @@ -30,4 +34,4 @@ jobs: - name: Run website_build.sh run: ./tools/ci/website_build.sh env: - DEPLOY_TOKEN: dummy + DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }} diff --git a/tests/wpt/tests/.github/workflows/update_codeowners.yml b/tests/wpt/tests/.github/workflows/update_codeowners.yml new file mode 100644 index 00000000000..9e3f23efa7d --- /dev/null +++ b/tests/wpt/tests/.github/workflows/update_codeowners.yml @@ -0,0 +1,39 @@ +name: update codeowners +on: + schedule: + - cron: 0 0 * * * + workflow_dispatch: +jobs: + update: + runs-on: ubuntu-24.04 + if: github.repository == 'web-platform-tests/wpt' + steps: + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Checkout wpt + uses: actions/checkout@v4 + with: + path: wpt + - name: Checkout wpt-metadata + uses: actions/checkout@v4 + with: + path: wpt-metadata + repository: web-platform-tests/wpt-metadata + - name: Update codeowners + run: | + cd wpt + ./wpt update-codeowners ../wpt-metadata + - name: Commit and create pull request + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + author: wpt-pr-bot + commit-message: Automated update of CODEOWNERS + title: Update CODEOWNERS + body: | + This automated pull request updates CODEOWNERS + + See the [workflow](https://github.com/web-platform-tests/wpt/blob/master/.github/workflows/update_codeowners.yml) for how this pull request was created. + branch: actions/update-codeowners diff --git a/tests/wpt/tests/CODEOWNERS b/tests/wpt/tests/CODEOWNERS index c5db991b400..da5b3f0d330 100644 --- a/tests/wpt/tests/CODEOWNERS +++ b/tests/wpt/tests/CODEOWNERS @@ -11,3 +11,3752 @@ tools/docker/Dockerfile @web-platform-tests/wpt-core-team @web-platform-tests/ad # Prevent accidentally touching wasm/core which is updated by a workflow wasm/core/ @web-platform-tests/wpt-core-team + +# GENERATED: interop-tests +css/css-anchor-position/anchor-as-multicol-crash.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-composited-scrolling-001-crash.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-composited-scrolling-002-crash.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-composited-scrolling-003-crash.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-composited-scrolling-004-crash.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-composited-scrolling-005-crash.html @web-platform-tests/interop +css/css-anchor-position/grid-anchor-center-crash.html @web-platform-tests/interop +css/css-anchor-position/inline-grid-try-fallbacks-crash.html @web-platform-tests/interop +css/css-anchor-position/position-try-invalid-anchor-crash.html @web-platform-tests/interop +css/css-cascade/scope-declaration-list-crash.html @web-platform-tests/interop +css/css-flexbox/contain-size-layout-abspos-flex-container-crash.html @web-platform-tests/interop +css/css-flexbox/fixedpos-video-in-abspos-quirk-crash.html @web-platform-tests/interop +css/css-flexbox/flex-shrink-large-value-crash.html @web-platform-tests/interop +css/css-flexbox/frameset-crash.html @web-platform-tests/interop +css/css-flexbox/inline-flex-editing-crash.html @web-platform-tests/interop +css/css-flexbox/inline-flex-editing-with-updating-text-crash.html @web-platform-tests/interop +css/css-flexbox/inline-flex-frameset-main-axis-crash.html @web-platform-tests/interop +css/css-flexbox/negative-flex-margins-crash.html @web-platform-tests/interop +css/css-flexbox/negative-flex-rounding-crash.html @web-platform-tests/interop +css/css-flexbox/negative-item-margins-002-crash.html @web-platform-tests/interop +css/css-flexbox/negative-item-margins-crash.html @web-platform-tests/interop +css/css-flexbox/orthogonal-flex-item-crash.html @web-platform-tests/interop +css/css-flexbox/position-relative-with-scrollable-with-abspos-crash.html @web-platform-tests/interop +css/css-flexbox/remove-out-of-flow-child-crash.html @web-platform-tests/interop +css/css-flexbox/zero-content-size-with-scrollbar-crash.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-crash.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-add-item-with-positioned-items-crash.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-add-positioned-block-item-after-inline-item-crash.html @web-platform-tests/interop +css/css-grid/subgrid/crashtests/contain-strict-nested-subgrid.html @web-platform-tests/interop +css/css-grid/subgrid/crashtests/contain-strict-subgrid.html @web-platform-tests/interop +css/css-grid/subgrid/crashtests/subgrid-reflow-root.html @web-platform-tests/interop +css/css-view-transitions/document-element-detached-crash.html @web-platform-tests/interop +css/css-view-transitions/empty-render-target-crash.html @web-platform-tests/interop +css/css-view-transitions/get-computed-style-crash.html @web-platform-tests/interop +css/css-view-transitions/iframe-transition-destroyed-document-crash.html @web-platform-tests/interop +css/css-view-transitions/list-style-position-style-change-crash.html @web-platform-tests/interop +css/css-view-transitions/root-element-cv-hidden-crash.html @web-platform-tests/interop +css/css-view-transitions/root-element-display-none-crash.html @web-platform-tests/interop +css/css-view-transitions/root-element-display-none-during-transition-crash.html @web-platform-tests/interop +css/css-viewport/zoom/perspective-small-effective-zoom-crash.html @web-platform-tests/interop +css/css-viewport/zoom/scroll-corner-crash.html @web-platform-tests/interop +css/css-viewport/zoom/scrollbar-crash.html @web-platform-tests/interop +css/css-viewport/zoom/textarea-very-small-zoom-crash.html @web-platform-tests/interop +css/css-writing-modes/link-writing-mode-dependency-crash.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-feimage-crash.html @web-platform-tests/interop +html/rendering/the-details-element/details-autofocus-crash.html @web-platform-tests/interop +html/rendering/the-details-element/empty-crash.html @web-platform-tests/interop +html/rendering/the-details-element/two-summaries-removal-crash.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-005-print.html @web-platform-tests/interop +css/css-cascade/scope-implicit-crash-print.html @web-platform-tests/interop +css/css-anchor-position/anchor-center-002.html @web-platform-tests/interop +css/css-anchor-position/anchor-center-scroll.html @web-platform-tests/interop +css/css-anchor-position/anchor-in-css-min-max-function.html @web-platform-tests/interop +css/css-anchor-position/anchor-name-005.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-circular.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-dynamic-005.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-multicol-007.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-multicol-008.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-multicol-011.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-top-layer-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-top-layer-002.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-top-layer-003.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-top-layer-004.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-top-layer-005.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-top-layer-006.html @web-platform-tests/interop +css/css-anchor-position/anchor-scope-scroll.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-chained-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-chained-002.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-chained-003.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-chained-004.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-chained-fallback.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-composited-scrolling-006.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-fixedpos-002.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-fixedpos.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-nested.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-overflow-hidden.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-position-try-012.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-scrollable-anchor.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-to-sticky-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-to-sticky-002.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-to-sticky-003.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-to-sticky-004.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-update-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-update-002.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-update-003.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-update-004.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-update-005.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-update-006.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-update-007.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-vlr.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-vrl.html @web-platform-tests/interop +css/css-anchor-position/position-anchor-001.html @web-platform-tests/interop +css/css-anchor-position/position-anchor-002.html @web-platform-tests/interop +css/css-anchor-position/position-anchor-004.html @web-platform-tests/interop +css/css-anchor-position/position-anchor-target-with-children.html @web-platform-tests/interop +css/css-anchor-position/position-area-abs-inline-container.html @web-platform-tests/interop +css/css-anchor-position/position-area-inline-container.html @web-platform-tests/interop +css/css-anchor-position/position-area-scroll-adjust.html @web-platform-tests/interop +css/css-anchor-position/position-try-switch-from-fixed-anchor.html @web-platform-tests/interop +css/css-anchor-position/position-try-switch-to-fixed-anchor.html @web-platform-tests/interop +css/css-anchor-position/position-visibility-add-no-overflow.html @web-platform-tests/interop +css/css-anchor-position/position-visibility-anchors-visible-after-scroll-in.html @web-platform-tests/interop +css/css-anchor-position/position-visibility-anchors-visible-after-scroll-out.html @web-platform-tests/interop +css/css-anchor-position/position-visibility-anchors-visible-chained-001.html @web-platform-tests/interop +css/css-anchor-position/position-visibility-anchors-visible-chained-002.html @web-platform-tests/interop +css/css-anchor-position/position-visibility-anchors-visible-chained-003.html @web-platform-tests/interop +css/css-anchor-position/position-visibility-anchors-visible-chained-004.html @web-platform-tests/interop +css/css-anchor-position/position-visibility-anchors-visible-change-anchor.html @web-platform-tests/interop +css/css-anchor-position/position-visibility-anchors-visible-change-css-visibility.html @web-platform-tests/interop +css/css-anchor-position/position-visibility-anchors-visible-css-visibility.html @web-platform-tests/interop +css/css-anchor-position/position-visibility-anchors-visible-non-intervening-container.html @web-platform-tests/interop +css/css-anchor-position/position-visibility-anchors-visible-stacked-child.html @web-platform-tests/interop +css/css-anchor-position/position-visibility-anchors-visible-with-position.html @web-platform-tests/interop +css/css-anchor-position/position-visibility-anchors-visible.html @web-platform-tests/interop +css/css-anchor-position/position-visibility-no-overflow-scroll.html @web-platform-tests/interop +css/css-anchor-position/position-visibility-no-overflow-stacked-child.html @web-platform-tests/interop +css/css-anchor-position/position-visibility-no-overflow.html @web-platform-tests/interop +css/css-anchor-position/position-visibility-remove-anchors-visible.html @web-platform-tests/interop +css/css-anchor-position/position-visibility-remove-no-overflow.html @web-platform-tests/interop +css/css-anchor-position/sticky-anchor-position-invalid.html @web-platform-tests/interop +css/css-break/transform-025.html @web-platform-tests/interop +css/css-cascade/scope-featureless.html @web-platform-tests/interop +css/css-cascade/scope-part.html @web-platform-tests/interop +css/css-cascade/scope-pseudo-element.html @web-platform-tests/interop +css/css-cascade/scope-shadow-sharing.html @web-platform-tests/interop +css/css-cascade/scope-visited.html @web-platform-tests/interop +css/css-flexbox/abspos/abspos-autopos-htb-ltr.html @web-platform-tests/interop +css/css-flexbox/abspos/abspos-autopos-htb-rtl.html @web-platform-tests/interop +css/css-flexbox/abspos/abspos-autopos-vlr-ltr.html @web-platform-tests/interop +css/css-flexbox/abspos/abspos-autopos-vlr-rtl.html @web-platform-tests/interop +css/css-flexbox/abspos/abspos-autopos-vrl-ltr.html @web-platform-tests/interop +css/css-flexbox/abspos/abspos-autopos-vrl-rtl.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-align-self-safe-001.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-fallback-justify-content-001.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-justify-self-001.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-margin-001.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-margin-002.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-margin-003.html @web-platform-tests/interop +css/css-flexbox/abspos/flexbox-abspos-child-001a.html @web-platform-tests/interop +css/css-flexbox/abspos/flexbox-abspos-child-001b.html @web-platform-tests/interop +css/css-flexbox/abspos/flexbox-abspos-child-002.html @web-platform-tests/interop +css/css-flexbox/abspos/flexbox_absolute-atomic.html @web-platform-tests/interop +css/css-flexbox/abspos/flexbox_inline-abspos.html @web-platform-tests/interop +css/css-flexbox/abspos/position-absolute-005.html @web-platform-tests/interop +css/css-flexbox/abspos/position-absolute-006.html @web-platform-tests/interop +css/css-flexbox/abspos/position-absolute-007.html @web-platform-tests/interop +css/css-flexbox/abspos/position-absolute-008.html @web-platform-tests/interop +css/css-flexbox/abspos/position-absolute-009.html @web-platform-tests/interop +css/css-flexbox/abspos/position-absolute-010.html @web-platform-tests/interop +css/css-flexbox/abspos/position-absolute-011.html @web-platform-tests/interop +css/css-flexbox/abspos/position-absolute-containing-block-001.html @web-platform-tests/interop +css/css-flexbox/abspos/position-absolute-containing-block-002.html @web-platform-tests/interop +css/css-flexbox/align-baseline.html @web-platform-tests/interop +css/css-flexbox/align-content-001.htm @web-platform-tests/interop +css/css-flexbox/align-content-002.htm @web-platform-tests/interop +css/css-flexbox/align-content-003.htm @web-platform-tests/interop +css/css-flexbox/align-content-004.htm @web-platform-tests/interop +css/css-flexbox/align-content-005.htm @web-platform-tests/interop +css/css-flexbox/align-content-006.htm @web-platform-tests/interop +css/css-flexbox/align-content-wrap-004.html @web-platform-tests/interop +css/css-flexbox/align-content_center.html @web-platform-tests/interop +css/css-flexbox/align-content_flex-end.html @web-platform-tests/interop +css/css-flexbox/align-content_flex-start.html @web-platform-tests/interop +css/css-flexbox/align-content_space-around.html @web-platform-tests/interop +css/css-flexbox/align-content_space-between.html @web-platform-tests/interop +css/css-flexbox/align-content_stretch.html @web-platform-tests/interop +css/css-flexbox/align-items-001.htm @web-platform-tests/interop +css/css-flexbox/align-items-002.htm @web-platform-tests/interop +css/css-flexbox/align-items-003.htm @web-platform-tests/interop +css/css-flexbox/align-items-004.htm @web-platform-tests/interop +css/css-flexbox/align-items-005.htm @web-platform-tests/interop +css/css-flexbox/align-items-006.html @web-platform-tests/interop +css/css-flexbox/align-items-007.html @web-platform-tests/interop +css/css-flexbox/align-items-008.html @web-platform-tests/interop +css/css-flexbox/align-items-009.html @web-platform-tests/interop +css/css-flexbox/align-items-baseline-column-horz.html @web-platform-tests/interop +css/css-flexbox/align-items-baseline-column-vert.html @web-platform-tests/interop +css/css-flexbox/align-items-baseline-overflow-non-visible.html @web-platform-tests/interop +css/css-flexbox/align-items-baseline-row-horz.html @web-platform-tests/interop +css/css-flexbox/align-items-baseline-row-vert.html @web-platform-tests/interop +css/css-flexbox/align-self-001.html @web-platform-tests/interop +css/css-flexbox/align-self-002.html @web-platform-tests/interop +css/css-flexbox/align-self-003.html @web-platform-tests/interop +css/css-flexbox/align-self-004.html @web-platform-tests/interop +css/css-flexbox/align-self-005.html @web-platform-tests/interop +css/css-flexbox/align-self-007.html @web-platform-tests/interop +css/css-flexbox/align-self-008.html @web-platform-tests/interop +css/css-flexbox/align-self-009.html @web-platform-tests/interop +css/css-flexbox/align-self-011.html @web-platform-tests/interop +css/css-flexbox/align-self-012.html @web-platform-tests/interop +css/css-flexbox/align-self-013.html @web-platform-tests/interop +css/css-flexbox/align-self-015.html @web-platform-tests/interop +css/css-flexbox/anonymous-block.html @web-platform-tests/interop +css/css-flexbox/anonymous-flex-item-001.html @web-platform-tests/interop +css/css-flexbox/anonymous-flex-item-002.html @web-platform-tests/interop +css/css-flexbox/anonymous-flex-item-003.html @web-platform-tests/interop +css/css-flexbox/anonymous-flex-item-004.html @web-platform-tests/interop +css/css-flexbox/anonymous-flex-item-005.html @web-platform-tests/interop +css/css-flexbox/anonymous-flex-item-006.html @web-platform-tests/interop +css/css-flexbox/aspect-ratio-intrinsic-size-001.html @web-platform-tests/interop +css/css-flexbox/aspect-ratio-intrinsic-size-002.html @web-platform-tests/interop +css/css-flexbox/aspect-ratio-intrinsic-size-003.html @web-platform-tests/interop +css/css-flexbox/aspect-ratio-intrinsic-size-004.html @web-platform-tests/interop +css/css-flexbox/aspect-ratio-intrinsic-size-005.html @web-platform-tests/interop +css/css-flexbox/aspect-ratio-intrinsic-size-006.html @web-platform-tests/interop +css/css-flexbox/aspect-ratio-intrinsic-size-007.html @web-platform-tests/interop +css/css-flexbox/auto-height-column-with-border-and-padding.html @web-platform-tests/interop +css/css-flexbox/auto-height-with-flex.html @web-platform-tests/interop +css/css-flexbox/auto-margins-001.html @web-platform-tests/interop +css/css-flexbox/auto-margins-002.html @web-platform-tests/interop +css/css-flexbox/auto-margins-003.html @web-platform-tests/interop +css/css-flexbox/baseline-synthesis-001.html @web-platform-tests/interop +css/css-flexbox/baseline-synthesis-002.html @web-platform-tests/interop +css/css-flexbox/baseline-synthesis-003.html @web-platform-tests/interop +css/css-flexbox/baseline-synthesis-004.html @web-platform-tests/interop +css/css-flexbox/canvas-contain-size.html @web-platform-tests/interop +css/css-flexbox/content-height-with-scrollbars.html @web-platform-tests/interop +css/css-flexbox/cross-axis-scrollbar.html @web-platform-tests/interop +css/css-flexbox/css-box-justify-content.html @web-platform-tests/interop +css/css-flexbox/css-flexbox-img-expand-evenly.html @web-platform-tests/interop +css/css-flexbox/css-flexbox-row-reverse-wrap-reverse.html @web-platform-tests/interop +css/css-flexbox/css-flexbox-row-reverse-wrap.html @web-platform-tests/interop +css/css-flexbox/css-flexbox-row-reverse.html @web-platform-tests/interop +css/css-flexbox/css-flexbox-row-wrap-reverse.html @web-platform-tests/interop +css/css-flexbox/css-flexbox-row-wrap.html @web-platform-tests/interop +css/css-flexbox/css-flexbox-row.html @web-platform-tests/interop +css/css-flexbox/css-flexbox-test1.html @web-platform-tests/interop +css/css-flexbox/display-flex-001.htm @web-platform-tests/interop +css/css-flexbox/dynamic-baseline-change-nested.html @web-platform-tests/interop +css/css-flexbox/dynamic-baseline-change.html @web-platform-tests/interop +css/css-flexbox/dynamic-bsize-change.html @web-platform-tests/interop +css/css-flexbox/dynamic-change-simplified-layout-002.html @web-platform-tests/interop +css/css-flexbox/dynamic-change-simplified-layout.html @web-platform-tests/interop +css/css-flexbox/dynamic-isize-change-001.html @web-platform-tests/interop +css/css-flexbox/dynamic-isize-change-002.html @web-platform-tests/interop +css/css-flexbox/dynamic-isize-change-003.html @web-platform-tests/interop +css/css-flexbox/dynamic-isize-change-004.html @web-platform-tests/interop +css/css-flexbox/dynamic-stretch-change.html @web-platform-tests/interop +css/css-flexbox/fieldset-as-item-dynamic.html @web-platform-tests/interop +css/css-flexbox/fieldset-as-item-overflow.html @web-platform-tests/interop +css/css-flexbox/fieldset-baseline-alignment.html @web-platform-tests/interop +css/css-flexbox/fit-content-item-001.html @web-platform-tests/interop +css/css-flexbox/fit-content-item-002.html @web-platform-tests/interop +css/css-flexbox/fit-content-item-003.html @web-platform-tests/interop +css/css-flexbox/fit-content-item-004.html @web-platform-tests/interop +css/css-flexbox/fixed-table-layout-with-percentage-width-in-flex-item.html @web-platform-tests/interop +css/css-flexbox/flex-001.htm @web-platform-tests/interop +css/css-flexbox/flex-002.htm @web-platform-tests/interop +css/css-flexbox/flex-003.htm @web-platform-tests/interop +css/css-flexbox/flex-004.htm @web-platform-tests/interop +css/css-flexbox/flex-align-content-center.html @web-platform-tests/interop +css/css-flexbox/flex-align-content-end.html @web-platform-tests/interop +css/css-flexbox/flex-align-content-space-around.html @web-platform-tests/interop +css/css-flexbox/flex-align-content-space-between.html @web-platform-tests/interop +css/css-flexbox/flex-align-content-start.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-column-001.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-column-002.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-column-003.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-column-004.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-column-005.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-column-006.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-column-007.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-column-008.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-column-009.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-column-010.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-column-012.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-column-013.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-column-014.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-column-015.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-column-016.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-column-018.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-row-001.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-row-002.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-row-003.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-row-004.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-row-006.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-row-007.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-row-008.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-row-009.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-row-010.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-row-011.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-row-012.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-row-014.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-row-015.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-row-016.html @web-platform-tests/interop +css/css-flexbox/flex-basis-001.html @web-platform-tests/interop +css/css-flexbox/flex-basis-002.html @web-platform-tests/interop +css/css-flexbox/flex-basis-003.html @web-platform-tests/interop +css/css-flexbox/flex-basis-004.html @web-platform-tests/interop +css/css-flexbox/flex-basis-005.html @web-platform-tests/interop +css/css-flexbox/flex-basis-006.html @web-platform-tests/interop +css/css-flexbox/flex-basis-007.html @web-platform-tests/interop +css/css-flexbox/flex-basis-008.html @web-platform-tests/interop +css/css-flexbox/flex-basis-010.html @web-platform-tests/interop +css/css-flexbox/flex-basis-011.html @web-platform-tests/interop +css/css-flexbox/flex-basis-012.html @web-platform-tests/interop +css/css-flexbox/flex-basis-item-margins-001.html @web-platform-tests/interop +css/css-flexbox/flex-box-wrap.html @web-platform-tests/interop +css/css-flexbox/flex-child-percent-basis-resize-1.html @web-platform-tests/interop +css/css-flexbox/flex-container-margin.html @web-platform-tests/interop +css/css-flexbox/flex-direction-column-reverse.html @web-platform-tests/interop +css/css-flexbox/flex-direction-column.html @web-platform-tests/interop +css/css-flexbox/flex-direction-modify.html @web-platform-tests/interop +css/css-flexbox/flex-direction-row-reverse.html @web-platform-tests/interop +css/css-flexbox/flex-direction-row-vertical.html @web-platform-tests/interop +css/css-flexbox/flex-direction-with-element-insert.html @web-platform-tests/interop +css/css-flexbox/flex-direction.html @web-platform-tests/interop +css/css-flexbox/flex-flow-001.html @web-platform-tests/interop +css/css-flexbox/flex-flow-002.html @web-platform-tests/interop +css/css-flexbox/flex-flow-003.html @web-platform-tests/interop +css/css-flexbox/flex-flow-004.html @web-platform-tests/interop +css/css-flexbox/flex-flow-005.html @web-platform-tests/interop +css/css-flexbox/flex-flow-006.html @web-platform-tests/interop +css/css-flexbox/flex-flow-007.html @web-platform-tests/interop +css/css-flexbox/flex-flow-008.html @web-platform-tests/interop +css/css-flexbox/flex-flow-009.html @web-platform-tests/interop +css/css-flexbox/flex-flow-010.html @web-platform-tests/interop +css/css-flexbox/flex-flow-011.html @web-platform-tests/interop +css/css-flexbox/flex-flow-012.html @web-platform-tests/interop +css/css-flexbox/flex-grow-001.xht @web-platform-tests/interop +css/css-flexbox/flex-grow-002.html @web-platform-tests/interop +css/css-flexbox/flex-grow-003.html @web-platform-tests/interop +css/css-flexbox/flex-grow-004.html @web-platform-tests/interop +css/css-flexbox/flex-grow-005.html @web-platform-tests/interop +css/css-flexbox/flex-grow-006.html @web-platform-tests/interop +css/css-flexbox/flex-grow-007.html @web-platform-tests/interop +css/css-flexbox/flex-grow-008.html @web-platform-tests/interop +css/css-flexbox/flex-height-min-content.html @web-platform-tests/interop +css/css-flexbox/flex-inline.html @web-platform-tests/interop +css/css-flexbox/flex-item-and-percentage-abspos.html @web-platform-tests/interop +css/css-flexbox/flex-item-contains-size-layout-001.html @web-platform-tests/interop +css/css-flexbox/flex-item-transferred-sizes-padding-border-sizing.html @web-platform-tests/interop +css/css-flexbox/flex-item-transferred-sizes-padding-content-sizing.html @web-platform-tests/interop +css/css-flexbox/flex-item-vertical-align.html @web-platform-tests/interop +css/css-flexbox/flex-item-z-ordering-001.html @web-platform-tests/interop +css/css-flexbox/flex-lines/multi-line-wrap-reverse-column-reverse.html @web-platform-tests/interop +css/css-flexbox/flex-lines/multi-line-wrap-reverse-row-reverse.html @web-platform-tests/interop +css/css-flexbox/flex-lines/multi-line-wrap-with-column-reverse.html @web-platform-tests/interop +css/css-flexbox/flex-lines/multi-line-wrap-with-row-reverse.html @web-platform-tests/interop +css/css-flexbox/flex-margin-no-collapse.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-001.xht @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-002.xht @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-003.xht @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-004.xht @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-005.xht @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-006.xht @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-007.xht @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-008.xht @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-011.xht @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-013.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-014.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-015.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-016.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-017.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-018.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-019.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-020.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-021.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-022.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-023.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-024.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-026.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-027.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-028.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-029.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-030.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-width-flex-items-001.xht @web-platform-tests/interop +css/css-flexbox/flex-minimum-width-flex-items-002.xht @web-platform-tests/interop +css/css-flexbox/flex-minimum-width-flex-items-003.xht @web-platform-tests/interop +css/css-flexbox/flex-minimum-width-flex-items-004.xht @web-platform-tests/interop +css/css-flexbox/flex-minimum-width-flex-items-005.xht @web-platform-tests/interop +css/css-flexbox/flex-minimum-width-flex-items-006.xht @web-platform-tests/interop +css/css-flexbox/flex-minimum-width-flex-items-007.xht @web-platform-tests/interop +css/css-flexbox/flex-minimum-width-flex-items-008.xht @web-platform-tests/interop +css/css-flexbox/flex-minimum-width-flex-items-009.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-width-flex-items-010.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-width-flex-items-011.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-width-flex-items-012.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-width-flex-items-013.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-width-flex-items-015.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-width-flex-items-016.html @web-platform-tests/interop +css/css-flexbox/flex-order.html @web-platform-tests/interop +css/css-flexbox/flex-shrink-001.html @web-platform-tests/interop +css/css-flexbox/flex-shrink-002.html @web-platform-tests/interop +css/css-flexbox/flex-shrink-003.html @web-platform-tests/interop +css/css-flexbox/flex-shrink-004.html @web-platform-tests/interop +css/css-flexbox/flex-shrink-005.html @web-platform-tests/interop +css/css-flexbox/flex-shrink-006.html @web-platform-tests/interop +css/css-flexbox/flex-shrink-007.html @web-platform-tests/interop +css/css-flexbox/flex-shrink-008.html @web-platform-tests/interop +css/css-flexbox/flex-vertical-align-effect.html @web-platform-tests/interop +css/css-flexbox/flex-wrap-002.html @web-platform-tests/interop +css/css-flexbox/flex-wrap-003.html @web-platform-tests/interop +css/css-flexbox/flex-wrap-004.html @web-platform-tests/interop +css/css-flexbox/flex-wrap-005.html @web-platform-tests/interop +css/css-flexbox/flex-wrap-006.html @web-platform-tests/interop +css/css-flexbox/flexbox-align-items-center-nested-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-baseline-horiz-001a.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-baseline-horiz-001b.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-baseline-horiz-002.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-baseline-horiz-003.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-baseline-horiz-004.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-baseline-horiz-005.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-baseline-horiz-006.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-baseline-horiz-007.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-baseline-horiz-008.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-horiz-001-block.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-horiz-001-table.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-horiz-002.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-horiz-003.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-horiz-004.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-horiz-005.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-stretch-vert-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-stretch-vert-002.html @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-vert-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-vert-002.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-vert-003.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-vert-004.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-vert-rtl-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-vert-rtl-002.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-vert-rtl-003.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-vert-rtl-004.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-align-self-vert-rtl-005.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-anonymous-items-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-baseline-align-self-baseline-horiz-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-baseline-align-self-baseline-vert-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-baseline-empty-001a.html @web-platform-tests/interop +css/css-flexbox/flexbox-baseline-empty-001b.html @web-platform-tests/interop +css/css-flexbox/flexbox-baseline-multi-item-horiz-001a.html @web-platform-tests/interop +css/css-flexbox/flexbox-baseline-multi-item-horiz-001b.html @web-platform-tests/interop +css/css-flexbox/flexbox-baseline-multi-item-vert-001a.html @web-platform-tests/interop +css/css-flexbox/flexbox-baseline-multi-item-vert-001b.html @web-platform-tests/interop +css/css-flexbox/flexbox-baseline-multi-line-horiz-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-baseline-multi-line-horiz-002.html @web-platform-tests/interop +css/css-flexbox/flexbox-baseline-multi-line-horiz-003.html @web-platform-tests/interop +css/css-flexbox/flexbox-baseline-multi-line-horiz-004.html @web-platform-tests/interop +css/css-flexbox/flexbox-baseline-multi-line-vert-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-baseline-multi-line-vert-002.html @web-platform-tests/interop +css/css-flexbox/flexbox-baseline-nested-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-baseline-single-item-001a.html @web-platform-tests/interop +css/css-flexbox/flexbox-baseline-single-item-001b.html @web-platform-tests/interop +css/css-flexbox/flexbox-basic-block-horiz-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-basic-block-horiz-001v.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-basic-block-vert-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-basic-block-vert-001v.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-basic-canvas-horiz-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-basic-canvas-horiz-001v.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-basic-canvas-vert-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-basic-canvas-vert-001v.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-basic-fieldset-horiz-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-basic-fieldset-vert-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-basic-iframe-horiz-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-basic-iframe-vert-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-basic-img-horiz-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-basic-img-vert-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-basic-textarea-horiz-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-basic-textarea-vert-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-basic-video-horiz-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-basic-video-vert-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-break-request-horiz-001a.html @web-platform-tests/interop +css/css-flexbox/flexbox-break-request-horiz-001b.html @web-platform-tests/interop +css/css-flexbox/flexbox-break-request-horiz-002a.html @web-platform-tests/interop +css/css-flexbox/flexbox-break-request-horiz-002b.html @web-platform-tests/interop +css/css-flexbox/flexbox-break-request-vert-001a.html @web-platform-tests/interop +css/css-flexbox/flexbox-break-request-vert-001b.html @web-platform-tests/interop +css/css-flexbox/flexbox-break-request-vert-002a.html @web-platform-tests/interop +css/css-flexbox/flexbox-break-request-vert-002b.html @web-platform-tests/interop +css/css-flexbox/flexbox-column-row-gap-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-column-row-gap-002.html @web-platform-tests/interop +css/css-flexbox/flexbox-column-row-gap-003.html @web-platform-tests/interop +css/css-flexbox/flexbox-column-row-gap-004.html @web-platform-tests/interop +css/css-flexbox/flexbox-definite-cross-size-constrained-percentage.html @web-platform-tests/interop +css/css-flexbox/flexbox-definite-sizes-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-definite-sizes-002.html @web-platform-tests/interop +css/css-flexbox/flexbox-definite-sizes-003.html @web-platform-tests/interop +css/css-flexbox/flexbox-definite-sizes-004.html @web-platform-tests/interop +css/css-flexbox/flexbox-definite-sizes-005.html @web-platform-tests/interop +css/css-flexbox/flexbox-definite-sizes-006.html @web-platform-tests/interop +css/css-flexbox/flexbox-dyn-resize-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-flex-basis-content-001a.html @web-platform-tests/interop +css/css-flexbox/flexbox-flex-basis-content-001b.html @web-platform-tests/interop +css/css-flexbox/flexbox-flex-basis-content-002a.html @web-platform-tests/interop +css/css-flexbox/flexbox-flex-basis-content-002b.html @web-platform-tests/interop +css/css-flexbox/flexbox-flex-basis-content-003a.html @web-platform-tests/interop +css/css-flexbox/flexbox-flex-basis-content-003b.html @web-platform-tests/interop +css/css-flexbox/flexbox-flex-basis-content-004a.html @web-platform-tests/interop +css/css-flexbox/flexbox-flex-basis-content-004b.html @web-platform-tests/interop +css/css-flexbox/flexbox-flex-direction-column-percentage-ignored.html @web-platform-tests/interop +css/css-flexbox/flexbox-flex-direction-column-reverse.htm @web-platform-tests/interop +css/css-flexbox/flexbox-flex-direction-column.htm @web-platform-tests/interop +css/css-flexbox/flexbox-flex-direction-default.htm @web-platform-tests/interop +css/css-flexbox/flexbox-flex-direction-row-reverse.htm @web-platform-tests/interop +css/css-flexbox/flexbox-flex-direction-row.htm @web-platform-tests/interop +css/css-flexbox/flexbox-flex-flow-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-flex-flow-002.html @web-platform-tests/interop +css/css-flexbox/flexbox-flex-wrap-default.htm @web-platform-tests/interop +css/css-flexbox/flexbox-flex-wrap-flexing.html @web-platform-tests/interop +css/css-flexbox/flexbox-flex-wrap-horiz-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-flex-wrap-horiz-002.html @web-platform-tests/interop +css/css-flexbox/flexbox-flex-wrap-nowrap.htm @web-platform-tests/interop +css/css-flexbox/flexbox-flex-wrap-vert-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-flex-wrap-vert-002.html @web-platform-tests/interop +css/css-flexbox/flexbox-flex-wrap-wrap-reverse.htm @web-platform-tests/interop +css/css-flexbox/flexbox-flex-wrap-wrap.htm @web-platform-tests/interop +css/css-flexbox/flexbox-gap-position-absolute.html @web-platform-tests/interop +css/css-flexbox/flexbox-iframe-intrinsic-size-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-items-as-stacking-contexts-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-items-as-stacking-contexts-002.html @web-platform-tests/interop +css/css-flexbox/flexbox-items-as-stacking-contexts-003.html @web-platform-tests/interop +css/css-flexbox/flexbox-justify-content-horiz-001a.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-justify-content-horiz-001b.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-justify-content-horiz-002.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-justify-content-horiz-003.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-justify-content-horiz-004.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-justify-content-horiz-005.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-justify-content-horiz-006.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-justify-content-vert-001a.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-justify-content-vert-001b.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-justify-content-vert-002.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-justify-content-vert-003.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-justify-content-vert-004.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-justify-content-vert-005.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-justify-content-vert-006.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-justify-content-wmvert-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-margin-auto-horiz-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-margin-auto-horiz-002.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-mbp-horiz-001-reverse.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-mbp-horiz-001-rtl-reverse.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-mbp-horiz-001-rtl.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-mbp-horiz-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-mbp-horiz-002a.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-mbp-horiz-002b.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-mbp-horiz-002v.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-mbp-horiz-003-reverse.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-mbp-horiz-003.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-mbp-horiz-003v.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-mbp-horiz-004.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-min-height-auto-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-min-height-auto-002a.html @web-platform-tests/interop +css/css-flexbox/flexbox-min-height-auto-002b.html @web-platform-tests/interop +css/css-flexbox/flexbox-min-height-auto-002c.html @web-platform-tests/interop +css/css-flexbox/flexbox-min-height-auto-003.html @web-platform-tests/interop +css/css-flexbox/flexbox-min-height-auto-004.html @web-platform-tests/interop +css/css-flexbox/flexbox-min-width-auto-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-min-width-auto-002a.html @web-platform-tests/interop +css/css-flexbox/flexbox-min-width-auto-002b.html @web-platform-tests/interop +css/css-flexbox/flexbox-min-width-auto-002c.html @web-platform-tests/interop +css/css-flexbox/flexbox-min-width-auto-003.html @web-platform-tests/interop +css/css-flexbox/flexbox-min-width-auto-004.html @web-platform-tests/interop +css/css-flexbox/flexbox-min-width-auto-005.html @web-platform-tests/interop +css/css-flexbox/flexbox-min-width-auto-006.html @web-platform-tests/interop +css/css-flexbox/flexbox-order-only-flexitems.html @web-platform-tests/interop +css/css-flexbox/flexbox-overflow-horiz-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-overflow-horiz-002.html @web-platform-tests/interop +css/css-flexbox/flexbox-overflow-horiz-003.html @web-platform-tests/interop +css/css-flexbox/flexbox-overflow-horiz-004.html @web-platform-tests/interop +css/css-flexbox/flexbox-overflow-horiz-005.html @web-platform-tests/interop +css/css-flexbox/flexbox-overflow-padding-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-overflow-padding-002.html @web-platform-tests/interop +css/css-flexbox/flexbox-overflow-vert-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-overflow-vert-002.html @web-platform-tests/interop +css/css-flexbox/flexbox-overflow-vert-003.html @web-platform-tests/interop +css/css-flexbox/flexbox-overflow-vert-004.html @web-platform-tests/interop +css/css-flexbox/flexbox-overflow-vert-005.html @web-platform-tests/interop +css/css-flexbox/flexbox-paint-ordering-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-paint-ordering-002.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-paint-ordering-003.html @web-platform-tests/interop +css/css-flexbox/flexbox-root-node-001a.html @web-platform-tests/interop +css/css-flexbox/flexbox-root-node-001b.html @web-platform-tests/interop +css/css-flexbox/flexbox-safe-overflow-position-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-single-line-clamp-1.html @web-platform-tests/interop +css/css-flexbox/flexbox-single-line-clamp-2.html @web-platform-tests/interop +css/css-flexbox/flexbox-single-line-clamp-3.html @web-platform-tests/interop +css/css-flexbox/flexbox-sizing-horiz-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-sizing-horiz-002.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-sizing-vert-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-sizing-vert-002.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-table-fixup-001.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-whitespace-handling-001a.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-whitespace-handling-001b.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-whitespace-handling-002.xhtml @web-platform-tests/interop +css/css-flexbox/flexbox-with-multi-column-property.html @web-platform-tests/interop +css/css-flexbox/flexbox-with-pseudo-elements-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-with-pseudo-elements-002.html @web-platform-tests/interop +css/css-flexbox/flexbox-with-pseudo-elements-003.html @web-platform-tests/interop +css/css-flexbox/flexbox-writing-mode-001.html @web-platform-tests/interop +css/css-flexbox/flexbox-writing-mode-002.html @web-platform-tests/interop +css/css-flexbox/flexbox-writing-mode-003.html @web-platform-tests/interop +css/css-flexbox/flexbox-writing-mode-004.html @web-platform-tests/interop +css/css-flexbox/flexbox-writing-mode-005.html @web-platform-tests/interop +css/css-flexbox/flexbox-writing-mode-006.html @web-platform-tests/interop +css/css-flexbox/flexbox-writing-mode-007.html @web-platform-tests/interop +css/css-flexbox/flexbox-writing-mode-008.html @web-platform-tests/interop +css/css-flexbox/flexbox-writing-mode-009.html @web-platform-tests/interop +css/css-flexbox/flexbox-writing-mode-010.html @web-platform-tests/interop +css/css-flexbox/flexbox-writing-mode-011.html @web-platform-tests/interop +css/css-flexbox/flexbox-writing-mode-012.html @web-platform-tests/interop +css/css-flexbox/flexbox-writing-mode-013.html @web-platform-tests/interop +css/css-flexbox/flexbox-writing-mode-014.html @web-platform-tests/interop +css/css-flexbox/flexbox-writing-mode-015.html @web-platform-tests/interop +css/css-flexbox/flexbox-writing-mode-016.html @web-platform-tests/interop +css/css-flexbox/flexbox-writing-mode-slr-row-mix.html @web-platform-tests/interop +css/css-flexbox/flexbox-writing-mode-slr-rtl.html @web-platform-tests/interop +css/css-flexbox/flexbox-writing-mode-slr.html @web-platform-tests/interop +css/css-flexbox/flexbox-writing-mode-srl-row-mix.html @web-platform-tests/interop +css/css-flexbox/flexbox-writing-mode-srl-rtl.html @web-platform-tests/interop +css/css-flexbox/flexbox-writing-mode-srl.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-content-center.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-content-flexend.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-content-flexstart.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-content-spacearound.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-content-spacebetween.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-content-stretch-2.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-content-stretch.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-items-baseline.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-items-center-2.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-items-center-3.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-items-center.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-items-flexend-2.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-items-flexend.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-items-flexstart-2.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-items-flexstart.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-items-stretch-2.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-items-stretch-3.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-items-stretch-4.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-items-stretch.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-self-auto.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-self-baseline.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-self-center.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-self-flexend.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-self-flexstart.html @web-platform-tests/interop +css/css-flexbox/flexbox_align-self-stretch.html @web-platform-tests/interop +css/css-flexbox/flexbox_block.html @web-platform-tests/interop +css/css-flexbox/flexbox_box-clear.html @web-platform-tests/interop +css/css-flexbox/flexbox_columns-flexitems-2.html @web-platform-tests/interop +css/css-flexbox/flexbox_columns-flexitems.html @web-platform-tests/interop +css/css-flexbox/flexbox_columns.html @web-platform-tests/interop +css/css-flexbox/flexbox_direction-column-reverse.html @web-platform-tests/interop +css/css-flexbox/flexbox_direction-column.html @web-platform-tests/interop +css/css-flexbox/flexbox_direction-row-reverse.html @web-platform-tests/interop +css/css-flexbox/flexbox_display.html @web-platform-tests/interop +css/css-flexbox/flexbox_fbfc.html @web-platform-tests/interop +css/css-flexbox/flexbox_fbfc2.html @web-platform-tests/interop +css/css-flexbox/flexbox_first-line.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-0-0-unitless.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-0-0.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-0-1-unitless-basis.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-0-N-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-0-N-unitless-basis.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-0-N.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-0-Npercent-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-0-Npercent.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-0-auto-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-0-auto.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-0.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-1-0-unitless.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-1-0.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-1-1-unitless-basis.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-1-N-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-1-N-unitless-basis.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-1-N.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-1-Npercent-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-1-Npercent.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-1-auto-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-1-auto.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-1.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-N-0-unitless.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-N-0.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-N-N-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-N-N.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-N-Npercent-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-N-Npercent.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-N-auto-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-N-auto.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-N.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-0-auto.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-0-0-unitless.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-0-0.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-0-N-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-0-N.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-0-Npercent-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-0-Npercent.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-0-auto-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-0-auto.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-0.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-1-0-unitless.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-1-0.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-1-N-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-1-N.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-1-Npercent-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-1-Npercent.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-1-auto-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-1-auto.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-1.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-N-0-unitless.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-N-0.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-N-N-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-N-N.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-N-Npercent-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-N-Npercent.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-N-auto-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-N-auto.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-1-N.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-0-0-unitless.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-0-0.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-0-N-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-0-N.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-0-Npercent-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-0-Npercent.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-0-auto-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-0-auto.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-0.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-1-0-unitless.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-1-0.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-1-N-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-1-N.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-1-Npercent-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-1-Npercent.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-1-auto-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-1-auto.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-1.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-N-0-unitless.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-N-0.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-N-N-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-N-N.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-N-Npercent-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-N-Npercent.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-N-auto-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-N-auto.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-N-N.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-auto.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-basis-shrink.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-basis.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-formatting-interop.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-initial-2.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-initial.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-natural-mixed-basis-auto.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-natural-mixed-basis.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-natural-variable-auto-basis.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-natural-variable-zero-basis.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-natural.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-none-wrappable-content.html @web-platform-tests/interop +css/css-flexbox/flexbox_flex-none.html @web-platform-tests/interop +css/css-flexbox/flexbox_flow-column-reverse-wrap-reverse.html @web-platform-tests/interop +css/css-flexbox/flexbox_flow-column-reverse-wrap.html @web-platform-tests/interop +css/css-flexbox/flexbox_flow-column-wrap-reverse.html @web-platform-tests/interop +css/css-flexbox/flexbox_flow-column-wrap.html @web-platform-tests/interop +css/css-flexbox/flexbox_flow-row-wrap-reverse.html @web-platform-tests/interop +css/css-flexbox/flexbox_flow-row-wrap.html @web-platform-tests/interop +css/css-flexbox/flexbox_generated-flex.html @web-platform-tests/interop +css/css-flexbox/flexbox_generated-nested-flex.html @web-platform-tests/interop +css/css-flexbox/flexbox_generated.html @web-platform-tests/interop +css/css-flexbox/flexbox_inline-float.html @web-platform-tests/interop +css/css-flexbox/flexbox_inline.html @web-platform-tests/interop +css/css-flexbox/flexbox_item-bottom-float.html @web-platform-tests/interop +css/css-flexbox/flexbox_item-clear.html @web-platform-tests/interop +css/css-flexbox/flexbox_item-float.html @web-platform-tests/interop +css/css-flexbox/flexbox_item-top-float.html @web-platform-tests/interop +css/css-flexbox/flexbox_item-vertical-align.html @web-platform-tests/interop +css/css-flexbox/flexbox_justifycontent-center.html @web-platform-tests/interop +css/css-flexbox/flexbox_justifycontent-end-rtl.html @web-platform-tests/interop +css/css-flexbox/flexbox_justifycontent-end.html @web-platform-tests/interop +css/css-flexbox/flexbox_justifycontent-flex-end.html @web-platform-tests/interop +css/css-flexbox/flexbox_justifycontent-flex-start.html @web-platform-tests/interop +css/css-flexbox/flexbox_justifycontent-left-001.html @web-platform-tests/interop +css/css-flexbox/flexbox_justifycontent-left-002.html @web-platform-tests/interop +css/css-flexbox/flexbox_justifycontent-right-001.html @web-platform-tests/interop +css/css-flexbox/flexbox_justifycontent-right-002.html @web-platform-tests/interop +css/css-flexbox/flexbox_justifycontent-spacearound-negative.html @web-platform-tests/interop +css/css-flexbox/flexbox_justifycontent-spacearound-only.html @web-platform-tests/interop +css/css-flexbox/flexbox_justifycontent-spacearound.html @web-platform-tests/interop +css/css-flexbox/flexbox_justifycontent-spacebetween-negative.html @web-platform-tests/interop +css/css-flexbox/flexbox_justifycontent-spacebetween-only.html @web-platform-tests/interop +css/css-flexbox/flexbox_justifycontent-spacebetween.html @web-platform-tests/interop +css/css-flexbox/flexbox_justifycontent-start-rtl.html @web-platform-tests/interop +css/css-flexbox/flexbox_justifycontent-start.html @web-platform-tests/interop +css/css-flexbox/flexbox_margin-auto-overflow.html @web-platform-tests/interop +css/css-flexbox/flexbox_margin-auto.html @web-platform-tests/interop +css/css-flexbox/flexbox_margin-left-ex.html @web-platform-tests/interop +css/css-flexbox/flexbox_margin.html @web-platform-tests/interop +css/css-flexbox/flexbox_nested-flex.html @web-platform-tests/interop +css/css-flexbox/flexbox_object.html @web-platform-tests/interop +css/css-flexbox/flexbox_order-abspos-space-around.html @web-platform-tests/interop +css/css-flexbox/flexbox_order-box.html @web-platform-tests/interop +css/css-flexbox/flexbox_order-noninteger-invalid.html @web-platform-tests/interop +css/css-flexbox/flexbox_order.html @web-platform-tests/interop +css/css-flexbox/flexbox_quirks_body.html @web-platform-tests/interop +css/css-flexbox/flexbox_rowspan-overflow-automatic.html @web-platform-tests/interop +css/css-flexbox/flexbox_rowspan-overflow.html @web-platform-tests/interop +css/css-flexbox/flexbox_rowspan.html @web-platform-tests/interop +css/css-flexbox/flexbox_rtl-direction.html @web-platform-tests/interop +css/css-flexbox/flexbox_rtl-flow-reverse.html @web-platform-tests/interop +css/css-flexbox/flexbox_rtl-flow.html @web-platform-tests/interop +css/css-flexbox/flexbox_rtl-order.html @web-platform-tests/interop +css/css-flexbox/flexbox_stf-abspos.html @web-platform-tests/interop +css/css-flexbox/flexbox_stf-fixpos.html @web-platform-tests/interop +css/css-flexbox/flexbox_stf-float.html @web-platform-tests/interop +css/css-flexbox/flexbox_stf-inline-block.html @web-platform-tests/interop +css/css-flexbox/flexbox_stf-table-caption.html @web-platform-tests/interop +css/css-flexbox/flexbox_stf-table-cell.html @web-platform-tests/interop +css/css-flexbox/flexbox_stf-table-row-group.html @web-platform-tests/interop +css/css-flexbox/flexbox_stf-table-row.html @web-platform-tests/interop +css/css-flexbox/flexbox_stf-table-singleline-2.html @web-platform-tests/interop +css/css-flexbox/flexbox_stf-table-singleline.html @web-platform-tests/interop +css/css-flexbox/flexbox_stf-table.html @web-platform-tests/interop +css/css-flexbox/flexbox_table-fixed-layout.html @web-platform-tests/interop +css/css-flexbox/flexbox_width-overflow.html @web-platform-tests/interop +css/css-flexbox/flexbox_wrap-long.html @web-platform-tests/interop +css/css-flexbox/flexbox_wrap-reverse.html @web-platform-tests/interop +css/css-flexbox/flexbox_wrap.html @web-platform-tests/interop +css/css-flexbox/flexbox_writing_mode_vertical_lays_out_contents_from_top_to_bottom.html @web-platform-tests/interop +css/css-flexbox/flexible-box-float.html @web-platform-tests/interop +css/css-flexbox/flexible-order.html @web-platform-tests/interop +css/css-flexbox/flexitem-stretch-range.html @web-platform-tests/interop +css/css-flexbox/gap-001-lr.html @web-platform-tests/interop +css/css-flexbox/gap-001-ltr.html @web-platform-tests/interop +css/css-flexbox/gap-001-rl.html @web-platform-tests/interop +css/css-flexbox/gap-001-rtl.html @web-platform-tests/interop +css/css-flexbox/gap-002-lr.html @web-platform-tests/interop +css/css-flexbox/gap-002-ltr.html @web-platform-tests/interop +css/css-flexbox/gap-002-rl.html @web-platform-tests/interop +css/css-flexbox/gap-002-rtl.html @web-platform-tests/interop +css/css-flexbox/gap-003-lr.html @web-platform-tests/interop +css/css-flexbox/gap-003-ltr.html @web-platform-tests/interop +css/css-flexbox/gap-003-rl.html @web-platform-tests/interop +css/css-flexbox/gap-003-rtl.html @web-platform-tests/interop +css/css-flexbox/gap-004-lr.html @web-platform-tests/interop +css/css-flexbox/gap-004-ltr.html @web-platform-tests/interop +css/css-flexbox/gap-004-rl.html @web-platform-tests/interop +css/css-flexbox/gap-004-rtl.html @web-platform-tests/interop +css/css-flexbox/gap-005-lr.html @web-platform-tests/interop +css/css-flexbox/gap-005-ltr.html @web-platform-tests/interop +css/css-flexbox/gap-005-rl.html @web-platform-tests/interop +css/css-flexbox/gap-005-rtl.html @web-platform-tests/interop +css/css-flexbox/gap-006-lr.html @web-platform-tests/interop +css/css-flexbox/gap-006-ltr.html @web-platform-tests/interop +css/css-flexbox/gap-006-rl.html @web-platform-tests/interop +css/css-flexbox/gap-006-rtl.html @web-platform-tests/interop +css/css-flexbox/gap-007-lr.html @web-platform-tests/interop +css/css-flexbox/gap-007-ltr.html @web-platform-tests/interop +css/css-flexbox/gap-007-rl.html @web-platform-tests/interop +css/css-flexbox/gap-007-rtl.html @web-platform-tests/interop +css/css-flexbox/gap-008-ltr.html @web-platform-tests/interop +css/css-flexbox/gap-009-ltr.html @web-platform-tests/interop +css/css-flexbox/gap-010-ltr.html @web-platform-tests/interop +css/css-flexbox/gap-011.html @web-platform-tests/interop +css/css-flexbox/gap-012.html @web-platform-tests/interop +css/css-flexbox/gap-013.html @web-platform-tests/interop +css/css-flexbox/gap-014.html @web-platform-tests/interop +css/css-flexbox/gap-015.html @web-platform-tests/interop +css/css-flexbox/gap-016.html @web-platform-tests/interop +css/css-flexbox/grid-flex-item-001.html @web-platform-tests/interop +css/css-flexbox/grid-flex-item-002.html @web-platform-tests/interop +css/css-flexbox/grid-flex-item-003.html @web-platform-tests/interop +css/css-flexbox/grid-flex-item-004.html @web-platform-tests/interop +css/css-flexbox/grid-flex-item-005.html @web-platform-tests/interop +css/css-flexbox/grid-flex-item-006.html @web-platform-tests/interop +css/css-flexbox/image-items-flake-001.html @web-platform-tests/interop +css/css-flexbox/image-nested-within-definite-column-flexbox.html @web-platform-tests/interop +css/css-flexbox/inline-flex-min-content-height.html @web-platform-tests/interop +css/css-flexbox/item-with-max-height-and-scrollbar.html @web-platform-tests/interop +css/css-flexbox/item-with-table-with-infinite-max-intrinsic-width.html @web-platform-tests/interop +css/css-flexbox/justify-content-001.htm @web-platform-tests/interop +css/css-flexbox/justify-content-002.htm @web-platform-tests/interop +css/css-flexbox/justify-content-003.htm @web-platform-tests/interop +css/css-flexbox/justify-content-004.htm @web-platform-tests/interop +css/css-flexbox/justify-content-005.htm @web-platform-tests/interop +css/css-flexbox/justify-content-sideways-001.html @web-platform-tests/interop +css/css-flexbox/layout-algorithm_algo-cross-line-001.html @web-platform-tests/interop +css/css-flexbox/layout-algorithm_algo-cross-line-002.html @web-platform-tests/interop +css/css-flexbox/multiline-column-max-height.html @web-platform-tests/interop +css/css-flexbox/multiline-reverse-wrap-baseline.html @web-platform-tests/interop +css/css-flexbox/negative-margins-001.html @web-platform-tests/interop +css/css-flexbox/nested-orthogonal-flexbox-relayout.html @web-platform-tests/interop +css/css-flexbox/order/order-abs-children-painting-order.html @web-platform-tests/interop +css/css-flexbox/order/order-with-column-reverse.html @web-platform-tests/interop +css/css-flexbox/order/order-with-row-reverse.html @web-platform-tests/interop +css/css-flexbox/order-painting.html @web-platform-tests/interop +css/css-flexbox/ortho-table-item-001.html @web-platform-tests/interop +css/css-flexbox/overflow-area-001.html @web-platform-tests/interop +css/css-flexbox/overflow-area-002.html @web-platform-tests/interop +css/css-flexbox/overflow-area-003.html @web-platform-tests/interop +css/css-flexbox/overflow-auto-001.html @web-platform-tests/interop +css/css-flexbox/overflow-auto-005.html @web-platform-tests/interop +css/css-flexbox/overflow-auto-007.html @web-platform-tests/interop +css/css-flexbox/overflow-top-left.html @web-platform-tests/interop +css/css-flexbox/padding-overflow.html @web-platform-tests/interop +css/css-flexbox/percentage-descendant-of-anonymous-flex-item.html @web-platform-tests/interop +css/css-flexbox/percentage-heights-002.html @web-platform-tests/interop +css/css-flexbox/percentage-heights-004.html @web-platform-tests/interop +css/css-flexbox/percentage-heights-005.html @web-platform-tests/interop +css/css-flexbox/percentage-heights-006.html @web-platform-tests/interop +css/css-flexbox/percentage-heights-007.html @web-platform-tests/interop +css/css-flexbox/percentage-heights-008.html @web-platform-tests/interop +css/css-flexbox/percentage-heights-009.html @web-platform-tests/interop +css/css-flexbox/percentage-heights-010.html @web-platform-tests/interop +css/css-flexbox/percentage-heights-014.html @web-platform-tests/interop +css/css-flexbox/percentage-heights-015.html @web-platform-tests/interop +css/css-flexbox/percentage-heights-016.html @web-platform-tests/interop +css/css-flexbox/percentage-heights-017.html @web-platform-tests/interop +css/css-flexbox/percentage-heights-018.html @web-platform-tests/interop +css/css-flexbox/percentage-heights-019.html @web-platform-tests/interop +css/css-flexbox/percentage-max-height-001.html @web-platform-tests/interop +css/css-flexbox/percentage-max-height-002.html @web-platform-tests/interop +css/css-flexbox/percentage-max-height-003.html @web-platform-tests/interop +css/css-flexbox/percentage-max-height-004.html @web-platform-tests/interop +css/css-flexbox/percentage-padding-002.html @web-platform-tests/interop +css/css-flexbox/percentage-size-subitems-001.html @web-platform-tests/interop +css/css-flexbox/percentage-widths-001.html @web-platform-tests/interop +css/css-flexbox/position-absolute-scrollbar-freeze.html @web-platform-tests/interop +css/css-flexbox/position-fixed-001.html @web-platform-tests/interop +css/css-flexbox/position-relative-percentage-top-002.html @web-platform-tests/interop +css/css-flexbox/position-relative-percentage-top-003.html @web-platform-tests/interop +css/css-flexbox/scrollbars-auto.html @web-platform-tests/interop +css/css-flexbox/scrollbars-no-margin.html @web-platform-tests/interop +css/css-flexbox/scrollbars.html @web-platform-tests/interop +css/css-flexbox/select-element-zero-height-001.html @web-platform-tests/interop +css/css-flexbox/select-element-zero-height-002.html @web-platform-tests/interop +css/css-flexbox/stretch-flex-item-checkbox-input.html @web-platform-tests/interop +css/css-flexbox/stretch-flex-item-radio-input.html @web-platform-tests/interop +css/css-flexbox/stretch-input-in-column.html @web-platform-tests/interop +css/css-flexbox/stretch-obeys-min-max-001.html @web-platform-tests/interop +css/css-flexbox/stretch-obeys-min-max-002.html @web-platform-tests/interop +css/css-flexbox/stretch-obeys-min-max-003.html @web-platform-tests/interop +css/css-flexbox/stretching-orthogonal-flows.html @web-platform-tests/interop +css/css-flexbox/svg-no-natural-size-grandchild.html @web-platform-tests/interop +css/css-flexbox/svg-root-as-flex-item-001.html @web-platform-tests/interop +css/css-flexbox/svg-root-as-flex-item-002.html @web-platform-tests/interop +css/css-flexbox/svg-root-as-flex-item-003.html @web-platform-tests/interop +css/css-flexbox/svg-root-as-flex-item-004.html @web-platform-tests/interop +css/css-flexbox/svg-root-as-flex-item-005.html @web-platform-tests/interop +css/css-flexbox/synthesize-vrl-baseline.html @web-platform-tests/interop +css/css-flexbox/table-as-item-auto-min-width.html @web-platform-tests/interop +css/css-flexbox/table-as-item-change-cell.html @web-platform-tests/interop +css/css-flexbox/table-as-item-fixed-min-width-2.html @web-platform-tests/interop +css/css-flexbox/table-as-item-fixed-min-width-3.html @web-platform-tests/interop +css/css-flexbox/table-as-item-fixed-min-width.html @web-platform-tests/interop +css/css-flexbox/table-as-item-flex-cross-size.html @web-platform-tests/interop +css/css-flexbox/table-as-item-inflexible-in-column-1.html @web-platform-tests/interop +css/css-flexbox/table-as-item-inflexible-in-column-2.html @web-platform-tests/interop +css/css-flexbox/table-as-item-inflexible-in-row-1.html @web-platform-tests/interop +css/css-flexbox/table-as-item-inflexible-in-row-2.html @web-platform-tests/interop +css/css-flexbox/table-as-item-large-intrinsic-size.html @web-platform-tests/interop +css/css-flexbox/table-as-item-min-content-height-1.tentative.html @web-platform-tests/interop +css/css-flexbox/table-as-item-min-content-height-2.tentative.html @web-platform-tests/interop +css/css-flexbox/table-as-item-min-height-1.html @web-platform-tests/interop +css/css-flexbox/table-as-item-narrow-content-2.html @web-platform-tests/interop +css/css-flexbox/table-as-item-narrow-content.html @web-platform-tests/interop +css/css-flexbox/table-as-item-percent-width-cell-001.html @web-platform-tests/interop +css/css-flexbox/table-as-item-specified-height.html @web-platform-tests/interop +css/css-flexbox/table-as-item-specified-width-vertical.html @web-platform-tests/interop +css/css-flexbox/table-as-item-specified-width.html @web-platform-tests/interop +css/css-flexbox/table-as-item-stretch-cross-size-2.html @web-platform-tests/interop +css/css-flexbox/table-as-item-stretch-cross-size-3.html @web-platform-tests/interop +css/css-flexbox/table-as-item-stretch-cross-size-4.html @web-platform-tests/interop +css/css-flexbox/table-as-item-stretch-cross-size-5.html @web-platform-tests/interop +css/css-flexbox/table-as-item-stretch-cross-size.html @web-platform-tests/interop +css/css-flexbox/table-as-item-wide-content.html @web-platform-tests/interop +css/css-flexbox/table-item-flex-percentage-min-width.html @web-platform-tests/interop +css/css-flexbox/table-item-flex-percentage-width.html @web-platform-tests/interop +css/css-flexbox/table-with-float-paint.html @web-platform-tests/interop +css/css-flexbox/table-with-infinite-max-intrinsic-width.html @web-platform-tests/interop +css/css-flexbox/text-overflow-on-flexbox-001.html @web-platform-tests/interop +css/css-flexbox/whitespace-in-flexitem-001.html @web-platform-tests/interop +css/css-grid/abspos/absolute-positioning-changing-containing-block-001.html @web-platform-tests/interop +css/css-grid/abspos/descendant-static-position-001.html @web-platform-tests/interop +css/css-grid/abspos/descendant-static-position-002.html @web-platform-tests/interop +css/css-grid/abspos/descendant-static-position-003.html @web-platform-tests/interop +css/css-grid/abspos/descendant-static-position-004.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-002.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-img-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-img-002.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-img-last-baseline-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-img-last-baseline-002.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-last-baseline-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-last-baseline-002.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-rtl-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-rtl-002.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-rtl-003.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-rtl-004.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-rtl-last-baseline-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-rtl-last-baseline-002.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-rtl-last-baseline-003.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-rtl-last-baseline-004.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-safe-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-vertWM-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-vertWM-002.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-vertWM-003.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-vertWM-004.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-vertWM-last-baseline-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-vertWM-last-baseline-002.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-vertWM-last-baseline-003.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-align-self-vertWM-last-baseline-004.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-002.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-img-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-img-002.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-img-last-baseline-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-img-last-baseline-002.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-last-baseline-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-last-baseline-002.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-rtl-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-rtl-002.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-rtl-003.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-rtl-004.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-rtl-last-baseline-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-rtl-last-baseline-002.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-rtl-last-baseline-003.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-rtl-last-baseline-004.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-vertWM-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-vertWM-002.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-vertWM-003.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-vertWM-004.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-vertWM-last-baseline-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-vertWM-last-baseline-002.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-vertWM-last-baseline-003.html @web-platform-tests/interop +css/css-grid/abspos/grid-abspos-staticpos-justify-self-vertWM-last-baseline-004.html @web-platform-tests/interop +css/css-grid/abspos/grid-item-absolute-positioning-dynamic-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-paint-positioned-children-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-children-writing-modes-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-item-dynamic-change-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-item-dynamic-change-002.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-item-dynamic-change-003.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-item-dynamic-change-004.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-item-dynamic-change-005.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-item-dynamic-change-006.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-item-dynamic-change-007.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-items-background-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-items-background-rtl-001.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-items-001.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-items-002.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-items-003.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-items-004.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-items-005.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-items-006.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-items-007.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-items-008.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-items-009.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-items-010.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-items-011.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-items-012.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-items-013.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-items-014.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-items-015.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-items-016.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-items-017.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-descendants-017.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-001.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-002.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-003.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-004.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-005.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-006.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-007.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-008.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-009.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-010.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-011.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-012.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-013.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-014.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-015.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-016.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-017.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-018.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-019.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-020.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-021.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-022.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-023.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-024.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-025.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-negative-indices-001.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-negative-indices-002.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-negative-indices-003.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-sizing-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-005.html @web-platform-tests/interop +css/css-grid/alignment/grid-baseline-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-baseline-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-baseline-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-baseline-align-cycles-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-block-axis-alignment-auto-margins-008.html @web-platform-tests/interop +css/css-grid/alignment/grid-container-auto-margins-scrollbars-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-004.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-005.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-006.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-007.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-008.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-009.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-010.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-011.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-012.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-013.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-014.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-015.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-016.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-017.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-018.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-019.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-020.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-021.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-022.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-023.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-024.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-025.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-026.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-027.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-028.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-004.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-005.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-006.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-007.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-008.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-009.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-010.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-011.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-012.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-013.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-014.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-015.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-016.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-017.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-018.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-019.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-020.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-021.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-022.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-023.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-distribution-with-collapsed-tracks-024.html @web-platform-tests/interop +css/css-grid/alignment/grid-gutters-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-gutters-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-gutters-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-gutters-004.html @web-platform-tests/interop +css/css-grid/alignment/grid-gutters-005.html @web-platform-tests/interop +css/css-grid/alignment/grid-gutters-006.html @web-platform-tests/interop +css/css-grid/alignment/grid-gutters-007.html @web-platform-tests/interop +css/css-grid/alignment/grid-gutters-008.html @web-platform-tests/interop +css/css-grid/alignment/grid-gutters-009.html @web-platform-tests/interop +css/css-grid/alignment/grid-gutters-010.html @web-platform-tests/interop +css/css-grid/alignment/grid-gutters-011.html @web-platform-tests/interop +css/css-grid/alignment/grid-gutters-012.html @web-platform-tests/interop +css/css-grid/alignment/grid-gutters-013.html @web-platform-tests/interop +css/css-grid/alignment/grid-gutters-014.html @web-platform-tests/interop +css/css-grid/alignment/grid-gutters-015.html @web-platform-tests/interop +css/css-grid/alignment/grid-gutters-016.html @web-platform-tests/interop +css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-008.html @web-platform-tests/interop +css/css-grid/alignment/grid-inline-baseline.html @web-platform-tests/interop +css/css-grid/alignment/grid-item-aspect-ratio-stretch-1.html @web-platform-tests/interop +css/css-grid/alignment/grid-item-aspect-ratio-stretch-2.html @web-platform-tests/interop +css/css-grid/alignment/grid-item-aspect-ratio-stretch-3.html @web-platform-tests/interop +css/css-grid/alignment/grid-item-aspect-ratio-stretch-4.html @web-platform-tests/interop +css/css-grid/alignment/grid-item-auto-margins-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-item-auto-margins-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-item-no-aspect-ratio-stretch-1.html @web-platform-tests/interop +css/css-grid/alignment/grid-item-no-aspect-ratio-stretch-2.html @web-platform-tests/interop +css/css-grid/alignment/grid-item-no-aspect-ratio-stretch-3.html @web-platform-tests/interop +css/css-grid/alignment/grid-item-no-aspect-ratio-stretch-4.html @web-platform-tests/interop +css/css-grid/alignment/grid-item-no-aspect-ratio-stretch-5.html @web-platform-tests/interop +css/css-grid/alignment/grid-item-no-aspect-ratio-stretch-6.html @web-platform-tests/interop +css/css-grid/alignment/grid-item-self-baseline-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-justify-baseline-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-baseline-with-grid-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-baseline-with-grid-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-baseline-with-grid-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-baseline-with-grid-004.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-input-range.html @web-platform-tests/interop +css/css-grid/alignment/replaced-alignment-with-aspect-ratio-001.html @web-platform-tests/interop +css/css-grid/alignment/replaced-alignment-with-aspect-ratio-002.html @web-platform-tests/interop +css/css-grid/alignment/replaced-alignment-with-aspect-ratio-003.tentative.html @web-platform-tests/interop +css/css-grid/alignment/replaced-alignment-with-aspect-ratio-004.html @web-platform-tests/interop +css/css-grid/alignment/replaced-alignment-with-aspect-ratio-005.html @web-platform-tests/interop +css/css-grid/alignment/replaced-alignment-with-aspect-ratio-006.html @web-platform-tests/interop +css/css-grid/alignment/replaced-alignment-with-aspect-ratio-007.html @web-platform-tests/interop +css/css-grid/alignment/replaced-alignment-with-aspect-ratio-008.html @web-platform-tests/interop +css/css-grid/alignment/replaced-alignment-with-aspect-ratio-009.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-001.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-002-b.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-002.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-003.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-004.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-005.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-006.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-007.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-008.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-001.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-002.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-003.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-004.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-005.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-006.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-007.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-008.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-009.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-010.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-011.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-012.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-horiz-001.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-horiz-002.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-horiz-003.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-horiz-004.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-horiz-005.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-horiz-006.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-horiz-007.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-lr-001.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-lr-002.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-lr-003.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-lr-004.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-lr-005.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-lr-006.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-lr-007.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-rl-001.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-rl-002.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-rl-003.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-rl-004.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-rl-005.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-rl-006.html @web-platform-tests/interop +css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-rl-007.html @web-platform-tests/interop +css/css-grid/animation/grid-template-columns-001.html @web-platform-tests/interop +css/css-grid/animation/grid-template-rows-001.html @web-platform-tests/interop +css/css-grid/anonymous-grid-items-001.html @web-platform-tests/interop +css/css-grid/child-border-box-and-max-content-001.html @web-platform-tests/interop +css/css-grid/child-border-box-and-max-content-002.html @web-platform-tests/interop +css/css-grid/chrome-bug-001.html @web-platform-tests/interop +css/css-grid/dynamic-grid-with-auto-fill.html @web-platform-tests/interop +css/css-grid/dynamic-grid-within-flexbox.html @web-platform-tests/interop +css/css-grid/empty-grid-within-flexbox.html @web-platform-tests/interop +css/css-grid/grid-child-percent-basis-resize-1.html @web-platform-tests/interop +css/css-grid/grid-container-baseline-synthesized-001.html @web-platform-tests/interop +css/css-grid/grid-container-baseline-synthesized-002.html @web-platform-tests/interop +css/css-grid/grid-container-baseline-synthesized-003.html @web-platform-tests/interop +css/css-grid/grid-container-baseline-synthesized-004.html @web-platform-tests/interop +css/css-grid/grid-definition/flex-item-grid-container-percentage-rows-001.html @web-platform-tests/interop +css/css-grid/grid-definition/fr-unit-with-percentage.html @web-platform-tests/interop +css/css-grid/grid-definition/fr-unit.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-repeat-aspect-ratio-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-repeat-aspect-ratio-002.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-repeat-dynamic-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-repeat-dynamic-002.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-repeat-dynamic-003.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-repeat-minmax.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-repeat-multiple-values-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-repeat-multiple-values-002.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-repeat-multiple-values-003.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-repeat-multiple-values-004.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-repeat-multiple-values-005.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-repeat-positioned-container-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-layout-auto-tracks.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-layout-basic.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-layout-repeat-notation.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-repeat-max-width-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-support-named-grid-lines-002.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-support-named-grid-lines-003.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-template-columns-fit-content-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-template-rows-fit-content-001.html @web-platform-tests/interop +css/css-grid/grid-item-non-auto-height-stretch-001.html @web-platform-tests/interop +css/css-grid/grid-item-non-auto-height-stretch-002.html @web-platform-tests/interop +css/css-grid/grid-item-non-auto-height-stretch-003.html @web-platform-tests/interop +css/css-grid/grid-item-non-auto-height-stretch-004.html @web-platform-tests/interop +css/css-grid/grid-item-percentage-quirk-001.html @web-platform-tests/interop +css/css-grid/grid-item-percentage-quirk-002.html @web-platform-tests/interop +css/css-grid/grid-items/anonymous-grid-item-001.html @web-platform-tests/interop +css/css-grid/grid-items/aspect-ratio-001.html @web-platform-tests/interop +css/css-grid/grid-items/aspect-ratio-002.html @web-platform-tests/interop +css/css-grid/grid-items/aspect-ratio-003.html @web-platform-tests/interop +css/css-grid/grid-items/aspect-ratio-004.html @web-platform-tests/interop +css/css-grid/grid-items/aspect-ratio-005.html @web-platform-tests/interop +css/css-grid/grid-items/explicitly-sized-grid-item-as-table.html @web-platform-tests/interop +css/css-grid/grid-items/grid-auto-margin-and-replaced-item-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-img-item-percent-max-height-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-items-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-items-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-items-003.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-items-inline-blocks-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-order-property-auto-placement-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-order-property-auto-placement-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-order-property-auto-placement-003.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-order-property-auto-placement-004.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-order-property-auto-placement-005.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-order-property-painting-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-order-property-painting-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-order-property-painting-003.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-order-property-painting-004.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-order-property-painting-005.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-z-axis-ordering-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-z-axis-ordering-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-z-axis-ordering-003.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-z-axis-ordering-004.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-z-axis-ordering-005.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-z-axis-ordering-overlapped-items-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-z-axis-ordering-overlapped-items-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-z-axis-ordering-overlapped-items-003.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-z-axis-ordering-overlapped-items-004.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-z-axis-ordering-overlapped-items-005.html @web-platform-tests/interop +css/css-grid/grid-items/grid-inline-z-axis-ordering-overlapped-items-006.html @web-platform-tests/interop +css/css-grid/grid-items/grid-intrinsic-maximums.html @web-platform-tests/interop +css/css-grid/grid-items/grid-item-containing-block-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-item-containing-block-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-item-containing-block-003.html @web-platform-tests/interop +css/css-grid/grid-items/grid-item-containing-block-004.html @web-platform-tests/interop +css/css-grid/grid-items/grid-item-inline-contribution-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-item-inline-contribution-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-item-inline-contribution-003.html @web-platform-tests/interop +css/css-grid/grid-items/grid-item-margins-and-writing-modes-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-item-overflow-auto-max-height-percentage.html @web-platform-tests/interop +css/css-grid/grid-items/grid-item-percentage-sizes-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-item-percentage-sizes-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-item-percentage-sizes-003.html @web-platform-tests/interop +css/css-grid/grid-items/grid-item-rel-pos-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-item-rel-pos-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-item-script-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-003.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-contribution-negative-margins.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-inline-blocks-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-margins-003.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-margins-004.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-margins-005.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-margins-006.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-margins-007.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-margins-008.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-margins-009.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-margins-010.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-margins-011.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-margins-012.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-margins-013.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-margins-014.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-paddings-003.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-paddings-004.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-paddings-005.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-paddings-006.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-paddings-007.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-paddings-008.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-paddings-009.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-paddings-010.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-paddings-011.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-paddings-012.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-paddings-013.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-paddings-014.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-sizing-alignment-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-layout-grid-in-grid.html @web-platform-tests/interop +css/css-grid/grid-items/grid-layout-z-order-a.html @web-platform-tests/interop +css/css-grid/grid-items/grid-layout-z-order-b.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-003.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-004.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-005.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-006.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-007.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-008.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-009.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-010.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-011.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-012.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-013.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-014.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-015.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-016.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-017.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-018.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-019.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-020.html @web-platform-tests/interop +css/css-grid/grid-items/grid-order-property-auto-placement-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-order-property-auto-placement-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-order-property-auto-placement-003.html @web-platform-tests/interop +css/css-grid/grid-items/grid-order-property-auto-placement-004.html @web-platform-tests/interop +css/css-grid/grid-items/grid-order-property-auto-placement-005.html @web-platform-tests/interop +css/css-grid/grid-items/grid-order-property-painting-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-order-property-painting-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-order-property-painting-003.html @web-platform-tests/interop +css/css-grid/grid-items/grid-order-property-painting-004.html @web-platform-tests/interop +css/css-grid/grid-items/grid-order-property-painting-005.html @web-platform-tests/interop +css/css-grid/grid-items/grid-size-with-orthogonal-child-dynamic.html @web-platform-tests/interop +css/css-grid/grid-items/grid-z-axis-ordering-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-z-axis-ordering-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-z-axis-ordering-003.html @web-platform-tests/interop +css/css-grid/grid-items/grid-z-axis-ordering-004.html @web-platform-tests/interop +css/css-grid/grid-items/grid-z-axis-ordering-005.html @web-platform-tests/interop +css/css-grid/grid-items/grid-z-axis-ordering-overlapped-items-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-z-axis-ordering-overlapped-items-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-z-axis-ordering-overlapped-items-003.html @web-platform-tests/interop +css/css-grid/grid-items/grid-z-axis-ordering-overlapped-items-004.html @web-platform-tests/interop +css/css-grid/grid-items/grid-z-axis-ordering-overlapped-items-005.html @web-platform-tests/interop +css/css-grid/grid-items/grid-z-axis-ordering-overlapped-items-006.html @web-platform-tests/interop +css/css-grid/grid-items/item-with-table-with-infinite-max-intrinsic-width.html @web-platform-tests/interop +css/css-grid/grid-items/percentage-margin-dynamic.html @web-platform-tests/interop +css/css-grid/grid-items/percentage-size-indefinite-replaced.html @web-platform-tests/interop +css/css-grid/grid-items/percentage-size-replaced-subitems-001.html @web-platform-tests/interop +css/css-grid/grid-items/percentage-size-subitems-001.html @web-platform-tests/interop +css/css-grid/grid-items/percentage-size-subitems-002.html @web-platform-tests/interop +css/css-grid/grid-items/remove-svg-grid-item-001.html @web-platform-tests/interop +css/css-grid/grid-items/replaced-element-001.html @web-platform-tests/interop +css/css-grid/grid-items/replaced-element-002.html @web-platform-tests/interop +css/css-grid/grid-items/replaced-element-007.html @web-platform-tests/interop +css/css-grid/grid-items/replaced-element-010.html @web-platform-tests/interop +css/css-grid/grid-items/replaced-element-011.html @web-platform-tests/interop +css/css-grid/grid-items/replaced-element-012.html @web-platform-tests/interop +css/css-grid/grid-items/replaced-element-013.html @web-platform-tests/interop +css/css-grid/grid-items/replaced-element-014.html @web-platform-tests/interop +css/css-grid/grid-items/replaced-element-015.html @web-platform-tests/interop +css/css-grid/grid-items/table-with-infinite-max-intrinsic-width.html @web-platform-tests/interop +css/css-grid/grid-items/whitespace-in-grid-item-001.html @web-platform-tests/interop +css/css-grid/grid-model/column-property-should-not-apply-on-grid-container-001.html @web-platform-tests/interop +css/css-grid/grid-model/display-grid.html @web-platform-tests/interop +css/css-grid/grid-model/display-inline-grid.html @web-platform-tests/interop +css/css-grid/grid-model/grid-areas-overflowing-grid-container-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-areas-overflowing-grid-container-002.html @web-platform-tests/interop +css/css-grid/grid-model/grid-areas-overflowing-grid-container-003.html @web-platform-tests/interop +css/css-grid/grid-model/grid-areas-overflowing-grid-container-004.html @web-platform-tests/interop +css/css-grid/grid-model/grid-areas-overflowing-grid-container-005.html @web-platform-tests/interop +css/css-grid/grid-model/grid-areas-overflowing-grid-container-006.html @web-platform-tests/interop +css/css-grid/grid-model/grid-areas-overflowing-grid-container-007.html @web-platform-tests/interop +css/css-grid/grid-model/grid-areas-overflowing-grid-container-008.html @web-platform-tests/interop +css/css-grid/grid-model/grid-areas-overflowing-grid-container-009.html @web-platform-tests/interop +css/css-grid/grid-model/grid-container-ignores-first-letter-002.html @web-platform-tests/interop +css/css-grid/grid-model/grid-container-scrollbar-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-container-scrollbar-vertical-lr-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-container-scrollbar-vertical-rl-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-container-scrollbars-sizing-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-container-scrollbars-sizing-002.html @web-platform-tests/interop +css/css-grid/grid-model/grid-display-grid-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-display-inline-grid-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-first-letter-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-first-letter-002.html @web-platform-tests/interop +css/css-grid/grid-model/grid-first-letter-003.html @web-platform-tests/interop +css/css-grid/grid-model/grid-first-line-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-first-line-002.html @web-platform-tests/interop +css/css-grid/grid-model/grid-first-line-003.html @web-platform-tests/interop +css/css-grid/grid-model/grid-float-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-floats-no-intrude-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-floats-no-intrude-002.html @web-platform-tests/interop +css/css-grid/grid-model/grid-inline-first-letter-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-inline-first-letter-002.html @web-platform-tests/interop +css/css-grid/grid-model/grid-inline-first-letter-003.html @web-platform-tests/interop +css/css-grid/grid-model/grid-inline-first-line-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-inline-first-line-002.html @web-platform-tests/interop +css/css-grid/grid-model/grid-inline-first-line-003.html @web-platform-tests/interop +css/css-grid/grid-model/grid-inline-float-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-inline-floats-no-intrude-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-inline-margins-no-collapse-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-inline-multicol-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-inline-vertical-align-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-layout-stale-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-layout-stale-002.html @web-platform-tests/interop +css/css-grid/grid-model/grid-margins-no-collapse-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-margins-no-collapse-002.html @web-platform-tests/interop +css/css-grid/grid-model/grid-multicol-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-overflow-padding-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-overflow-padding-002.html @web-platform-tests/interop +css/css-grid/grid-model/grid-vertical-align-001.html @web-platform-tests/interop +css/css-grid/grid-with-content-dynamic-display-001.html @web-platform-tests/interop +css/css-grid/grid-with-content-dynamic-display-002.html @web-platform-tests/interop +css/css-grid/grid-with-dynamic-img.html @web-platform-tests/interop +css/css-grid/grid-with-orthogonal-child-within-flexbox.html @web-platform-tests/interop +css/css-grid/grid-within-flexbox-definite-change.html @web-platform-tests/interop +css/css-grid/grid-within-flexbox-indefinite.html @web-platform-tests/interop +css/css-grid/implicit-grids/grid-support-grid-auto-columns-rows-001.html @web-platform-tests/interop +css/css-grid/implicit-grids/grid-support-grid-auto-columns-rows-002.html @web-platform-tests/interop +css/css-grid/implicit-grids/grid-support-grid-auto-columns-rows-003.html @web-platform-tests/interop +css/css-grid/layout-algorithm/auto-margins-ignored-during-track-sizing-001.html @web-platform-tests/interop +css/css-grid/layout-algorithm/flex-and-intrinsic-sizes-002.html @web-platform-tests/interop +css/css-grid/layout-algorithm/flex-sizing-rows-indefinite-height.html @web-platform-tests/interop +css/css-grid/layout-algorithm/flex-tracks-with-fractional-size.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-as-flex-item-should-not-shrink-to-fit-001.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-as-flex-item-should-not-shrink-to-fit-002.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-as-flex-item-should-not-shrink-to-fit-003.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-as-flex-item-should-not-shrink-to-fit-004.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-as-flex-item-should-not-shrink-to-fit-005.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-as-flex-item-should-not-shrink-to-fit-006.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-as-flex-item-should-not-shrink-to-fit-007.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-as-flex-item-should-not-shrink-to-fit-008.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-find-fr-size-restart-algorithm.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-intrinsic-size-dynamic-block-size.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-layout-free-space-unit.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-percent-cols-filled-shrinkwrap-001.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-percent-cols-spanned-shrinkwrap-001.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-percent-rows-filled-shrinkwrap-001.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-percent-rows-spanned-shrinkwrap-001.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-stretch-respects-min-size-001.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-template-flexible-rerun-track-sizing.html @web-platform-tests/interop +css/css-grid/nested-grid-item-block-size-001.html @web-platform-tests/interop +css/css-grid/placement/grid-layout-grid-span.html @web-platform-tests/interop +css/css-grid/placement/grid-layout-lines-shorthands.html @web-platform-tests/interop +css/css-grid/placement/grid-layout-lines.html @web-platform-tests/interop +css/css-grid/placement/grid-layout-placement-shorthands.html @web-platform-tests/interop +css/css-grid/placement/grid-placement-items-spanning-multiple-rows-001.html @web-platform-tests/interop +css/css-grid/placement/grid-placement-items-spanning-multiple-rows-002.html @web-platform-tests/interop +css/css-grid/placement/grid-placement-using-named-grid-lines-001.html @web-platform-tests/interop +css/css-grid/placement/grid-placement-using-named-grid-lines-002.html @web-platform-tests/interop +css/css-grid/placement/grid-placement-using-named-grid-lines-003.html @web-platform-tests/interop +css/css-grid/placement/grid-placement-using-named-grid-lines-004.html @web-platform-tests/interop +css/css-grid/placement/grid-placement-using-named-grid-lines-005.html @web-platform-tests/interop +css/css-grid/placement/grid-placement-using-named-grid-lines-006.html @web-platform-tests/interop +css/css-grid/placement/grid-placement-using-named-grid-lines-007.html @web-platform-tests/interop +css/css-grid/placement/grid-placement-using-named-grid-lines-008.html @web-platform-tests/interop +css/css-grid/placement/grid-placement-using-named-grid-lines-009.html @web-platform-tests/interop +css/css-grid/placement/grid-template-areas-must-keep-named-columns-order-001.html @web-platform-tests/interop +css/css-grid/relative-grandchild.html @web-platform-tests/interop +css/css-grid/stretch-grid-item-checkbox-input.html @web-platform-tests/interop +css/css-grid/stretch-grid-item-radio-input.html @web-platform-tests/interop +css/css-grid/subgrid/abs-pos-001.html @web-platform-tests/interop +css/css-grid/subgrid/abs-pos-002.html @web-platform-tests/interop +css/css-grid/subgrid/auto-track-sizing-001.html @web-platform-tests/interop +css/css-grid/subgrid/auto-track-sizing-002.html @web-platform-tests/interop +css/css-grid/subgrid/auto-track-sizing-003.html @web-platform-tests/interop +css/css-grid/subgrid/baseline-001.html @web-platform-tests/interop +css/css-grid/subgrid/contribution-size-flex-tracks-001.html @web-platform-tests/interop +css/css-grid/subgrid/grid-gap-001.html @web-platform-tests/interop +css/css-grid/subgrid/grid-gap-002.html @web-platform-tests/interop +css/css-grid/subgrid/grid-gap-003.html @web-platform-tests/interop +css/css-grid/subgrid/grid-gap-004.html @web-platform-tests/interop +css/css-grid/subgrid/grid-gap-005.html @web-platform-tests/interop +css/css-grid/subgrid/grid-gap-006.html @web-platform-tests/interop +css/css-grid/subgrid/grid-gap-007.html @web-platform-tests/interop +css/css-grid/subgrid/grid-gap-008.html @web-platform-tests/interop +css/css-grid/subgrid/grid-gap-009.html @web-platform-tests/interop +css/css-grid/subgrid/grid-gap-010.html @web-platform-tests/interop +css/css-grid/subgrid/grid-gap-011.html @web-platform-tests/interop +css/css-grid/subgrid/grid-gap-larger-001.html @web-platform-tests/interop +css/css-grid/subgrid/grid-gap-larger-002.html @web-platform-tests/interop +css/css-grid/subgrid/grid-gap-normal-001.html @web-platform-tests/interop +css/css-grid/subgrid/grid-gap-smaller-001.html @web-platform-tests/interop +css/css-grid/subgrid/independent-formatting-context.html @web-platform-tests/interop +css/css-grid/subgrid/item-percentage-height-001.html @web-platform-tests/interop +css/css-grid/subgrid/line-names-001.html @web-platform-tests/interop +css/css-grid/subgrid/line-names-002.html @web-platform-tests/interop +css/css-grid/subgrid/line-names-003.html @web-platform-tests/interop +css/css-grid/subgrid/line-names-004.html @web-platform-tests/interop +css/css-grid/subgrid/line-names-005.html @web-platform-tests/interop +css/css-grid/subgrid/line-names-006.html @web-platform-tests/interop +css/css-grid/subgrid/line-names-007.html @web-platform-tests/interop +css/css-grid/subgrid/line-names-008.html @web-platform-tests/interop +css/css-grid/subgrid/line-names-009.html @web-platform-tests/interop +css/css-grid/subgrid/line-names-010.html @web-platform-tests/interop +css/css-grid/subgrid/line-names-011.html @web-platform-tests/interop +css/css-grid/subgrid/line-names-012.html @web-platform-tests/interop +css/css-grid/subgrid/orthogonal-writing-mode-001.html @web-platform-tests/interop +css/css-grid/subgrid/orthogonal-writing-mode-002.html @web-platform-tests/interop +css/css-grid/subgrid/orthogonal-writing-mode-003.html @web-platform-tests/interop +css/css-grid/subgrid/orthogonal-writing-mode-004.html @web-platform-tests/interop +css/css-grid/subgrid/orthogonal-writing-mode-005.html @web-platform-tests/interop +css/css-grid/subgrid/orthogonal-writing-mode-006.html @web-platform-tests/interop +css/css-grid/subgrid/parent-repeat-auto-fit-001.html @web-platform-tests/interop +css/css-grid/subgrid/parent-repeat-auto-fit-002.html @web-platform-tests/interop +css/css-grid/subgrid/placement-implicit-001.html @web-platform-tests/interop +css/css-grid/subgrid/repeat-auto-fill-001.html @web-platform-tests/interop +css/css-grid/subgrid/repeat-auto-fill-002.html @web-platform-tests/interop +css/css-grid/subgrid/repeat-auto-fill-003.html @web-platform-tests/interop +css/css-grid/subgrid/repeat-auto-fill-004.html @web-platform-tests/interop +css/css-grid/subgrid/repeat-auto-fill-005.html @web-platform-tests/interop +css/css-grid/subgrid/repeat-auto-fill-006.html @web-platform-tests/interop +css/css-grid/subgrid/repeat-auto-fill-007.html @web-platform-tests/interop +css/css-grid/subgrid/repeat-auto-fill-008.html @web-platform-tests/interop +css/css-grid/subgrid/subgrid-baseline-001.html @web-platform-tests/interop +css/css-grid/subgrid/subgrid-baseline-002.html @web-platform-tests/interop +css/css-grid/subgrid/subgrid-baseline-003.html @web-platform-tests/interop +css/css-grid/subgrid/subgrid-baseline-004.html @web-platform-tests/interop +css/css-grid/subgrid/subgrid-item-block-size-001.html @web-platform-tests/interop +css/css-grid/subgrid/subgrid-no-items-on-edges-001.html @web-platform-tests/interop +css/css-grid/subgrid/subgrid-no-items-on-edges-002.html @web-platform-tests/interop +css/css-grid/subgrid/subgrid-stretch.html @web-platform-tests/interop +css/css-grid/subgrid/writing-directions-001.html @web-platform-tests/interop +css/css-grid/subgrid/writing-directions-002.html @web-platform-tests/interop +css/css-grid/subgrid/writing-directions-003.html @web-platform-tests/interop +css/css-grid/table-grid-item-dynamic-001.html @web-platform-tests/interop +css/css-grid/table-grid-item-dynamic-002.html @web-platform-tests/interop +css/css-grid/table-grid-item-dynamic-003.html @web-platform-tests/interop +css/css-grid/table-grid-item-dynamic-004.html @web-platform-tests/interop +css/css-grid/whitespace-reattach.html @web-platform-tests/interop +css/css-lists/list-style-position-001.html @web-platform-tests/interop +css/css-multicol/multicol-span-all-rule-002.html @web-platform-tests/interop +css/css-shapes/shape-outside/shape-box/shape-outside-border-box-border-radius-009.html @web-platform-tests/interop +css/css-shapes/shape-outside/shape-box/shape-outside-border-box-border-radius-010.html @web-platform-tests/interop +css/css-shapes/shape-outside/shape-box/shape-outside-border-box-border-radius-011.html @web-platform-tests/interop +css/css-shapes/shape-outside/shape-box/shape-outside-border-box-border-radius-012.html @web-platform-tests/interop +css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-007.html @web-platform-tests/interop +css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-008.html @web-platform-tests/interop +css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-013.html @web-platform-tests/interop +css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-014.html @web-platform-tests/interop +css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-052.html @web-platform-tests/interop +css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-053.html @web-platform-tests/interop +css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-050.html @web-platform-tests/interop +css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-051.html @web-platform-tests/interop +css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-026.html @web-platform-tests/interop +css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-027.html @web-platform-tests/interop +css/css-shapes/shape-outside/supported-shapes/polygon/shape-outside-polygon-024.html @web-platform-tests/interop +css/css-shapes/shape-outside/supported-shapes/polygon/shape-outside-polygon-025.html @web-platform-tests/interop +css/css-text-decor/text-decoration-skip-ink-sidewayslr-001.html @web-platform-tests/interop +css/css-text-decor/text-decoration-skip-ink-sidewayslr-002.html @web-platform-tests/interop +css/css-text-decor/text-decoration-skip-ink-sidewaysrl-001.html @web-platform-tests/interop +css/css-text-decor/text-decoration-skip-ink-sidewaysrl-002.html @web-platform-tests/interop +css/css-text-decor/text-underline-offset-vertical-002.html @web-platform-tests/interop +css/css-text-decor/text-underline-position-001a.html @web-platform-tests/interop +css/css-text-decor/text-underline-position-001b.html @web-platform-tests/interop +css/css-ui/compute-kind-widget-generated/grouped-kind-of-widget-fallback-background-attachment-001.html @web-platform-tests/interop +css/css-ui/compute-kind-widget-generated/grouped-kind-of-widget-fallback-background-color-001.html @web-platform-tests/interop +css/css-ui/compute-kind-widget-generated/grouped-kind-of-widget-fallback-border-block-start-color-001.html @web-platform-tests/interop +css/css-ui/compute-kind-widget-generated/grouped-kind-of-widget-fallback-border-block-start-style-001.html @web-platform-tests/interop +css/css-ui/compute-kind-widget-generated/grouped-kind-of-widget-fallback-border-block-start-width-001.html @web-platform-tests/interop +css/css-ui/compute-kind-widget-generated/grouped-kind-of-widget-fallback-border-image-source-001.html @web-platform-tests/interop +css/css-ui/compute-kind-widget-generated/grouped-kind-of-widget-fallback-border-top-color-001.html @web-platform-tests/interop +css/css-ui/compute-kind-widget-generated/grouped-kind-of-widget-fallback-border-top-left-radius-001.html @web-platform-tests/interop +css/css-ui/compute-kind-widget-generated/grouped-kind-of-widget-fallback-border-top-style-001.html @web-platform-tests/interop +css/css-ui/compute-kind-widget-generated/grouped-kind-of-widget-fallback-border-top-width-001.html @web-platform-tests/interop +css/css-view-transitions/3d-transform-incoming.html @web-platform-tests/interop +css/css-view-transitions/3d-transform-outgoing.html @web-platform-tests/interop +css/css-view-transitions/active-view-transition-on-non-root.html @web-platform-tests/interop +css/css-view-transitions/active-view-transition-pseudo-class-match.html @web-platform-tests/interop +css/css-view-transitions/active-view-transition-type-on-non-root.html @web-platform-tests/interop +css/css-view-transitions/animating-new-content-subset.html @web-platform-tests/interop +css/css-view-transitions/animating-new-content.html @web-platform-tests/interop +css/css-view-transitions/auto-name-from-id-shadow.html @web-platform-tests/interop +css/css-view-transitions/auto-name-from-id.html @web-platform-tests/interop +css/css-view-transitions/auto-name.html @web-platform-tests/interop +css/css-view-transitions/backdrop-filter-animated.html @web-platform-tests/interop +css/css-view-transitions/backdrop-filter-captured.html @web-platform-tests/interop +css/css-view-transitions/block-with-overflowing-text.html @web-platform-tests/interop +css/css-view-transitions/break-inside-avoid-child.html @web-platform-tests/interop +css/css-view-transitions/capture-old-state-error-flag-cleanup.html @web-platform-tests/interop +css/css-view-transitions/capture-with-offscreen-child-translated.html @web-platform-tests/interop +css/css-view-transitions/capture-with-offscreen-child.html @web-platform-tests/interop +css/css-view-transitions/capture-with-opacity-zero-child.html @web-platform-tests/interop +css/css-view-transitions/capture-with-visibility-hidden-child.html @web-platform-tests/interop +css/css-view-transitions/capture-with-visibility-mixed-descendants.html @web-platform-tests/interop +css/css-view-transitions/class-specificity.html @web-platform-tests/interop +css/css-view-transitions/clip-path-larger-than-border-box-on-child-of-named-element.html @web-platform-tests/interop +css/css-view-transitions/content-smaller-than-box-size.html @web-platform-tests/interop +css/css-view-transitions/content-visibility-auto-shared-element.html @web-platform-tests/interop +css/css-view-transitions/content-with-child-with-transparent-background.html @web-platform-tests/interop +css/css-view-transitions/content-with-clip-root.html @web-platform-tests/interop +css/css-view-transitions/content-with-clip.html @web-platform-tests/interop +css/css-view-transitions/content-with-inline-child.html @web-platform-tests/interop +css/css-view-transitions/content-with-transform-new-image.html @web-platform-tests/interop +css/css-view-transitions/content-with-transform-old-image.html @web-platform-tests/interop +css/css-view-transitions/content-with-transparent-background.html @web-platform-tests/interop +css/css-view-transitions/css-tags-paint-order-with-entry.html @web-platform-tests/interop +css/css-view-transitions/css-tags-paint-order.html @web-platform-tests/interop +css/css-view-transitions/css-tags-shared-element.html @web-platform-tests/interop +css/css-view-transitions/dialog-in-rtl-iframe.html @web-platform-tests/interop +css/css-view-transitions/dialog-in-top-layer-during-transition-new.html @web-platform-tests/interop +css/css-view-transitions/dialog-in-top-layer-during-transition-old.html @web-platform-tests/interop +css/css-view-transitions/element-is-grouping-during-animation.html @web-platform-tests/interop +css/css-view-transitions/element-stops-grouping-after-animation.html @web-platform-tests/interop +css/css-view-transitions/element-with-overflow.html @web-platform-tests/interop +css/css-view-transitions/exit-transition-with-anonymous-layout-object.html @web-platform-tests/interop +css/css-view-transitions/far-away-capture.html @web-platform-tests/interop +css/css-view-transitions/fractional-box-new.html @web-platform-tests/interop +css/css-view-transitions/fractional-box-old.html @web-platform-tests/interop +css/css-view-transitions/fractional-box-with-overflow-children-new.html @web-platform-tests/interop +css/css-view-transitions/fractional-box-with-overflow-children-old.html @web-platform-tests/interop +css/css-view-transitions/fractional-box-with-shadow-new.html @web-platform-tests/interop +css/css-view-transitions/fractional-box-with-shadow-old.html @web-platform-tests/interop +css/css-view-transitions/fractional-translation-from-position.html @web-platform-tests/interop +css/css-view-transitions/fractional-translation-from-transform.html @web-platform-tests/interop +css/css-view-transitions/fragmented-at-start-ignored.html @web-platform-tests/interop +css/css-view-transitions/fragmented-during-transition-skips.html @web-platform-tests/interop +css/css-view-transitions/hit-test-unpainted-element.html @web-platform-tests/interop +css/css-view-transitions/hit-test-unrelated-element.html @web-platform-tests/interop +css/css-view-transitions/iframe-and-main-frame-transition-new-main-new-iframe.html @web-platform-tests/interop +css/css-view-transitions/iframe-and-main-frame-transition-new-main-old-iframe.html @web-platform-tests/interop +css/css-view-transitions/iframe-and-main-frame-transition-old-main-new-iframe.html @web-platform-tests/interop +css/css-view-transitions/iframe-and-main-frame-transition-old-main-old-iframe.html @web-platform-tests/interop +css/css-view-transitions/iframe-and-main-frame-transition-old-main.html @web-platform-tests/interop +css/css-view-transitions/iframe-and-main-frame-transition-with-name-on-iframe.html @web-platform-tests/interop +css/css-view-transitions/iframe-new-has-scrollbar.html @web-platform-tests/interop +css/css-view-transitions/iframe-old-has-scrollbar.html @web-platform-tests/interop +css/css-view-transitions/iframe-transition.sub.html @web-platform-tests/interop +css/css-view-transitions/inline-child-with-filter.html @web-platform-tests/interop +css/css-view-transitions/inline-child-with-overflow-shadow.html @web-platform-tests/interop +css/css-view-transitions/inline-element-size.html @web-platform-tests/interop +css/css-view-transitions/inline-with-offset-from-containing-block.html @web-platform-tests/interop +css/css-view-transitions/japanese-tag.html @web-platform-tests/interop +css/css-view-transitions/massive-element-below-and-on-top-of-viewport-partially-onscreen-new.html @web-platform-tests/interop +css/css-view-transitions/massive-element-below-and-on-top-of-viewport-partially-onscreen-old.html @web-platform-tests/interop +css/css-view-transitions/massive-element-below-viewport-offscreen-new.html @web-platform-tests/interop +css/css-view-transitions/massive-element-below-viewport-offscreen-old.html @web-platform-tests/interop +css/css-view-transitions/massive-element-below-viewport-partially-onscreen-new.html @web-platform-tests/interop +css/css-view-transitions/massive-element-below-viewport-partially-onscreen-old.html @web-platform-tests/interop +css/css-view-transitions/massive-element-left-of-viewport-offscreen-new.html @web-platform-tests/interop +css/css-view-transitions/massive-element-left-of-viewport-offscreen-old.html @web-platform-tests/interop +css/css-view-transitions/massive-element-left-of-viewport-partially-onscreen-new.html @web-platform-tests/interop +css/css-view-transitions/massive-element-left-of-viewport-partially-onscreen-old.html @web-platform-tests/interop +css/css-view-transitions/massive-element-on-top-of-viewport-offscreen-new.html @web-platform-tests/interop +css/css-view-transitions/massive-element-on-top-of-viewport-offscreen-old.html @web-platform-tests/interop +css/css-view-transitions/massive-element-on-top-of-viewport-partially-onscreen-new.html @web-platform-tests/interop +css/css-view-transitions/massive-element-on-top-of-viewport-partially-onscreen-old.html @web-platform-tests/interop +css/css-view-transitions/massive-element-right-and-left-of-viewport-partially-onscreen-new.html @web-platform-tests/interop +css/css-view-transitions/massive-element-right-and-left-of-viewport-partially-onscreen-old.html @web-platform-tests/interop +css/css-view-transitions/massive-element-right-of-viewport-offscreen-new.html @web-platform-tests/interop +css/css-view-transitions/massive-element-right-of-viewport-offscreen-old.html @web-platform-tests/interop +css/css-view-transitions/massive-element-right-of-viewport-partially-onscreen-new.html @web-platform-tests/interop +css/css-view-transitions/massive-element-right-of-viewport-partially-onscreen-old.html @web-platform-tests/interop +css/css-view-transitions/match-element-name.html @web-platform-tests/interop +css/css-view-transitions/modify-style-via-cssom.html @web-platform-tests/interop +css/css-view-transitions/multiline-span-with-overflowing-text-and-box-decorations.html @web-platform-tests/interop +css/css-view-transitions/named-element-with-fix-pos-child-new.html @web-platform-tests/interop +css/css-view-transitions/named-element-with-fix-pos-child-old.html @web-platform-tests/interop +css/css-view-transitions/names-are-tree-scoped.html @web-platform-tests/interop +css/css-view-transitions/nested-elements-in-overflow.html @web-platform-tests/interop +css/css-view-transitions/new-and-old-sizes-match.html @web-platform-tests/interop +css/css-view-transitions/new-content-ancestor-clipped-2.html @web-platform-tests/interop +css/css-view-transitions/new-content-ancestor-clipped.html @web-platform-tests/interop +css/css-view-transitions/new-content-captures-clip-path.html @web-platform-tests/interop +css/css-view-transitions/new-content-captures-different-size.html @web-platform-tests/interop +css/css-view-transitions/new-content-captures-opacity.html @web-platform-tests/interop +css/css-view-transitions/new-content-captures-positioned-spans.html @web-platform-tests/interop +css/css-view-transitions/new-content-captures-root.html @web-platform-tests/interop +css/css-view-transitions/new-content-captures-spans.html @web-platform-tests/interop +css/css-view-transitions/new-content-changes-overflow-left.html @web-platform-tests/interop +css/css-view-transitions/new-content-changes-overflow.html @web-platform-tests/interop +css/css-view-transitions/new-content-container-writing-modes.html @web-platform-tests/interop +css/css-view-transitions/new-content-element-writing-modes.html @web-platform-tests/interop +css/css-view-transitions/new-content-escapes-clip-with-abspos-child.html @web-platform-tests/interop +css/css-view-transitions/new-content-flat-transform-ancestor.html @web-platform-tests/interop +css/css-view-transitions/new-content-from-root-display-none.html @web-platform-tests/interop +css/css-view-transitions/new-content-has-scrollbars.html @web-platform-tests/interop +css/css-view-transitions/new-content-inline-with-offset-from-containing-block-clipped.html @web-platform-tests/interop +css/css-view-transitions/new-content-intrinsic-aspect-ratio.html @web-platform-tests/interop +css/css-view-transitions/new-content-is-empty-div.html @web-platform-tests/interop +css/css-view-transitions/new-content-is-inline.html @web-platform-tests/interop +css/css-view-transitions/new-content-object-fit-fill.html @web-platform-tests/interop +css/css-view-transitions/new-content-object-fit-none.html @web-platform-tests/interop +css/css-view-transitions/new-content-preserve-3d-ancestor.html @web-platform-tests/interop +css/css-view-transitions/new-content-root-scrollbar-with-fixed-background.html @web-platform-tests/interop +css/css-view-transitions/new-content-scaling.html @web-platform-tests/interop +css/css-view-transitions/new-content-transform-position-fixed.html @web-platform-tests/interop +css/css-view-transitions/new-content-with-overflow-zoomed.html @web-platform-tests/interop +css/css-view-transitions/new-content-with-overflow.html @web-platform-tests/interop +css/css-view-transitions/new-element-on-start.html @web-platform-tests/interop +css/css-view-transitions/new-root-vertical-writing-mode.html @web-platform-tests/interop +css/css-view-transitions/no-named-elements.html @web-platform-tests/interop +css/css-view-transitions/no-painting-while-render-blocked.html @web-platform-tests/interop +css/css-view-transitions/no-root-capture.html @web-platform-tests/interop +css/css-view-transitions/no-white-flash-before-activation.html @web-platform-tests/interop +css/css-view-transitions/nothing-captured.html @web-platform-tests/interop +css/css-view-transitions/offscreen-element-modified-before-coming-onscreen.html @web-platform-tests/interop +css/css-view-transitions/old-content-captures-clip-path.html @web-platform-tests/interop +css/css-view-transitions/old-content-captures-different-size.html @web-platform-tests/interop +css/css-view-transitions/old-content-captures-opacity.html @web-platform-tests/interop +css/css-view-transitions/old-content-captures-root.html @web-platform-tests/interop +css/css-view-transitions/old-content-container-writing-modes.html @web-platform-tests/interop +css/css-view-transitions/old-content-element-writing-modes.html @web-platform-tests/interop +css/css-view-transitions/old-content-escapes-clip-with-abspos-child.html @web-platform-tests/interop +css/css-view-transitions/old-content-has-scrollbars.html @web-platform-tests/interop +css/css-view-transitions/old-content-inline-with-offset-from-containing-block-clipped.html @web-platform-tests/interop +css/css-view-transitions/old-content-intrinsic-aspect-ratio.html @web-platform-tests/interop +css/css-view-transitions/old-content-is-empty-div.html @web-platform-tests/interop +css/css-view-transitions/old-content-is-inline.html @web-platform-tests/interop +css/css-view-transitions/old-content-object-fit-fill.html @web-platform-tests/interop +css/css-view-transitions/old-content-object-fit-none.html @web-platform-tests/interop +css/css-view-transitions/old-content-root-scrollbar-with-fixed-background.html @web-platform-tests/interop +css/css-view-transitions/old-content-with-overflow-zoomed.html @web-platform-tests/interop +css/css-view-transitions/old-content-with-overflow.html @web-platform-tests/interop +css/css-view-transitions/old-root-vertical-writing-mode.html @web-platform-tests/interop +css/css-view-transitions/outer-padding-inner-background.html @web-platform-tests/interop +css/css-view-transitions/paint-holding-in-iframe.html @web-platform-tests/interop +css/css-view-transitions/pseudo-element-overflow-hidden.html @web-platform-tests/interop +css/css-view-transitions/pseudo-element-preserve-3d.html @web-platform-tests/interop +css/css-view-transitions/pseudo-rendering-invalidation.html @web-platform-tests/interop +css/css-view-transitions/pseudo-with-classes-entry.html @web-platform-tests/interop +css/css-view-transitions/pseudo-with-classes-exit.html @web-platform-tests/interop +css/css-view-transitions/pseudo-with-classes-match-ident.html @web-platform-tests/interop +css/css-view-transitions/pseudo-with-classes-match-multiple-wildcard.html @web-platform-tests/interop +css/css-view-transitions/pseudo-with-classes-match-multiple.html @web-platform-tests/interop +css/css-view-transitions/pseudo-with-classes-match-wildcard-no-star.html @web-platform-tests/interop +css/css-view-transitions/pseudo-with-classes-match-wildcard.html @web-platform-tests/interop +css/css-view-transitions/pseudo-with-classes-mismatch-ident.html @web-platform-tests/interop +css/css-view-transitions/pseudo-with-classes-mismatch-partial.html @web-platform-tests/interop +css/css-view-transitions/pseudo-with-classes-mismatch-wildcard.html @web-platform-tests/interop +css/css-view-transitions/pseudo-with-classes-multiple-vt-classes.html @web-platform-tests/interop +css/css-view-transitions/pseudo-with-classes-new-with-class-old-without.html @web-platform-tests/interop +css/css-view-transitions/pseudo-with-classes-old-with-class-new-without.html @web-platform-tests/interop +css/css-view-transitions/pseudo-with-classes-view-transition-group.html @web-platform-tests/interop +css/css-view-transitions/pseudo-with-classes-view-transition-image-pair.html @web-platform-tests/interop +css/css-view-transitions/reset-state-after-scrolled-view-transition.html @web-platform-tests/interop +css/css-view-transitions/root-captured-as-different-tag.html @web-platform-tests/interop +css/css-view-transitions/root-style-change-during-animation.html @web-platform-tests/interop +css/css-view-transitions/root-to-shared-animation-end.html @web-platform-tests/interop +css/css-view-transitions/root-to-shared-animation-incoming.html @web-platform-tests/interop +css/css-view-transitions/root-to-shared-animation-start.html @web-platform-tests/interop +css/css-view-transitions/rotated-cat-off-top-edge.html @web-platform-tests/interop +css/css-view-transitions/rtl-with-scrollbar.html @web-platform-tests/interop +css/css-view-transitions/scroller-child-abspos.html @web-platform-tests/interop +css/css-view-transitions/scroller-child.html @web-platform-tests/interop +css/css-view-transitions/scroller.html @web-platform-tests/interop +css/css-view-transitions/set-current-time-transform.html @web-platform-tests/interop +css/css-view-transitions/set-current-time.html @web-platform-tests/interop +css/css-view-transitions/set-universal-specificity.html @web-platform-tests/interop +css/css-view-transitions/shadow-part-with-class-inside-shadow-important.html @web-platform-tests/interop +css/css-view-transitions/shadow-part-with-class-inside-shadow.html @web-platform-tests/interop +css/css-view-transitions/shadow-part-with-class.html @web-platform-tests/interop +css/css-view-transitions/shadow-part-with-name-nested.html @web-platform-tests/interop +css/css-view-transitions/shadow-part-with-name-overridden-by-important.html @web-platform-tests/interop +css/css-view-transitions/shadow-part-with-name.html @web-platform-tests/interop +css/css-view-transitions/sibling-frames-transition.html @web-platform-tests/interop +css/css-view-transitions/snapshot-containing-block-absolute.html @web-platform-tests/interop +css/css-view-transitions/snapshot-containing-block-includes-scrollbar-gutter.html @web-platform-tests/interop +css/css-view-transitions/snapshot-containing-block-static.html @web-platform-tests/interop +css/css-view-transitions/span-with-overflowing-text-and-box-decorations.html @web-platform-tests/interop +css/css-view-transitions/span-with-overflowing-text-hidden.html @web-platform-tests/interop +css/css-view-transitions/span-with-overflowing-text.html @web-platform-tests/interop +css/css-view-transitions/transform-origin-view-transition-group.html @web-platform-tests/interop +css/css-view-transitions/transformed-element-scroll-transform.html @web-platform-tests/interop +css/css-view-transitions/transition-in-empty-iframe.html @web-platform-tests/interop +css/css-view-transitions/update-callback-called-once.html @web-platform-tests/interop +css/css-view-transitions/view-transition-name-is-backdrop-filter-root.html @web-platform-tests/interop +css/css-view-transitions/view-transition-name-is-grouping.html @web-platform-tests/interop +css/css-view-transitions/view-transition-name-on-document-root.html @web-platform-tests/interop +css/css-view-transitions/view-transition-name-removed-mid-transition.html @web-platform-tests/interop +css/css-view-transitions/web-animations-api-parse-pseudo-argument.html @web-platform-tests/interop +css/css-view-transitions/web-animations-api.html @web-platform-tests/interop +css/css-view-transitions/writing-mode-container-resize.html @web-platform-tests/interop +css/css-viewport/zoom/background-image.html @web-platform-tests/interop +css/css-viewport/zoom/basic.html @web-platform-tests/interop +css/css-viewport/zoom/border-spacing.html @web-platform-tests/interop +css/css-viewport/zoom/canvas.html @web-platform-tests/interop +css/css-viewport/zoom/container-queries.html @web-platform-tests/interop +css/css-viewport/zoom/font-size.html @web-platform-tests/interop +css/css-viewport/zoom/iframe-zoom-nested.html @web-platform-tests/interop +css/css-viewport/zoom/iframe-zoom.sub.html @web-platform-tests/interop +css/css-viewport/zoom/image-intrinsic-size.html @web-platform-tests/interop +css/css-viewport/zoom/inherited-length.html @web-platform-tests/interop +css/css-viewport/zoom/inherited.html @web-platform-tests/interop +css/css-viewport/zoom/letter-spacing.html @web-platform-tests/interop +css/css-viewport/zoom/line-height.html @web-platform-tests/interop +css/css-viewport/zoom/list-style-image.html @web-platform-tests/interop +css/css-viewport/zoom/relative-units-from-parent.html @web-platform-tests/interop +css/css-viewport/zoom/stroke.html @web-platform-tests/interop +css/css-viewport/zoom/svg-path-simple.html @web-platform-tests/interop +css/css-viewport/zoom/svg-path.html @web-platform-tests/interop +css/css-viewport/zoom/svg-transform.html @web-platform-tests/interop +css/css-viewport/zoom/svg-viewBox.html @web-platform-tests/interop +css/css-viewport/zoom/svg.html @web-platform-tests/interop +css/css-viewport/zoom/text-indent.html @web-platform-tests/interop +css/css-viewport/zoom/text-shadow.html @web-platform-tests/interop +css/css-viewport/zoom/text-stroke-width.html @web-platform-tests/interop +css/css-viewport/zoom/text-underline-offset.html @web-platform-tests/interop +css/css-viewport/zoom/word-spacing.html @web-platform-tests/interop +css/css-writing-modes/abs-pos-border-offset-002.html @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-slr-043.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-slr-047.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-slr-048.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-slr-050.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-slr-054.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-slr-055.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-slr-056.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-slr-058.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-slr-060.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-slr-062.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-slr-063.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-slr-066.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-srl-042.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-srl-045.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-srl-046.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-srl-049.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-srl-051.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-srl-052.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-srl-053.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-srl-057.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-srl-059.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-srl-061.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-srl-064.xht @web-platform-tests/interop +css/css-writing-modes/block-flow-direction-srl-065.xht @web-platform-tests/interop +css/css-writing-modes/inline-block-alignment-slr-009.xht @web-platform-tests/interop +css/css-writing-modes/inline-block-alignment-srl-008.xht @web-platform-tests/interop +css/css-writing-modes/line-box-direction-slr-043.xht @web-platform-tests/interop +css/css-writing-modes/line-box-direction-slr-047.xht @web-platform-tests/interop +css/css-writing-modes/line-box-direction-slr-048.xht @web-platform-tests/interop +css/css-writing-modes/line-box-direction-slr-050.xht @web-platform-tests/interop +css/css-writing-modes/line-box-direction-slr-053.xht @web-platform-tests/interop +css/css-writing-modes/line-box-direction-slr-054.xht @web-platform-tests/interop +css/css-writing-modes/line-box-direction-slr-056.xht @web-platform-tests/interop +css/css-writing-modes/line-box-direction-slr-058.xht @web-platform-tests/interop +css/css-writing-modes/line-box-direction-slr-060.xht @web-platform-tests/interop +css/css-writing-modes/line-box-direction-srl-042.xht @web-platform-tests/interop +css/css-writing-modes/line-box-direction-srl-045.xht @web-platform-tests/interop +css/css-writing-modes/line-box-direction-srl-046.xht @web-platform-tests/interop +css/css-writing-modes/line-box-direction-srl-049.xht @web-platform-tests/interop +css/css-writing-modes/line-box-direction-srl-051.xht @web-platform-tests/interop +css/css-writing-modes/line-box-direction-srl-052.xht @web-platform-tests/interop +css/css-writing-modes/line-box-direction-srl-055.xht @web-platform-tests/interop +css/css-writing-modes/line-box-direction-srl-057.xht @web-platform-tests/interop +css/css-writing-modes/line-box-direction-srl-059.xht @web-platform-tests/interop +css/css-writing-modes/logical-physical-mapping-001.html @web-platform-tests/interop +css/css-writing-modes/row-progression-slr-023.xht @web-platform-tests/interop +css/css-writing-modes/row-progression-slr-029.xht @web-platform-tests/interop +css/css-writing-modes/row-progression-srl-022.xht @web-platform-tests/interop +css/css-writing-modes/row-progression-srl-028.xht @web-platform-tests/interop +css/css-writing-modes/row-progression-vlr-009.xht @web-platform-tests/interop +css/css-writing-modes/slr-alongside-vlr-floats.html @web-platform-tests/interop +css/css-writing-modes/srl-alongside-vrl-floats.html @web-platform-tests/interop +css/css-writing-modes/table-column-order-slr-007.xht @web-platform-tests/interop +css/css-writing-modes/table-column-order-srl-006.xht @web-platform-tests/interop +css/css-writing-modes/table-progression-slr-001.html @web-platform-tests/interop +css/css-writing-modes/table-progression-slr-002.html @web-platform-tests/interop +css/css-writing-modes/table-progression-srl-001.html @web-platform-tests/interop +css/css-writing-modes/table-progression-srl-002.html @web-platform-tests/interop +css/css-writing-modes/text-baseline-slr-009.xht @web-platform-tests/interop +css/css-writing-modes/text-baseline-srl-008.xht @web-platform-tests/interop +css/css-writing-modes/text-combine-upright-sideways-001.html @web-platform-tests/interop +css/css-writing-modes/text-combine-upright-sideways-002.html @web-platform-tests/interop +css/css-writing-modes/text-orientation-mixed-srl-016.xht @web-platform-tests/interop +css/css-writing-modes/text-orientation-upright-directionality-001.html @web-platform-tests/interop +css/css-writing-modes/text-orientation-upright-srl-018.xht @web-platform-tests/interop +css/css-writing-modes/vertical-alignment-slr-029.xht @web-platform-tests/interop +css/css-writing-modes/vertical-alignment-slr-031.xht @web-platform-tests/interop +css/css-writing-modes/vertical-alignment-slr-033.xht @web-platform-tests/interop +css/css-writing-modes/vertical-alignment-slr-035.xht @web-platform-tests/interop +css/css-writing-modes/vertical-alignment-slr-041.xht @web-platform-tests/interop +css/css-writing-modes/vertical-alignment-srl-028.xht @web-platform-tests/interop +css/css-writing-modes/vertical-alignment-srl-030.xht @web-platform-tests/interop +css/css-writing-modes/vertical-alignment-srl-032.xht @web-platform-tests/interop +css/css-writing-modes/vertical-alignment-srl-034.xht @web-platform-tests/interop +css/css-writing-modes/vertical-alignment-srl-040.xht @web-platform-tests/interop +css/css-writing-modes/wm-propagation-body-032.html @web-platform-tests/interop +css/css-writing-modes/wm-propagation-body-033.html @web-platform-tests/interop +css/css-writing-modes/wm-propagation-body-034.html @web-platform-tests/interop +css/css-writing-modes/wm-propagation-body-035.html @web-platform-tests/interop +css/css-writing-modes/wm-propagation-body-036.html @web-platform-tests/interop +css/css-writing-modes/wm-propagation-body-037.html @web-platform-tests/interop +css/css-writing-modes/wm-propagation-body-038.html @web-platform-tests/interop +css/css-writing-modes/wm-propagation-body-039.html @web-platform-tests/interop +css/css-writing-modes/wm-propagation-body-040.html @web-platform-tests/interop +css/css-writing-modes/wm-propagation-body-041.html @web-platform-tests/interop +css/css-writing-modes/wm-propagation-body-043.html @web-platform-tests/interop +css/css-writing-modes/wm-propagation-body-044.html @web-platform-tests/interop +css/css-writing-modes/wm-propagation-body-045.html @web-platform-tests/interop +css/css-writing-modes/wm-propagation-body-046.html @web-platform-tests/interop +css/css-writing-modes/wm-propagation-body-048.html @web-platform-tests/interop +css/css-writing-modes/wm-propagation-body-050.html @web-platform-tests/interop +css/css-writing-modes/wm-propagation-body-051.html @web-platform-tests/interop +css/css-writing-modes/wm-propagation-body-052.html @web-platform-tests/interop +css/css-writing-modes/wm-propagation-body-053.html @web-platform-tests/interop +css/css-writing-modes/wm-propagation-body-055.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-backdrop-root-backdrop-filter.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-backdrop-root-clip-path.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-backdrop-root-filter.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-backdrop-root-mix-blend-mode.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-backdrop-root-opacity.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-basic-blur.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-basic-opacity-2.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-basic-opacity.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-basic.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-boundary.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-clip-radius-zoom.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-clip-rect-2.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-clip-rect-zoom.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-clip-rect.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-clip-rounded-clip.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-clipped.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-containing-block.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-edge-behavior.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-edge-clipping-2.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-edge-clipping.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-edge-mirror.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-edge-pixels-2.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-edge-pixels.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-fixed-clip.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-invalid.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-isolation-fixed.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-isolation-isolate.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-opacity-rounded-clip.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-paint-order.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-plus-filter.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-plus-mask-large.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-plus-mask.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-plus-opacity.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-plus-will-change-opacity.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-reference-filter-mutated.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-reference-filter.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-root-element.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-svg-background-image-blur.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-svg-foreignObject.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-svg.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-transform.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-update.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-zero-size.html @web-platform-tests/interop +css/filter-effects/backdrop-filters-brightness.html @web-platform-tests/interop +css/filter-effects/backdrop-filters-contrast.html @web-platform-tests/interop +css/filter-effects/backdrop-filters-grayscale-001.html @web-platform-tests/interop +css/filter-effects/backdrop-filters-grayscale-002.html @web-platform-tests/interop +css/filter-effects/backdrop-filters-grayscale-003.html @web-platform-tests/interop +css/filter-effects/backdrop-filters-hue-rotate.html @web-platform-tests/interop +css/filter-effects/backdrop-filters-invert.html @web-platform-tests/interop +css/filter-effects/backdrop-filters-opacity.html @web-platform-tests/interop +css/filter-effects/backdrop-filters-saturate.html @web-platform-tests/interop +css/filter-effects/backdrop-filters-sepia.html @web-platform-tests/interop +css/filter-effects/css-backdrop-filters-animation-blur.html @web-platform-tests/interop +css/filter-effects/css-backdrop-filters-animation-brightness.html @web-platform-tests/interop +css/filter-effects/css-backdrop-filters-animation-combined.html @web-platform-tests/interop +css/filter-effects/css-backdrop-filters-animation-contrast.html @web-platform-tests/interop +css/filter-effects/css-backdrop-filters-animation-drop-shadow.html @web-platform-tests/interop +css/filter-effects/css-backdrop-filters-animation-grayscale.html @web-platform-tests/interop +css/filter-effects/css-backdrop-filters-animation-hue-rotate.html @web-platform-tests/interop +css/filter-effects/css-backdrop-filters-animation-invert.html @web-platform-tests/interop +css/filter-effects/css-backdrop-filters-animation-opacity.html @web-platform-tests/interop +css/filter-effects/css-backdrop-filters-animation-saturate.html @web-platform-tests/interop +css/filter-effects/css-backdrop-filters-animation-sepia.html @web-platform-tests/interop +css/filter-effects/repaint-added-backdrop-filter.html @web-platform-tests/interop +html/editing/the-hidden-attribute/hidden-until-found-001.html @web-platform-tests/interop +html/editing/the-hidden-attribute/hidden-until-found-004.html @web-platform-tests/interop +html/editing/the-hidden-attribute/hidden-until-found-005.html @web-platform-tests/interop +html/editing/the-hidden-attribute/hidden-until-found-007.html @web-platform-tests/interop +html/rendering/the-details-element/details-after.html @web-platform-tests/interop +html/rendering/the-details-element/details-before.html @web-platform-tests/interop +html/rendering/the-details-element/details-display-type-001-ref.html @web-platform-tests/interop +html/rendering/the-details-element/details-display-type-001.html @web-platform-tests/interop +html/rendering/the-details-element/details-display-type-002.html @web-platform-tests/interop +html/rendering/the-details-element/details-pseudo-elements-001.html @web-platform-tests/interop +html/rendering/the-details-element/details-pseudo-elements-002.html @web-platform-tests/interop +html/rendering/the-details-element/details-pseudo-elements-003.html @web-platform-tests/interop +html/rendering/the-details-element/details-pseudo-elements-004.html @web-platform-tests/interop +html/rendering/the-details-element/details-pseudo-elements-005.html @web-platform-tests/interop +html/rendering/the-details-element/details-revert.html @web-platform-tests/interop +html/rendering/the-details-element/details-summary-display-inline-001.html @web-platform-tests/interop +html/rendering/the-details-element/details-summary-display-inline-002.html @web-platform-tests/interop +html/rendering/the-details-element/summary-display-flex.html @web-platform-tests/interop +html/rendering/the-details-element/summary-display-grid.html @web-platform-tests/interop +html/rendering/the-details-element/summary-display-inline-flex.html @web-platform-tests/interop +html/rendering/the-details-element/summary-display-inline-grid.html @web-platform-tests/interop +html/rendering/the-details-element/summary-display-list-item-001.html @web-platform-tests/interop +html/rendering/the-details-element/summary-display-list-item-002.html @web-platform-tests/interop +html/rendering/the-details-element/summary-in-ol.html @web-platform-tests/interop +html/rendering/the-details-element/summary-text-decoration.html @web-platform-tests/interop +css/css-anchor-position/anchor-center-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-center-htb-htb.html @web-platform-tests/interop +css/css-anchor-position/anchor-center-htb-vrl.html @web-platform-tests/interop +css/css-anchor-position/anchor-center-offset-change.html @web-platform-tests/interop +css/css-anchor-position/anchor-center-vrl-htb.html @web-platform-tests/interop +css/css-anchor-position/anchor-center-vrl-vrl.html @web-platform-tests/interop +css/css-anchor-position/anchor-fallback-invalidation.html @web-platform-tests/interop +css/css-anchor-position/anchor-getComputedStyle-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-getComputedStyle-002.html @web-platform-tests/interop +css/css-anchor-position/anchor-getComputedStyle-003.html @web-platform-tests/interop +css/css-anchor-position/anchor-inherited.html @web-platform-tests/interop +css/css-anchor-position/anchor-inside-outside.html @web-platform-tests/interop +css/css-anchor-position/anchor-invalid-fallback.html @web-platform-tests/interop +css/css-anchor-position/anchor-name-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-name-002.html @web-platform-tests/interop +css/css-anchor-position/anchor-name-003.html @web-platform-tests/interop +css/css-anchor-position/anchor-name-004.html @web-platform-tests/interop +css/css-anchor-position/anchor-name-basics.html @web-platform-tests/interop +css/css-anchor-position/anchor-name-cross-shadow.html @web-platform-tests/interop +css/css-anchor-position/anchor-name-in-shadow-002.html @web-platform-tests/interop +css/css-anchor-position/anchor-name-in-shadow.html @web-platform-tests/interop +css/css-anchor-position/anchor-name-inline-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-name-multicol-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-name-multicol-002.html @web-platform-tests/interop +css/css-anchor-position/anchor-name-multicol-003.html @web-platform-tests/interop +css/css-anchor-position/anchor-name-multicol-004.html @web-platform-tests/interop +css/css-anchor-position/anchor-parse-invalid.html @web-platform-tests/interop +css/css-anchor-position/anchor-parse-valid.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-002.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-003.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-004.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-borders-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-borders-002.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-dynamic-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-dynamic-002.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-dynamic-003.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-dynamic-004.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-grid-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-inline-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-inline-002.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-inline-003.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-inline-004.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-multicol-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-multicol-002.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-multicol-003.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-multicol-004.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-multicol-005.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-multicol-006.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-multicol-colspan-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-multicol-colspan-002.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-multicol-fixed-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-multicol-nested-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-principal-box.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-top-layer-007.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-writing-modes-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-position-writing-modes-002.html @web-platform-tests/interop +css/css-anchor-position/anchor-query-custom-property-registration.html @web-platform-tests/interop +css/css-anchor-position/anchor-query-fallback.html @web-platform-tests/interop +css/css-anchor-position/anchor-scope-basic.html @web-platform-tests/interop +css/css-anchor-position/anchor-scope-dynamic.html @web-platform-tests/interop +css/css-anchor-position/anchor-scope-shadow-all.html @web-platform-tests/interop +css/css-anchor-position/anchor-scope-shadow-flat-tree.html @web-platform-tests/interop +css/css-anchor-position/anchor-scope-shadow-names.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-002.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-003.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-004.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-005.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-006.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-007.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-cleanup.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-js-expose.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-position-try-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-position-try-002.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-position-try-003.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-position-try-004.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-position-try-005.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-position-try-006.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-position-try-007.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-position-try-008.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-position-try-009.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-position-try-010.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-position-try-011.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-position-try-013.html @web-platform-tests/interop +css/css-anchor-position/anchor-scroll-position-try-014.html @web-platform-tests/interop +css/css-anchor-position/anchor-size-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-size-minmax-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-size-parse-invalid.html @web-platform-tests/interop +css/css-anchor-position/anchor-size-parse-valid.html @web-platform-tests/interop +css/css-anchor-position/anchor-size-replaced-001.html @web-platform-tests/interop +css/css-anchor-position/anchor-size-writing-modes-001.html @web-platform-tests/interop +css/css-anchor-position/at-position-try-allowed-declarations.html @web-platform-tests/interop +css/css-anchor-position/at-position-try-cssom.html @web-platform-tests/interop +css/css-anchor-position/at-position-try-invalidation-shadow-dom.html @web-platform-tests/interop +css/css-anchor-position/at-position-try-invalidation.html @web-platform-tests/interop +css/css-anchor-position/at-position-try-parse.html @web-platform-tests/interop +css/css-anchor-position/auto-margins-position-area.html @web-platform-tests/interop +css/css-anchor-position/base-style-invalidation.html @web-platform-tests/interop +css/css-anchor-position/idlharness.html @web-platform-tests/interop +css/css-anchor-position/last-successful-basic.html @web-platform-tests/interop +css/css-anchor-position/last-successful-change-fallbacks.html @web-platform-tests/interop +css/css-anchor-position/last-successful-change-try-rule.html @web-platform-tests/interop +css/css-anchor-position/last-successful-iframe.html @web-platform-tests/interop +css/css-anchor-position/last-successful-intermediate-ignored.html @web-platform-tests/interop +css/css-anchor-position/parsing/anchor-scope-computed.html @web-platform-tests/interop +css/css-anchor-position/parsing/anchor-scope-parsing.html @web-platform-tests/interop +css/css-anchor-position/parsing/position-try-computed.html @web-platform-tests/interop +css/css-anchor-position/parsing/position-try-fallbacks-computed.html @web-platform-tests/interop +css/css-anchor-position/parsing/position-try-fallbacks-parsing.html @web-platform-tests/interop +css/css-anchor-position/parsing/position-try-order-computed.html @web-platform-tests/interop +css/css-anchor-position/parsing/position-try-order-parsing.html @web-platform-tests/interop +css/css-anchor-position/parsing/position-try-parsing.html @web-platform-tests/interop +css/css-anchor-position/popover-implicit-anchor.html @web-platform-tests/interop +css/css-anchor-position/position-anchor-003.html @web-platform-tests/interop +css/css-anchor-position/position-anchor-basics.html @web-platform-tests/interop +css/css-anchor-position/position-area-align-justify-wm-dir.html @web-platform-tests/interop +css/css-anchor-position/position-area-align-justify.html @web-platform-tests/interop +css/css-anchor-position/position-area-anchor-outside.html @web-platform-tests/interop +css/css-anchor-position/position-area-anchor-partially-outside.html @web-platform-tests/interop +css/css-anchor-position/position-area-basic.html @web-platform-tests/interop +css/css-anchor-position/position-area-computed-insets.html @web-platform-tests/interop +css/css-anchor-position/position-area-computed.html @web-platform-tests/interop +css/css-anchor-position/position-area-in-grid.html @web-platform-tests/interop +css/css-anchor-position/position-area-in-position-try.html @web-platform-tests/interop +css/css-anchor-position/position-area-interpolation.html @web-platform-tests/interop +css/css-anchor-position/position-area-parsing.html @web-platform-tests/interop +css/css-anchor-position/position-area-value.html @web-platform-tests/interop +css/css-anchor-position/position-area-with-insets.html @web-platform-tests/interop +css/css-anchor-position/position-area-wm-dir.html @web-platform-tests/interop +css/css-anchor-position/position-try-001.html @web-platform-tests/interop +css/css-anchor-position/position-try-002.html @web-platform-tests/interop +css/css-anchor-position/position-try-003.html @web-platform-tests/interop +css/css-anchor-position/position-try-004.html @web-platform-tests/interop +css/css-anchor-position/position-try-backdrop.html @web-platform-tests/interop +css/css-anchor-position/position-try-cascade-layer-reorder.html @web-platform-tests/interop +css/css-anchor-position/position-try-cascade.html @web-platform-tests/interop +css/css-anchor-position/position-try-container-query.html @web-platform-tests/interop +css/css-anchor-position/position-try-custom-property.html @web-platform-tests/interop +css/css-anchor-position/position-try-dynamic.html @web-platform-tests/interop +css/css-anchor-position/position-try-fallbacks-limit.html @web-platform-tests/interop +css/css-anchor-position/position-try-grid-001.html @web-platform-tests/interop +css/css-anchor-position/position-try-order-basic.html @web-platform-tests/interop +css/css-anchor-position/position-try-order-position-area.html @web-platform-tests/interop +css/css-anchor-position/position-try-position-anchor.html @web-platform-tests/interop +css/css-anchor-position/position-try-pseudo-element.html @web-platform-tests/interop +css/css-anchor-position/position-try-tree-scoped.html @web-platform-tests/interop +css/css-anchor-position/property-interpolations.html @web-platform-tests/interop +css/css-anchor-position/pseudo-element-anchor-dynamic.html @web-platform-tests/interop +css/css-anchor-position/pseudo-element-anchor.html @web-platform-tests/interop +css/css-anchor-position/remove-anchor-dirty-layout.html @web-platform-tests/interop +css/css-anchor-position/try-tactic-alignment.html @web-platform-tests/interop +css/css-anchor-position/try-tactic-anchor.html @web-platform-tests/interop +css/css-anchor-position/try-tactic-back-to-base.html @web-platform-tests/interop +css/css-anchor-position/try-tactic-base.html @web-platform-tests/interop +css/css-anchor-position/try-tactic-basic.html @web-platform-tests/interop +css/css-anchor-position/try-tactic-margin.html @web-platform-tests/interop +css/css-anchor-position/try-tactic-percentage.html @web-platform-tests/interop +css/css-anchor-position/try-tactic-position-area.html @web-platform-tests/interop +css/css-anchor-position/try-tactic-sizing.html @web-platform-tests/interop +css/css-anchor-position/try-tactic-wm.html @web-platform-tests/interop +css/css-cascade/at-scope-parsing.html @web-platform-tests/interop +css/css-cascade/at-scope-relative-syntax.html @web-platform-tests/interop +css/css-cascade/scope-container.html @web-platform-tests/interop +css/css-cascade/scope-cssom.html @web-platform-tests/interop +css/css-cascade/scope-declarations.html @web-platform-tests/interop +css/css-cascade/scope-deep.html @web-platform-tests/interop +css/css-cascade/scope-evaluation.html @web-platform-tests/interop +css/css-cascade/scope-focus.html @web-platform-tests/interop +css/css-cascade/scope-hover.html @web-platform-tests/interop +css/css-cascade/scope-implicit-external.html @web-platform-tests/interop +css/css-cascade/scope-implicit.html @web-platform-tests/interop +css/css-cascade/scope-invalidation.html @web-platform-tests/interop +css/css-cascade/scope-layer.html @web-platform-tests/interop +css/css-cascade/scope-media.html @web-platform-tests/interop +css/css-cascade/scope-name-defining-rules.html @web-platform-tests/interop +css/css-cascade/scope-nesting.html @web-platform-tests/interop +css/css-cascade/scope-overlapping-has.html @web-platform-tests/interop +css/css-cascade/scope-proximity.html @web-platform-tests/interop +css/css-cascade/scope-shadow.html @web-platform-tests/interop +css/css-cascade/scope-specificity.html @web-platform-tests/interop +css/css-cascade/scope-starting-style.html @web-platform-tests/interop +css/css-cascade/scope-style-sharing-001.html @web-platform-tests/interop +css/css-cascade/scope-style-sharing-002.html @web-platform-tests/interop +css/css-cascade/scope-supports.html @web-platform-tests/interop +css/css-cascade/scope-visited-cssom.html @web-platform-tests/interop +css/css-flexbox/abspos/abspos-descendent-001.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-align-content-001.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-align-self-001.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-align-self-002.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-align-self-003.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-align-self-004.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-align-self-005.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-align-self-006.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-align-self-007.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-align-self-008.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-align-self-rtl-001.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-align-self-rtl-002.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-align-self-rtl-003.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-align-self-rtl-004.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-align-self-vertWM-001.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-align-self-vertWM-002.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-align-self-vertWM-003.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-align-self-vertWM-004.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-justify-content-001.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-justify-content-002.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-justify-content-003.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-justify-content-004.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-justify-content-005.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-justify-content-006.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-justify-content-007.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-justify-content-008.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-justify-content-rtl-001.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-justify-content-rtl-002.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-justify-content-vertWM-001.html @web-platform-tests/interop +css/css-flexbox/abspos/flex-abspos-staticpos-justify-content-vertWM-002.html @web-platform-tests/interop +css/css-flexbox/abspos/position-absolute-001.html @web-platform-tests/interop +css/css-flexbox/abspos/position-absolute-002.html @web-platform-tests/interop +css/css-flexbox/abspos/position-absolute-003.html @web-platform-tests/interop +css/css-flexbox/abspos/position-absolute-004.html @web-platform-tests/interop +css/css-flexbox/abspos/position-absolute-012.html @web-platform-tests/interop +css/css-flexbox/abspos/position-absolute-013.html @web-platform-tests/interop +css/css-flexbox/abspos/position-absolute-014.html @web-platform-tests/interop +css/css-flexbox/abspos/position-absolute-015.html @web-platform-tests/interop +css/css-flexbox/align-content-horiz-001a.html @web-platform-tests/interop +css/css-flexbox/align-content-horiz-001b.html @web-platform-tests/interop +css/css-flexbox/align-content-horiz-002.html @web-platform-tests/interop +css/css-flexbox/align-content-vert-001a.html @web-platform-tests/interop +css/css-flexbox/align-content-vert-001b.html @web-platform-tests/interop +css/css-flexbox/align-content-vert-002.html @web-platform-tests/interop +css/css-flexbox/align-content-wmvert-001.html @web-platform-tests/interop +css/css-flexbox/align-content-wrap-001.html @web-platform-tests/interop +css/css-flexbox/align-content-wrap-002.html @web-platform-tests/interop +css/css-flexbox/align-content-wrap-003.html @web-platform-tests/interop +css/css-flexbox/align-content-wrap-005.html @web-platform-tests/interop +css/css-flexbox/align-self-014.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-001.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-002.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-003.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-004.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-005.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-006.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-007.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-fieldset-001.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-fieldset-002.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-fieldset-003.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-flex-001.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-flex-002.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-flex-003.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-flex-004.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-grid-001.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-grid-002.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-grid-003.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-multicol-001.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-multicol-002.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-multicol-003.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-overflow-001.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-overflow-002.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-overflow-003.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-table-001.html @web-platform-tests/interop +css/css-flexbox/alignment/flex-align-baseline-table-003.html @web-platform-tests/interop +css/css-flexbox/animation/flex-basis-composition.html @web-platform-tests/interop +css/css-flexbox/animation/flex-basis-interpolation.html @web-platform-tests/interop +css/css-flexbox/animation/flex-grow-interpolation.html @web-platform-tests/interop +css/css-flexbox/animation/flex-shrink-interpolation.html @web-platform-tests/interop +css/css-flexbox/animation/order-interpolation.html @web-platform-tests/interop +css/css-flexbox/box-sizing-001.html @web-platform-tests/interop +css/css-flexbox/box-sizing-min-max-sizes-001.html @web-platform-tests/interop +css/css-flexbox/canvas-dynamic-change-001.html @web-platform-tests/interop +css/css-flexbox/change-column-flex-width.html @web-platform-tests/interop +css/css-flexbox/column-flex-child-with-overflow-scroll.html @web-platform-tests/interop +css/css-flexbox/column-reverse-gap.html @web-platform-tests/interop +css/css-flexbox/columns-height-set-via-top-bottom.html @web-platform-tests/interop +css/css-flexbox/dynamic-grid-flex-abspos.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-column-011.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-column-017.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-row-005.html @web-platform-tests/interop +css/css-flexbox/flex-aspect-ratio-img-row-013.html @web-platform-tests/interop +css/css-flexbox/flex-basis-009.html @web-platform-tests/interop +css/css-flexbox/flex-basis-intrinsics-001.html @web-platform-tests/interop +css/css-flexbox/flex-column-relayout-assert.html @web-platform-tests/interop +css/css-flexbox/flex-direction-column-overlap-001.html @web-platform-tests/interop +css/css-flexbox/flex-factor-less-than-one.html @web-platform-tests/interop +css/css-flexbox/flex-flow-013.html @web-platform-tests/interop +css/css-flexbox/flex-item-compressible-001.html @web-platform-tests/interop +css/css-flexbox/flex-item-compressible-002.html @web-platform-tests/interop +css/css-flexbox/flex-item-contains-strict.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-009.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-010.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-012.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-025.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-height-flex-items-031.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-size-001.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-size-002.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-size-003.html @web-platform-tests/interop +css/css-flexbox/flex-minimum-width-flex-items-014.html @web-platform-tests/interop +css/css-flexbox/flex-one-sets-flex-basis-to-zero-px.html @web-platform-tests/interop +css/css-flexbox/flex-outer-flexbox-column-recalculate-height-on-resize-001.html @web-platform-tests/interop +css/css-flexbox/flex-shorthand-flex-basis-middle.html @web-platform-tests/interop +css/css-flexbox/flexbox-justify-content-wmvert-002.html @web-platform-tests/interop +css/css-flexbox/flexbox-justify-content-wmvert-003.html @web-platform-tests/interop +css/css-flexbox/flexbox-lines-must-be-stretched-by-default.html @web-platform-tests/interop +css/css-flexbox/flexbox_first-letter.html @web-platform-tests/interop +css/css-flexbox/flexbox_justifycontent-center-overflow.html @web-platform-tests/interop +css/css-flexbox/flexbox_justifycontent-rtl-001.html @web-platform-tests/interop +css/css-flexbox/flexbox_justifycontent-rtl-002.html @web-platform-tests/interop +css/css-flexbox/flexbox_width-change-and-relayout-children.html @web-platform-tests/interop +css/css-flexbox/flexbox_width-wrapping-column.html @web-platform-tests/interop +css/css-flexbox/flexitem-no-margin-collapsing.html @web-platform-tests/interop +css/css-flexbox/flexitem-stretch-image.html @web-platform-tests/interop +css/css-flexbox/gap-017.html @web-platform-tests/interop +css/css-flexbox/gap-018.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-content-center.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-content-flex-end.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-content-flex-start.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-content-space-around.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-content-space-between.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-items-baseline.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-items-center.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-items-flex-end.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-items-flex-start.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-items-invalid.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-items-stretch.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-self-baseline.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-self-center.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-self-flex-end.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-self-flex-start.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-self-invalid.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-self-stretch.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_display-inline.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_display.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-basis-0.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-basis-0percent.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-basis-auto.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-basis-percent.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-direction-column-reverse.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-direction-column.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-direction-invalid.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-direction-row-reverse.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-direction-row.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-flow-column-nowrap.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-flow-column-reverse-nowrap.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-flow-column-reverse-wrap.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-flow-column-reverse.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-flow-column-wrap-reverse.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-flow-column-wrap.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-flow-column.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-flow-nowrap.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-flow-row-nowrap.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-flow-row-reverse-nowrap.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-flow-row-reverse-wrap-reverse.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-flow-row-reverse-wrap.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-flow-row-reverse.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-flow-row-wrap-reverse.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-flow-row-wrap.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-flow-row.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-flow-wrap.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-grow-0.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-grow-invalid.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-grow-number.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-shorthand-0-auto.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-shorthand-auto.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-shorthand-initial.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-shorthand-invalid.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-shorthand-none.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-shorthand-number.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-shorthand.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-shrink-0.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-shrink-invalid.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-shrink-number.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-wrap-invalid.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-wrap-nowrap.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-wrap-wrap-reverse.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-wrap-wrap.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_justify-content-center.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_justify-content-flex-end.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_justify-content-flex-start.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_justify-content-space-around.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_justify-content-space-between.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_min-auto-size.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_min-height-auto.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_min-width-auto.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_order-inherit.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_order-integer.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_order-invalid.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_order-negative.html @web-platform-tests/interop +css/css-flexbox/getcomputedstyle/flexbox_computedstyle_order.html @web-platform-tests/interop +css/css-flexbox/height-percentage-with-dynamic-container-size.html @web-platform-tests/interop +css/css-flexbox/hittest-anonymous-box.html @web-platform-tests/interop +css/css-flexbox/hittest-before-pseudo.html @web-platform-tests/interop +css/css-flexbox/hittest-overlapping-margin.html @web-platform-tests/interop +css/css-flexbox/hittest-overlapping-order.html @web-platform-tests/interop +css/css-flexbox/hittest-overlapping-relative.html @web-platform-tests/interop +css/css-flexbox/image-as-flexitem-size-001.html @web-platform-tests/interop +css/css-flexbox/image-as-flexitem-size-001v.html @web-platform-tests/interop +css/css-flexbox/image-as-flexitem-size-002.html @web-platform-tests/interop +css/css-flexbox/image-as-flexitem-size-002v.html @web-platform-tests/interop +css/css-flexbox/image-as-flexitem-size-003.html @web-platform-tests/interop +css/css-flexbox/image-as-flexitem-size-003v.html @web-platform-tests/interop +css/css-flexbox/image-as-flexitem-size-004.html @web-platform-tests/interop +css/css-flexbox/image-as-flexitem-size-004v.html @web-platform-tests/interop +css/css-flexbox/image-as-flexitem-size-005.html @web-platform-tests/interop +css/css-flexbox/image-as-flexitem-size-005v.html @web-platform-tests/interop +css/css-flexbox/image-as-flexitem-size-006.html @web-platform-tests/interop +css/css-flexbox/image-as-flexitem-size-006v.html @web-platform-tests/interop +css/css-flexbox/image-as-flexitem-size-007.html @web-platform-tests/interop +css/css-flexbox/image-as-flexitem-size-007v.html @web-platform-tests/interop +css/css-flexbox/inheritance.html @web-platform-tests/interop +css/css-flexbox/inline-flex.html @web-platform-tests/interop +css/css-flexbox/intrinsic-width-orthogonal-writing-mode.html @web-platform-tests/interop +css/css-flexbox/justify-content-006.html @web-platform-tests/interop +css/css-flexbox/justify-content_space-between-002.html @web-platform-tests/interop +css/css-flexbox/layout-with-inline-svg-001.html @web-platform-tests/interop +css/css-flexbox/max-width-violation.html @web-platform-tests/interop +css/css-flexbox/multiline-min-max.html @web-platform-tests/interop +css/css-flexbox/multiline-min-preferred-width.html @web-platform-tests/interop +css/css-flexbox/negative-overflow-002.html @web-platform-tests/interop +css/css-flexbox/negative-overflow-003.html @web-platform-tests/interop +css/css-flexbox/negative-overflow.html @web-platform-tests/interop +css/css-flexbox/order_value.html @web-platform-tests/interop +css/css-flexbox/orthogonal-writing-modes-and-intrinsic-sizing.html @web-platform-tests/interop +css/css-flexbox/overflow-auto-002.html @web-platform-tests/interop +css/css-flexbox/overflow-auto-003.html @web-platform-tests/interop +css/css-flexbox/overflow-auto-004.html @web-platform-tests/interop +css/css-flexbox/overflow-auto-006.html @web-platform-tests/interop +css/css-flexbox/overflow-auto-008.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-basis-computed.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-basis-invalid.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-basis-valid.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-computed.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-direction-computed.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-direction-invalid.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-direction-valid.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-flow-computed.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-flow-invalid.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-flow-shorthand.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-flow-valid.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-grow-computed.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-grow-invalid.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-grow-valid.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-invalid.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-shorthand.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-shrink-computed.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-shrink-invalid.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-shrink-valid.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-valid.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-wrap-computed.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-wrap-invalid.html @web-platform-tests/interop +css/css-flexbox/parsing/flex-wrap-valid.html @web-platform-tests/interop +css/css-flexbox/parsing/order-computed.html @web-platform-tests/interop +css/css-flexbox/parsing/order-invalid.html @web-platform-tests/interop +css/css-flexbox/parsing/order-valid.html @web-platform-tests/interop +css/css-flexbox/percentage-heights-000.html @web-platform-tests/interop +css/css-flexbox/percentage-heights-001.html @web-platform-tests/interop +css/css-flexbox/percentage-heights-003.html @web-platform-tests/interop +css/css-flexbox/percentage-heights-011.html @web-platform-tests/interop +css/css-flexbox/percentage-heights-012.html @web-platform-tests/interop +css/css-flexbox/percentage-heights-013.html @web-platform-tests/interop +css/css-flexbox/percentage-margins-001.html @web-platform-tests/interop +css/css-flexbox/percentage-max-width-cross-axis.html @web-platform-tests/interop +css/css-flexbox/percentage-padding-001.html @web-platform-tests/interop +css/css-flexbox/percentage-size-quirks-002.html @web-platform-tests/interop +css/css-flexbox/percentage-size-quirks.html @web-platform-tests/interop +css/css-flexbox/percentage-size.html @web-platform-tests/interop +css/css-flexbox/position-relative-percentage-top-001.html @web-platform-tests/interop +css/css-flexbox/quirks-auto-block-size-with-percentage-item.html @web-platform-tests/interop +css/css-flexbox/radiobutton-min-size.html @web-platform-tests/interop +css/css-flexbox/relayout-align-items.html @web-platform-tests/interop +css/css-flexbox/relayout-image-load.html @web-platform-tests/interop +css/css-flexbox/relayout-input.html @web-platform-tests/interop +css/css-flexbox/scrollbars-auto-min-content-sizing.html @web-platform-tests/interop +css/css-flexbox/shrinking-column-flexbox.html @web-platform-tests/interop +css/css-flexbox/stretch-after-sibling-size-change.html @web-platform-tests/interop +css/css-flexbox/stretched-child-shrink-on-relayout.html @web-platform-tests/interop +css/css-flexbox/svg-root-as-flex-item-006.html @web-platform-tests/interop +css/css-flexbox/table-as-item-cross-size.html @web-platform-tests/interop +css/css-flexbox/table-with-percent-intrinsic-width.html @web-platform-tests/interop +css/css-flexbox/text-as-flexitem-size-001.html @web-platform-tests/interop +css/css-grid/abspos/absolute-positioning-definite-sizes-001.html @web-platform-tests/interop +css/css-grid/abspos/absolute-positioning-grid-container-containing-block-001.html @web-platform-tests/interop +css/css-grid/abspos/absolute-positioning-grid-container-parent-001.html @web-platform-tests/interop +css/css-grid/abspos/empty-grid-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-002.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-003.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-004.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-005.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-006.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-007.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-items-content-alignment-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-items-content-alignment-rtl-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-items-gaps-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-items-gaps-002-rtl.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-items-gaps-002.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-items-gaps-rtl-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-items-implicit-grid-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-items-implicit-grid-line-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-items-padding-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-items-unknown-named-grid-line-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-positioned-items-within-grid-implicit-track-001.html @web-platform-tests/interop +css/css-grid/abspos/grid-sizing-positioned-items-001.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-descendants-001.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-descendants-002.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-descendants-003.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-descendants-004.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-descendants-005.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-descendants-006.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-descendants-007.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-descendants-008.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-descendants-009.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-descendants-010.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-descendants-011.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-descendants-012.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-descendants-013.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-descendants-014.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-descendants-015.html @web-platform-tests/interop +css/css-grid/abspos/orthogonal-positioned-grid-descendants-016.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-descendants-001.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-descendants-002.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-descendants-003.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-descendants-004.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-descendants-005.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-descendants-006.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-descendants-007.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-descendants-008.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-descendants-009.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-descendants-010.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-descendants-011.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-descendants-012.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-descendants-013.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-descendants-014.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-descendants-015.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-descendants-016.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-should-not-create-implicit-tracks-001.html @web-platform-tests/interop +css/css-grid/abspos/positioned-grid-items-should-not-take-up-space-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-004.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-fieldset-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-fieldset-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-fieldset-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-flex-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-flex-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-flex-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-flex-004.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-grid-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-grid-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-grid-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-multicol-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-multicol-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-multicol-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-overflow-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-overflow-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-overflow-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-table-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-table-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline-vertical.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-baseline.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-content-distribution-vertical-lr.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-content-distribution-vertical-rl.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-content-distribution.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-content-vertical-lr.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-content-vertical-rl.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-content.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-justify-margin-border-padding-vertical-lr.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-justify-margin-border-padding-vertical-rl.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-justify-margin-border-padding.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-justify-overflow.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-justify-stretch-with-orthogonal-flows.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-justify-stretch.html @web-platform-tests/interop +css/css-grid/alignment/grid-align-stretching-replaced-items.html @web-platform-tests/interop +css/css-grid/alignment/grid-align.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-004.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-005.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-006.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-007.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-008.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-009.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-010.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-011.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-012.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-013.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-014.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-015.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-016.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-017.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-018.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-019.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-020.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-021.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-022.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-023.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-024.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-025.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-026.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-027.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-028.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-029.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-030.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-031.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-032.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-033.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-034.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-035.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-implies-size-change-036.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-style-changes-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-style-changes-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-style-changes-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-style-changes-004.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-style-changes-005.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-style-changes-006.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-style-changes-007.html @web-platform-tests/interop +css/css-grid/alignment/grid-alignment-style-changes-008.html @web-platform-tests/interop +css/css-grid/alignment/grid-baseline-004.html @web-platform-tests/interop +css/css-grid/alignment/grid-block-axis-alignment-auto-margins-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-block-axis-alignment-auto-margins-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-block-axis-alignment-auto-margins-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-block-axis-alignment-auto-margins-004.html @web-platform-tests/interop +css/css-grid/alignment/grid-block-axis-alignment-auto-margins-005.html @web-platform-tests/interop +css/css-grid/alignment/grid-block-axis-alignment-auto-margins-006.html @web-platform-tests/interop +css/css-grid/alignment/grid-block-axis-alignment-auto-margins-007.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-alignment-positioned-items-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-alignment-positioned-items-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-alignment-positioned-items-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-alignment-positioned-items-004.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-alignment-positioned-items-005.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-alignment-positioned-items-006.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-alignment-positioned-items-007.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-alignment-positioned-items-008.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-alignment-positioned-items-009.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-alignment-positioned-items-010.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-alignment-positioned-items-011.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-alignment-positioned-items-012.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-alignment-positioned-items-013.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-alignment-positioned-items-014.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-alignment-positioned-items-015.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-alignment-positioned-items-016.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-alignment-positioned-items-017.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-alignment-sticky-positioned-items-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-alignment-sticky-positioned-items-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-self-baseline-synthesized-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-self-baseline-synthesized-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-self-baseline-synthesized-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-self-baseline-synthesized-004.html @web-platform-tests/interop +css/css-grid/alignment/grid-column-axis-self-baseline-synthesized-005.html @web-platform-tests/interop +css/css-grid/alignment/grid-container-baseline-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-alignment-and-self-alignment-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-alignment-and-self-alignment-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-alignment-auto-sized-tracks-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-alignment-overflow-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-alignment-overflow-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-alignment-second-pass-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-alignment-second-pass-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-alignment-with-abspos-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-alignment-with-span-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-alignment-with-span-vertical-lr-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-content-alignment-with-span-vertical-rl-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-fit-content-tracks-dont-stretch-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-gutters-and-alignment.html @web-platform-tests/interop +css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-004.html @web-platform-tests/interop +css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-005.html @web-platform-tests/interop +css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-006.html @web-platform-tests/interop +css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-007.html @web-platform-tests/interop +css/css-grid/alignment/grid-item-alignment-with-orthogonal-flows-vertical-lr.html @web-platform-tests/interop +css/css-grid/alignment/grid-item-alignment-with-orthogonal-flows-vertical-rl.html @web-platform-tests/interop +css/css-grid/alignment/grid-item-alignment-with-orthogonal-flows.html @web-platform-tests/interop +css/css-grid/alignment/grid-item-auto-margins-alignment-vertical-lr.html @web-platform-tests/interop +css/css-grid/alignment/grid-item-auto-margins-alignment-vertical-rl.html @web-platform-tests/interop +css/css-grid/alignment/grid-item-auto-margins-alignment.html @web-platform-tests/interop +css/css-grid/alignment/grid-justify-baseline-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-justify-baseline-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-justify-baseline-004.html @web-platform-tests/interop +css/css-grid/alignment/grid-justify-baseline-005.html @web-platform-tests/interop +css/css-grid/alignment/grid-place-content-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-alignment-positioned-items-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-alignment-positioned-items-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-alignment-positioned-items-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-alignment-positioned-items-004.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-alignment-positioned-items-005.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-alignment-positioned-items-006.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-alignment-positioned-items-007.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-alignment-positioned-items-008.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-alignment-positioned-items-009.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-alignment-positioned-items-010.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-alignment-positioned-items-011.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-alignment-positioned-items-012.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-alignment-positioned-items-013.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-alignment-positioned-items-014.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-alignment-positioned-items-015.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-alignment-positioned-items-016.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-alignment-positioned-items-017.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-alignment-sticky-positioned-items-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-alignment-sticky-positioned-items-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-self-baseline-synthesized-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-self-baseline-synthesized-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-self-baseline-synthesized-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-self-baseline-synthesized-004.html @web-platform-tests/interop +css/css-grid/alignment/grid-row-axis-self-baseline-synthesized-005.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-non-static-positioned-items-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-non-static-positioned-items-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-non-static-positioned-items-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-non-static-positioned-items-004.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-non-static-positioned-items-005.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-non-static-positioned-items-006.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-non-static-positioned-items-007.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-non-static-positioned-items-008.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-non-static-positioned-items-009.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-non-static-positioned-items-010.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-non-static-positioned-items-011.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-non-static-positioned-items-012.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-positioned-items-with-margin-border-padding-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-positioned-items-with-margin-border-padding-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-positioned-items-with-margin-border-padding-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-positioned-items-with-margin-border-padding-004.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-positioned-items-with-margin-border-padding-005.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-positioned-items-with-margin-border-padding-006.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-positioned-items-with-margin-border-padding-007.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-positioned-items-with-margin-border-padding-008.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-positioned-items-with-margin-border-padding-009.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-positioned-items-with-margin-border-padding-010.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-positioned-items-with-margin-border-padding-011.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-positioned-items-with-margin-border-padding-012.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-positioned-items-with-margin-border-padding-013.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-positioned-items-with-margin-border-padding-014.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-positioned-items-with-margin-border-padding-015.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-positioned-items-with-margin-border-padding-016.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-004.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-005.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-006.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-007.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-008.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-009.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-010.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-011.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-012.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-013.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-014.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-015.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-016.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-lr-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-lr-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-lr-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-lr-004.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-lr-005.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-lr-006.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-lr-007.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-lr-008.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-lr-009.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-lr-010.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-lr-011.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-lr-012.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-lr-013.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-lr-014.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-lr-015.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-lr-016.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-rl-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-rl-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-rl-003.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-rl-004.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-rl-005.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-rl-006.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-rl-007.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-rl-008.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-rl-009.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-rl-010.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-rl-011.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-rl-012.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-rl-013.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-rl-014.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-rl-015.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment-stretch-vertical-rl-016.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-alignment.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-baseline-not-applied-if-sizing-cyclic-dependency-001.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-baseline-not-applied-if-sizing-cyclic-dependency-002.html @web-platform-tests/interop +css/css-grid/alignment/grid-self-baseline-not-applied-if-sizing-cyclic-dependency-003.html @web-platform-tests/interop +css/css-grid/animation/grid-template-columns-composition.html @web-platform-tests/interop +css/css-grid/animation/grid-template-columns-interpolation.html @web-platform-tests/interop +css/css-grid/animation/grid-template-columns-neutral-keyframe-001.html @web-platform-tests/interop +css/css-grid/animation/grid-template-columns-neutral-keyframe-002.html @web-platform-tests/interop +css/css-grid/animation/grid-template-columns-neutral-keyframe-003.html @web-platform-tests/interop +css/css-grid/animation/grid-template-columns-neutral-keyframe-004.html @web-platform-tests/interop +css/css-grid/animation/grid-template-rows-composition.html @web-platform-tests/interop +css/css-grid/animation/grid-template-rows-interpolation.html @web-platform-tests/interop +css/css-grid/animation/grid-template-rows-neutral-keyframe-001.html @web-platform-tests/interop +css/css-grid/animation/grid-template-rows-neutral-keyframe-002.html @web-platform-tests/interop +css/css-grid/animation/grid-template-rows-neutral-keyframe-003.html @web-platform-tests/interop +css/css-grid/animation/grid-template-rows-neutral-keyframe-004.html @web-platform-tests/interop +css/css-grid/chrome-crash-001.html @web-platform-tests/interop +css/css-grid/grid-definition/explicit-grid-size-001.html @web-platform-tests/interop +css/css-grid/grid-definition/flex-content-distribution-001.html @web-platform-tests/interop +css/css-grid/grid-definition/flex-content-resolution-columns-001.html @web-platform-tests/interop +css/css-grid/grid-definition/flex-content-resolution-columns-002.html @web-platform-tests/interop +css/css-grid/grid-definition/flex-content-resolution-rows-001.html @web-platform-tests/interop +css/css-grid/grid-definition/flex-content-resolution-rows-002.html @web-platform-tests/interop +css/css-grid/grid-definition/flex-factor-sum-less-than-1-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-explicit-rows-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-fill-columns-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-fill-rows-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-fit-columns-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-fit-rows-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-repeat-intrinsic-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-repeat-max-size-001.tentative.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-repeat-max-size-002.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-repeat-min-max-size-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-repeat-min-size-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-repeat-min-size-002.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-repeat-min-size-003.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-auto-repeat-min-size-004.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-change-auto-repeat-tracks.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-change-fit-content-argument-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-change-intrinsic-size-with-auto-repeat-tracks-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-inline-auto-repeat-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-inline-support-flexible-lengths-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-inline-support-grid-template-areas-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-inline-support-grid-template-columns-rows-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-inline-support-named-grid-lines-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-inline-support-repeat-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-inline-template-columns-rows-resolved-values-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-limits-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-minimum-contribution-with-percentages.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-percentage-rows-indefinite-height-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-percentage-rows-indefinite-height-002.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-shorthand-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-support-flexible-lengths-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-support-grid-template-areas-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-support-grid-template-columns-rows-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-support-named-grid-lines-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-support-repeat-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-support-repeat-002.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-template-columns-rows-changes-001.html @web-platform-tests/interop +css/css-grid/grid-definition/grid-template-columns-rows-resolved-values-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-automatic-minimum-intrinsic-aspect-ratio-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-item-dynamic-min-contribution-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-item-flex-container-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-item-min-auto-size-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-minimum-height-orthogonal-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-minimum-width-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-minimum-width-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-minimum-width-orthogonal-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-minimum-width-orthogonal-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-minimum-width-vertical-lr-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-minimum-width-vertical-lr-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-minimum-width-vertical-rl-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-minimum-width-vertical-rl-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-margins-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-margins-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-margins-vertical-lr-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-margins-vertical-lr-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-margins-vertical-rl-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-margins-vertical-rl-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-paddings-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-paddings-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-paddings-015.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-paddings-vertical-lr-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-paddings-vertical-lr-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-paddings-vertical-rl-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-percentage-paddings-vertical-rl-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-relative-offsets-001.html @web-platform-tests/interop +css/css-grid/grid-items/grid-items-relative-offsets-002.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-021.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-022.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-023.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-024.html @web-platform-tests/interop +css/css-grid/grid-items/grid-minimum-size-grid-items-025.html @web-platform-tests/interop +css/css-grid/grid-layout-properties.html @web-platform-tests/interop +css/css-grid/grid-model/compute-intrinsic-widths-scrollbar-001.html @web-platform-tests/interop +css/css-grid/grid-model/fixed-width-intrinsic-width-should-exclude-scrollbar-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-box-sizing-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-button-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-computed-value-display-floated-items-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-container-ignores-first-letter-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-container-ignores-first-line-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-container-margin-border-padding-scrollbar-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-container-sizing-constraints-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-float-002.html @web-platform-tests/interop +css/css-grid/grid-model/grid-gutters-and-flex-content-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-gutters-and-tracks-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-gutters-as-percentage-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-item-accepts-first-letter-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-item-accepts-first-line-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-item-hit-test.html @web-platform-tests/interop +css/css-grid/grid-model/grid-min-max-height-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-size-shrink-to-fit-001.html @web-platform-tests/interop +css/css-grid/grid-model/grid-support-display-001.html @web-platform-tests/interop +css/css-grid/grid-tracks-stretched-with-different-flex-factors-sum.html @web-platform-tests/interop +css/css-grid/inheritance.html @web-platform-tests/interop +css/css-grid/layout-algorithm/baseline-alignment-affects-intrinsic-size-001.html @web-platform-tests/interop +css/css-grid/layout-algorithm/baseline-alignment-affects-intrinsic-size-002.html @web-platform-tests/interop +css/css-grid/layout-algorithm/baseline-alignment-affects-intrinsic-size-003.html @web-platform-tests/interop +css/css-grid/layout-algorithm/baseline-alignment-affects-intrinsic-size-004.html @web-platform-tests/interop +css/css-grid/layout-algorithm/baseline-alignment-affects-intrinsic-size-005.html @web-platform-tests/interop +css/css-grid/layout-algorithm/baseline-alignment-affects-intrinsic-size-006.html @web-platform-tests/interop +css/css-grid/layout-algorithm/flex-and-intrinsic-sizes-001.html @web-platform-tests/interop +css/css-grid/layout-algorithm/flex-sizing-columns-min-max-width-001.html @web-platform-tests/interop +css/css-grid/layout-algorithm/flex-sizing-rows-min-max-height-001.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-automatic-minimum-for-auto-columns-001.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-container-percentage-001.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-container-percentage-002.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-content-distribution-must-account-for-track-sizing-001.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-content-distribution-must-account-for-track-sizing-002.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-content-distribution-must-account-for-track-sizing-003.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-content-distribution-must-account-for-track-sizing-004.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-find-fr-size-gutters-001.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-find-fr-size-gutters-002.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-fit-content-percentage.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-flex-track-intrinsic-sizes-001.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-flex-track-intrinsic-sizes-002.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-flex-track-intrinsic-sizes-003.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-intrinsic-size-with-orthogonal-items.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-intrinsic-track-sizes-001.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-item-margin-auto-columns-rows-001.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-item-margin-auto-columns-rows-vertical-lr-001.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-item-margin-auto-columns-rows-vertical-rl-001.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-minimum-contribution-baseline-shim-vertical-lr.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-minimum-contribution-baseline-shim-vertical-rl.html @web-platform-tests/interop +css/css-grid/layout-algorithm/grid-minimum-contribution-baseline-shim.html @web-platform-tests/interop +css/css-grid/parsing/grid-area-computed.html @web-platform-tests/interop +css/css-grid/parsing/grid-area-invalid.html @web-platform-tests/interop +css/css-grid/parsing/grid-area-shorthand.html @web-platform-tests/interop +css/css-grid/parsing/grid-area-valid.html @web-platform-tests/interop +css/css-grid/parsing/grid-auto-columns-computed.html @web-platform-tests/interop +css/css-grid/parsing/grid-auto-columns-invalid.html @web-platform-tests/interop +css/css-grid/parsing/grid-auto-columns-valid.html @web-platform-tests/interop +css/css-grid/parsing/grid-auto-flow-computed.html @web-platform-tests/interop +css/css-grid/parsing/grid-auto-flow-invalid.html @web-platform-tests/interop +css/css-grid/parsing/grid-auto-flow-valid.html @web-platform-tests/interop +css/css-grid/parsing/grid-auto-rows-computed.html @web-platform-tests/interop +css/css-grid/parsing/grid-auto-rows-invalid.html @web-platform-tests/interop +css/css-grid/parsing/grid-auto-rows-valid.html @web-platform-tests/interop +css/css-grid/parsing/grid-columns-rows-get-set-multiple.html @web-platform-tests/interop +css/css-grid/parsing/grid-content-sized-columns-resolution.html @web-platform-tests/interop +css/css-grid/parsing/grid-shorthand-invalid.html @web-platform-tests/interop +css/css-grid/parsing/grid-shorthand-serialization.html @web-platform-tests/interop +css/css-grid/parsing/grid-shorthand-valid.html @web-platform-tests/interop +css/css-grid/parsing/grid-shorthand.html @web-platform-tests/interop +css/css-grid/parsing/grid-template-areas-computed.html @web-platform-tests/interop +css/css-grid/parsing/grid-template-areas-invalid.html @web-platform-tests/interop +css/css-grid/parsing/grid-template-areas-one-cell.html @web-platform-tests/interop +css/css-grid/parsing/grid-template-areas-valid.html @web-platform-tests/interop +css/css-grid/parsing/grid-template-columns-computed-implicit-track.html @web-platform-tests/interop +css/css-grid/parsing/grid-template-columns-computed-nogrid.html @web-platform-tests/interop +css/css-grid/parsing/grid-template-columns-computed-withcontent.html @web-platform-tests/interop +css/css-grid/parsing/grid-template-columns-computed.html @web-platform-tests/interop +css/css-grid/parsing/grid-template-columns-invalid.html @web-platform-tests/interop +css/css-grid/parsing/grid-template-columns-valid.html @web-platform-tests/interop +css/css-grid/parsing/grid-template-repeat-auto-computed-withcontent-001.html @web-platform-tests/interop +css/css-grid/parsing/grid-template-repeat-auto-computed-withcontent-002.html @web-platform-tests/interop +css/css-grid/parsing/grid-template-rows-computed-implicit-track.html @web-platform-tests/interop +css/css-grid/parsing/grid-template-rows-computed-nogrid.html @web-platform-tests/interop +css/css-grid/parsing/grid-template-rows-computed-withcontent.html @web-platform-tests/interop +css/css-grid/parsing/grid-template-rows-computed.html @web-platform-tests/interop +css/css-grid/parsing/grid-template-rows-invalid.html @web-platform-tests/interop +css/css-grid/parsing/grid-template-rows-valid.html @web-platform-tests/interop +css/css-grid/parsing/grid-template-shorthand-areas-valid.html @web-platform-tests/interop +css/css-grid/parsing/grid-template-shorthand-invalid.html @web-platform-tests/interop +css/css-grid/parsing/grid-template-shorthand-valid.html @web-platform-tests/interop +css/css-grid/parsing/grid-template-shorthand.html @web-platform-tests/interop +css/css-grid/placement/grid-auto-flow-sparse-001.html @web-platform-tests/interop +css/css-grid/placement/grid-auto-placement-implicit-tracks-001.html @web-platform-tests/interop +css/css-grid/placement/grid-container-change-grid-tracks-recompute-child-positions-001.html @web-platform-tests/interop +css/css-grid/placement/grid-container-change-named-grid-recompute-child-positions-001.html @web-platform-tests/interop +css/css-grid/subgrid/grid-template-computed-nogrid.html @web-platform-tests/interop +css/css-grid/subgrid/grid-template-invalid.html @web-platform-tests/interop +css/css-grid/subgrid/grid-template-valid.html @web-platform-tests/interop +css/css-grid/subgrid/subgrid-baseline-005.html @web-platform-tests/interop +css/css-grid/subgrid/subgrid-baseline-006.html @web-platform-tests/interop +css/css-grid/subgrid/subgrid-baseline-007.html @web-platform-tests/interop +css/css-grid/subgrid/subgrid-baseline-008.html @web-platform-tests/interop +css/css-grid/subgrid/subgrid-baseline-009.html @web-platform-tests/interop +css/css-grid/table-grid-item-005.html @web-platform-tests/interop +css/css-logical/logical-box-border-color.html @web-platform-tests/interop +css/css-logical/logical-box-border-radius.html @web-platform-tests/interop +css/css-logical/logical-box-border-shorthands.html @web-platform-tests/interop +css/css-logical/logical-box-border-style.html @web-platform-tests/interop +css/css-logical/logical-box-border-width.html @web-platform-tests/interop +css/css-logical/logical-box-inset.html @web-platform-tests/interop +css/css-logical/logical-box-margin.html @web-platform-tests/interop +css/css-logical/logical-box-padding.html @web-platform-tests/interop +css/css-logical/logical-box-size.html @web-platform-tests/interop +css/css-multicol/getclientrects-001.html @web-platform-tests/interop +css/css-overflow/logical-overflow-001.html @web-platform-tests/interop +css/css-overflow/overflow-shorthand-002.html @web-platform-tests/interop +css/css-overflow/parsing/overflow-computed.html @web-platform-tests/interop +css/css-overflow/parsing/overflow-invalid.html @web-platform-tests/interop +css/css-overflow/parsing/overflow-valid.html @web-platform-tests/interop +css/css-overscroll-behavior/overscroll-behavior-root.html @web-platform-tests/interop +css/css-text-decor/parsing/text-decoration-color-computed.html @web-platform-tests/interop +css/css-text-decor/parsing/text-decoration-color-invalid.html @web-platform-tests/interop +css/css-text-decor/parsing/text-decoration-color-valid.html @web-platform-tests/interop +css/css-text-decor/parsing/text-decoration-computed.html @web-platform-tests/interop +css/css-text-decor/parsing/text-decoration-invalid.html @web-platform-tests/interop +css/css-text-decor/parsing/text-decoration-line-computed.html @web-platform-tests/interop +css/css-text-decor/parsing/text-decoration-line-invalid.html @web-platform-tests/interop +css/css-text-decor/parsing/text-decoration-line-valid.html @web-platform-tests/interop +css/css-text-decor/parsing/text-decoration-shorthand.html @web-platform-tests/interop +css/css-text-decor/parsing/text-decoration-skip-ink-computed.html @web-platform-tests/interop +css/css-text-decor/parsing/text-decoration-skip-ink-invalid.html @web-platform-tests/interop +css/css-text-decor/parsing/text-decoration-skip-ink-valid.html @web-platform-tests/interop +css/css-text-decor/parsing/text-decoration-style-computed.html @web-platform-tests/interop +css/css-text-decor/parsing/text-decoration-style-invalid.html @web-platform-tests/interop +css/css-text-decor/parsing/text-decoration-style-valid.html @web-platform-tests/interop +css/css-text-decor/parsing/text-decoration-valid.html @web-platform-tests/interop +css/css-view-transitions/auto-name-get-animations.html @web-platform-tests/interop +css/css-view-transitions/duplicate-tag-rejects-capture.html @web-platform-tests/interop +css/css-view-transitions/duplicate-tag-rejects-start.html @web-platform-tests/interop +css/css-view-transitions/dynamic-stylesheet-animations.html @web-platform-tests/interop +css/css-view-transitions/event-pseudo-name.html @web-platform-tests/interop +css/css-view-transitions/group-animation-for-root-transition.html @web-platform-tests/interop +css/css-view-transitions/hit-test-pseudo-element-element-from-point.html @web-platform-tests/interop +css/css-view-transitions/hit-test-unpainted-element-from-point.html @web-platform-tests/interop +css/css-view-transitions/input-targets-root-while-render-blocked.html @web-platform-tests/interop +css/css-view-transitions/mix-blend-mode-only-on-transition.html @web-platform-tests/interop +css/css-view-transitions/no-crash-set-exception.html @web-platform-tests/interop +css/css-view-transitions/no-crash-view-transition-in-massive-iframe.html @web-platform-tests/interop +css/css-view-transitions/no-css-animation-while-render-blocked.html @web-platform-tests/interop +css/css-view-transitions/no-raf-while-render-blocked.html @web-platform-tests/interop +css/css-view-transitions/only-child-group.html @web-platform-tests/interop +css/css-view-transitions/only-child-image-pair.html @web-platform-tests/interop +css/css-view-transitions/only-child-new.html @web-platform-tests/interop +css/css-view-transitions/only-child-no-transition.html @web-platform-tests/interop +css/css-view-transitions/only-child-old.html @web-platform-tests/interop +css/css-view-transitions/only-child-on-root-element-with-view-transition.html @web-platform-tests/interop +css/css-view-transitions/only-child-view-transition.html @web-platform-tests/interop +css/css-view-transitions/parsing/pseudo-elements-invalid-with-classes.html @web-platform-tests/interop +css/css-view-transitions/parsing/pseudo-elements-invalid.html @web-platform-tests/interop +css/css-view-transitions/parsing/pseudo-elements-valid-with-classes.html @web-platform-tests/interop +css/css-view-transitions/parsing/pseudo-elements-valid.html @web-platform-tests/interop +css/css-view-transitions/parsing/view-transition-class-computed.html @web-platform-tests/interop +css/css-view-transitions/parsing/view-transition-class-invalid.html @web-platform-tests/interop +css/css-view-transitions/parsing/view-transition-class-valid.html @web-platform-tests/interop +css/css-view-transitions/parsing/view-transition-name-computed.html @web-platform-tests/interop +css/css-view-transitions/parsing/view-transition-name-invalid.html @web-platform-tests/interop +css/css-view-transitions/parsing/view-transition-name-valid.html @web-platform-tests/interop +css/css-view-transitions/paused-animation-at-end.html @web-platform-tests/interop +css/css-view-transitions/pseudo-computed-style-stays-in-sync-with-new-element.html @web-platform-tests/interop +css/css-view-transitions/pseudo-element-animations-rerun.html @web-platform-tests/interop +css/css-view-transitions/pseudo-element-animations.html @web-platform-tests/interop +css/css-view-transitions/pseudo-get-computed-style.html @web-platform-tests/interop +css/css-view-transitions/ready_resolves_after_dom_before_raf.html @web-platform-tests/interop +css/css-view-transitions/style-inheritance.html @web-platform-tests/interop +css/css-view-transitions/synchronous-callback-skipped-before-run.html @web-platform-tests/interop +css/css-view-transitions/transition-in-hidden-page.html @web-platform-tests/interop +css/css-view-transitions/transition-skipped-after-animation-started.html @web-platform-tests/interop +css/css-view-transitions/transition-skipped-from-invalid-callback.html @web-platform-tests/interop +css/css-view-transitions/unset-and-initial-view-transition-name.html @web-platform-tests/interop +css/css-view-transitions/update-callback-timeout.html @web-platform-tests/interop +css/css-view-transitions/view-transition-name-on-removed-element.html @web-platform-tests/interop +css/css-view-transitions/web-animation-pseudo-incorrect-name.html @web-platform-tests/interop +css/css-view-transitions/window-resize-aborts-transition-before-ready.html @web-platform-tests/interop +css/css-view-transitions/window-resize-aborts-transition.html @web-platform-tests/interop +css/css-viewport/zoom/parsing/zoom-computed.html @web-platform-tests/interop +css/css-viewport/zoom/parsing/zoom-valid.html @web-platform-tests/interop +css/css-viewport/zoom/relative-units.html @web-platform-tests/interop +css/css-viewport/zoom/scroll-top-test-with-zoom.html @web-platform-tests/interop +css/css-viewport/zoom/widget.html @web-platform-tests/interop +css/css-writing-modes/forms/select-multiple-keyboard-selection.optional.html @web-platform-tests/interop +css/css-writing-modes/forms/select-multiple-options-visual-order.html @web-platform-tests/interop +css/css-writing-modes/forms/select-multiple-scrolling.optional.html @web-platform-tests/interop +css/css-writing-modes/forms/select-size-scrolling-and-sizing.optional.html @web-platform-tests/interop +css/css-writing-modes/forms/text-input-block-size.optional.html @web-platform-tests/interop +css/css-writing-modes/forms/text-input-vertical-overflow-no-scroll.html @web-platform-tests/interop +css/css-writing-modes/forms/textarea-rows-cols-sizing.html @web-platform-tests/interop +css/css-writing-modes/writing-mode-parsing-sideways-lr-001.html @web-platform-tests/interop +css/css-writing-modes/writing-mode-parsing-sideways-rl-001.html @web-platform-tests/interop +css/cssom/caretPositionFromPoint-with-transformation.html @web-platform-tests/interop +css/cssom/caretPositionFromPoint.html @web-platform-tests/interop +css/cssom-view/scrollIntoView-sideways-lr-writing-mode-and-rtl-direction.html @web-platform-tests/interop +css/cssom-view/scrollIntoView-sideways-lr-writing-mode.html @web-platform-tests/interop +css/cssom-view/scrollIntoView-sideways-rl-writing-mode-and-rtl-direction.html @web-platform-tests/interop +css/cssom-view/scrollIntoView-sideways-rl-writing-mode.html @web-platform-tests/interop +css/filter-effects/animation/backdrop-filter-interpolation-001.html @web-platform-tests/interop +css/filter-effects/animation/backdrop-filter-interpolation-002.html @web-platform-tests/interop +css/filter-effects/animation/backdrop-filter-interpolation-003.html @web-platform-tests/interop +css/filter-effects/animation/backdrop-filter-interpolation-004.html @web-platform-tests/interop +css/filter-effects/backdrop-filter-important.html @web-platform-tests/interop +css/filter-effects/parsing/backdrop-filter-computed.html @web-platform-tests/interop +css/filter-effects/parsing/backdrop-filter-parsing-invalid.html @web-platform-tests/interop +css/filter-effects/parsing/backdrop-filter-parsing-valid.html @web-platform-tests/interop +dom/events/scrolling/scrollend-event-fired-for-mandatory-snap-point-after-load.html @web-platform-tests/interop +dom/events/scrolling/scrollend-event-fired-for-programmatic-scroll.html @web-platform-tests/interop +dom/events/scrolling/scrollend-event-fired-for-scroll-attr-change.html @web-platform-tests/interop +dom/events/scrolling/scrollend-event-fired-for-scrollIntoView.html @web-platform-tests/interop +dom/events/scrolling/scrollend-event-fired-to-document.html @web-platform-tests/interop +dom/events/scrolling/scrollend-event-fired-to-window.html @web-platform-tests/interop +dom/events/scrolling/scrollend-event-handler-content-attributes.html @web-platform-tests/interop +dom/events/scrolling/scrollend-event-not-fired-on-no-scroll.html @web-platform-tests/interop +dom/events/scrolling/scrollend-fires-to-text-input.html @web-platform-tests/interop +dom/events/scrolling/scrollend-with-snap-on-fractional-offset.html @web-platform-tests/interop +dom/historical-mutation-events.html @web-platform-tests/interop +event-timing/auxclick.html @web-platform-tests/interop +event-timing/buffered-and-duration-threshold.html @web-platform-tests/interop +event-timing/buffered-flag.html @web-platform-tests/interop +event-timing/click-timing.html @web-platform-tests/interop +event-timing/click.html @web-platform-tests/interop +event-timing/contextmenu.html @web-platform-tests/interop +event-timing/crossiframe.html @web-platform-tests/interop +event-timing/dblclick.html @web-platform-tests/interop +event-timing/disconnect-target.html @web-platform-tests/interop +event-timing/duration-with-target-low.html @web-platform-tests/interop +event-timing/event-click-counts.html @web-platform-tests/interop +event-timing/event-click-visibilitychange.html @web-platform-tests/interop +event-timing/event-counts-zero.html @web-platform-tests/interop +event-timing/event-retarget.html @web-platform-tests/interop +event-timing/first-input-interactionid-click.html @web-platform-tests/interop +event-timing/first-input-interactionid-key.html @web-platform-tests/interop +event-timing/first-input-interactionid-tap.html @web-platform-tests/interop +event-timing/first-input-shadow-dom.html @web-platform-tests/interop +event-timing/idlharness.any.js @web-platform-tests/interop +event-timing/interaction-count-click.html @web-platform-tests/interop +event-timing/interaction-count-press-key.html @web-platform-tests/interop +event-timing/interaction-count-tap.html @web-platform-tests/interop +event-timing/interactionid-aux-pointerdown-and-pointerdown.html @web-platform-tests/interop +event-timing/interactionid-aux-pointerdown.html @web-platform-tests/interop +event-timing/interactionid-auxclick.html @web-platform-tests/interop +event-timing/interactionid-click.html @web-platform-tests/interop +event-timing/interactionid-keyboard-event-simulated-click-button-space.html @web-platform-tests/interop +event-timing/interactionid-keyboard-event-simulated-click-checkbox-space.html @web-platform-tests/interop +event-timing/interactionid-keyboard-event-simulated-click-link-enter.html @web-platform-tests/interop +event-timing/interactionid-keypress.html @web-platform-tests/interop +event-timing/interactionid-orphan-pointerup.html @web-platform-tests/interop +event-timing/interactionid-press-key-as-input.html @web-platform-tests/interop +event-timing/interactionid-press-key-no-effect.html @web-platform-tests/interop +event-timing/interactionid-tap.html @web-platform-tests/interop +event-timing/keydown.html @web-platform-tests/interop +event-timing/keyup.html @web-platform-tests/interop +event-timing/large-duration-threshold.html @web-platform-tests/interop +event-timing/medium-duration-threshold.html @web-platform-tests/interop +event-timing/min-duration-threshold.html @web-platform-tests/interop +event-timing/modal-dialog-interrupt-paint.html @web-platform-tests/interop +event-timing/mousedown.html @web-platform-tests/interop +event-timing/mouseenter.html @web-platform-tests/interop +event-timing/mouseleave.html @web-platform-tests/interop +event-timing/mouseout.html @web-platform-tests/interop +event-timing/mouseover.html @web-platform-tests/interop +event-timing/mouseup.html @web-platform-tests/interop +event-timing/only-observe-firstInput.html @web-platform-tests/interop +event-timing/pointerdown.html @web-platform-tests/interop +event-timing/pointerenter.html @web-platform-tests/interop +event-timing/pointerleave.html @web-platform-tests/interop +event-timing/pointerout.html @web-platform-tests/interop +event-timing/pointerover.html @web-platform-tests/interop +event-timing/pointerup.html @web-platform-tests/interop +event-timing/programmatic-click-not-observed.html @web-platform-tests/interop +event-timing/retrievability.html @web-platform-tests/interop +event-timing/retrieve-firstInput.html @web-platform-tests/interop +event-timing/selection-autoscroll.html @web-platform-tests/interop +event-timing/shadow-dom-null-target.html @web-platform-tests/interop +event-timing/supported-types-consistent-with-self.html @web-platform-tests/interop +event-timing/supported-types.window.js @web-platform-tests/interop +event-timing/timingconditions.html @web-platform-tests/interop +event-timing/toJSON.html @web-platform-tests/interop +html/editing/the-hidden-attribute/beforematch-element-fragment-navigation.html @web-platform-tests/interop +html/editing/the-hidden-attribute/beforematch-scroll-to-text-fragment.html @web-platform-tests/interop +html/editing/the-hidden-attribute/hidden-idl.html @web-platform-tests/interop +html/editing/the-hidden-attribute/hidden-ua-stylesheet.html @web-platform-tests/interop +html/editing/the-hidden-attribute/hidden-until-found-002.html @web-platform-tests/interop +html/editing/the-hidden-attribute/hidden-until-found-text-fragment.html @web-platform-tests/interop +html/rendering/the-details-element/auto-expand-details-text-fragment.html @web-platform-tests/interop +html/rendering/the-details-element/details-blockification.html @web-platform-tests/interop +html/rendering/the-details-element/details-display.html @web-platform-tests/interop +html/rendering/the-details-element/details-pseudo-elements-006.html @web-platform-tests/interop +html/semantics/disabled-elements/disabled-event-dispatch.tentative.html @web-platform-tests/interop +html/semantics/disabled-elements/event-propagate-disabled.tentative.html @web-platform-tests/interop +html/semantics/scripting-1/the-script-element/import-attributes/dynamic-import-with-attributes-argument.any.js @web-platform-tests/interop +html/semantics/scripting-1/the-script-element/import-attributes/empty-attributes-clause.html @web-platform-tests/interop +html/semantics/scripting-1/the-script-element/import-attributes/invalid-import-errors-order.html @web-platform-tests/interop +html/semantics/scripting-1/the-script-element/import-attributes/invalid-type-attribute-error.html @web-platform-tests/interop +html/semantics/scripting-1/the-script-element/import-attributes/unsupported-attribute.html @web-platform-tests/interop +html/semantics/scripting-1/the-script-element/json-module/charset-2.html @web-platform-tests/interop +html/semantics/scripting-1/the-script-element/json-module/charset-bom.any.js @web-platform-tests/interop +html/semantics/scripting-1/the-script-element/json-module/charset.html @web-platform-tests/interop +html/semantics/scripting-1/the-script-element/json-module/cors-crossorigin-requests.html @web-platform-tests/interop +html/semantics/scripting-1/the-script-element/json-module/credentials.sub.html @web-platform-tests/interop +html/semantics/scripting-1/the-script-element/json-module/integrity.html @web-platform-tests/interop +html/semantics/scripting-1/the-script-element/json-module/invalid-content-type.any.js @web-platform-tests/interop +html/semantics/scripting-1/the-script-element/json-module/json-module-service-worker-test.https.html @web-platform-tests/interop +html/semantics/scripting-1/the-script-element/json-module/load-error-events.html @web-platform-tests/interop +html/semantics/scripting-1/the-script-element/json-module/module.html @web-platform-tests/interop +html/semantics/scripting-1/the-script-element/json-module/non-object.any.js @web-platform-tests/interop +html/semantics/scripting-1/the-script-element/json-module/parse-error.html @web-platform-tests/interop +html/semantics/scripting-1/the-script-element/json-module/referrer-policies.sub.html @web-platform-tests/interop +html/semantics/scripting-1/the-script-element/json-module/repeated-imports.any.js @web-platform-tests/interop +html/semantics/scripting-1/the-script-element/json-module/script-element-json-src.html @web-platform-tests/interop +html/semantics/scripting-1/the-script-element/json-module/valid-content-type.html @web-platform-tests/interop +html/user-activation/activation-trigger-pointerevent.html @web-platform-tests/interop +largest-contentful-paint/background-image-set-image.html @web-platform-tests/interop +largest-contentful-paint/broken-image-icon.html @web-platform-tests/interop +largest-contentful-paint/contracted-image.html @web-platform-tests/interop +largest-contentful-paint/cross-origin-image.sub.html @web-platform-tests/interop +largest-contentful-paint/element-only-when-fully-active.html @web-platform-tests/interop +largest-contentful-paint/expanded-image.html @web-platform-tests/interop +largest-contentful-paint/first-letter-background.html @web-platform-tests/interop +largest-contentful-paint/first-paint-equals-lcp-text.html @web-platform-tests/interop +largest-contentful-paint/idlharness.html @web-platform-tests/interop +largest-contentful-paint/iframe-content-not-observed.html @web-platform-tests/interop +largest-contentful-paint/image-TAO.sub.html @web-platform-tests/interop +largest-contentful-paint/image-full-viewport.html @web-platform-tests/interop +largest-contentful-paint/image-inside-svg.html @web-platform-tests/interop +largest-contentful-paint/image-not-fully-visible.html @web-platform-tests/interop +largest-contentful-paint/image-removed-before-load.html @web-platform-tests/interop +largest-contentful-paint/image-src-change.html @web-platform-tests/interop +largest-contentful-paint/image-sw-same-origin.https.html @web-platform-tests/interop +largest-contentful-paint/image-upscaling.html @web-platform-tests/interop +largest-contentful-paint/initially-invisible-images.html @web-platform-tests/interop +largest-contentful-paint/invisible-images-composited-1.html @web-platform-tests/interop +largest-contentful-paint/invisible-images-composited-2.html @web-platform-tests/interop +largest-contentful-paint/invisible-images.html @web-platform-tests/interop +largest-contentful-paint/larger-image.html @web-platform-tests/interop +largest-contentful-paint/larger-text.html @web-platform-tests/interop +largest-contentful-paint/loadTime-after-appendChild.html @web-platform-tests/interop +largest-contentful-paint/multiple-image-same-src.html @web-platform-tests/interop +largest-contentful-paint/multiple-redirects-TAO.html @web-platform-tests/interop +largest-contentful-paint/observe-after-untrusted-scroll.html @web-platform-tests/interop +largest-contentful-paint/observe-css-generated-image.html @web-platform-tests/interop +largest-contentful-paint/observe-css-generated-text.html @web-platform-tests/interop +largest-contentful-paint/observe-image.html @web-platform-tests/interop +largest-contentful-paint/observe-mathml.html @web-platform-tests/interop +largest-contentful-paint/observe-random-namespace.html @web-platform-tests/interop +largest-contentful-paint/observe-svg-background-image.html @web-platform-tests/interop +largest-contentful-paint/observe-svg-data-uri-background-image.html @web-platform-tests/interop +largest-contentful-paint/observe-svg-data-uri-image.html @web-platform-tests/interop +largest-contentful-paint/observe-svg-image.html @web-platform-tests/interop +largest-contentful-paint/observe-text.html @web-platform-tests/interop +largest-contentful-paint/placeholder-image.html @web-platform-tests/interop +largest-contentful-paint/progressively-loaded-image.html @web-platform-tests/interop +largest-contentful-paint/redirects-tao-star.html @web-platform-tests/interop +largest-contentful-paint/repeated-image.html @web-platform-tests/interop +largest-contentful-paint/resized-image-not-reconsidered.html @web-platform-tests/interop +largest-contentful-paint/same-origin-redirects.html @web-platform-tests/interop +largest-contentful-paint/supported-lcp-type.html @web-platform-tests/interop +largest-contentful-paint/text-with-display-style.html @web-platform-tests/interop +largest-contentful-paint/toJSON.html @web-platform-tests/interop +largest-contentful-paint/transparent-text-with-shadow.html @web-platform-tests/interop +largest-contentful-paint/transparent-text-with-text-stroke.html @web-platform-tests/interop +largest-contentful-paint/transparent-text.html @web-platform-tests/interop +largest-contentful-paint/update-on-style-change.html @web-platform-tests/interop +largest-contentful-paint/video-data-uri.html @web-platform-tests/interop +largest-contentful-paint/video-play-after-poster.html @web-platform-tests/interop +largest-contentful-paint/video-poster.html @web-platform-tests/interop +largest-contentful-paint/web-font-styled-text-resize-block.html @web-platform-tests/interop +largest-contentful-paint/web-font-styled-text-resize-swap-after-interaction.html @web-platform-tests/interop +navigation-api/currententrychange-event/anchor-click.html @web-platform-tests/interop +navigation-api/currententrychange-event/constructor.html @web-platform-tests/interop +navigation-api/currententrychange-event/history-back-same-doc.html @web-platform-tests/interop +navigation-api/currententrychange-event/history-pushState.html @web-platform-tests/interop +navigation-api/currententrychange-event/history-replaceState.html @web-platform-tests/interop +navigation-api/currententrychange-event/location-api.html @web-platform-tests/interop +navigation-api/currententrychange-event/navigate-from-initial-about-blank-same-doc-popup.html @web-platform-tests/interop +navigation-api/currententrychange-event/navigate-from-initial-about-blank-same-doc.html @web-platform-tests/interop +navigation-api/currententrychange-event/navigate-from-initial-about-blank.html @web-platform-tests/interop +navigation-api/currententrychange-event/navigation-back-forward-cross-doc.html @web-platform-tests/interop +navigation-api/currententrychange-event/navigation-back-forward-same-doc.html @web-platform-tests/interop +navigation-api/currententrychange-event/navigation-navigate-cross-doc.html @web-platform-tests/interop +navigation-api/currententrychange-event/navigation-navigate-intercept.html @web-platform-tests/interop +navigation-api/currententrychange-event/navigation-navigate-preventDefault.html @web-platform-tests/interop +navigation-api/currententrychange-event/navigation-navigate-replace-cross-doc.html @web-platform-tests/interop +navigation-api/currententrychange-event/navigation-navigate-replace-intercept.html @web-platform-tests/interop +navigation-api/currententrychange-event/navigation-navigate-replace-same-doc.html @web-platform-tests/interop +navigation-api/currententrychange-event/navigation-navigate-same-doc.html @web-platform-tests/interop +navigation-api/currententrychange-event/navigation-reload-cross-doc.html @web-platform-tests/interop +navigation-api/currententrychange-event/navigation-reload-intercept.html @web-platform-tests/interop +navigation-api/currententrychange-event/navigation-updateCurrentEntry.html @web-platform-tests/interop +navigation-api/currententrychange-event/not-on-load.html @web-platform-tests/interop +navigation-api/currententrychange-event/properties.html @web-platform-tests/interop +navigation-api/focus-reset/autofocus.html @web-platform-tests/interop +navigation-api/focus-reset/basic.html @web-platform-tests/interop +navigation-api/focus-reset/change-focus-again-in-blur-during-intercept.html @web-platform-tests/interop +navigation-api/focus-reset/change-focus-back-to-origial-during-intercept.html @web-platform-tests/interop +navigation-api/focus-reset/change-focus-during-intercept.html @web-platform-tests/interop +navigation-api/focus-reset/change-focus-then-remove-during-intercept.html @web-platform-tests/interop +navigation-api/focus-reset/focus-reset-timing.html @web-platform-tests/interop +navigation-api/focus-reset/multiple-intercept.html @web-platform-tests/interop +navigation-api/navigate-event/cross-origin-traversal-does-not-fire-navigate.html @web-platform-tests/interop +navigation-api/navigate-event/cross-origin-traversal-redirect.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/click-crossdocument-crossorigin-sameorigindomain.sub.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/click-crossdocument-crossorigin.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/click-crossdocument-sameorigin.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/click-samedocument-crossorigin-sameorigindomain.sub.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/click-samedocument-crossorigin.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/click-samedocument-sameorigin.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/location-crossdocument-crossorigin-sameorigindomain.sub.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/location-crossdocument-crossorigin.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/location-crossdocument-sameorigin.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/location-samedocument-crossorigin-sameorigindomain.sub.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/location-samedocument-crossorigin.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/location-samedocument-sameorigin.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/open-crossdocument-crossorigin-sameorigindomain.sub.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/open-crossdocument-crossorigin.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/open-crossdocument-sameorigin.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/open-samedocument-crossorigin-sameorigindomain.sub.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/open-samedocument-crossorigin.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/open-samedocument-sameorigin.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/submit-crossdocument-crossorigin-sameorigindomain.sub.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/submit-crossdocument-crossorigin.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/submit-crossdocument-sameorigin.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/submit-samedocument-crossorigin-sameorigindomain.sub.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/submit-samedocument-crossorigin.html @web-platform-tests/interop +navigation-api/navigate-event/cross-window/submit-samedocument-sameorigin.html @web-platform-tests/interop +navigation-api/navigate-event/defaultPrevented-navigation-preempted.html @web-platform-tests/interop +navigation-api/navigate-event/defaultPrevented-window-stop-after-dispatch.html @web-platform-tests/interop +navigation-api/navigate-event/event-constructor.html @web-platform-tests/interop +navigation-api/navigate-event/intercept-after-dispatch.html @web-platform-tests/interop +navigation-api/navigate-event/intercept-and-navigate.html @web-platform-tests/interop +navigation-api/navigate-event/intercept-canceled-event.html @web-platform-tests/interop +navigation-api/navigate-event/intercept-cross-document-same-origin.html @web-platform-tests/interop +navigation-api/navigate-event/intercept-cross-origin.html @web-platform-tests/interop +navigation-api/navigate-event/intercept-detach-multiple.html @web-platform-tests/interop +navigation-api/navigate-event/intercept-detach.html @web-platform-tests/interop +navigation-api/navigate-event/intercept-handler-null-or-undefined.html @web-platform-tests/interop +navigation-api/navigate-event/intercept-handler-returns-non-promise.html @web-platform-tests/interop +navigation-api/navigate-event/intercept-handler-throws.html @web-platform-tests/interop +navigation-api/navigate-event/intercept-history-pushState.html @web-platform-tests/interop +navigation-api/navigate-event/intercept-history-replaceState.html @web-platform-tests/interop +navigation-api/navigate-event/intercept-multiple-times-reject.html @web-platform-tests/interop +navigation-api/navigate-event/intercept-multiple-times.html @web-platform-tests/interop +navigation-api/navigate-event/intercept-navigation-back.html @web-platform-tests/interop +navigation-api/navigate-event/intercept-on-synthetic-event.html @web-platform-tests/interop +navigation-api/navigate-event/intercept-popstate.html @web-platform-tests/interop +navigation-api/navigate-event/intercept-reject.html @web-platform-tests/interop +navigation-api/navigate-event/intercept-resolve.html @web-platform-tests/interop +navigation-api/navigate-event/intercept-same-document-history-back.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-anchor-cross-origin.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-anchor-download-userInitiated.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-anchor-download.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-anchor-fragment.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-anchor-same-origin-cross-document.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-anchor-userInitiated.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-anchor-with-target.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-destination-after-detach.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-destination-dynamic-index.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-destination-getState-back-forward.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-destination-getState-navigate.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-destination-getState-reload.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-form-get.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-form-reload.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-form-requestSubmit.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-form-traverse.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-form-userInitiated.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-form-with-target.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-form.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-history-back-after-fragment.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-history-back-after-pushState.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-history-back-bfcache.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-history-back-cross-document.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-history-back-noop.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-history-go-0.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-history-pushState.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-history-replaceState.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-iframe-location.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-location.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-meta-refresh.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-navigation-back-cross-document.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-navigation-back-same-document-in-iframe.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-navigation-back-same-document.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-navigation-navigate.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-svg-anchor-fragment.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-to-javascript.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-to-srcdoc.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-window-open-self.html @web-platform-tests/interop +navigation-api/navigate-event/navigate-window-open.html @web-platform-tests/interop +navigation-api/navigate-event/navigatesuccess-cross-document.html @web-platform-tests/interop +navigation-api/navigate-event/navigatesuccess-same-document.html @web-platform-tests/interop +navigation-api/navigate-event/navigation-back-cross-document-preventDefault.html @web-platform-tests/interop +navigation-api/navigate-event/navigation-back-same-document-preventDefault.html @web-platform-tests/interop +navigation-api/navigate-event/navigation-traverseTo-in-iframe-same-document-preventDefault.html @web-platform-tests/interop +navigation-api/navigate-event/navigation-traverseTo-navigates-top-and-same-doc-child-and-cross-doc-child.html @web-platform-tests/interop +navigation-api/navigate-event/navigation-traverseTo-same-document-preventDefault-multiple-windows.html @web-platform-tests/interop +navigation-api/navigate-event/navigation-traverseTo-top-cancels-cross-document-child.html @web-platform-tests/interop +navigation-api/navigate-event/replaceState-inside-back-handler.html @web-platform-tests/interop +navigation-api/navigate-event/same-url-replace-cross-document.html @web-platform-tests/interop +navigation-api/navigate-event/same-url-replace-same-document.html @web-platform-tests/interop +navigation-api/navigate-event/signal-abort-detach-in-onnavigate.html @web-platform-tests/interop +navigation-api/navigate-event/signal-abort-intercept.html @web-platform-tests/interop +navigation-api/navigate-event/signal-abort-preventDefault.html @web-platform-tests/interop +navigation-api/navigate-event/signal-abort-window-stop-after-intercept.html @web-platform-tests/interop +navigation-api/navigate-event/signal-abort-window-stop-in-onnavigate.html @web-platform-tests/interop +navigation-api/navigate-event/signal-abort-window-stop.html @web-platform-tests/interop +navigation-api/navigation-activation/activation-after-bfcache-cross-origin.html @web-platform-tests/interop +navigation-api/navigation-activation/activation-after-bfcache.html @web-platform-tests/interop +navigation-api/navigation-activation/activation-history-pushState.html @web-platform-tests/interop +navigation-api/navigation-activation/activation-history-replaceState.html @web-platform-tests/interop +navigation-api/navigation-activation/activation-initial-about-blank.html @web-platform-tests/interop +navigation-api/navigation-activation/activation-push-cross-origin.html @web-platform-tests/interop +navigation-api/navigation-activation/activation-push.html @web-platform-tests/interop +navigation-api/navigation-activation/activation-reload.html @web-platform-tests/interop +navigation-api/navigation-activation/activation-replace-cross-origin.html @web-platform-tests/interop +navigation-api/navigation-activation/activation-replace.html @web-platform-tests/interop +navigation-api/navigation-activation/activation-same-document-then-cross-document.html @web-platform-tests/interop +navigation-api/navigation-activation/activation-traverse-not-in-entries.html @web-platform-tests/interop +navigation-api/navigation-activation/activation-traverse-then-clobber.html @web-platform-tests/interop +navigation-api/navigation-activation/activation-traverse.html @web-platform-tests/interop +navigation-api/navigation-history-entry/after-detach.html @web-platform-tests/interop +navigation-api/navigation-history-entry/current-basic.html @web-platform-tests/interop +navigation-api/navigation-history-entry/entries-across-origins.html @web-platform-tests/interop +navigation-api/navigation-history-entry/entries-after-bfcache-in-iframe.html @web-platform-tests/interop +navigation-api/navigation-history-entry/entries-after-bfcache.html @web-platform-tests/interop +navigation-api/navigation-history-entry/entries-after-blank-navigation-from-cross-origin.html @web-platform-tests/interop +navigation-api/navigation-history-entry/entries-after-blank-navigation.html @web-platform-tests/interop +navigation-api/navigation-history-entry/entries-after-blob-navigation.html @web-platform-tests/interop +navigation-api/navigation-history-entry/entries-after-cross-document-forward-pruning.html @web-platform-tests/interop +navigation-api/navigation-history-entry/entries-after-javascript-url-navigation.html @web-platform-tests/interop +navigation-api/navigation-history-entry/entries-after-navigations-in-multiple-windows.html @web-platform-tests/interop +navigation-api/navigation-history-entry/entries-after-srcdoc-navigation.html @web-platform-tests/interop +navigation-api/navigation-history-entry/entries-array-equality.html @web-platform-tests/interop +navigation-api/navigation-history-entry/entries-in-new-javascript-url-iframe.html @web-platform-tests/interop +navigation-api/navigation-history-entry/entries-in-new-srcdoc-iframe.html @web-platform-tests/interop +navigation-api/navigation-history-entry/entries-when-inactive.html @web-platform-tests/interop +navigation-api/navigation-history-entry/entry-after-detach.html @web-platform-tests/interop +navigation-api/navigation-history-entry/index-not-in-entries.html @web-platform-tests/interop +navigation-api/navigation-history-entry/key-id-back-cross-document.html @web-platform-tests/interop +navigation-api/navigation-history-entry/key-id-back-same-document.html @web-platform-tests/interop +navigation-api/navigation-history-entry/key-id-location-reload-intercept.html @web-platform-tests/interop +navigation-api/navigation-history-entry/key-id-location-reload.html @web-platform-tests/interop +navigation-api/navigation-history-entry/key-id-location-replace-cross-origin.html @web-platform-tests/interop +navigation-api/navigation-history-entry/key-id-location-replace.html @web-platform-tests/interop +navigation-api/navigation-history-entry/no-referrer-dynamic-url-censored.html @web-platform-tests/interop +navigation-api/navigation-history-entry/no-referrer-from-meta-url-censored.html @web-platform-tests/interop +navigation-api/navigation-history-entry/no-referrer-url-censored.html @web-platform-tests/interop +navigation-api/navigation-history-entry/opaque-origin-data-url.html @web-platform-tests/interop +navigation-api/navigation-history-entry/opaque-origin.html @web-platform-tests/interop +navigation-api/navigation-history-entry/sameDocument-after-fragment-navigate.html @web-platform-tests/interop +navigation-api/navigation-history-entry/sameDocument-after-navigate-restore.html @web-platform-tests/interop +navigation-api/navigation-history-entry/sameDocument-after-navigate.html @web-platform-tests/interop +navigation-api/navigation-history-entry/state-after-navigate-restore.html @web-platform-tests/interop +navigation-api/navigation-methods/back-forward-multiple-frames.html @web-platform-tests/interop +navigation-api/navigation-methods/disambigaute-back.html @web-platform-tests/interop +navigation-api/navigation-methods/disambigaute-forward.html @web-platform-tests/interop +navigation-api/navigation-methods/disambigaute-traverseTo-back-multiple.html @web-platform-tests/interop +navigation-api/navigation-methods/disambigaute-traverseTo-forward-multiple.html @web-platform-tests/interop +navigation-api/navigation-methods/forward-to-pruned-entry.html @web-platform-tests/interop +navigation-api/navigation-methods/navigate-base-url.html @web-platform-tests/interop +navigation-api/navigation-methods/navigate-from-initial-about-blank-gc.html @web-platform-tests/interop +navigation-api/navigation-methods/navigate-from-initial-about-blank-src.html @web-platform-tests/interop +navigation-api/navigation-methods/navigate-from-initial-about-blank.html @web-platform-tests/interop +navigation-api/navigation-methods/navigate-history-push-not-loaded.html @web-platform-tests/interop +navigation-api/navigation-methods/navigate-history-push-same-url-cross-document.html @web-platform-tests/interop +navigation-api/navigation-methods/navigate-history-push-same-url.html @web-platform-tests/interop +navigation-api/navigation-methods/navigate-history-state-replace.html @web-platform-tests/interop +navigation-api/navigation-methods/navigate-history-state.html @web-platform-tests/interop +navigation-api/navigation-methods/navigate-info-and-state.html @web-platform-tests/interop +navigation-api/navigation-methods/navigate-intercept-history-state.html @web-platform-tests/interop +navigation-api/navigation-methods/navigate-relative-url-utf8.html @web-platform-tests/interop +navigation-api/navigation-methods/navigate-relative-url.html @web-platform-tests/interop +navigation-api/navigation-methods/navigate-replace-cross-document.html @web-platform-tests/interop +navigation-api/navigation-methods/navigate-replace-same-document.html @web-platform-tests/interop +navigation-api/navigation-methods/navigate-same-document.html @web-platform-tests/interop +navigation-api/navigation-methods/navigate-state-repeated-await.html @web-platform-tests/interop +navigation-api/navigation-methods/navigate-state-repeated.html @web-platform-tests/interop +navigation-api/navigation-methods/reload-base-url.html @web-platform-tests/interop +navigation-api/navigation-methods/reload-info.html @web-platform-tests/interop +navigation-api/navigation-methods/reload-navigation-timing.html @web-platform-tests/interop +navigation-api/navigation-methods/reload-no-args.html @web-platform-tests/interop +navigation-api/navigation-methods/reload-service-worker-fetch-event.html @web-platform-tests/interop +navigation-api/navigation-methods/reload-state-and-info.html @web-platform-tests/interop +navigation-api/navigation-methods/reload-state-undefined.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/back-204-205-download.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/back-already-detached.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/back-beforeunload.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/back-forward-initial-about-blank.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/back-forward-opaque-origin.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/back-forward-out-of-bounds.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/back-intercept-rejected.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/back-intercept.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/back.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/forward-already-detached.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/forward-beforeunload.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/forward-intercept-rejected.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/forward-intercept.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/forward.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-204-205-download.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-already-detached.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-beforeunload.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-cross-document.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-detach-in-onnavigate.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-detach-in-serialization.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-file-url.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-initial-about-blank-cross-document.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-initial-about-blank.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-intercept-interrupted.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-intercept-rejected.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-intercept.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-interrupted-within-onnavigate.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-interrupted.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-invalid-url.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-opaque-origin.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-pagehide.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-preventDefault.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-push-initial-about-blank.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-push-javascript-url.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-rejection-order-beforeunload-unserializablestate.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-rejection-order-detached-unserializablestate.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-beforeunload.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-detached.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-pagehide.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-unserializablestate.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-rejection-order-pagehide-unserializablestate.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate-unserializable-state.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/navigate.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/reload-already-detached.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/reload-beforeunload.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/reload-detach-in-onnavigate.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/reload-detach-in-serialization.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/reload-initial-about-blank.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/reload-intercept-rejected.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/reload-intercept.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/reload-pagehide.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/reload-preventDefault.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/reload-rejection-order-beforeunload-unserializablestate.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/reload-rejection-order-detached-unserializablestate.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/reload-rejection-order-pagehide-unserializablestate.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/reload-unserializable-state.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/reload.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/traverseTo-already-detached.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/traverseTo-beforeunload.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/traverseTo-cross-document-preventDefault.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/traverseTo-current.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/traverseTo-detach-cross-document-before-navigate-event.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/traverseTo-detach-cross-document.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/traverseTo-detach-same-document-before-navigate-event.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/traverseTo-detach-same-document.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/traverseTo-intercept-rejected.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/traverseTo-intercept.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/traverseTo-invalid-key.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/traverseTo-repeated.html @web-platform-tests/interop +navigation-api/navigation-methods/return-value/traverseTo.html @web-platform-tests/interop +navigation-api/navigation-methods/sandboxing-back-parent.html @web-platform-tests/interop +navigation-api/navigation-methods/sandboxing-back-sibling.html @web-platform-tests/interop +navigation-api/navigation-methods/sandboxing-navigate-parent.html @web-platform-tests/interop +navigation-api/navigation-methods/sandboxing-navigate-sibling.html @web-platform-tests/interop +navigation-api/navigation-methods/traverseTo-after-adding-iframe.html @web-platform-tests/interop +navigation-api/navigation-methods/traverseTo-after-data-url.html @web-platform-tests/interop +navigation-api/navigation-methods/traverseTo-cross-document.html @web-platform-tests/interop +navigation-api/navigation-methods/traverseTo-detach-between-navigate-and-navigatesuccess.html @web-platform-tests/interop +navigation-api/navigation-methods/traverseTo-multiple-steps.html @web-platform-tests/interop +navigation-api/navigation-methods/traverseTo-navigates-multiple-iframes.html @web-platform-tests/interop +navigation-api/navigation-methods/traverseTo-same-document.html @web-platform-tests/interop +navigation-api/navigation-methods/traverseTo-with-cross-origin-in-history.html @web-platform-tests/interop +navigation-api/ordering-and-transition/anchor-download-intercept-reject.html @web-platform-tests/interop +navigation-api/ordering-and-transition/anchor-download-intercept.html @web-platform-tests/interop +navigation-api/ordering-and-transition/anchor-download.html @web-platform-tests/interop +navigation-api/ordering-and-transition/back-cross-document-event-order.html @web-platform-tests/interop +navigation-api/ordering-and-transition/back-same-document-intercept-reject.html @web-platform-tests/interop +navigation-api/ordering-and-transition/back-same-document-intercept.html @web-platform-tests/interop +navigation-api/ordering-and-transition/back-same-document.html @web-platform-tests/interop +navigation-api/ordering-and-transition/currententrychange-before-popstate-intercept.html @web-platform-tests/interop +navigation-api/ordering-and-transition/currententrychange-dispose-ordering.html @web-platform-tests/interop +navigation-api/ordering-and-transition/intercept-async.html @web-platform-tests/interop +navigation-api/ordering-and-transition/location-href-canceled.html @web-platform-tests/interop +navigation-api/ordering-and-transition/location-href-double-intercept.html @web-platform-tests/interop +navigation-api/ordering-and-transition/location-href-intercept-reentrant.html @web-platform-tests/interop +navigation-api/ordering-and-transition/location-href-intercept-reject.html @web-platform-tests/interop +navigation-api/ordering-and-transition/location-href-intercept.html @web-platform-tests/interop +navigation-api/ordering-and-transition/navigate-204-205-download-then-same-document.html @web-platform-tests/interop +navigation-api/ordering-and-transition/navigate-canceled.html @web-platform-tests/interop +navigation-api/ordering-and-transition/navigate-cross-document-double.html @web-platform-tests/interop +navigation-api/ordering-and-transition/navigate-cross-document-event-order.html @web-platform-tests/interop +navigation-api/ordering-and-transition/navigate-double-intercept.html @web-platform-tests/interop +navigation-api/ordering-and-transition/navigate-in-transition-finished.html @web-platform-tests/interop +navigation-api/ordering-and-transition/navigate-intercept-stop.html @web-platform-tests/interop +navigation-api/ordering-and-transition/navigate-intercept.html @web-platform-tests/interop +navigation-api/ordering-and-transition/navigate-same-document-intercept-reentrant.html @web-platform-tests/interop +navigation-api/ordering-and-transition/navigate-same-document-intercept-reject.html @web-platform-tests/interop +navigation-api/ordering-and-transition/navigate-same-document.html @web-platform-tests/interop +navigation-api/ordering-and-transition/reload-canceled.html @web-platform-tests/interop +navigation-api/ordering-and-transition/reload-intercept-reject.html @web-platform-tests/interop +navigation-api/ordering-and-transition/reload-intercept.html @web-platform-tests/interop +navigation-api/ordering-and-transition/transition-cross-document.html @web-platform-tests/interop +navigation-api/ordering-and-transition/transition-finished-mark-as-handled.html @web-platform-tests/interop +navigation-api/ordering-and-transition/transition-realms-and-identity.html @web-platform-tests/interop +navigation-api/per-entry-events/dispose-after-bfcache.html @web-platform-tests/interop +navigation-api/per-entry-events/dispose-cross-document.html @web-platform-tests/interop +navigation-api/per-entry-events/dispose-for-navigation-in-child.html @web-platform-tests/interop +navigation-api/per-entry-events/dispose-same-document-intercept.html @web-platform-tests/interop +navigation-api/per-entry-events/dispose-same-document-navigate-during.html @web-platform-tests/interop +navigation-api/per-entry-events/dispose-same-document-reload-with-intercept.html @web-platform-tests/interop +navigation-api/per-entry-events/dispose-same-document-replace-with-intercept.html @web-platform-tests/interop +navigation-api/per-entry-events/dispose-same-document-replaceState.html @web-platform-tests/interop +navigation-api/per-entry-events/dispose-same-document.html @web-platform-tests/interop +navigation-api/per-entry-events/dispose-skip-current-on-truncate.html @web-platform-tests/interop +navigation-api/scroll-behavior/after-transition-basic.html @web-platform-tests/interop +navigation-api/scroll-behavior/after-transition-change-history-scroll-restoration-during-promise.html @web-platform-tests/interop +navigation-api/scroll-behavior/after-transition-explicit-scroll.html @web-platform-tests/interop +navigation-api/scroll-behavior/after-transition-intercept-handler-modifies.html @web-platform-tests/interop +navigation-api/scroll-behavior/after-transition-push.html @web-platform-tests/interop +navigation-api/scroll-behavior/after-transition-reject.html @web-platform-tests/interop +navigation-api/scroll-behavior/after-transition-reload.html @web-platform-tests/interop +navigation-api/scroll-behavior/after-transition-replace.html @web-platform-tests/interop +navigation-api/scroll-behavior/after-transition-timing.html @web-platform-tests/interop +navigation-api/scroll-behavior/manual-basic.html @web-platform-tests/interop +navigation-api/scroll-behavior/manual-immediate-scroll.html @web-platform-tests/interop +navigation-api/scroll-behavior/manual-scroll-after-dispatch.html @web-platform-tests/interop +navigation-api/scroll-behavior/manual-scroll-after-resolve.html @web-platform-tests/interop +navigation-api/scroll-behavior/manual-scroll-fragment-does-not-exist.html @web-platform-tests/interop +navigation-api/scroll-behavior/manual-scroll-push.html @web-platform-tests/interop +navigation-api/scroll-behavior/manual-scroll-reload.html @web-platform-tests/interop +navigation-api/scroll-behavior/manual-scroll-repeated.html @web-platform-tests/interop +navigation-api/scroll-behavior/manual-scroll-replace.html @web-platform-tests/interop +navigation-api/scroll-behavior/manual-scroll-resets-when-no-fragment.html @web-platform-tests/interop +navigation-api/scroll-behavior/scroll-after-preventDefault.html @web-platform-tests/interop +navigation-api/scroll-behavior/scroll-on-synthetic-event.html @web-platform-tests/interop +navigation-api/scroll-behavior/scroll-without-intercept.html @web-platform-tests/interop +navigation-api/state/cross-document-away-and-back.html @web-platform-tests/interop +navigation-api/state/cross-document-getState-undefined.html @web-platform-tests/interop +navigation-api/state/cross-document-getState.html @web-platform-tests/interop +navigation-api/state/cross-document-location-api.html @web-platform-tests/interop +navigation-api/state/history-pushState.html @web-platform-tests/interop +navigation-api/state/history-replaceState.html @web-platform-tests/interop +navigation-api/state/location-reload.html @web-platform-tests/interop +navigation-api/state/same-document-away-and-back-location-api.html @web-platform-tests/interop +navigation-api/state/same-document-away-and-back-navigation-api.html @web-platform-tests/interop +navigation-api/updateCurrentEntry-method/basic.html @web-platform-tests/interop +navigation-api/updateCurrentEntry-method/exception-order-initial-about-blank-unserializablestate.html @web-platform-tests/interop +navigation-api/updateCurrentEntry-method/exception-order-not-fully-active-unserializablestate.html @web-platform-tests/interop +navigation-api/updateCurrentEntry-method/initial-about-blank.html @web-platform-tests/interop +navigation-api/updateCurrentEntry-method/no-args.html @web-platform-tests/interop +navigation-api/updateCurrentEntry-method/not-fully-active.html @web-platform-tests/interop +navigation-api/updateCurrentEntry-method/opaque-origin.html @web-platform-tests/interop +navigation-api/updateCurrentEntry-method/same-document-away-and-back-location-api.html @web-platform-tests/interop +navigation-api/updateCurrentEntry-method/unserializable.html @web-platform-tests/interop +performance-timeline/buffered-flag-with-entryTypes-observer.tentative.any.js @web-platform-tests/interop +pointerevents/capturing_boundary_event_handler_at_ua_shadowdom.html @web-platform-tests/interop +pointerevents/coalesced_events_attributes.https.html @web-platform-tests/interop +pointerevents/idlharness.https.window.js @web-platform-tests/interop +pointerevents/mouse-pointer-boundary-events-for-shadowdom.html @web-platform-tests/interop +pointerevents/pointerevent_after_target_appended.html @web-platform-tests/interop +pointerevents/pointerevent_after_target_removed.html @web-platform-tests/interop +pointerevents/pointerevent_attributes.html @web-platform-tests/interop +pointerevents/pointerevent_auxclick_is_a_pointerevent.html @web-platform-tests/interop +pointerevents/pointerevent_boundary_events_at_implicit_release_hoverable_pointers.html @web-platform-tests/interop +pointerevents/pointerevent_capture_suppressing_mouse.html @web-platform-tests/interop +pointerevents/pointerevent_click_during_capture.html @web-platform-tests/interop +pointerevents/pointerevent_click_is_a_pointerevent.html @web-platform-tests/interop +pointerevents/pointerevent_click_is_a_pointerevent_multiple_clicks.html @web-platform-tests/interop +pointerevents/pointerevent_constructor.html @web-platform-tests/interop +pointerevents/pointerevent_contextmenu_is_a_pointerevent.html @web-platform-tests/interop +pointerevents/pointerevent_disabled_form_control.html @web-platform-tests/interop +pointerevents/pointerevent_fractional_coordinates.html @web-platform-tests/interop +pointerevents/pointerevent_hit_test_scroll_visible_descendant.html @web-platform-tests/interop +pointerevents/pointerevent_lostpointercapture_for_disconnected_node.html @web-platform-tests/interop +pointerevents/pointerevent_lostpointercapture_remove_setcapture_node.html @web-platform-tests/interop +pointerevents/pointerevent_mouse_capture_change_hover.html @web-platform-tests/interop +pointerevents/pointerevent_mouse_pointercapture_inactivate_pointer.html @web-platform-tests/interop +pointerevents/pointerevent_movementxy.html @web-platform-tests/interop +pointerevents/pointerevent_pointer_boundary_events_after_removing_last_over_element.html @web-platform-tests/interop +pointerevents/pointerevent_pointercapture_in_frame.html @web-platform-tests/interop +pointerevents/pointerevent_pointermove_on_chorded_mouse_button.html @web-platform-tests/interop +pointerevents/pointerevent_pointerout_no_pointer_movement.html @web-platform-tests/interop +pointerevents/pointerevent_pointerrawupdate.html @web-platform-tests/interop +pointerevents/pointerevent_releasepointercapture_events_to_original_target.html @web-platform-tests/interop +pointerevents/pointerevent_sequence_at_implicit_release_on_click.html @web-platform-tests/interop +pointerevents/pointerevent_setpointercapture_inactive_button_mouse.html @web-platform-tests/interop +pointerevents/pointerevent_touch-action-keyboard.html @web-platform-tests/interop +pointerevents/pointerup_after_pointerdown_target_removed.html @web-platform-tests/interop +pointerevents/predicted_events_attributes.html @web-platform-tests/interop +shadow-dom/Document-caretPositionFromPoint.tentative.html @web-platform-tests/interop +storage-access-api/hasStorageAccess-ABA.sub.https.window.js @web-platform-tests/interop +storage-access-api/hasStorageAccess-insecure.sub.window.js @web-platform-tests/interop +storage-access-api/hasStorageAccess.sub.https.window.js @web-platform-tests/interop +storage-access-api/idlharness.window.js @web-platform-tests/interop +storage-access-api/requestStorageAccess-ABA.sub.https.window.js @web-platform-tests/interop +storage-access-api/requestStorageAccess-cross-origin-iframe-navigation-relax.sub.https.window.js @web-platform-tests/interop +storage-access-api/requestStorageAccess-cross-origin-iframe-navigation.sub.https.window.js @web-platform-tests/interop +storage-access-api/requestStorageAccess-cross-site-iframe.sub.https.window.js @web-platform-tests/interop +storage-access-api/requestStorageAccess-cross-site-sibling-iframes.sub.https.window.js @web-platform-tests/interop +storage-access-api/requestStorageAccess-dedicated-worker.sub.https.window.js @web-platform-tests/interop +storage-access-api/requestStorageAccess-insecure.sub.window.js @web-platform-tests/interop +storage-access-api/requestStorageAccess-nested-cross-origin-iframe.sub.https.window.js @web-platform-tests/interop +storage-access-api/requestStorageAccess-nested-cross-site-iframe.sub.https.window.js @web-platform-tests/interop +storage-access-api/requestStorageAccess-nested-same-origin-iframe.sub.https.window.js @web-platform-tests/interop +storage-access-api/requestStorageAccess-non-fully-active.sub.https.window.js @web-platform-tests/interop +storage-access-api/requestStorageAccess-same-site-iframe.sub.https.window.js @web-platform-tests/interop +storage-access-api/requestStorageAccess-sandboxed-iframe-allow-storage-access.sub.https.window.js @web-platform-tests/interop +storage-access-api/requestStorageAccess-sandboxed-iframe-no-storage-access.sub.https.window.js @web-platform-tests/interop +storage-access-api/requestStorageAccess-web-socket.sub.https.window.js @web-platform-tests/interop +storage-access-api/requestStorageAccess.sub.https.window.js @web-platform-tests/interop +storage-access-api/storage-access-permission.sub.https.window.js @web-platform-tests/interop +storage-access-api/storageAccess.testdriver.sub.html @web-platform-tests/interop +uievents/mouse/attributes.html @web-platform-tests/interop +uievents/mouse/cancel-mousedown-in-subframe.html @web-platform-tests/interop +uievents/mouse/mousemove_after_mouseover_target_removed.html @web-platform-tests/interop +uievents/mouse/mousemove_prevent_default_action.html @web-platform-tests/interop +urlpattern/urlpattern-hasregexpgroups.any.js @web-platform-tests/interop +urlpattern/urlpattern.any.js @web-platform-tests/interop +urlpattern/urlpattern.https.any.js @web-platform-tests/interop +wasm/jsapi/js-string/basic.any.js @web-platform-tests/interop +wasm/jsapi/js-string/constants.any.js @web-platform-tests/interop +wasm/jsapi/js-string/imports.any.js @web-platform-tests/interop +wasm/jsapi/memory/to-fixed-length-buffer.any.js @web-platform-tests/interop +wasm/jsapi/memory/to-resizable-buffer-shared.any.js @web-platform-tests/interop +wasm/jsapi/memory/to-resizable-buffer.any.js @web-platform-tests/interop +webrtc/transfer-datachannel.html @web-platform-tests/interop +webrtc-encoded-transform/RTCRtpScriptTransform-bad-chunk.https.html @web-platform-tests/interop +webrtc-encoded-transform/RTCRtpScriptTransform-encoded-transform.https.html @web-platform-tests/interop +webrtc-encoded-transform/RTCRtpScriptTransform-sender-worker-single-frame.https.html @web-platform-tests/interop +webrtc-encoded-transform/idlharness.https.window.js @web-platform-tests/interop +webrtc-encoded-transform/script-audio-transform.https.html @web-platform-tests/interop +webrtc-encoded-transform/script-change-transform.https.html @web-platform-tests/interop +webrtc-encoded-transform/script-metadata-transform.https.html @web-platform-tests/interop +webrtc-encoded-transform/script-transform-generateKeyFrame-simulcast.https.html @web-platform-tests/interop +webrtc-encoded-transform/script-transform-generateKeyFrame.https.html @web-platform-tests/interop +webrtc-encoded-transform/script-transform-sendKeyFrameRequest.https.html @web-platform-tests/interop +webrtc-encoded-transform/script-transform.https.html @web-platform-tests/interop +webrtc-encoded-transform/script-write-twice-transform.https.html @web-platform-tests/interop +workers/constructors/SharedWorker/same-origin.html @web-platform-tests/interop +workers/constructors/Worker/same-origin.html @web-platform-tests/interop diff --git a/tests/wpt/tests/IndexedDB/idbcursor-request-source.any.js b/tests/wpt/tests/IndexedDB/idbcursor-request-source.any.js index 2fe8c66f2e5..8e1b34ee798 100644 --- a/tests/wpt/tests/IndexedDB/idbcursor-request-source.any.js +++ b/tests/wpt/tests/IndexedDB/idbcursor-request-source.any.js @@ -6,21 +6,111 @@ 'use strict'; -[cursor => cursor.update(0), cursor => cursor.delete()].forEach( - func => indexeddb_test( - (t, db) => { - db.createObjectStore('store', {autoIncrement: true}); - }, - (t, db) => { - const tx = db.transaction('store', 'readwrite'); - const store = tx.objectStore('store'); - store.put('value'); - store.openCursor().onsuccess = t.step_func(e => { - const cursor = e.target.result; - assert_equals( - func(cursor).source, cursor, - `${func}.source should be the cursor itself`); +// Setup each test by populating an object store with an index for the cursor to +// iterate and manipulate. +function initializeDatabase(db) { + const store = db.createObjectStore('store', {autoIncrement: true}); + store.createIndex('index', /*keypath=*/ 'value'); + store.put({value: 'z'}); + store.put({value: 'y'}); + store.put({value: 'x'}); + store.put({value: 'w'}); +} + +function isIndex(cursorSourceType) { + return cursorSourceType === 'IDBIndex'; +} + +// Return the object store or index, depending on the test's `cursorSourceType`. +function getCursorSource(transaction, cursorSourceType) { + let cursorSource = transaction.objectStore('store'); + if (isIndex(cursorSourceType)) { + cursorSource = cursorSource.index('index'); + } + return cursorSource; +} + +// Verify the request source after calling delete() or update() on the cursor. +function cursor_request_source_test( + cursorSourceType, createRequestFunctionName, createRequestFunctionArgs) { + indexeddb_test( + (t, db) => initializeDatabase(db), + (t, db) => { + const tx = db.transaction('store', 'readwrite'); + const cursorSource = getCursorSource(tx, cursorSourceType); + + // Open the cursor. + const openCursorRequest = cursorSource.openCursor(); + openCursorRequest.onerror = + t.unreached_func('The cursor must not fail to open.'); + + openCursorRequest.onsuccess = t.step_func(e => { + // Use the cursor to create a new request. + const cursor = e.target.result; + const request = + cursor[createRequestFunctionName](...createRequestFunctionArgs); + assert_equals( + request.source, cursor, + `The request's source must be the cursor itself.`); + t.done(); + }); + }, + `The source of the request from ${cursorSourceType}::${ + createRequestFunctionName}() is the cursor itself`); +} + +// Verify the request source after calling openCursor() or openKeyCursor() and +// then using the cursor to iterate. +function open_cursor_request_source_test( + cursorSourceType, openCursorFunctionName) { + indexeddb_test( + (t, db) => initializeDatabase(db), + (t, db) => { + const tx = db.transaction('store', 'readonly'); + const cursorSource = getCursorSource(tx, cursorSourceType); + + // Open the cursor. + const openCursorRequest = cursorSource[openCursorFunctionName](); + openCursorRequest.onerror = + t.unreached_func('The cursor must not fail to open or iterate.'); + + assert_equals( + openCursorRequest.source, cursorSource, + 'The request source must be the opener of the cursor.'); + + // Verify the cursor's `request.source` after iterating with + // `advance()`, `continue()`, and `continuePrimaryKey()`. + let iterationCount = 0; + openCursorRequest.onsuccess = t.step_func(e => { + assert_equals( + openCursorRequest.source, cursorSource, + 'The request source must be the opener of the cursor after iterating.'); + + const cursor = e.target.result; + ++iterationCount; + + if (iterationCount == 1) { + cursor.advance(1); + } else if (iterationCount == 2) { + cursor.continue(); + } else if (iterationCount == 3 && isIndex(cursorSourceType)) { + cursor.continuePrimaryKey('z', 0); + } else { t.done(); - }); - }, - `The source of the request from ${func} is the cursor itself`)); + } + }); + }, + `${cursorSourceType}::${ + openCursorFunctionName}'s request source must be the ${ + cursorSourceType} instance that opened the cursor`); +} + +open_cursor_request_source_test('IDBObjectStore', 'openCursor'); +open_cursor_request_source_test('IDBObjectStore', 'openKeyCursor'); +open_cursor_request_source_test('IDBIndex', 'openCursor'); +open_cursor_request_source_test('IDBIndex', 'openKeyCursor'); + +cursor_request_source_test('IDBObjectStore', 'update', /*args=*/[0]); +cursor_request_source_test('IDBObjectStore', 'delete', /*args=*/[]); +cursor_request_source_test('IDBIndex', 'update', /*args=*/[0]); +cursor_request_source_test('IDBIndex', 'delete', /*args=*/[]); diff --git a/tests/wpt/tests/WebCryptoAPI/idlharness.https.any.js b/tests/wpt/tests/WebCryptoAPI/idlharness.https.any.js index ae65eb49f21..5ddf7eab6db 100644 --- a/tests/wpt/tests/WebCryptoAPI/idlharness.https.any.js +++ b/tests/wpt/tests/WebCryptoAPI/idlharness.https.any.js @@ -5,7 +5,7 @@ // https://w3c.github.io/webcrypto/Overview.html idl_test( - ['WebCryptoAPI'], + ['webcrypto'], ['html', 'dom'], idl_array => { idl_array.add_objects({ diff --git a/tests/wpt/tests/WebCryptoAPI/import_export/importKey_failures.js b/tests/wpt/tests/WebCryptoAPI/import_export/importKey_failures.js index e073c3490fc..39c70a85470 100644 --- a/tests/wpt/tests/WebCryptoAPI/import_export/importKey_failures.js +++ b/tests/wpt/tests/WebCryptoAPI/import_export/importKey_failures.js @@ -267,4 +267,23 @@ function run_test(algorithmNames) { }); }); }); + + // Use an 'alg' field with incorrect casing. + testVectors.forEach(function(vector) { + var name = vector.name; + if (name !== "Ed25519" && name !== "Ed448") + return; // The rest ignore the 'alg' field. + allAlgorithmSpecifiersFor(name).forEach(function(algorithm) { + getValidKeyData(algorithm).forEach(function(test) { + if (test.format === "jwk") { + var data = {crv: test.data.crv, kty: test.data.kty, d: test.data.d, x: test.data.x, y: test.data.y}; + var usages = validUsages(vector, 'jwk', test.data); + [name.toLowerCase(), name.toUpperCase()].forEach(function(algName) { + data.alg = algName; + testError('jwk', algorithm, data, name, usages, true, "DataError", "Invalid 'alg' field '" + data.alg + "'"); + }); + } + }); + }); + }); } diff --git a/tests/wpt/tests/accelerometer/Accelerometer-iframe-access.https.html b/tests/wpt/tests/accelerometer/Accelerometer-iframe-access.https.html index 56005696a7f..887c6139061 100644 --- a/tests/wpt/tests/accelerometer/Accelerometer-iframe-access.https.html +++ b/tests/wpt/tests/accelerometer/Accelerometer-iframe-access.https.html @@ -6,7 +6,7 @@ - + diff --git a/tests/wpt/tests/accelerometer/Accelerometer.https.html b/tests/wpt/tests/accelerometer/Accelerometer.https.html index d422fef7264..6d6dae5c619 100644 --- a/tests/wpt/tests/accelerometer/Accelerometer.https.html +++ b/tests/wpt/tests/accelerometer/Accelerometer.https.html @@ -6,7 +6,7 @@ - + diff --git a/tests/wpt/tests/accelerometer/GravitySensor.https.html b/tests/wpt/tests/accelerometer/GravitySensor.https.html index 0f98f3e00de..c632f0ffe31 100644 --- a/tests/wpt/tests/accelerometer/GravitySensor.https.html +++ b/tests/wpt/tests/accelerometer/GravitySensor.https.html @@ -6,7 +6,7 @@ - + diff --git a/tests/wpt/tests/accelerometer/LinearAccelerationSensor.https.html b/tests/wpt/tests/accelerometer/LinearAccelerationSensor.https.html index 91035cc9628..c8883b4c8a1 100644 --- a/tests/wpt/tests/accelerometer/LinearAccelerationSensor.https.html +++ b/tests/wpt/tests/accelerometer/LinearAccelerationSensor.https.html @@ -6,7 +6,7 @@ - + diff --git a/tests/wpt/tests/ai/language_detection/availability-detached-crash.tentative.https.html b/tests/wpt/tests/ai/language_detection/availability-detached-crash.https.html similarity index 50% rename from tests/wpt/tests/ai/language_detection/availability-detached-crash.tentative.https.html rename to tests/wpt/tests/ai/language_detection/availability-detached-crash.https.html index 5f76d59eb35..ee5e27e35b6 100644 --- a/tests/wpt/tests/ai/language_detection/availability-detached-crash.tentative.https.html +++ b/tests/wpt/tests/ai/language_detection/availability-detached-crash.https.html @@ -2,12 +2,19 @@ Assures no crash upon call of LanguageDetector.availability() on a detached document + + diff --git a/tests/wpt/tests/ai/language_detection/detector-iframe.tentative.https.html b/tests/wpt/tests/ai/language_detection/detector-iframe.https.html similarity index 79% rename from tests/wpt/tests/ai/language_detection/detector-iframe.tentative.https.html rename to tests/wpt/tests/ai/language_detection/detector-iframe.https.html index 3e90d36b5aa..9dc39d44a00 100644 --- a/tests/wpt/tests/ai/language_detection/detector-iframe.tentative.https.html +++ b/tests/wpt/tests/ai/language_detection/detector-iframe.https.html @@ -1,4 +1,7 @@ + + + @@ -10,11 +13,21 @@ const { HTTPS_ORIGIN, HTTPS_NOTSAMESITE_ORIGIN } = get_host_info(); const PATH = location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1); const IFRAME_PATH = PATH + 'resources/iframe-helper.html'; +const getId = (() => { + let idCount = 0; + return () => idCount++; +})(); + function run_iframe_test(iframe, test_name) { - iframe.contentWindow.postMessage({type: test_name}, '*'); + const id = getId(); + iframe.contentWindow.postMessage({id, type: test_name}, '*'); const {promise, resolve, reject} = Promise.withResolvers(); window.onmessage = message => { + if (message.data.id !== id){ + return; + } + if (message.data.success) { resolve(message.data.success); } else { @@ -27,14 +40,16 @@ function run_iframe_test(iframe, test_name) { function load_iframe(src, permission_policy) { let iframe = document.createElement('iframe'); - return new Promise((resolve, reject) => { - iframe.onload = () => { - resolve(iframe); - } - iframe.src = src; - iframe.allow = permission_policy; - document.body.appendChild(iframe); - }); + const {promise, resolve} = Promise.withResolvers(); + + iframe.onload = () => { + resolve(iframe); + } + iframe.src = src; + iframe.allow = permission_policy; + document.body.appendChild(iframe); + + return promise; } promise_test(async t => { @@ -46,7 +61,7 @@ promise_test(async t => { promise_test(async t => { const src = HTTPS_NOTSAMESITE_ORIGIN + IFRAME_PATH; - const iframe = await load_iframe(src, "languageDetector"); + const iframe = await load_iframe(src, "language-detector"); assert_equals( await run_iframe_test(iframe, "LanguageDetectorCreate"), 'Success'); @@ -70,7 +85,7 @@ promise_test(async t => { promise_test(async t => { const src = HTTPS_NOTSAMESITE_ORIGIN + IFRAME_PATH; - const iframe = await load_iframe(src, "languageDetector"); + const iframe = await load_iframe(src, "language-detector"); assert_in_array( await run_iframe_test(iframe, "LanguageDetectorAvailability"), diff --git a/tests/wpt/tests/ai/language_detection/detector-locale.https.tentative.any.js b/tests/wpt/tests/ai/language_detection/detector-locale.https.window.js similarity index 66% rename from tests/wpt/tests/ai/language_detection/detector-locale.https.tentative.any.js rename to tests/wpt/tests/ai/language_detection/detector-locale.https.window.js index 1991ac3ecfb..80cbfa485e3 100644 --- a/tests/wpt/tests/ai/language_detection/detector-locale.https.tentative.any.js +++ b/tests/wpt/tests/ai/language_detection/detector-locale.https.window.js @@ -1,5 +1,8 @@ // META: title=Detect english // META: global=window +// META: timeout=long +// META: script=resources/util.js +// META: script=/resources/testdriver.js // META: script=../resources/util.js // META: script=../resources/locale-util.js @@ -45,7 +48,7 @@ promise_test(async t => { async function getExpectedInputLanguages(expectedInputLanguages) { - return (await LanguageDetector.create({expectedInputLanguages})) + return (await createLanguageDetector({expectedInputLanguages})) .expectedInputLanguages; } @@ -63,10 +66,14 @@ function uniqueCount(array) { promise_test(async t => { for (const [languageSubtag, variations] of Object.entries( valid_language_tags)) { - await assert_valid_expected_input_languages(languageSubtag) + if (await getAvailability([languageSubtag]) === 'unavailable') { + continue; + } + + await assert_valid_expected_input_languages(languageSubtag); for (const variation of variations) { - await assert_valid_expected_input_languages(variation) + await assert_valid_expected_input_languages(variation); } const expectedInputLanguages = await getExpectedInputLanguages(variations); @@ -81,3 +88,26 @@ promise_test(async t => { } } }, 'LanguageDetector has valid expectedInputLanguages'); + +function assert_rejects_invalid_expected_input_languages( + t, method, expectedInputLanguages) { + return promise_rejects_js(t, RangeError, method({expectedInputLanguages})); +} + +promise_test(async t => { + for (const languageTag of invalid_language_tags) { + assert_rejects_invalid_expected_input_languages( + t, createLanguageDetector, [languageTag]); + } + assert_rejects_invalid_expected_input_languages( + t, createLanguageDetector, invalid_language_tags); +}, 'LanguageDetector.create() throws RangeError for invalid language tags'); + +promise_test(async t => { + for (const languageTag of invalid_language_tags) { + assert_rejects_invalid_expected_input_languages( + t, LanguageDetector.availability, [languageTag]); + } + assert_rejects_invalid_expected_input_languages( + t, LanguageDetector.availability, invalid_language_tags); +}, 'LanguageDetector.availability() throws RangeError for invalid language tags'); diff --git a/tests/wpt/tests/ai/language_detection/detector.https.tentative.any.js b/tests/wpt/tests/ai/language_detection/detector.https.window.js similarity index 53% rename from tests/wpt/tests/ai/language_detection/detector.https.tentative.any.js rename to tests/wpt/tests/ai/language_detection/detector.https.window.js index bdff2364b0b..379b8741534 100644 --- a/tests/wpt/tests/ai/language_detection/detector.https.tentative.any.js +++ b/tests/wpt/tests/ai/language_detection/detector.https.window.js @@ -1,22 +1,36 @@ // META: title=Detect english -// META: global=window,worker +// META: global=window +// META: timeout=long +// META: script=resources/util.js +// META: script=/resources/testdriver.js // META: script=../resources/util.js +// META: script=../resources/locale-util.js 'use strict'; promise_test(async t => { - // Language detection is available after call to `create()`. - await LanguageDetector.create(); + // Creating the language detector without user activation rejects with + // NotAllowedError. + const createPromise = LanguageDetector.create(); + await promise_rejects_dom(t, 'NotAllowedError', createPromise); + + // Creating the translator with user activation succeeds. + await createLanguageDetector(); + + // Creating it should have switched it to available. const availability = await LanguageDetector.availability(); assert_equals(availability, 'available'); -}, 'Simple LanguageDetector.availability() call'); + + // Now that it is available, we should no longer need user activation. + await LanguageDetector.create(); +}, 'LanguageDetector.create() requires user activation when availability is "downloadable.'); promise_test(async t => { - const detector = await LanguageDetector.create(); - const results = await detector.detect('this string is in English'); - // "en" should be highest confidence. - assert_equals(results[0].detectedLanguage, 'en'); + const detector = await createLanguageDetector(); + const results = await detector.detect('Hello world!'); + // must at least have the 'und' result. + assert_greater_than_equal(results.length, 1); // The last result should be 'und'. const undResult = results.pop(); @@ -25,24 +39,31 @@ promise_test(async t => { let total_confidence_without_und = 0; let last_confidence = 1; - for (const {confidence} of results) { + for (const {detectedLanguage, confidence} of results) { + // All results must be in canonical form. + assert_is_canonical(detectedLanguage); + assert_greater_than(confidence, 0); + assert_greater_than(confidence, undResult.confidence); total_confidence_without_und += confidence; - // Except for 'und', results should be from high to low confidence. + // Except for 'und', results must be from high to low confidence. assert_greater_than_equal(last_confidence, confidence); last_confidence = confidence; } - // Confidences, excluding both 'und' and the last non-'und' result, should be - // less than 0.99. - assert_less_than( - total_confidence_without_und - results.at(-1).confidence, 0.99); + // If we have non-und results, their confidences, excluding the last non-'und' + // result, must be less than 0.99. + if (results.length > 0) { + assert_less_than( + total_confidence_without_und - results.at(-1).confidence, 0.99); + } - // Confidences, including 'und', should add up to 1. - assert_equals(total_confidence_without_und + undResult.confidence, 1); -}, 'Simple LanguageDetector.detect() call'); + // Confidences, including 'und', should be less than or equal to one. + assert_less_than_equal( + total_confidence_without_und + undResult.confidence, 1); +}, 'LanguageDetector.detect() returns valid results'); promise_test(async t => { const error = new Error('CreateMonitorCallback threw an error'); @@ -54,32 +75,32 @@ promise_test(async t => { throw error; } - await promise_rejects_exactly(t, error, LanguageDetector.create({monitor})); + await promise_rejects_exactly(t, error, createLanguageDetector({monitor})); }, 'If monitor throws an error, LanguageDetector.create() rejects with that error'); promise_test(async t => { - testMonitor(LanguageDetector.create); + testMonitor(createLanguageDetector); }, 'LanguageDetector.create() notifies its monitor on downloadprogress'); promise_test(async t => { const controller = new AbortController(); controller.abort(); - const createPromise = LanguageDetector.create({signal: controller.signal}); + const createPromise = createLanguageDetector({signal: controller.signal}); await promise_rejects_dom(t, 'AbortError', createPromise); }, 'LanguageDetector.create() call with an aborted signal.'); promise_test(async t => { await testAbortPromise(t, signal => { - return LanguageDetector.create({signal}); + return createLanguageDetector({signal}); }); -}, 'Aborting LanguageDetector.create().'); +}, 'Aborting createLanguageDetector().'); promise_test(async t => { - const detector = await LanguageDetector.create(); + const detector = await createLanguageDetector(); - const text = 'this string is in English'; + const text = 'Hello world!'; const promises = [detector.detect(text), detector.measureInputUsage(text)]; detector.destroy(); @@ -93,9 +114,9 @@ promise_test(async t => { promise_test(async t => { const controller = new AbortController(); - const detector = await LanguageDetector.create({signal: controller.signal}); + const detector = await createLanguageDetector({signal: controller.signal}); - const text = 'this string is in English'; + const text = 'Hello world!'; const promises = [detector.detect(text), detector.measureInputUsage(text)]; const error = new Error('The create abort signal was aborted.'); @@ -112,35 +133,36 @@ promise_test(async t => { const controller = new AbortController(); controller.abort(); - const detector = await LanguageDetector.create(); + const detector = await createLanguageDetector(); const detectPromise = - detector.detect('this string is in English', {signal: controller.signal}); + detector.detect('Hello world!', {signal: controller.signal}); await promise_rejects_dom(t, 'AbortError', detectPromise); }, 'LanguageDetector.detect() call with an aborted signal.'); promise_test(async t => { - const detector = await LanguageDetector.create(); + const detector = await createLanguageDetector(); await testAbortPromise(t, signal => { - return detector.detect('this string is in English', {signal}); + return detector.detect('Hello world!', {signal}); }); }, 'Aborting LanguageDetector.detect().'); promise_test(async t => { - const detector = await LanguageDetector.create(); + const detector = await createLanguageDetector(); - const text = 'this string is in English'; - const inputUsage = await detector.measureInputUsage(text); + const text = 'Hello world!'; + const largeText = text.repeat(10000); + const inputUsage = await detector.measureInputUsage(largeText); assert_greater_than_equal(detector.inputQuota, 0); assert_greater_than_equal(inputUsage, 0); const detectPromise = detector.detect(text); - if (inputUsage < detector.inputQuota) { - assert_equals((await detectPromise)[0].detectedLanguage, 'en'); - } else { + if (inputUsage >= detector.inputQuota) { await promise_rejects_dom(t, 'QuotaExceededError', detectPromise); + } else { + await detectPromise; } }, 'LanguageDetector.measureInputUsage() and inputQuota basic usage.'); @@ -148,7 +170,7 @@ promise_test(async t => { const controller = new AbortController(); controller.abort(); - const detector = await LanguageDetector.create(); + const detector = await createLanguageDetector(); const measureInputUsagePromise = detector.measureInputUsage('hello', {signal: controller.signal}); @@ -156,15 +178,22 @@ promise_test(async t => { }, 'LanguageDetector.measureInputUsage() call with an aborted signal.'); promise_test(async t => { - const detector = await LanguageDetector.create(); + const detector = await createLanguageDetector(); await testAbortPromise(t, signal => { return detector.measureInputUsage('hello', {signal}); }); }, 'Aborting LanguageDetector.measureInputUsage().'); promise_test(async () => { - const expectedLanguages = ['en', 'es']; - const detector = await LanguageDetector.create( - {expectedInputLanguages: expectedLanguages}); - assert_array_equals(detector.expectedInputLanguages, expectedLanguages); -}, 'Creating LanguageDetector with expectedInputLanguages'); + const detector = await createLanguageDetector({expectedInputLanguages: []}); + assert_equals(detector.expectedInputLanguages, null); +}, 'Creating LanguageDetector with empty expectedInputLanguages array'); + +promise_test(async () => { + const detector = await createLanguageDetector(); + assert_equals(detector.expectedInputLanguages, null); +}, 'Creating LanguageDetector without expectedInputLanguages'); + +promise_test(async t => { + await testCreateMonitorWithAbort(t, createLanguageDetector); +}, 'Progress events are not emitted after aborted.'); diff --git a/tests/wpt/tests/ai/language_detection/detector.optional.https.window.js b/tests/wpt/tests/ai/language_detection/detector.optional.https.window.js new file mode 100644 index 00000000000..1dd248a9eca --- /dev/null +++ b/tests/wpt/tests/ai/language_detection/detector.optional.https.window.js @@ -0,0 +1,54 @@ +// META: title=Detect english +// META: global=window +// META: timeout=long +// META: script=resources/util.js +// META: script=/resources/testdriver.js +// META: script=../resources/util.js + +'use strict'; + +async function assert_detects_correct_language( + detector, input, expectedLanguage) { + const results = await detector.detect(input); + // The highest confidence language should be + assert_equals(results[0].detectedLanguage, expectedLanguage); +} + +promise_test(async t => { + const testInput = { + af: 'Dit is \'n voorbeeldsin.', + el: 'Αυτή είναι μια παραδειγματική πρόταση.', + 'el-Latn': 'Aete einai mia paratheiymatike protase.', + en: 'This is an example sentence.', + es: 'Esta es una oración de ejemplo.', + fr: 'Ceci est un exemple de phrase.', + hi: 'यह एक उदाहरण वाक्य है.', + 'hi-Latn': 'yh ek udaahrn vaaky hai.', + it: 'Questa è una frase di esempio.', + ja: 'これは例文です。', + 'ja-Latn': 'Kore wa reibundesu.', + ko: '이것은 예문입니다.', + mi: 'He tauira rerenga korero tenei.', + nl: 'Dit is een voorbeeldzin.', + ru: 'Это пример предложения.', + sr: 'Ово је пример реченице.', + tr: 'Bu bir örnek cümledir.', + zh: '这是一个例句。', + zu: 'Lona umusho oyisibonelo.', + } + + const expectedInputLanguages = Object.keys(testInput); + + const detector = await createLanguageDetector({expectedInputLanguages}); + + for (const [language, input] of Object.entries(testInput)) { + await assert_detects_correct_language(detector, input, language); + } +}, 'LanguageDetector.detect() detects the correct language'); + +promise_test(async () => { + const expectedInputLanguages = ['en', 'es']; + const detector = await createLanguageDetector({expectedInputLanguages}); + assert_array_equals(detector.expectedInputLanguages, expectedInputLanguages); + assert_true(Object.isFrozen(detector.expectedInputLanguages)); +}, 'Creating LanguageDetector with expectedInputLanguages'); diff --git a/tests/wpt/tests/ai/language_detection/resources/iframe-helper.html b/tests/wpt/tests/ai/language_detection/resources/iframe-helper.html index fe07de3f8ab..35ba8525587 100644 --- a/tests/wpt/tests/ai/language_detection/resources/iframe-helper.html +++ b/tests/wpt/tests/ai/language_detection/resources/iframe-helper.html @@ -1,18 +1,28 @@ + + + diff --git a/tests/wpt/tests/ai/language_detection/resources/util.js b/tests/wpt/tests/ai/language_detection/resources/util.js new file mode 100644 index 00000000000..7cb3e7c2e13 --- /dev/null +++ b/tests/wpt/tests/ai/language_detection/resources/util.js @@ -0,0 +1,4 @@ +async function createLanguageDetector(options = {}) { + await test_driver.bless(); + return await LanguageDetector.create(options); +} diff --git a/tests/wpt/tests/ai/resources/locale-util.js b/tests/wpt/tests/ai/resources/locale-util.js index 87d32ae620b..cacb11f4f25 100644 --- a/tests/wpt/tests/ai/resources/locale-util.js +++ b/tests/wpt/tests/ai/resources/locale-util.js @@ -7,6 +7,13 @@ const valid_language_tags = { 'en-Latn-fonipa-scouse', 'en-Latn-GB-fonipa-scouse', 'en-Latn-x-this-is-a-private-use-extensio-n', + 'EN', + 'en-lATN', + 'EN-lATN-gb', + 'EN-gb', + 'EN-scouse-fonipa', + 'EN-lATN-scouse-fonipa', + 'EN-lATN-gb-scouse-fonipa', ], es: [ 'es-419', @@ -15,6 +22,16 @@ const valid_language_tags = { ], }; +const invalid_language_tags = [ + 'e', + 'Latn', + 'enLatnGBfonipa', + '11', + 'en_Latn', + 'en-Lat', + 'en-A999', +]; + function assert_is_canonical(language_tag) { const locale = new Intl.Locale(language_tag); assert_equals(locale.toString(), language_tag); diff --git a/tests/wpt/tests/ai/resources/util.js b/tests/wpt/tests/ai/resources/util.js index 0cbdc68e260..84507a409e9 100644 --- a/tests/wpt/tests/ai/resources/util.js +++ b/tests/wpt/tests/ai/resources/util.js @@ -3,6 +3,12 @@ const kValidAvailabilities = const kAvailableAvailabilities = ['downloadable', 'downloading', 'available']; const kTestPrompt = 'Please write a sentence in English.'; +const kTestContext = 'This is a test; this is only a test.'; + +const getId = (() => { + let idCount = 0; + return () => idCount++; +})(); // Takes an array of dictionaries mapping keys to value arrays, e.g.: // [ {Shape: ["Square", "Circle", undefined]}, {Count: [1, 2]} ] @@ -11,41 +17,39 @@ const kTestPrompt = 'Please write a sentence in English.'; // {Shape: "Circle", Count: 1}, {Shape: "Circle", Count: 2}, // {Shape: undefined, Count: 1}, {Shape: undefined, Count: 2} ] // Omits dictionary members when the value is undefined; supports array values. -const generateOptionCombinations = - (optionsSpec) => { - // 1. Extract keys from the input specification. - const keys = optionsSpec.map(o => Object.keys(o)[0]); - // 2. Extract the arrays of possible values for each key. - const valueArrays = optionsSpec.map(o => Object.values(o)[0]); - // 3. Compute the Cartesian product of the value arrays using reduce. - const valueCombinations = - valueArrays.reduce((accumulator, currentValues) => { - // Init the empty accumulator (first iteration), with single-element - // arrays. - if (accumulator.length === 0) { - return currentValues.map(value => [value]); - } - // Otherwise, expand existing combinations with current values. - return accumulator.flatMap( - existingCombo => currentValues.map( - currentValue => [...existingCombo, currentValue])); - }, []); - - // 4. Map each value combination to a result dictionary, skipping - // undefined. - return valueCombinations.map(combination => { - const result = {}; - keys.forEach((key, index) => { - if (combination[index] !== undefined) { - result[key] = combination[index]; - } - }); - return result; - }); +function generateOptionCombinations(optionsSpec) { + // 1. Extract keys from the input specification. + const keys = optionsSpec.map(o => Object.keys(o)[0]); + // 2. Extract the arrays of possible values for each key. + const valueArrays = optionsSpec.map(o => Object.values(o)[0]); + // 3. Compute the Cartesian product of the value arrays using reduce. + const valueCombinations = valueArrays.reduce((accumulator, currentValues) => { + // Init the empty accumulator (first iteration), with single-element + // arrays. + if (accumulator.length === 0) { + return currentValues.map(value => [value]); } + // Otherwise, expand existing combinations with current values. + return accumulator.flatMap( + existingCombo => currentValues.map( + currentValue => [...existingCombo, currentValue])); + }, []); + + // 4. Map each value combination to a result dictionary, skipping + // undefined. + return valueCombinations.map(combination => { + const result = {}; + keys.forEach((key, index) => { + if (combination[index] !== undefined) { + result[key] = combination[index]; + } + }); + return result; + }); +} // The method should take the AbortSignal as an option and return a promise. -const testAbortPromise = async (t, method) => { +async function testAbortPromise(t, method) { // Test abort signal without custom error. { const controller = new AbortController(); @@ -72,9 +76,47 @@ const testAbortPromise = async (t, method) => { } }; +async function testCreateMonitorWithAbortAt( + t, loadedToAbortAt, method, options = {}) { + const {promise: eventPromise, resolve} = Promise.withResolvers(); + let hadEvent = false; + function monitor(m) { + m.addEventListener('downloadprogress', e => { + if (e.loaded != loadedToAbortAt) { + return; + } + + if (hadEvent) { + assert_unreached( + 'This should never be reached since LanguageDetector.create() was aborted.'); + return; + } + + resolve(); + hadEvent = true; + }); + } + + const controller = new AbortController(); + + const createPromise = + method({...options, monitor, signal: controller.signal}); + + await eventPromise; + + const err = new Error('test'); + controller.abort(err); + await promise_rejects_exactly(t, err, createPromise); +} + +async function testCreateMonitorWithAbort(t, method, options = {}) { + await testCreateMonitorWithAbortAt(t, 0, method, options); + await testCreateMonitorWithAbortAt(t, 1, method, options); +} + // The method should take the AbortSignal as an option and return a // ReadableStream. -const testAbortReadableStream = async (t, method) => { +async function testAbortReadableStream(t, method) { // Test abort signal without custom error. { const controller = new AbortController(); @@ -139,3 +181,47 @@ async function testMonitor(createFunc, options = {}) { } return result; } + +function run_iframe_test(iframe, test_name) { + const id = getId(); + iframe.contentWindow.postMessage({id, type: test_name}, '*'); + const {promise, resolve, reject} = Promise.withResolvers(); + window.onmessage = message => { + if (message.data.id !== id) { + return; + } + if (message.data.success) { + resolve(message.data.success); + } else { + reject(message.data.err) + } + }; + return promise; +} + +function load_iframe(src, permission_policy) { + let iframe = document.createElement('iframe'); + const {promise, resolve} = Promise.withResolvers(); + iframe.onload = () => { + resolve(iframe); + }; + iframe.src = src; + iframe.allow = permission_policy; + document.body.appendChild(iframe); + return promise; +} + +async function createSummarizer(options = {}) { + await test_driver.bless(); + return await Summarizer.create(options); +} + +async function createWriter(options = {}) { + await test_driver.bless(); + return await Writer.create(options); +} + +async function createRewriter(options = {}) { + await test_driver.bless(); + return await Rewriter.create(options); +} diff --git a/tests/wpt/tests/ai/rewriter/resources/iframe-helper.html b/tests/wpt/tests/ai/rewriter/resources/iframe-helper.html new file mode 100644 index 00000000000..c9cefebfd74 --- /dev/null +++ b/tests/wpt/tests/ai/rewriter/resources/iframe-helper.html @@ -0,0 +1,25 @@ + + + + + diff --git a/tests/wpt/tests/ai/rewriter/rewriter-abort.tentative.https.window.js b/tests/wpt/tests/ai/rewriter/rewriter-abort.tentative.https.window.js new file mode 100644 index 00000000000..0eb716f398b --- /dev/null +++ b/tests/wpt/tests/ai/rewriter/rewriter-abort.tentative.https.window.js @@ -0,0 +1,35 @@ +// META: title=Rewriter Abort +// META: script=/resources/testdriver.js +// META: script=../resources/util.js +// META: timeout=long + +'use strict'; + +promise_test(async t => { + await testAbortPromise(t, signal => { + return createRewriter({signal: signal}); + }); +}, 'Aborting Rewriter.create().'); + +promise_test(async t => { + const rewriter = await createRewriter(); + await testAbortPromise(t, signal => { + return rewriter.rewrite(kTestPrompt, { signal: signal }); + }); +}, 'Aborting Rewriter.rewrite().'); + +promise_test(async t => { + const rewriter = await createRewriter(); + await testAbortReadableStream(t, signal => { + return rewriter.rewriteStreaming(kTestPrompt, { signal: signal }); + }); +}, 'Aborting Rewriter.rewriteStreaming().'); + +promise_test(async t => { + const rewriter = await createRewriter(); + const controller = new AbortController(); + const streamingResponse = rewriter.rewriteStreaming( + kTestPrompt, { signal: controller.signal }); + for await (const chunk of streamingResponse); // Do nothing + controller.abort(); +}, 'Aborting Rewriter.rewriteStreaming() after finished reading.'); diff --git a/tests/wpt/tests/ai/rewriter/rewriter-availability-available.tentative.https.window.js b/tests/wpt/tests/ai/rewriter/rewriter-availability-available.tentative.https.window.js new file mode 100644 index 00000000000..a2117486e23 --- /dev/null +++ b/tests/wpt/tests/ai/rewriter/rewriter-availability-available.tentative.https.window.js @@ -0,0 +1,34 @@ +// META: title=Rewriter Availability Available +// META: script=../resources/util.js +// META: timeout=long + +'use strict'; + +promise_test(async () => { + const availability = await Rewriter.availability(); + assert_in_array(availability, kAvailableAvailabilities); +}, 'Rewriter.availability() is available with no options'); + +promise_test(async () => { + const availability = await Rewriter.availability({ + tone: 'as-is', + format: 'as-is', + length: 'as-is', + expectedInputLanguages: ['en-GB'], + expectedContextLanguages: ['en'], + outputLanguage: 'en', + }); + assert_in_array(availability, kAvailableAvailabilities); +}, 'Rewriter.availability() returns available with supported options'); + +promise_test(async () => { + const availability = await Rewriter.availability({ + tone: 'as-is', + format: 'as-is', + length: 'as-is', + expectedInputLanguages: ['es'], // not supported + expectedContextLanguages: ['en'], + outputLanguage: 'es', // not supported + }); + assert_equals(availability, 'unavailable'); +}, 'Rewriter.availability() returns unavailable for unsupported languages'); diff --git a/tests/wpt/tests/ai/rewriter/rewriter-availability.tentative.https.window.js b/tests/wpt/tests/ai/rewriter/rewriter-availability.tentative.https.window.js new file mode 100644 index 00000000000..e966b5df31e --- /dev/null +++ b/tests/wpt/tests/ai/rewriter/rewriter-availability.tentative.https.window.js @@ -0,0 +1,31 @@ +// META: title=Rewriter Availability +// META: script=../resources/util.js +// META: timeout=long + +'use strict'; + +promise_test(async () => { + assert_true(!!Rewriter); + assert_equals(typeof Rewriter.availability, 'function'); +}, 'Rewriter.availability() is defined'); + +promise_test(async () => { + const availability = await Rewriter.availability(); + assert_in_array(availability, kValidAvailabilities); +}, 'Rewriter.availability() returns a valid value with no options'); + +promise_test(async () => { + // An array of plausible test option values. + const kCreateOptionsSpec = [ + {tone: [undefined, 'as-is', 'more-formal', 'more-casual']}, + {format: [undefined, 'as-is', 'plain-text', 'markdown']}, + {length: [undefined, 'as-is', 'shorter', 'longer']}, + {expectedInputLanguages: [[], ['en'], ['es'], ['jp', 'fr']]}, + {expectedContextLanguages: [[], ['en'], ['es'], ['jp', 'fr']]}, + {outputLanguage: [undefined, 'en', 'es', 'jp', 'fr']} + ]; + for (const options of generateOptionCombinations(kCreateOptionsSpec)) { + const availability = await Rewriter.availability(options); + assert_in_array(availability, kValidAvailabilities, options); + } +}, 'Rewriter.availability() returns a valid value with plausible options'); diff --git a/tests/wpt/tests/ai/rewriter/rewriter-from-detached-iframe.tentative.https.window.js b/tests/wpt/tests/ai/rewriter/rewriter-from-detached-iframe.tentative.https.window.js new file mode 100644 index 00000000000..d7c473438cf --- /dev/null +++ b/tests/wpt/tests/ai/rewriter/rewriter-from-detached-iframe.tentative.https.window.js @@ -0,0 +1,56 @@ +// META: title=Rewriter Detached Iframe +// META: script=/resources/testdriver.js +// META: script=../resources/util.js +// META: timeout=long + +'use strict'; + +promise_test(async (t) => { + const iframe = document.body.appendChild(document.createElement('iframe')); + await test_driver.bless('Rewriter create()', null, iframe.contentWindow); + iframe.contentWindow.Rewriter.create(); + iframe.remove(); +}, 'Detaching iframe during Rewriter.create() should not leak memory'); + +promise_test(async (t) => { + const iframe = document.body.appendChild(document.createElement('iframe')); + await test_driver.bless('Rewriter create()', null, iframe.contentWindow); + const iframeWindow = iframe.contentWindow; + const iframeDOMException = iframeWindow.DOMException; + const iframeRewriter = iframeWindow.Rewriter; + iframe.remove(); + + await promise_rejects_dom( + t, 'InvalidStateError', iframeDOMException, iframeRewriter.create()); +}, 'Rewriter.create() fails on a detached iframe.'); + +promise_test(async (t) => { + const iframe = document.body.appendChild(document.createElement('iframe')); + await test_driver.bless('Rewriter create()', null, iframe.contentWindow); + const iframeDOMException = iframe.contentWindow.DOMException; + const rewriter = await iframe.contentWindow.Rewriter.create(); + iframe.remove(); + + await promise_rejects_dom( + t, 'InvalidStateError', iframeDOMException, rewriter.rewrite('hello')); +}, 'Rewriter.rewrite() fails on a detached iframe.'); + +promise_test(async (t) => { + const iframe = document.body.appendChild(document.createElement('iframe')); + await test_driver.bless('Rewriter create()', null, iframe.contentWindow); + const iframeWindow = iframe.contentWindow; + const iframeDOMException = iframeWindow.DOMException; + const rewriter = await iframeWindow.Rewriter.create(); + iframe.remove(); + + assert_throws_dom( + 'InvalidStateError', iframeDOMException, () => rewriter.rewriteStreaming('hello')); +}, 'Rewriter.rewriteStreaming() fails on a detached iframe.'); + +promise_test(async (t) => { + const iframe = document.body.appendChild(document.createElement('iframe')); + await test_driver.bless('Rewriter create()', null, iframe.contentWindow); + const rewriter = await iframe.contentWindow.Rewriter.create(); + rewriter.rewrite('hello'); + iframe.remove(); +}, 'Detaching iframe during Rewriter.rewrite() should not leak memory'); diff --git a/tests/wpt/tests/ai/rewriter/rewriter-iframe.tentative.https.html b/tests/wpt/tests/ai/rewriter/rewriter-iframe.tentative.https.html new file mode 100644 index 00000000000..202ee780af7 --- /dev/null +++ b/tests/wpt/tests/ai/rewriter/rewriter-iframe.tentative.https.html @@ -0,0 +1,58 @@ + + + + + + + + + + diff --git a/tests/wpt/tests/ai/rewriter/rewriter.tentative.https.window.js b/tests/wpt/tests/ai/rewriter/rewriter.tentative.https.window.js new file mode 100644 index 00000000000..da3f002a82b --- /dev/null +++ b/tests/wpt/tests/ai/rewriter/rewriter.tentative.https.window.js @@ -0,0 +1,173 @@ +// META: title=Rewriter +// META: script=/resources/testdriver.js +// META: script=../resources/util.js +// META: timeout=long + +'use strict'; + +promise_test(async () => { + assert_true(!!Rewriter); +}, 'Rewriter must be defined.'); + +promise_test(async t => { + // Creating Rewriter without user activation rejects with NotAllowedError. + await promise_rejects_dom(t, 'NotAllowedError', Rewriter.create()); + + // Creating Rewriter with user activation succeeds. + await createRewriter(); + + // Expect available after create. + assert_equals(await Rewriter.availability(), 'available'); + + // Now that it is available, we should no longer need user activation. + await Rewriter.create(); +}, 'Rewriter.create() requires user activation when availability is "downloadable."'); + +promise_test(async () => { + const rewriter = await createRewriter(); + assert_equals(Object.prototype.toString.call(rewriter), '[object Rewriter]'); +}, 'Rewriter.create() must be return a Rewriter.'); + +promise_test(async () => { + await testMonitor(createRewriter); +}, 'Rewriter.create() notifies its monitor on downloadprogress'); + +promise_test(async t => { + await testCreateMonitorWithAbort(t, Rewriter.create); +}, 'Progress events are not emitted after aborted.'); + +promise_test(async () => { + const rewriter = await createRewriter(); + assert_equals(rewriter.sharedContext, ''); + assert_equals(rewriter.tone, 'as-is'); + assert_equals(rewriter.format, 'as-is'); + assert_equals(rewriter.length, 'as-is'); +}, 'Rewriter.create() default values.'); + +promise_test(async () => { + const sharedContext = 'This is a shared context string'; + const rewriter = await createRewriter({sharedContext: sharedContext}); + assert_equals(rewriter.sharedContext, sharedContext); +}, 'Rewriter.sharedContext'); + +promise_test(async () => { + const rewriter = await createRewriter({tone: 'more-formal'}); + assert_equals(rewriter.tone, 'more-formal'); +}, 'Creating a Rewriter with "more-formal" tone'); + +promise_test(async () => { + const rewriter = await createRewriter({tone: 'more-casual'}); + assert_equals(rewriter.tone, 'more-casual'); +}, 'Creating a Rewriter with "more-casual" tone'); + +promise_test(async () => { + const rewriter = await createRewriter({format: 'plain-text'}); + assert_equals(rewriter.format, 'plain-text'); +}, 'Creating a Rewriter with "plain-text" format'); + +promise_test(async () => { + const rewriter = await createRewriter({format: 'markdown'}); + assert_equals(rewriter.format, 'markdown'); +}, 'Creating a Rewriter with "markdown" format'); + +promise_test(async () => { + const rewriter = await createRewriter({length: 'shorter'}); + assert_equals(rewriter.length, 'shorter'); +}, 'Creating a Rewriter with "shorter" length'); + +promise_test(async () => { + const rewriter = await createRewriter({length: 'longer'}); + assert_equals(rewriter.length, 'longer'); +}, 'Creating a Rewriter with "longer" length'); + +promise_test(async () => { + const rewriter = await createRewriter({expectedInputLanguages: ['en']}); + assert_array_equals(rewriter.expectedInputLanguages, ['en']); +}, 'Creating a Rewriter with expectedInputLanguages'); + +promise_test(async () => { + const rewriter = await createRewriter({expectedContextLanguages: ['en']}); + assert_array_equals(rewriter.expectedContextLanguages, ['en']); +}, 'Creating a Rewriter with expectedContextLanguages'); + +promise_test(async () => { + const rewriter = await createRewriter({outputLanguage: 'en'}); + assert_equals(rewriter.outputLanguage, 'en'); +}, 'Creating a Rewriter with outputLanguage'); + +promise_test(async () => { + const rewriter = await createRewriter({}); + assert_equals(rewriter.expectedInputLanguages, null); + assert_equals(rewriter.expectedContextLanguages, null); + assert_equals(rewriter.outputLanguage, null); +}, 'Creating a Rewriter without optional attributes'); + +promise_test( + async (t) => { + const rewriter = await createRewriter(); + let result = await rewriter.rewrite(''); + assert_equals(result, ''); + result = await rewriter.rewrite(' '); + assert_equals(result, ' '); + }, + 'Rewriter.rewrite() with an empty input or whitespace returns the ' + + 'original input'); + +promise_test(async (t) => { + const rewriter = await createRewriter(); + const result = await rewriter.rewrite('hello', {context: ' '}); + assert_not_equals(result, ''); +}, 'Rewriter.rewrite() with a whitespace context returns a non-empty result'); + +promise_test(async (t) => { + const rewriter = await createRewriter(); + rewriter.destroy(); + await promise_rejects_dom(t, 'InvalidStateError', rewriter.rewrite('hello')); +}, 'Rewriter.rewrite() fails after destroyed'); + +promise_test(async (t) => { + const rewriter = await createRewriter(); + rewriter.destroy(); + assert_throws_dom( + 'InvalidStateError', () => rewriter.rewriteStreaming('hello')); +}, 'Rewriter.rewriteStreaming() fails after destroyed'); + +promise_test(async () => { + const rewriter = await createRewriter(); + const result = await rewriter.measureInputUsage(kTestPrompt); + assert_greater_than(result, 0); +}, 'Rewriter.measureInputUsage() returns non-empty result'); + +promise_test(async () => { + const rewriter = await createRewriter(); + const result = await rewriter.rewrite(kTestPrompt, {context: kTestContext}); + assert_equals(typeof result, 'string'); +}, 'Simple Rewriter.rewrite() call'); + +promise_test(async () => { + const rewriter = await createRewriter(); + const streamingResponse = + rewriter.rewriteStreaming(kTestPrompt, {context: kTestContext}); + assert_equals( + Object.prototype.toString.call(streamingResponse), + '[object ReadableStream]'); + let result = ''; + for await (const chunk of streamingResponse) { + result += chunk; + } + assert_greater_than(result.length, 0); +}, 'Simple Rewriter.rewriteStreaming() call'); + +promise_test(async () => { + const rewriter = await createRewriter(); + await Promise.all( + [rewriter.rewrite(kTestPrompt), rewriter.rewrite(kTestPrompt)]); +}, 'Multiple Rewriter.rewrite() calls are resolved successfully.'); + +promise_test(async () => { + const rewriter = await createRewriter(); + await Promise.all([ + rewriter.rewriteStreaming(kTestPrompt), + rewriter.rewriteStreaming(kTestPrompt) + ]); +}, 'Multiple Rewriter.rewriteStreaming() calls are resolved successfully.'); diff --git a/tests/wpt/tests/ai/summarizer/resources/iframe-helper.html b/tests/wpt/tests/ai/summarizer/resources/iframe-helper.html index 8db84705a2b..2d417e04f62 100644 --- a/tests/wpt/tests/ai/summarizer/resources/iframe-helper.html +++ b/tests/wpt/tests/ai/summarizer/resources/iframe-helper.html @@ -1,21 +1,26 @@ + + + diff --git a/tests/wpt/tests/ai/summarizer/summarizer-abort.tentative.https.any.js b/tests/wpt/tests/ai/summarizer/summarizer-abort.tentative.https.window.js similarity index 59% rename from tests/wpt/tests/ai/summarizer/summarizer-abort.tentative.https.any.js rename to tests/wpt/tests/ai/summarizer/summarizer-abort.tentative.https.window.js index 64595ea930c..14c89056dfb 100644 --- a/tests/wpt/tests/ai/summarizer/summarizer-abort.tentative.https.any.js +++ b/tests/wpt/tests/ai/summarizer/summarizer-abort.tentative.https.window.js @@ -1,25 +1,25 @@ // META: title=Summarizer Abort -// META: global=window,worker +// META: script=/resources/testdriver.js // META: script=../resources/util.js 'use strict'; promise_test(async t => { await testAbortPromise(t, signal => { - return Summarizer.create({ signal: signal }); + return createSummarizer({signal: signal}); }); -}, "Aborting Summarizer.create()."); +}, 'Aborting Summarizer.create().'); promise_test(async t => { - const session = await Summarizer.create(); + const session = await createSummarizer(); await testAbortPromise(t, signal => { return session.summarize(kTestPrompt, { signal: signal }); }); -}, "Aborting Summarizer.summarize()."); +}, 'Aborting Summarizer.summarize().'); promise_test(async t => { - const session = await Summarizer.create(); + const session = await createSummarizer(); await testAbortReadableStream(t, signal => { return session.summarizeStreaming(kTestPrompt, { signal: signal }); }); -}, "Aborting Summarizer.summarizeStreaming()."); +}, 'Aborting Summarizer.summarizeStreaming().'); diff --git a/tests/wpt/tests/ai/summarizer/summarizer-availability-available.tentative.https.any.js b/tests/wpt/tests/ai/summarizer/summarizer-availability-available.tentative.https.window.js similarity index 96% rename from tests/wpt/tests/ai/summarizer/summarizer-availability-available.tentative.https.any.js rename to tests/wpt/tests/ai/summarizer/summarizer-availability-available.tentative.https.window.js index 31c21ca777f..f569cb43b0f 100644 --- a/tests/wpt/tests/ai/summarizer/summarizer-availability-available.tentative.https.any.js +++ b/tests/wpt/tests/ai/summarizer/summarizer-availability-available.tentative.https.window.js @@ -1,4 +1,5 @@ // META: title=Summarizer Availability Available +// META: script=/resources/testdriver.js // META: script=../resources/util.js // META: timeout=long diff --git a/tests/wpt/tests/ai/summarizer/summarizer-availability.tentative.https.any.js b/tests/wpt/tests/ai/summarizer/summarizer-availability.tentative.https.window.js similarity index 96% rename from tests/wpt/tests/ai/summarizer/summarizer-availability.tentative.https.any.js rename to tests/wpt/tests/ai/summarizer/summarizer-availability.tentative.https.window.js index 8691765f693..85282959f9e 100644 --- a/tests/wpt/tests/ai/summarizer/summarizer-availability.tentative.https.any.js +++ b/tests/wpt/tests/ai/summarizer/summarizer-availability.tentative.https.window.js @@ -1,4 +1,5 @@ // META: title=Summarizer Availability +// META: script=/resources/testdriver.js // META: script=../resources/util.js // META: timeout=long diff --git a/tests/wpt/tests/ai/summarizer/summarizer-create-available.tentative.https.any.js b/tests/wpt/tests/ai/summarizer/summarizer-create-available.tentative.https.window.js similarity index 73% rename from tests/wpt/tests/ai/summarizer/summarizer-create-available.tentative.https.any.js rename to tests/wpt/tests/ai/summarizer/summarizer-create-available.tentative.https.window.js index c7e27d9766b..1f108ddd021 100644 --- a/tests/wpt/tests/ai/summarizer/summarizer-create-available.tentative.https.any.js +++ b/tests/wpt/tests/ai/summarizer/summarizer-create-available.tentative.https.window.js @@ -1,11 +1,12 @@ // META: title=Summarizer Create Available +// META: script=/resources/testdriver.js // META: script=../resources/util.js // META: timeout=long 'use strict'; promise_test(async () => { - const summarizer = await Summarizer.create(); + const summarizer = await createSummarizer(); assert_equals(typeof summarizer, 'object'); assert_equals(typeof summarizer.summarize, 'function'); @@ -29,54 +30,52 @@ promise_test(async () => { }, 'Summarizer.create() returns a valid object with default options'); promise_test(async () => { - const summarizer = await testMonitor(Summarizer.create); + const summarizer = await testMonitor(createSummarizer); assert_equals(typeof summarizer, 'object'); }, 'Summarizer.create() notifies its monitor on downloadprogress'); +promise_test(async t => { + await testCreateMonitorWithAbort(t, Summarizer.create); +}, 'Progress events are not emitted after aborted.'); + promise_test(async () => { const sharedContext = 'This is a shared context string'; - const summarizer = await Summarizer.create({sharedContext: sharedContext}); + const summarizer = await createSummarizer({sharedContext: sharedContext}); assert_equals(summarizer.sharedContext, sharedContext); }, 'Summarizer.sharedContext'); promise_test(async () => { - const summarizer = await Summarizer.create({type: 'headline'}); + const summarizer = await createSummarizer({type: 'headline'}); assert_equals(summarizer.type, 'headline'); }, 'Summarizer.type'); promise_test(async () => { - const summarizer = await Summarizer.create({format: 'plain-text'}); + const summarizer = await createSummarizer({format: 'plain-text'}); assert_equals(summarizer.format, 'plain-text'); }, 'Summarizer.format'); promise_test(async () => { - const summarizer = await Summarizer.create({length: 'medium'}); + const summarizer = await createSummarizer({length: 'medium'}); assert_equals(summarizer.length, 'medium'); }, 'Summarizer.length'); promise_test(async () => { - const summarizer = await Summarizer.create({ - expectedInputLanguages: ['en'] - }); + const summarizer = await createSummarizer({expectedInputLanguages: ['en']}); assert_array_equals(summarizer.expectedInputLanguages, ['en']); }, 'Summarizer.expectedInputLanguages'); promise_test(async () => { - const summarizer = await Summarizer.create({ - expectedContextLanguages: ['en'] - }); + const summarizer = await createSummarizer({expectedContextLanguages: ['en']}); assert_array_equals(summarizer.expectedContextLanguages, ['en']); }, 'Summarizer.expectedContextLanguages'); promise_test(async () => { - const summarizer = await Summarizer.create({ - outputLanguage: 'en' - }); + const summarizer = await createSummarizer({outputLanguage: 'en'}); assert_equals(summarizer.outputLanguage, 'en'); }, 'Summarizer.outputLanguage'); promise_test(async () => { - const summarizer = await Summarizer.create(); + const summarizer = await createSummarizer(); assert_equals(summarizer.expectedInputLanguages, null); assert_equals(summarizer.expectedContextLanguages, null); assert_equals(summarizer.outputLanguage, null); diff --git a/tests/wpt/tests/ai/summarizer/summarizer-create.tentative.https.any.js b/tests/wpt/tests/ai/summarizer/summarizer-create.tentative.https.any.js deleted file mode 100644 index 328cceefefb..00000000000 --- a/tests/wpt/tests/ai/summarizer/summarizer-create.tentative.https.any.js +++ /dev/null @@ -1,10 +0,0 @@ -// META: title=Summarizer Create -// META: script=../resources/util.js -// META: timeout=long - -'use strict'; - -promise_test(async () => { - assert_true(!!Summarizer); - assert_equals(typeof Summarizer.create, 'function'); -}, 'Summarizer.create() is defined'); diff --git a/tests/wpt/tests/ai/summarizer/summarizer-create.tentative.https.window.js b/tests/wpt/tests/ai/summarizer/summarizer-create.tentative.https.window.js new file mode 100644 index 00000000000..d9df70cc2e7 --- /dev/null +++ b/tests/wpt/tests/ai/summarizer/summarizer-create.tentative.https.window.js @@ -0,0 +1,25 @@ +// META: title=Summarizer Create +// META: script=/resources/testdriver.js +// META: script=../resources/util.js +// META: timeout=long + +'use strict'; + +promise_test(async () => { + assert_true(!!Summarizer); + assert_equals(typeof Summarizer.create, 'function'); +}, 'Summarizer.create() is defined'); + +promise_test(async t => { + // Creating Summarizer without user activation rejects with NotAllowedError. + await promise_rejects_dom(t, 'NotAllowedError', Summarizer.create()); + + // Creating Summarizer with user activation succeeds. + await createSummarizer(); + + // Expect available after create. + assert_equals(await Summarizer.availability(), 'available'); + + // Now that it is available, we should no longer need user activation. + await Summarizer.create(); +}, 'Summarizer.create() requires user activation when availability is "downloadable."'); diff --git a/tests/wpt/tests/ai/summarizer/summarizer-iframe.tentative.https.html b/tests/wpt/tests/ai/summarizer/summarizer-iframe.tentative.https.html index 219811c8831..9de80e1fd91 100644 --- a/tests/wpt/tests/ai/summarizer/summarizer-iframe.tentative.https.html +++ b/tests/wpt/tests/ai/summarizer/summarizer-iframe.tentative.https.html @@ -1,7 +1,11 @@ + + + + diff --git a/tests/wpt/tests/ai/summarizer/summarizer-measureInputUsage.tentative.https.any.js b/tests/wpt/tests/ai/summarizer/summarizer-measureInputUsage.tentative.https.window.js similarity index 79% rename from tests/wpt/tests/ai/summarizer/summarizer-measureInputUsage.tentative.https.any.js rename to tests/wpt/tests/ai/summarizer/summarizer-measureInputUsage.tentative.https.window.js index ce9745acf17..8368096f1f3 100644 --- a/tests/wpt/tests/ai/summarizer/summarizer-measureInputUsage.tentative.https.any.js +++ b/tests/wpt/tests/ai/summarizer/summarizer-measureInputUsage.tentative.https.window.js @@ -1,11 +1,12 @@ // META: title=Summarizer measureInputUsage +// META: script=/resources/testdriver.js // META: script=../resources/util.js // META: timeout=long 'use strict'; promise_test(async () => { - const summarizer = await Summarizer.create(); + const summarizer = await createSummarizer(); const result = await summarizer.measureInputUsage(kTestPrompt); assert_equals(typeof result, 'number'); assert_greater_than(result, 0); diff --git a/tests/wpt/tests/ai/summarizer/summarizer-summarize-streaming.tentative.https.any.js b/tests/wpt/tests/ai/summarizer/summarizer-summarize-streaming.tentative.https.window.js similarity index 70% rename from tests/wpt/tests/ai/summarizer/summarizer-summarize-streaming.tentative.https.any.js rename to tests/wpt/tests/ai/summarizer/summarizer-summarize-streaming.tentative.https.window.js index b88ccb325e2..59aa01aeb31 100644 --- a/tests/wpt/tests/ai/summarizer/summarizer-summarize-streaming.tentative.https.any.js +++ b/tests/wpt/tests/ai/summarizer/summarizer-summarize-streaming.tentative.https.window.js @@ -1,11 +1,12 @@ // META: title=Summarizer Summarize Streaming +// META: script=/resources/testdriver.js // META: script=../resources/util.js // META: timeout=long 'use strict'; promise_test(async t => { - const summarizer = await Summarizer.create(); + const summarizer = await createSummarizer(); const streamingResponse = summarizer.summarizeStreaming( "The web-platform-tests Project is a cross-browser test suite for the Web-platform stack. Writing tests in a way that allows them to be run in all browsers gives browser projects confidence that they are shipping software that is compatible with other implementations, and that later implementations will be compatible with their implementations. This in turn gives Web authors/developers confidence that they can actually rely on the Web platform to deliver on the promise of working across browsers and devices without needing extra layers of abstraction to paper over the gaps left by specification editors and implementors."); assert_equals( @@ -19,13 +20,13 @@ promise_test(async t => { if (done) { break; } - result = value; + result += value; } assert_greater_than(result.length, 0); -}, 'Summarizer.summarizeStreaming returns ReadableStream with a non-empty text.'); +}, 'Summarizer.summarizeStreaming() returns ReadableStream with a non-empty text.'); promise_test(async t => { - const summarizer = await Summarizer.create(); + const summarizer = await createSummarizer(); const streamingResponse = summarizer.summarizeStreaming(""); assert_equals( Object.prototype.toString.call(streamingResponse), @@ -33,4 +34,12 @@ promise_test(async t => { ); const { result, done } = await streamingResponse.getReader().read(); assert_true(done); -}, 'Summarizer.summarizeStreaming returns a ReadableStream without any chunk on an empty input.'); +}, 'Summarizer.summarizeStreaming() returns a ReadableStream without any chunk on an empty input.'); + +promise_test(async () => { + const summarizer = await createSummarizer(); + await Promise.all([ + summarizer.summarizeStreaming(kTestPrompt), + summarizer.summarizeStreaming(kTestPrompt) + ]); +}, 'Multiple Summarizer.summarizeStreaming() calls are resolved successfully.'); diff --git a/tests/wpt/tests/ai/summarizer/summarizer-summarize.tentative.https.any.js b/tests/wpt/tests/ai/summarizer/summarizer-summarize.tentative.https.any.js deleted file mode 100644 index f019746fbdd..00000000000 --- a/tests/wpt/tests/ai/summarizer/summarizer-summarize.tentative.https.any.js +++ /dev/null @@ -1,12 +0,0 @@ -// META: title=Summarizer Create Available -// META: script=../resources/util.js -// META: timeout=long - -'use strict'; - -promise_test(async () => { - const summarizer = await Summarizer.create(); - const result = await summarizer.summarize(kTestPrompt); - assert_equals(typeof result, 'string'); - assert_greater_than(result.length, 0); -}, 'Summarizer.summarize() returns non-empty result'); diff --git a/tests/wpt/tests/ai/summarizer/summarizer-summarize.tentative.https.window.js b/tests/wpt/tests/ai/summarizer/summarizer-summarize.tentative.https.window.js new file mode 100644 index 00000000000..c54052f8b36 --- /dev/null +++ b/tests/wpt/tests/ai/summarizer/summarizer-summarize.tentative.https.window.js @@ -0,0 +1,21 @@ +// META: title=Summarizer Create Available +// META: script=/resources/testdriver.js +// META: script=../resources/util.js +// META: timeout=long + +'use strict'; + +promise_test(async () => { + const summarizer = await createSummarizer(); + const result = await summarizer.summarize(kTestPrompt); + assert_equals(typeof result, 'string'); + assert_greater_than(result.length, 0); +}, 'Summarizer.summarize() returns non-empty result.'); + +promise_test(async () => { + const summarizer = await createSummarizer(); + await Promise.all([ + summarizer.summarize(kTestPrompt), + summarizer.summarize(kTestPrompt) + ]); +}, 'Multiple Summarizer.summarize() calls are resolved successfully.'); diff --git a/tests/wpt/tests/ai/translator/resources/util.js b/tests/wpt/tests/ai/translator/resources/util.js new file mode 100644 index 00000000000..ad06086a123 --- /dev/null +++ b/tests/wpt/tests/ai/translator/resources/util.js @@ -0,0 +1,4 @@ +async function createTranslator(options) { + await test_driver.bless(); + return await Translator.create(options); +} diff --git a/tests/wpt/tests/ai/translator/translator_bad_input.tentative.https.any.js b/tests/wpt/tests/ai/translator/translator-bad-input.https.window.js similarity index 85% rename from tests/wpt/tests/ai/translator/translator_bad_input.tentative.https.any.js rename to tests/wpt/tests/ai/translator/translator-bad-input.https.window.js index 2879543f29a..db8905a61f6 100644 --- a/tests/wpt/tests/ai/translator/translator_bad_input.tentative.https.any.js +++ b/tests/wpt/tests/ai/translator/translator-bad-input.https.window.js @@ -1,5 +1,5 @@ // META: title=translator.create without options do not crash -// META: global=window,worker +// META: global=window // META: timeout=long // // Setting `timeout=long` as this test may require downloading the translation @@ -8,8 +8,7 @@ 'use strict'; promise_test(async t => { - await promise_rejects_js( - t, TypeError, Translator.create(/*empty options*/)); + await promise_rejects_js(t, TypeError, Translator.create(/*empty options*/)); }, 'Translator.create rejects with TypeError if no options are passed.'); promise_test(async t => { diff --git a/tests/wpt/tests/ai/translator/translator_translate.tentative.https.any.js b/tests/wpt/tests/ai/translator/translator.optional.https.window.js similarity index 81% rename from tests/wpt/tests/ai/translator/translator_translate.tentative.https.any.js rename to tests/wpt/tests/ai/translator/translator.optional.https.window.js index a8aad5e03e1..2a4c5a6c5dd 100644 --- a/tests/wpt/tests/ai/translator/translator_translate.tentative.https.any.js +++ b/tests/wpt/tests/ai/translator/translator.optional.https.window.js @@ -1,21 +1,16 @@ -// META: title=Translate from English to Japanese +// META: title=Translator Translate // META: global=window // META: timeout=long // META: script=../resources/util.js // META: script=../resources/language_codes.js // META: script=/resources/testdriver.js +// META: script=resources/util.js // // Setting `timeout=long` as this test may require downloading the translation // library and the language models. 'use strict'; -async function createTranslator(options) { - return await test_driver.bless('Create translator', async () => { - return await Translator.create(options); - }); -} - promise_test(async t => { const languagePair = {sourceLanguage: 'en', targetLanguage: 'ja'}; @@ -62,14 +57,6 @@ promise_test(async t => { assert_equals(translator.targetLanguage, 'ja'); }, 'Translator: sourceLanguage and targetLanguage are equal to their respective option passed in to Translator.create.'); -promise_test(async (t) => { - const translator = - await createTranslator({sourceLanguage: 'en', targetLanguage: 'ja'}); - translator.destroy(); - await promise_rejects_dom( - t, 'InvalidStateError', translator.translate('hello')); -}, 'Translator.translate() fails after destroyed'); - promise_test(async t => { const controller = new AbortController(); controller.abort(); @@ -107,11 +94,53 @@ promise_test(async t => { }); }, 'Aborting Translator.translate().'); +promise_test(async t => { + const translator = + await createTranslator({sourceLanguage: 'en', targetLanguage: 'ja'}); + + const text = 'this string is in English'; + const promises = + [translator.translate(text), translator.measureInputUsage(text)]; + + translator.destroy(); + + promises.push(translator.translate(text), translator.measureInputUsage(text)); + + for (const promise of promises) { + await promise_rejects_dom(t, 'AbortError', promise); + } +}, 'Calling Translator.destroy() aborts calls to translate and measureInputUsage.'); + +promise_test(async t => { + const controller = new AbortController(); + const translator = await createTranslator( + {sourceLanguage: 'en', targetLanguage: 'ja', signal: controller.signal}); + + const text = 'this string is in English'; + const promises = + [translator.translate(text), translator.measureInputUsage(text)]; + + const error = new Error('The create abort signal was aborted.'); + controller.abort(error); + + promises.push(translator.translate(text), translator.measureInputUsage(text)); + + for (const promise of promises) { + await promise_rejects_exactly(t, error, promise); + } +}, 'Translator.create()\'s abort signal destroys its Translator after creation.'); + + promise_test(async t => { await testMonitor( createTranslator, {sourceLanguage: 'en', targetLanguage: 'ja'}); }, 'Translator.create() notifies its monitor on downloadprogress'); +promise_test(async t => { + await testCreateMonitorWithAbort( + t, createTranslator, {sourceLanguage: 'en', targetLanguage: 'ja'}); +}, 'Progress events are not emitted after aborted.'); + promise_test(async t => { const translator = await createTranslator({sourceLanguage: 'en', targetLanguage: 'ja'}); @@ -141,7 +170,7 @@ promise_test(async t => { for (let i = 0; i < translatableStrings.length; i++) { assert_not_equals(translatedTranslatableString[i], translatableStrings[i]); } -}, 'Translator.translate() echos non-translatable content'); +}, 'Translator.translate() echoes non-translatable content'); promise_test(async t => { const translator = diff --git a/tests/wpt/tests/ai/writer/resources/iframe-helper.html b/tests/wpt/tests/ai/writer/resources/iframe-helper.html new file mode 100644 index 00000000000..bca56e1ecfd --- /dev/null +++ b/tests/wpt/tests/ai/writer/resources/iframe-helper.html @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/tests/wpt/tests/ai/writer/writer-abort.tentative.https.window.js b/tests/wpt/tests/ai/writer/writer-abort.tentative.https.window.js new file mode 100644 index 00000000000..bb877f6d73b --- /dev/null +++ b/tests/wpt/tests/ai/writer/writer-abort.tentative.https.window.js @@ -0,0 +1,37 @@ +// META: title=Writer Abort +// META: script=/resources/testdriver.js +// META: script=../resources/util.js +// META: timeout=long + +'use strict'; + +promise_test(async t => { + await testAbortPromise(t, signal => { + return createWriter({signal: signal}); + }); +}, 'Aborting Writer.create().'); + +promise_test(async t => { + const writer = await createWriter(); + await testAbortPromise(t, signal => { + return writer.write(kTestPrompt, { signal: signal }); + }); +}, 'Aborting Writer.write().'); + +promise_test(async t => { + const writer = await createWriter(); + await testAbortReadableStream(t, signal => { + return writer.writeStreaming(kTestPrompt, { signal: signal }); + }); +}, 'Aborting Writer.writeStreaming().'); + +promise_test(async (t) => { + const writer = await createWriter(); + const controller = new AbortController(); + const streamingResponse = writer.writeStreaming(kTestPrompt, { + signal: controller.signal, + context: kTestContext, + }); + for await (const chunk of streamingResponse); // Do nothing + controller.abort(); +}, 'Aborting Writer.writeStreaming() after finished reading.'); diff --git a/tests/wpt/tests/ai/writer/writer-availability-available.tentative.https.window.js b/tests/wpt/tests/ai/writer/writer-availability-available.tentative.https.window.js new file mode 100644 index 00000000000..0e3106a14c2 --- /dev/null +++ b/tests/wpt/tests/ai/writer/writer-availability-available.tentative.https.window.js @@ -0,0 +1,34 @@ +// META: title=Writer Availability Available +// META: script=../resources/util.js +// META: timeout=long + +'use strict'; + +promise_test(async () => { + const availability = await Writer.availability(); + assert_in_array(availability, kAvailableAvailabilities); +}, 'Writer.availability() is available with no options'); + +promise_test(async () => { + const availability = await Writer.availability({ + tone: 'neutral', + format: 'plain-text', + length: 'medium', + expectedInputLanguages: ['en-GB'], + expectedContextLanguages: ['en'], + outputLanguage: 'en', + }); + assert_in_array(availability, kAvailableAvailabilities); +}, 'Writer.availability() returns available with supported options'); + +promise_test(async () => { + const availability = await Writer.availability({ + tone: 'neutral', + format: 'plain-text', + length: 'medium', + expectedInputLanguages: ['es'], // not supported + expectedContextLanguages: ['en'], + outputLanguage: 'es', // not supported + }); + assert_equals(availability, 'unavailable'); +}, 'Writer.availability() returns unavailable for unsupported languages'); diff --git a/tests/wpt/tests/ai/writer/writer-availability.tentative.https.window.js b/tests/wpt/tests/ai/writer/writer-availability.tentative.https.window.js new file mode 100644 index 00000000000..a68b7d35e64 --- /dev/null +++ b/tests/wpt/tests/ai/writer/writer-availability.tentative.https.window.js @@ -0,0 +1,31 @@ +// META: title=Writer Availability +// META: script=../resources/util.js +// META: timeout=long + +'use strict'; + +promise_test(async () => { + assert_true(!!Writer); + assert_equals(typeof Writer.availability, 'function'); +}, 'Writer.availability() is defined'); + +promise_test(async () => { + const availability = await Writer.availability(); + assert_in_array(availability, kValidAvailabilities); +}, 'Writer.availability() returns a valid value with no options'); + +promise_test(async () => { + // An array of plausible test option values. + const kCreateOptionsSpec = [ + {tone: [undefined, 'formal', 'neutral', 'casual']}, + {format: [undefined, 'plain-text', 'markdown']}, + {length: [undefined, 'short', 'medium', 'long']}, + {expectedInputLanguages: [[], ['en'], ['es'], ['jp', 'fr']]}, + {expectedContextLanguages: [[], ['en'], ['es'], ['jp', 'fr']]}, + {outputLanguage: [undefined, 'en', 'es', 'jp', 'fr']} + ]; + for (const options of generateOptionCombinations(kCreateOptionsSpec)) { + const availability = await Writer.availability(options); + assert_in_array(availability, kValidAvailabilities, options); + } +}, 'Writer.availability() returns a valid value with plausible options'); diff --git a/tests/wpt/tests/ai/writer/writer-from-detached-iframe.tentative.https.window.js b/tests/wpt/tests/ai/writer/writer-from-detached-iframe.tentative.https.window.js new file mode 100644 index 00000000000..c63d8bb77c9 --- /dev/null +++ b/tests/wpt/tests/ai/writer/writer-from-detached-iframe.tentative.https.window.js @@ -0,0 +1,56 @@ +// META: title=Writer Detached Iframe +// META: script=/resources/testdriver.js +// META: script=../resources/util.js +// META: timeout=long + +'use strict'; + +promise_test(async (t) => { + const iframe = document.body.appendChild(document.createElement('iframe')); + await test_driver.bless('Writer create()', null, iframe.contentWindow); + iframe.contentWindow.Writer.create(); + iframe.remove(); +}, 'Detaching iframe during Writer.create() should not leak memory'); + +promise_test(async (t) => { + const iframe = document.body.appendChild(document.createElement('iframe')); + await test_driver.bless('Writer create()', null, iframe.contentWindow); + const iframeWindow = iframe.contentWindow; + const iframeDOMException = iframeWindow.DOMException; + const iframeWriter = iframeWindow.Writer; + iframe.remove(); + + await promise_rejects_dom( + t, 'InvalidStateError', iframeDOMException, iframeWriter.create()); +}, 'Writer.create() fails on a detached iframe.'); + +promise_test(async (t) => { + const iframe = document.body.appendChild(document.createElement('iframe')); + await test_driver.bless('Writer create()', null, iframe.contentWindow); + const iframeDOMException = iframe.contentWindow.DOMException; + const writer = await iframe.contentWindow.Writer.create(); + iframe.remove(); + + await promise_rejects_dom( + t, 'InvalidStateError', iframeDOMException, writer.write('hello')); +}, 'Writer.write() fails on a detached iframe.'); + +promise_test(async (t) => { + const iframe = document.body.appendChild(document.createElement('iframe')); + await test_driver.bless('Writer create()', null, iframe.contentWindow); + const iframeWindow = iframe.contentWindow; + const iframeDOMException = iframeWindow.DOMException; + const writer = await iframeWindow.Writer.create(); + iframe.remove(); + + assert_throws_dom( + 'InvalidStateError', iframeDOMException, () => writer.writeStreaming('hello')); +}, 'Writer.writeStreaming() fails on a detached iframe.'); + +promise_test(async (t) => { + const iframe = document.body.appendChild(document.createElement('iframe')); + await test_driver.bless('Writer create()', null, iframe.contentWindow); + const writer = await iframe.contentWindow.Writer.create(); + writer.write('hello'); + iframe.remove(); +}, 'Detaching iframe during Writer.write() should not leak memory'); diff --git a/tests/wpt/tests/ai/writer/writer-iframe.tentative.https.html b/tests/wpt/tests/ai/writer/writer-iframe.tentative.https.html new file mode 100644 index 00000000000..004ba9ea307 --- /dev/null +++ b/tests/wpt/tests/ai/writer/writer-iframe.tentative.https.html @@ -0,0 +1,58 @@ + + + + + + + + + + diff --git a/tests/wpt/tests/ai/writer/writer.tentative.https.window.js b/tests/wpt/tests/ai/writer/writer.tentative.https.window.js new file mode 100644 index 00000000000..6606f217448 --- /dev/null +++ b/tests/wpt/tests/ai/writer/writer.tentative.https.window.js @@ -0,0 +1,175 @@ +// META: title=Writer +// META: script=/resources/testdriver.js +// META: script=../resources/util.js +// META: timeout=long + +'use strict'; + +promise_test(async () => { + assert_true(!!Writer); +}, 'Writer must be defined.'); + +promise_test(async t => { + // Creating Writer without user activation rejects with NotAllowedError. + await promise_rejects_dom(t, 'NotAllowedError', Writer.create()); + + // Creating Writer with user activation succeeds. + await createWriter(); + + // Expect available after create. + assert_equals(await Writer.availability(), 'available'); + + // Now that it is available, we should no longer need user activation. + await Writer.create(); +}, 'Writer.create() requires user activation when availability is "downloadable."'); + +promise_test(async () => { + const writer = await createWriter(); + assert_equals(Object.prototype.toString.call(writer), '[object Writer]'); +}, 'Writer.create() must be return a Writer.'); + +promise_test(async () => { + await testMonitor(createWriter); +}, 'Writer.create() notifies its monitor on downloadprogress'); + +promise_test(async t => { + await testCreateMonitorWithAbort(t, Writer.create); +}, 'Progress events are not emitted after aborted.'); + +promise_test(async () => { + const writer = await createWriter(); + assert_equals(writer.sharedContext, ''); + assert_equals(writer.tone, 'neutral'); + assert_equals(writer.format, 'plain-text'); + assert_equals(writer.length, 'medium'); +}, 'Writer.create() default values.'); + +promise_test(async (t) => { + const controller = new AbortController(); + controller.abort(); + const createPromise = createWriter({signal: controller.signal}); + await promise_rejects_dom(t, 'AbortError', createPromise); +}, 'Writer.create() call with an aborted signal.'); + +promise_test(async () => { + const sharedContext = 'This is a shared context string'; + const writer = await createWriter({sharedContext: sharedContext}); + assert_equals(writer.sharedContext, sharedContext); +}, 'Writer.sharedContext'); + +promise_test(async () => { + const writer = await createWriter({tone: 'formal'}); + assert_equals(writer.tone, 'formal'); +}, 'Creating a Writer with "formal" tone'); + +promise_test(async () => { + const writer = await createWriter({tone: 'casual'}); + assert_equals(writer.tone, 'casual'); +}, 'Creating a Writer with "casual" tone'); + +promise_test(async () => { + const writer = await createWriter({format: 'markdown'}); + assert_equals(writer.format, 'markdown'); +}, 'Creating a Writer with "markdown" format'); + +promise_test(async () => { + const writer = await createWriter({length: 'short'}); + assert_equals(writer.length, 'short'); +}, 'Creating a Writer with "short" length'); + +promise_test(async () => { + const writer = await createWriter({length: 'long'}); + assert_equals(writer.length, 'long'); +}, 'Creating a Writer with "long" length'); + +promise_test(async () => { + const writer = await createWriter({expectedInputLanguages: ['en']}); + assert_array_equals(writer.expectedInputLanguages, ['en']); +}, 'Creating a Writer with expectedInputLanguages'); + + +promise_test(async (t) => { + promise_rejects_js( + t, RangeError, + createWriter({expectedInputLanguages: ['en-abc-invalid']})); +}, 'Creating a Writer with malformed language string'); + +promise_test(async () => { + const writer = await createWriter({expectedContextLanguages: ['en']}); + assert_array_equals(writer.expectedContextLanguages, ['en']); +}, 'Creating a Writer with expectedContextLanguages'); + +promise_test(async () => { + const writer = await createWriter({outputLanguage: 'en'}); + assert_equals(writer.outputLanguage, 'en'); +}, 'Creating a Writer with outputLanguage'); + +promise_test(async () => { + const writer = await createWriter({}); + assert_equals(writer.expectedInputLanguages, null); + assert_equals(writer.expectedContextLanguages, null); + assert_equals(writer.outputLanguage, null); +}, 'Creating a Writer without optional attributes'); + +promise_test(async (t) => { + const writer = await createWriter(); + let result = await writer.write(''); + assert_equals(result, ''); + result = await writer.write(' '); + assert_equals(result, ''); +}, 'Writer.write() with an empty input or whitespace returns an empty text'); + +promise_test(async (t) => { + const writer = await createWriter(); + const result = await writer.write('hello', {context: ' '}); + assert_not_equals(result, ''); +}, 'Writer.write() with a whitespace context returns a non-empty result'); + +promise_test(async (t) => { + const writer = await createWriter(); + writer.destroy(); + await promise_rejects_dom(t, 'InvalidStateError', writer.write('hello')); +}, 'Writer.write() fails after destroyed'); + +promise_test(async (t) => { + const writer = await createWriter(); + writer.destroy(); + assert_throws_dom('InvalidStateError', () => writer.writeStreaming('hello')); +}, 'Writer.writeStreaming() fails after destroyed'); + +promise_test(async () => { + const writer = await createWriter(); + const result = await writer.measureInputUsage(kTestPrompt); + assert_greater_than(result, 0); +}, 'Writer.measureInputUsage() returns non-empty result'); + +promise_test(async () => { + const writer = await createWriter(); + const result = await writer.write(kTestPrompt, {context: kTestContext}); + assert_equals(typeof result, 'string'); +}, 'Simple Writer.write() call'); + +promise_test(async () => { + const writer = await createWriter(); + const streamingResponse = + writer.writeStreaming(kTestPrompt, {context: kTestContext}); + assert_equals( + Object.prototype.toString.call(streamingResponse), + '[object ReadableStream]'); + let result = ''; + for await (const chunk of streamingResponse) { + result += chunk; + } + assert_greater_than(result.length, 0); +}, 'Simple Writer.writeStreaming() call'); + +promise_test(async () => { + const writer = await createWriter(); + await Promise.all([writer.write(kTestPrompt), writer.write(kTestPrompt)]); +}, 'Multiple Writer.write() calls are resolved successfully.'); + +promise_test(async () => { + const writer = await createWriter(); + await Promise.all( + [writer.writeStreaming(kTestPrompt), writer.writeStreaming(kTestPrompt)]); +}, 'Multiple Writer.writeStreaming() calls are resolved successfully.'); diff --git a/tests/wpt/tests/ambient-light/AmbientLightSensor-iframe-access.https.html b/tests/wpt/tests/ambient-light/AmbientLightSensor-iframe-access.https.html index 765c1bee1f8..c45804a51f7 100644 --- a/tests/wpt/tests/ambient-light/AmbientLightSensor-iframe-access.https.html +++ b/tests/wpt/tests/ambient-light/AmbientLightSensor-iframe-access.https.html @@ -5,7 +5,7 @@ - + diff --git a/tests/wpt/tests/ambient-light/AmbientLightSensor.https.html b/tests/wpt/tests/ambient-light/AmbientLightSensor.https.html index f2ed655082e..65049ed826f 100644 --- a/tests/wpt/tests/ambient-light/AmbientLightSensor.https.html +++ b/tests/wpt/tests/ambient-light/AmbientLightSensor.https.html @@ -6,7 +6,7 @@ - + diff --git a/tests/wpt/tests/audio-output/setSinkId.https.html b/tests/wpt/tests/audio-output/setSinkId.https.html index be65f0ac81b..662596910ee 100644 --- a/tests/wpt/tests/audio-output/setSinkId.https.html +++ b/tests/wpt/tests/audio-output/setSinkId.https.html @@ -26,14 +26,8 @@ promise_test(async t => { const list = await navigator.mediaDevices.enumerateDevices(); assert_greater_than(list.length, 0, "media device list includes at least one device"); - const audioInputList = list.filter(({kind}) => kind == "audioinput"); const outputDevicesList = list.filter(({kind}) => kind == "audiooutput"); - // List of exposed microphone groupIds - const exposedGroupIds = new Set(audioInputList.map(device => device.groupId)); - for (const { deviceId, groupId } of outputDevicesList) { - assert_true(exposedGroupIds.has(groupId), - "audiooutput device groupId must match an exposed microphone"); assert_greater_than(deviceId.length, 0, "deviceId.length"); const p1 = audio.setSinkId(deviceId); diff --git a/tests/wpt/tests/badging/WEB_FEATURES.yml b/tests/wpt/tests/badging/WEB_FEATURES.yml new file mode 100644 index 00000000000..3c4f69200f6 --- /dev/null +++ b/tests/wpt/tests/badging/WEB_FEATURES.yml @@ -0,0 +1,3 @@ +features: +- name: badging + files: "**" diff --git a/tests/wpt/tests/clear-site-data/WEB_FEATURES.yml b/tests/wpt/tests/clear-site-data/WEB_FEATURES.yml new file mode 100644 index 00000000000..d55b5faaf11 --- /dev/null +++ b/tests/wpt/tests/clear-site-data/WEB_FEATURES.yml @@ -0,0 +1,3 @@ +features: +- name: clear-site-data + files: "**" diff --git a/tests/wpt/tests/clear-site-data/support/clear-site-data-prefetchCache.py b/tests/wpt/tests/clear-site-data/support/clear-site-data-prefetchCache.py new file mode 100644 index 00000000000..3a332dd58b2 --- /dev/null +++ b/tests/wpt/tests/clear-site-data/support/clear-site-data-prefetchCache.py @@ -0,0 +1,18 @@ +def main(request, response): + headers = [(b"Content-Type", b"text/html")] + headers += [(b"Clear-Site-Data", b'"prefetchCache"')] + content = f''' + + + {request.url} + ''' + return 200, headers, content diff --git a/tests/wpt/tests/clear-site-data/support/clear-site-data-prerenderCache.py b/tests/wpt/tests/clear-site-data/support/clear-site-data-prerenderCache.py new file mode 100644 index 00000000000..bf845cfb699 --- /dev/null +++ b/tests/wpt/tests/clear-site-data/support/clear-site-data-prerenderCache.py @@ -0,0 +1,18 @@ +def main(request, response): + headers = [(b"Content-Type", b"text/html")] + headers += [(b"Clear-Site-Data", b'"prerenderCache"')] + content = f''' + + + {request.url} + ''' + return 200, headers, content diff --git a/tests/wpt/tests/clipboard-apis/async-navigator-clipboard-basics.https.html b/tests/wpt/tests/clipboard-apis/async-navigator-clipboard-basics.https.html index f7aed80b17e..b71d6665bcb 100644 --- a/tests/wpt/tests/clipboard-apis/async-navigator-clipboard-basics.https.html +++ b/tests/wpt/tests/clipboard-apis/async-navigator-clipboard-basics.https.html @@ -24,56 +24,6 @@ test(() => { assert_equals(navigator.clipboard, navigator.clipboard); }, 'navigator.clipboard exists'); -promise_test(async t => { - await getPermissions(); - const text_plain = "This text was copied using `Clipboard.prototype.write`."; - const html_text = "

Test

"; - await navigator.clipboard.write([ - new ClipboardItem({ - "text/plain": text_plain, - "text/html" : html_text - }), - ]); - }, 'navigator.clipboard.write(DOMString) succeeds'); - -promise_test(async () => { - await getPermissions(); - const promise_text_string = Promise.resolve('hello'); - const promise_html_string = Promise.resolve("

hello

"); - const item = new ClipboardItem({ - 'text/plain': promise_text_string, - 'text/html': promise_html_string - }); - await navigator.clipboard.write([item]); -}, 'navigator.clipboard.write(Promise) succeeds'); - -promise_test(async t => { - await getPermissions(); - const text_plain = 'hello'; - const html_text = "

hello

"; - const image = await fetch("/clipboard-apis/resources/greenbox.png"); - const item = new ClipboardItem({ - 'text/plain': text_plain, - 'text/html': new Blob([html_text], {type: 'text/html'}), - 'image/png': image.blob(), // Promise - 'web text/csv': 'hello,world' - }); - await navigator.clipboard.write([item]); -}, 'navigator.clipboard.write(web_custom_format) succeeds'); - -promise_test(async () => { - await getPermissions(); - const html_text = "

Test

"; - const item = new ClipboardItem({ - 'text/plain': 'hello', - 'text/html': new Blob([html_text], {type: 'text/html'}) - }); - const text = await item.getType('text/plain'); - const blob = await item.getType('text/html'); - assert_true(text instanceof Blob, "item.getType('text/plain') didn't return a Blob"); - assert_true(blob instanceof Blob, "item.getType('text/html') didn't return a Blob"); -}, 'validate GetType(type) on a contructed ClipboardItem returns Blob'); - promise_test(async () => { await getPermissions(); const blob = new Blob(['hello'], {type: 'text/plain'}); diff --git a/tests/wpt/tests/clipboard-apis/async-navigator-clipboard-change-event.tentative.https.html b/tests/wpt/tests/clipboard-apis/async-navigator-clipboard-change-event.tentative.https.html new file mode 100644 index 00000000000..37e2e6a7bde --- /dev/null +++ b/tests/wpt/tests/clipboard-apis/async-navigator-clipboard-change-event.tentative.https.html @@ -0,0 +1,127 @@ + + + + 'clipboardchange' event should be fired upon setting clipboard using JS + + + + + Body needed for test_driver.click() +

+
+ + + + + + + + + + + \ No newline at end of file diff --git a/tests/wpt/tests/clipboard-apis/async-navigator-clipboard-write-domstring.https.html b/tests/wpt/tests/clipboard-apis/async-navigator-clipboard-write-domstring.https.html new file mode 100644 index 00000000000..a16f358f46d --- /dev/null +++ b/tests/wpt/tests/clipboard-apis/async-navigator-clipboard-write-domstring.https.html @@ -0,0 +1,112 @@ + + +Async Clipboard input type validation tests - DOMString input in write API + + +Body needed for test_driver.click() + + + + + + \ No newline at end of file diff --git a/tests/wpt/tests/close-watcher/iframes/dialog-same-origin-nn.html b/tests/wpt/tests/close-watcher/iframes/dialog-same-origin-nn.html new file mode 100644 index 00000000000..250fbf7e5f7 --- /dev/null +++ b/tests/wpt/tests/close-watcher/iframes/dialog-same-origin-nn.html @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + diff --git a/tests/wpt/tests/close-watcher/iframes/dialog-same-origin-ynn.html b/tests/wpt/tests/close-watcher/iframes/dialog-same-origin-ynn.html new file mode 100644 index 00000000000..b5cfab5c5d9 --- /dev/null +++ b/tests/wpt/tests/close-watcher/iframes/dialog-same-origin-ynn.html @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + diff --git a/tests/wpt/tests/close-watcher/iframes/dialog-same-origin-ynyn.html b/tests/wpt/tests/close-watcher/iframes/dialog-same-origin-ynyn.html new file mode 100644 index 00000000000..4f55e9b24aa --- /dev/null +++ b/tests/wpt/tests/close-watcher/iframes/dialog-same-origin-ynyn.html @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + diff --git a/tests/wpt/tests/close-watcher/iframes/resources/dialog-prevents-close.html b/tests/wpt/tests/close-watcher/iframes/resources/dialog-prevents-close.html new file mode 100644 index 00000000000..580e5220dae --- /dev/null +++ b/tests/wpt/tests/close-watcher/iframes/resources/dialog-prevents-close.html @@ -0,0 +1,21 @@ + + + + + + + + + + + diff --git a/tests/wpt/tests/compat/webkit-box-ignores-flex-wrap.tentative.html b/tests/wpt/tests/compat/webkit-box-ignores-flex-wrap.tentative.html new file mode 100644 index 00000000000..98086f83570 --- /dev/null +++ b/tests/wpt/tests/compat/webkit-box-ignores-flex-wrap.tentative.html @@ -0,0 +1,7 @@ + + + +
+
+
+
diff --git a/tests/wpt/tests/compute-pressure/compute_pressure_basic.https.window.js b/tests/wpt/tests/compute-pressure/compute_pressure_basic.https.window.js index a120f97a403..a13d1766c53 100644 --- a/tests/wpt/tests/compute-pressure/compute_pressure_basic.https.window.js +++ b/tests/wpt/tests/compute-pressure/compute_pressure_basic.https.window.js @@ -21,6 +21,26 @@ pressure_test(async (t) => { return promise_rejects_dom(t, 'NotSupportedError', observer.observe('cpu')); }, 'Return NotSupportedError when calling observer()'); +pressure_test(async (t) => { + await create_virtual_pressure_source('cpu'); + t.add_cleanup(async () => { + await remove_virtual_pressure_source('cpu'); + }); + + const changes = await new Promise((resolve, reject) => { + const observer = new PressureObserver(resolve); + t.add_cleanup(() => observer.disconnect()); + observer.observe('cpu').catch(reject); + update_virtual_pressure_source('cpu', 'critical', 0.5).catch(reject); + }); + assert_equals(1, changes.length); + assert_equals(changes[0].state, 'critical'); + assert_equals(changes[0].source, 'cpu'); + assert_equals(typeof changes[0].time, 'number'); + assert_equals(typeof changes[0].ownContributionEstimate, 'number'); + assert_equals(changes[0].ownContributionEstimate, 0.5); +}, 'Basic functionality test'); + pressure_test(async (t) => { await create_virtual_pressure_source('cpu'); t.add_cleanup(async () => { @@ -37,7 +57,8 @@ pressure_test(async (t) => { assert_equals(changes[0].state, 'critical'); assert_equals(changes[0].source, 'cpu'); assert_equals(typeof changes[0].time, 'number'); -}, 'Basic functionality test'); + assert_equals(changes[0].ownContributionEstimate, null); +}, 'Basic functionality test with no ownContributionEstimate'); pressure_test(async (t) => { await create_virtual_pressure_source('cpu'); diff --git a/tests/wpt/tests/compute-pressure/compute_pressure_duplicate_updates.https.window.js b/tests/wpt/tests/compute-pressure/compute_pressure_duplicate_updates.https.window.js index 231d4afc345..abf53854b40 100644 --- a/tests/wpt/tests/compute-pressure/compute_pressure_duplicate_updates.https.window.js +++ b/tests/wpt/tests/compute-pressure/compute_pressure_duplicate_updates.https.window.js @@ -19,15 +19,15 @@ pressure_test(async (t) => { const syncObserver = new SyncPressureObserver(t); await syncObserver.observer().observe('cpu'); - await update_virtual_pressure_source('cpu', 'critical'); + await update_virtual_pressure_source('cpu', 'critical', 0.2); await syncObserver.waitForUpdate(); assert_equals(syncObserver.changes()[0][0].state, 'critical'); - await update_virtual_pressure_source('cpu', 'critical'); + await update_virtual_pressure_source('cpu', 'critical', 0.2); await new Promise(resolve => {t.step_timeout(resolve, 3000)}); assert_equals(syncObserver.changes().length, 1); - await update_virtual_pressure_source('cpu', 'nominal'); + await update_virtual_pressure_source('cpu', 'nominal'), 0.2; await syncObserver.waitForUpdate(); assert_equals(syncObserver.changes()[1][0].state, 'nominal'); diff --git a/tests/wpt/tests/compute-pressure/resources/worker-support.js b/tests/wpt/tests/compute-pressure/resources/worker-support.js index 39caeb6192f..72e1fbfb204 100644 --- a/tests/wpt/tests/compute-pressure/resources/worker-support.js +++ b/tests/wpt/tests/compute-pressure/resources/worker-support.js @@ -43,8 +43,8 @@ function remove_virtual_pressure_source(source) { return send_message({command: 'remove', params: [source]}); } -function update_virtual_pressure_source(source, state) { - return send_message({command: 'update', params: [source, state]}); +function update_virtual_pressure_source(source, state, estimate) { + return send_message({command: 'update', params: [source, state, estimate]}); } const uuid = new URLSearchParams(location.search).get('uuid'); diff --git a/tests/wpt/tests/content-security-policy/frame-src/frame-src-blocked.sub.html b/tests/wpt/tests/content-security-policy/frame-src/frame-src-blocked.sub.html index a4957f8715c..76fcc2cbb53 100644 --- a/tests/wpt/tests/content-security-policy/frame-src/frame-src-blocked.sub.html +++ b/tests/wpt/tests/content-security-policy/frame-src/frame-src-blocked.sub.html @@ -18,17 +18,17 @@ }, false); function alert_assert(msg) { - t_alert.step(function() { + t_log.step(function() { if (msg.match(/^FAIL/i)) { assert_unreached(msg); - t_alert.done(); + t_log.done(); } for (var i = 0; i < expected_alerts.length; i++) { if (expected_alerts[i] == msg) { assert_equals(expected_alerts[i], msg); expected_alerts.splice(i, 1); if (expected_alerts.length == 0) { - t_alert.done(); + t_log.done(); } return; } diff --git a/tests/wpt/tests/content-security-policy/img-src/icon-blocked.sub.html b/tests/wpt/tests/content-security-policy/img-src/icon-blocked.sub.html index cc882347a1a..4c39e5dec73 100644 --- a/tests/wpt/tests/content-security-policy/img-src/icon-blocked.sub.html +++ b/tests/wpt/tests/content-security-policy/img-src/icon-blocked.sub.html @@ -12,6 +12,7 @@ var t_spv = async_test("Test that spv event is fired"); window.addEventListener("securitypolicyviolation", t_spv.step_func_done(function(e) { assert_equals(e.violatedDirective, 'img-src'); + assert_equals(e.target, document); assert_true(e.blockedURI.endsWith('/support/fail.png')); })); diff --git a/tests/wpt/tests/content-security-policy/img-src/img-src-targeting.html b/tests/wpt/tests/content-security-policy/img-src/img-src-targeting.html new file mode 100644 index 00000000000..3b4fe7c690b --- /dev/null +++ b/tests/wpt/tests/content-security-policy/img-src/img-src-targeting.html @@ -0,0 +1,24 @@ + + + + + + + + +

Check that img-src sets correct target

+ + + + + diff --git a/tests/wpt/tests/content-security-policy/style-src/style-src-inline-style-with-csstext.html b/tests/wpt/tests/content-security-policy/style-src/style-src-inline-style-with-csstext.html new file mode 100644 index 00000000000..5e812b4aee9 --- /dev/null +++ b/tests/wpt/tests/content-security-policy/style-src/style-src-inline-style-with-csstext.html @@ -0,0 +1,29 @@ + + + + + + + + + + +
+ +
Lorem ipsum
+ + + + + diff --git a/tests/wpt/tests/content-security-policy/tentative/require-sri-for/script-allowed-meta.https.html b/tests/wpt/tests/content-security-policy/tentative/require-sri-for/script-allowed-meta.https.html deleted file mode 100644 index 087498f0c57..00000000000 --- a/tests/wpt/tests/content-security-policy/tentative/require-sri-for/script-allowed-meta.https.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - diff --git a/tests/wpt/tests/content-security-policy/tentative/require-sri-for/script-blocked-meta.https.html b/tests/wpt/tests/content-security-policy/tentative/require-sri-for/script-blocked-meta.https.html deleted file mode 100644 index fe69c61f5b6..00000000000 --- a/tests/wpt/tests/content-security-policy/tentative/require-sri-for/script-blocked-meta.https.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - diff --git a/tests/wpt/tests/content-security-policy/unsafe-eval/eval-blocked-in-about-blank-iframe.html b/tests/wpt/tests/content-security-policy/unsafe-eval/eval-blocked-in-about-blank-iframe.html index 054e75b5274..b2286f56a23 100644 --- a/tests/wpt/tests/content-security-policy/unsafe-eval/eval-blocked-in-about-blank-iframe.html +++ b/tests/wpt/tests/content-security-policy/unsafe-eval/eval-blocked-in-about-blank-iframe.html @@ -19,18 +19,27 @@ const document_loaded = new Promise(resolve => window.onload = resolve); await document_loaded; - const eval_error = new Promise(resolve => { - window.addEventListener('message', function(e) { - assert_not_equals(e.data, 'FAIL', 'eval was executed in the frame'); - if (e.data === 'PASS') - resolve(); + const eval_error = new Promise((resolve, reject) => { + window.addEventListener('message', function(event) { + try { + assert_not_equals(event.data, 'FAIL', 'eval was executed in the frame'); + if (event.data === 'PASS') { + resolve(); + } + } catch (e) { + reject(e); + } }); }); - const csp_violation_report = new Promise(resolve => { - window.addEventListener('message', function(e) { - if (e.data["violated-directive"]) { - assert_equals(e.data["violated-directive"], "script-src"); - resolve(); + const csp_violation_report = new Promise((resolve, reject) => { + window.addEventListener('message', function(event) { + try { + if (event.data["violated-directive"]) { + assert_equals(event.data["violated-directive"], "script-src"); + resolve(); + } + } catch (e) { + reject(e); } }); }); diff --git a/tests/wpt/tests/cookie-store/change_eventhandler_for_already_expired.https.window.js b/tests/wpt/tests/cookie-store/change_eventhandler_for_already_expired.https.window.js index 89d84b13d1b..f3bbe0ea560 100644 --- a/tests/wpt/tests/cookie-store/change_eventhandler_for_already_expired.https.window.js +++ b/tests/wpt/tests/cookie-store/change_eventhandler_for_already_expired.https.window.js @@ -20,3 +20,22 @@ cookie_test(async t => { {deleted: [], changed: [{name: 'alt-cookie', value: 'IGNORE'}]}, 'Deletion not observed after document.cookie sets already-expired cookie'); }, 'CookieStore setting already-expired cookie should not be observed'); + +cookie_test(async t => { + const eventPromise = observeNextCookieChangeEvent(); + await cookieStore.set({ + name: 'cookie', + value: 'ALREADY-EXPIRED', + expires: new Date(new Date() - 10_000), + partitioned: true, + }); + await cookieStore.set('alt-cookie', 'IGNORE'); + assert_equals( + await getCookieString(), + 'alt-cookie=IGNORE', + 'Already-expired cookie not included in CookieStore'); + await verifyCookieChangeEvent( + eventPromise, + {deleted: [], changed: [{name: 'alt-cookie', value: 'IGNORE'}]}, + 'Deletion not observed after document.cookie sets already-expired cookie'); +}, 'CookieStore setting already-expired partitioned cookie should not be observed'); diff --git a/tests/wpt/tests/cookie-store/change_eventhandler_for_document_cookie.https.window.js b/tests/wpt/tests/cookie-store/change_eventhandler_for_document_cookie.https.window.js index 82b0f2baa2b..1937ebdae35 100644 --- a/tests/wpt/tests/cookie-store/change_eventhandler_for_document_cookie.https.window.js +++ b/tests/wpt/tests/cookie-store/change_eventhandler_for_document_cookie.https.window.js @@ -78,6 +78,21 @@ cookie_test(async t => { 'Deletion not observed after document.cookie sets already-expired cookie'); }, 'document.cookie set already-expired cookie should not be observed by CookieStore'); +cookie_test(async t => { + let eventPromise = observeNextCookieChangeEvent(); + await setCookieStringDocument('DOCUMENT-cookie=VALUE; path=/'); + await verifyCookieChangeEvent( + eventPromise, {changed: [{name: 'DOCUMENT-cookie', value: 'VALUE'}]}, + 'Original cookie is observed.'); + + eventPromise = observeNextCookieChangeEvent(); + // Overwrite the original cookie with a duplicate, this should not dispatch an event. + await setCookieStringDocument('DOCUMENT-cookie=VALUE; path=/'); + await setCookieStringDocument('DOCUMENT-alt-cookie=IGNORE; path=/'); + await verifyCookieChangeEvent( + eventPromise, {changed: [{name: 'DOCUMENT-alt-cookie', value: 'IGNORE'}]}, + 'Duplicate cookie is not observed.'); +}, 'document.cookie duplicate cookie should not be observed by CookieStore'); cookie_test(async t => { let eventPromise = observeNextCookieChangeEvent(); diff --git a/tests/wpt/tests/cookie-store/change_eventhandler_for_http_cookie_and_set_cookie_headers.https.window.js b/tests/wpt/tests/cookie-store/change_eventhandler_for_http_cookie_and_set_cookie_headers.https.window.js index 8f5ef1cabbe..8517995acfe 100644 --- a/tests/wpt/tests/cookie-store/change_eventhandler_for_http_cookie_and_set_cookie_headers.https.window.js +++ b/tests/wpt/tests/cookie-store/change_eventhandler_for_http_cookie_and_set_cookie_headers.https.window.js @@ -62,6 +62,22 @@ cookie_test(async t => { 'Deletion not observed after HTTP sets already-expired cookie'); }, 'HTTP set already-expired cookie should not be observed by CookieStore'); +cookie_test(async t => { + let eventPromise = observeNextCookieChangeEvent(); + await setCookieStringHttp('HTTP-cookie=VALUE; path=/'); + await verifyCookieChangeEvent( + eventPromise, {changed: [{name: 'HTTP-cookie', value: 'VALUE'}]}, + 'Original cookie is observed.'); + + eventPromise = observeNextCookieChangeEvent(); + // Overwrite the original cookie with a duplicate, this should not dispatch an event. + await setCookieStringHttp('HTTP-cookie=VALUE; path=/'); + await setCookieStringHttp('HTTP-alt-cookie=IGNORE; path=/'); + await verifyCookieChangeEvent( + eventPromise, {changed: [{name: 'HTTP-alt-cookie', value: 'IGNORE'}]}, + 'Duplicate cookie is not observed.'); +}, 'HTTP duplicate cookie should not be observed by CookieStore'); + cookie_test(async t => { let eventPromise = observeNextCookieChangeEvent(); diff --git a/tests/wpt/tests/cookie-store/change_eventhandler_for_no_change.https.window.js b/tests/wpt/tests/cookie-store/change_eventhandler_for_no_change.https.window.js new file mode 100644 index 00000000000..5f9c2927433 --- /dev/null +++ b/tests/wpt/tests/cookie-store/change_eventhandler_for_no_change.https.window.js @@ -0,0 +1,43 @@ +// META: title=Cookie Store API: Test that setting a duplicate cookie does not fire a second event. +// META: script=resources/cookie-test-helpers.js + +'use strict'; + +cookie_test(async t => { + let eventPromise = observeNextCookieChangeEvent(); + await cookieStore.set('cookie', 'VALUE'); + await verifyCookieChangeEvent( + eventPromise, {changed: [{name: 'cookie', value: 'VALUE'}]}, + 'Original cookie is observed.'); + + eventPromise = observeNextCookieChangeEvent(); + await cookieStore.set('cookie', 'VALUE'); + await cookieStore.set('alt-cookie', 'IGNORE'); + await verifyCookieChangeEvent( + eventPromise, {changed: [{name: 'alt-cookie', value: 'IGNORE'}]}, + 'Duplicate cookie is not observed.'); +}, 'CookieStore duplicate cookie should not be observed'); + +cookie_test(async t => { + let eventPromise = observeNextCookieChangeEvent(); + await cookieStore.set({ + name: 'cookie', + value: 'VALUE', + partitioned: true, + }); + await verifyCookieChangeEvent( + eventPromise, + {changed: [{name: 'cookie', value: 'VALUE', partitioned: true}]}, + 'Original cookie is observed.'); + + eventPromise = observeNextCookieChangeEvent(); + await cookieStore.set({ + name: 'cookie', + value: 'VALUE', + partitioned: true, + }); + await cookieStore.set('alt-cookie', 'IGNORE'); + await verifyCookieChangeEvent( + eventPromise, {changed: [{name: 'alt-cookie', value: 'IGNORE'}]}, + 'Duplicate cookie is not observed.'); +}, 'CookieStore duplicate partitioned cookie should not be observed'); diff --git a/tests/wpt/tests/cookie-store/cookieListItem_attributes.https.any.js b/tests/wpt/tests/cookie-store/cookieListItem_attributes.https.any.js index 6716d91788d..b42a745d3e4 100644 --- a/tests/wpt/tests/cookie-store/cookieListItem_attributes.https.any.js +++ b/tests/wpt/tests/cookie-store/cookieListItem_attributes.https.any.js @@ -161,7 +161,7 @@ promise_test(async testCase => { assert_equals(cookie.name, 'cookie-name'); assert_equals(cookie.value, 'cookie-value'); assert_equals(cookie.domain, null); - assert_equals(cookie.path, currentDirectory + '/'); + assert_equals(cookie.path, currentDirectory); assert_equals(cookie.expires, null); assert_equals(cookie.secure, true); assert_equals(cookie.sameSite, 'strict'); @@ -169,7 +169,7 @@ promise_test(async testCase => { for (const key of kCookieListItemKeys) { assert_in_array(key, itemKeys); } -}, 'CookieListItem - cookieStore.set adds / to path if it does not end with /'); +}, 'CookieListItem - cookieStore.set does not add / to path if it does not end with /'); ['strict', 'lax', 'none'].forEach(sameSiteValue => { promise_test(async testCase => { @@ -207,3 +207,23 @@ promise_test(async testCase => { const cookie = await cookieStore.get('cookie-name'); assert_equals(cookie.secure, true); }, 'CookieListItem - secure defaults to true'); + + +if (self.GLOBAL.isWindow()) { + promise_test(async testCase => { + await cookieStore.delete('cookie-name'); + testCase.add_cleanup(async () => { + await cookieStore.delete('cookie-name'); + }); + + let encodedCookie = encodeURIComponent(JSON.stringify("cookie-name=1; max-age=99999999999999999999999999999; path=/")); + await fetch(`/cookies/resources/cookie.py?set=${encodedCookie}`); + + assert_equals(document.cookie, "cookie-name=1", 'The cookie was set as expected.'); + + const cookie = await cookieStore.get('cookie-name'); + assert_equals(cookie.name, 'cookie-name'); + assert_equals(cookie.value, '1'); + assert_approx_equals(cookie.expires, kFourHundredDaysFromNow, kOneDay); + }, "Test max-age attribute over the 400 days"); +} diff --git a/tests/wpt/tests/cookie-store/cookieStore_delete_arguments.https.any.js b/tests/wpt/tests/cookie-store/cookieStore_delete_arguments.https.any.js index 37a551b3a9f..2503de0abb0 100644 --- a/tests/wpt/tests/cookie-store/cookieStore_delete_arguments.https.any.js +++ b/tests/wpt/tests/cookie-store/cookieStore_delete_arguments.https.any.js @@ -1,11 +1,14 @@ // META: title=Cookie Store API: cookieStore.delete() arguments +// META: script=resources/cookie-test-helpers.js // META: global=window,serviceworker 'use strict'; promise_test(async testCase => { await cookieStore.set('cookie-name', 'cookie-value'); - + testCase.add_cleanup(async () => { + await setCookieStringHttp(`cookie-name=deleted; Max-Age=0`); + }); await cookieStore.delete('cookie-name'); const cookie = await cookieStore.get('cookie-name'); assert_equals(cookie, null); @@ -14,7 +17,7 @@ promise_test(async testCase => { promise_test(async testCase => { await cookieStore.set('cookie-name', 'cookie-value'); testCase.add_cleanup(async () => { - await cookieStore.delete('cookie-name'); + await setCookieStringHttp(`cookie-name=deleted; Max-Age=0`); }); await cookieStore.delete({ name: 'cookie-name' }); @@ -42,7 +45,7 @@ promise_test(async testCase => { await cookieStore.set( { name: 'cookie-name', value: 'cookie-value', domain: currentDomain }); testCase.add_cleanup(async () => { - await cookieStore.delete({ name: 'cookie-name', domain: currentDomain }); + await setCookieStringHttp(`cookie-name=deleted; Domain=${currentDomain}; Max-Age=0`); }); await cookieStore.delete({ name: 'cookie-name', domain: currentDomain }); @@ -79,7 +82,8 @@ promise_test(async testCase => { await cookieStore.set( { name: 'cookie-name', value: 'cookie-value', path: currentDirectory }); testCase.add_cleanup(async () => { - await cookieStore.delete({ name: 'cookie-name', path: currentDirectory }); + await setCookieStringHttp(`cookie-name=deleted; Path=${currentDirectory}; Max-Age=0`); + await setCookieStringHttp(`cookie-name=deleted; Path=${currentDirectory}/; Max-Age=0`); }); await cookieStore.delete({ name: 'cookie-name', path: currentDirectory }); @@ -96,7 +100,8 @@ promise_test(async testCase => { await cookieStore.set( { name: 'cookie-name', value: 'cookie-value', path: currentDirectory }); testCase.add_cleanup(async () => { - await cookieStore.delete({ name: 'cookie-name', path: currentDirectory }); + await setCookieStringHttp(`cookie-name=deleted; Path=${currentDirectory}; Max-Age=0`); + await setCookieStringHttp(`cookie-name=deleted; Path=${currentDirectory}/; Max-Age=0`); }); await cookieStore.delete({ name: 'cookie-name', path: subDirectory }); @@ -109,18 +114,36 @@ promise_test(async testCase => { const currentUrl = new URL(self.location.href); const currentPath = currentUrl.pathname; const currentDirectory = currentPath.substr(0, currentPath.lastIndexOf('/')); - await cookieStore.set( - { name: 'cookie-name', - value: 'cookie-value', - path: currentDirectory + '/' }); + await setCookieStringHttp(`cookie-name=cookie-value; Path=${currentDirectory};`); + testCase.add_cleanup(async () => { - await cookieStore.delete({ name: 'cookie-name', path: currentDirectory }); + await setCookieStringHttp(`cookie-name=deleted; Path=${currentDirectory}; Max-Age=0`); + await setCookieStringHttp(`cookie-name=deleted; Path=${currentDirectory}/; Max-Age=0`); }); await cookieStore.delete({ name: 'cookie-name', path: currentDirectory }); const cookie = await cookieStore.get('cookie-name'); assert_equals(cookie, null); -}, 'cookieStore.delete with missing / at the end of path'); +}, 'cookieStore.delete does not append / at the end of path'); + +promise_test(async testCase => { + if (typeof self.document === 'undefined') { + // The test is being run from a service worker context where document is undefined + testCase.done(); + return; + } + const currentUrl = new URL(self.location.href); + const currentPath = currentUrl.pathname; + const currentDirectory = currentPath.substr(0, currentPath.lastIndexOf('/')); + await setCookieStringDocument('cookie-name=cookie-value; path=' + currentDirectory); + testCase.add_cleanup(async () => { + await setCookieStringHttp(`cookie-name=deleted; Path=${currentDirectory}; Max-Age=0`); + await setCookieStringHttp(`cookie-name=deleted; Path=${currentDirectory}/; Max-Age=0`); + }); + await cookieStore.delete({ name: 'cookie-name', path: currentDirectory }); + const cookie = await cookieStore.get('cookie-name'); + assert_equals(cookie, null); +}, 'cookieStore.delete can delete a cookie set by document.cookie if document is defined'); promise_test(async testCase => { const currentUrl = new URL(self.location.href); @@ -136,7 +159,7 @@ promise_test(async testCase => { promise_test(async testCase => { await cookieStore.set('cookie-name', 'cookie-value'); testCase.add_cleanup(async () => { - await cookieStore.delete('cookie-name'); + await setCookieStringHttp(`cookie-name=deleted; Max-Age=0`); }); const cookie_attributes = await cookieStore.get('cookie-name'); @@ -151,7 +174,7 @@ promise_test(async testCase => { promise_test(async testCase => { await cookieStore.set('', 'cookie-value'); testCase.add_cleanup(async () => { - await cookieStore.delete(''); + await setCookieStringHttp(`=deleted; Max-Age=0`); }); await cookieStore.delete(''); @@ -162,10 +185,25 @@ promise_test(async testCase => { promise_test(async testCase => { await cookieStore.set('', 'cookie-value'); testCase.add_cleanup(async () => { - await cookieStore.delete(''); + await setCookieStringHttp(`=deleted; Max-Age=0`); }); await cookieStore.delete({ name: '' }); const cookie = await cookieStore.get(''); assert_equals(cookie, null); }, 'cookieStore.delete with empty name in options'); + +promise_test(async testCase => { + // Cookies having a __Host- prefix are not allowed to specify a domain + await cookieStore.delete('cookie-name'); + + const currentUrl = new URL(self.location.href); + const currentDomain = currentUrl.hostname; + + await promise_rejects_js(testCase, TypeError, cookieStore.delete( + { name: '__Host-cookie-name', + value: 'cookie-value', + domain: currentDomain })); + const cookie = await cookieStore.get('cookie-name'); + assert_equals(cookie, null); +}, 'cookieStore.delete with a __Host- prefix should not have a domain'); diff --git a/tests/wpt/tests/cookie-store/cookieStore_get_set_across_origins.sub.https.html b/tests/wpt/tests/cookie-store/cookieStore_get_set_across_origins.sub.https.html index c67ef98bcc9..004e37630ac 100644 --- a/tests/wpt/tests/cookie-store/cookieStore_get_set_across_origins.sub.https.html +++ b/tests/wpt/tests/cookie-store/cookieStore_get_set_across_origins.sub.https.html @@ -63,4 +63,87 @@ promise_test(async t => { assert_equals(frameCookie.name, 'cookie-name'); assert_equals(frameCookie.value, 'cookie-value'); }, 'cookieStore.get() in cross-origin frame sees cookieStore.set()'); + +promise_test(async t => { + const iframe = await createIframe(kCorsUrl, t); + assert_true(iframe != null); + + document.cookie = "__Host-test=a; Path=/; Secure"; + iframe.contentWindow.postMessage({ + opname: 'set-host-cookie', + name: '__Host-test', + value: 'b', + }, kCorsBase); + + const message = await waitForMessage(); + t.add_cleanup(async () => { + await cookieStore.delete({ name: '__Host-test'}); + }); + + assert_equals(document.cookie, '__Host-test=a'); + + // Cleanup iframe cookie + iframe.contentWindow.postMessage({ + opname: 'delete-host-cookie', + name: '__Host-test', + }, kCorsBase); + await waitForMessage(); +}, 'cookieStore.set() in cross-origin does not overwrite the __Host- cookie'); + +promise_test(async t => { + const iframe = await createIframe(kCorsUrl, t); + assert_true(iframe != null); + + document.cookie = "__Host-test=a; Path=/; Secure"; + await cookieStore.set({name: "__Host-test", value: "a", path: "/"}); + t.add_cleanup(async () => { + await cookieStore.delete({ name: '__Host-test'}); + }); + + iframe.contentWindow.postMessage({ + opname: 'set-host-cookie', + name: '__Host-test', + value: 'b', + }, kCorsBase); + + let message = await waitForMessage(); + + let cookies = await cookieStore.getAll(); + assert_equals(cookies.length, 1); + assert_equals(cookies[0].name, '__Host-test'); + assert_equals(cookies[0].value, 'a'); + + iframe.contentWindow.postMessage({ + opname: 'get-cookie', + name: '__Host-test', + }, kCorsBase); + message = await waitForMessage(); + let { frameCookie } = message; + assert_not_equals(frameCookie, null); + assert_equals(frameCookie.name, '__Host-test'); + assert_equals(frameCookie.value, 'b'); + + // Make sure deleting the cookie doesn't affect the other domain's cookie + await cookieStore.delete({ name: '__Host-test'}); + cookies = await cookieStore.getAll(); + assert_equals(cookies.length, 0); + + iframe.contentWindow.postMessage({ + opname: 'get-cookie', + name: '__Host-test', + }, kCorsBase); + message = await waitForMessage(); + ({ frameCookie } = message); + assert_not_equals(frameCookie, null); + assert_equals(frameCookie.name, '__Host-test'); + assert_equals(frameCookie.value, 'b'); + + // Cleanup iframe cookie + iframe.contentWindow.postMessage({ + opname: 'delete-host-cookie', + name: '__Host-test', + }, kCorsBase); + await waitForMessage(); +}, "__Host- cookies set via cookieStore.set() in same-site domains don't overwrite each other"); + diff --git a/tests/wpt/tests/cookie-store/cookieStore_set_arguments.https.any.js b/tests/wpt/tests/cookie-store/cookieStore_set_arguments.https.any.js index 064fcc5de52..3e9ac5f3e98 100644 --- a/tests/wpt/tests/cookie-store/cookieStore_set_arguments.https.any.js +++ b/tests/wpt/tests/cookie-store/cookieStore_set_arguments.https.any.js @@ -1,4 +1,5 @@ // META: title=Cookie Store API: cookieStore.set() arguments +// META: script=resources/cookie-test-helpers.js // META: global=window,serviceworker 'use strict'; @@ -8,7 +9,7 @@ promise_test(async testCase => { await cookieStore.set('cookie-name', 'cookie-value'); testCase.add_cleanup(async () => { - await cookieStore.delete('cookie-name'); + await setCookieStringHttp(`cookie-name=deleted; Max-Age=0`); }); const cookie = await cookieStore.get('cookie-name'); @@ -21,7 +22,7 @@ promise_test(async testCase => { await cookieStore.set({ name: 'cookie-name', value: 'cookie-value' }); testCase.add_cleanup(async () => { - await cookieStore.delete('cookie-name'); + await setCookieStringHttp(`cookie-name=deleted; Max-Age=0`); }); const cookie = await cookieStore.get('cookie-name'); assert_equals(cookie.name, 'cookie-name'); @@ -42,7 +43,7 @@ promise_test(async testCase => { await cookieStore.delete('cookie-name'); cookieStore.set('cookie-name', 'suspicious-value=resembles-name-and-value'); testCase.add_cleanup(async () => { - await cookieStore.delete('cookie-name'); + await setCookieStringHttp(`cookie-name=deleted; Max-Age=0`); }); const cookie = await cookieStore.get('cookie-name'); assert_equals(cookie.name, 'cookie-name'); @@ -81,7 +82,7 @@ promise_test(async testCase => { value: 'cookie-value', expires: new Date(tenYearsFromNow) }); testCase.add_cleanup(async () => { - await cookieStore.delete('cookie-name'); + await setCookieStringHttp(`cookie-name=deleted; Max-Age=0`); }); const cookie = await cookieStore.get('cookie-name'); assert_equals(cookie.name, 'cookie-name'); @@ -98,7 +99,7 @@ promise_test(async testCase => { value: 'cookie-value', expires: new Date(tenYearsAgo) }); testCase.add_cleanup(async () => { - await cookieStore.delete('cookie-name'); + await setCookieStringHttp(`cookie-name=deleted; Max-Age=0`); }); const cookie = await cookieStore.get('cookie-name'); assert_equals(cookie, null); @@ -112,7 +113,7 @@ promise_test(async testCase => { await cookieStore.set( { name: 'cookie-name', value: 'cookie-value', expires: tenYearsFromNow }); testCase.add_cleanup(async () => { - await cookieStore.delete('cookie-name'); + await setCookieStringHttp(`cookie-name=deleted; Max-Age=0`); }); const cookie = await cookieStore.get('cookie-name'); assert_equals(cookie.name, 'cookie-name'); @@ -127,7 +128,7 @@ promise_test(async testCase => { await cookieStore.set( { name: 'cookie-name', value: 'cookie-value', expires: tenYearsAgo }); testCase.add_cleanup(async () => { - await cookieStore.delete('cookie-name'); + await setCookieStringHttp(`cookie-name=deleted; Max-Age=0`); }); const cookie = await cookieStore.get('cookie-name'); assert_equals(cookie, null); @@ -156,7 +157,7 @@ promise_test(async testCase => { await cookieStore.set( { name: 'cookie-name', value: 'cookie-value', domain: currentDomain }); testCase.add_cleanup(async () => { - await cookieStore.delete({ name: 'cookie-name', domain: currentDomain }); + await setCookieStringHttp(`cookie-name=deleted; Domain=${currentDomain}; Path=/; Max-Age=0`); }); const cookie = await cookieStore.get('cookie-name'); assert_equals(cookie.name, 'cookie-name'); @@ -195,12 +196,12 @@ promise_test(async testCase => { await cookieStore.set('cookie-name', 'cookie-value1'); testCase.add_cleanup(async () => { - await cookieStore.delete('cookie-name'); + await setCookieStringHttp(`cookie-name=deleted; Path=/; Max-Age=0`); }); await cookieStore.set( { name: 'cookie-name', value: 'cookie-value2', domain: currentDomain }); testCase.add_cleanup(async () => { - await cookieStore.delete({ name: 'cookie-name', domain: currentDomain }); + await setCookieStringHttp(`cookie-name=deleted; Domain=${currentDomain}; Path=/; Max-Age=0`); }); const cookies = await cookieStore.getAll('cookie-name'); @@ -224,7 +225,7 @@ promise_test(async testCase => { await cookieStore.set( { name: 'cookie-name', value: 'cookie-value', path: currentDirectory }); testCase.add_cleanup(async () => { - await cookieStore.delete({ name: 'cookie-name', path: currentDirectory }); + await setCookieStringHttp(`cookie-name=deleted; Path=${currentDirectory}; Max-Age=0`); }); const cookie = await cookieStore.get('cookie-name'); assert_equals(cookie.name, 'cookie-name'); @@ -243,7 +244,7 @@ promise_test(async testCase => { await cookieStore.set( { name: 'cookie-name', value: 'cookie-value', path: subDirectory }); testCase.add_cleanup(async () => { - await cookieStore.delete({ name: 'cookie-name', path: subDirectory }); + await setCookieStringHttp(`cookie-name=deleted; Path=${subDirectory}; Max-Age=0`); }); const cookie = await cookieStore.get('cookie-name'); assert_equals(cookie, null); @@ -254,12 +255,13 @@ promise_test(async testCase => { await cookieStore.set('cookie-name', 'cookie-old-value'); testCase.add_cleanup(async () => { - await cookieStore.delete('cookie-name'); + await setCookieStringHttp(`cookie-name=deleted; Max-Age=0`); }); + await cookieStore.set( { name: 'cookie-name', value: 'cookie-new-value', path: '/' }); testCase.add_cleanup(async () => { - await cookieStore.delete({ name: 'cookie-name', path: '/' }); + await setCookieStringHttp(`cookie-name=deleted; Path=/; Max-Age=0`); }); const cookies = await cookieStore.getAll('cookie-name'); @@ -277,13 +279,34 @@ promise_test(async testCase => { await cookieStore.set( { name: 'cookie-name', value: 'cookie-value', path: currentDirectory }); testCase.add_cleanup(async () => { - await cookieStore.delete({ name: 'cookie-name', path: currentDirectory }); + await setCookieStringHttp(`cookie-name=deleted; Path=${currentDirectory}/; Max-Age=0`); }); + await setCookieStringHttp(`cookie-name=deleted; Path=${currentDirectory}; Max-Age=0`); const cookie = await cookieStore.get('cookie-name'); - assert_equals(cookie.name, 'cookie-name'); - assert_equals(cookie.value, 'cookie-value'); - assert_equals(cookie.path, currentDirectory + '/'); -}, 'cookieStore.set adds / to path that does not end with /'); + assert_equals(cookie, null); +}, 'cookieStore.set does not add / to path that does not end with /'); + +promise_test(async testCase => { + if (typeof self.document === 'undefined') { + // The test is being run from a service worker context where document is undefined + testCase.done(); + return; + } + const currentUrl = new URL(self.location.href); + const currentPath = currentUrl.pathname; + const currentDirectory = currentPath.substr(0, currentPath.lastIndexOf('/')); + await setCookieStringDocument('cookie-name=cookie-value; path=' + currentDirectory); + await cookieStore.set( + { name: 'cookie-name', value: 'new-cookie-value', path: currentDirectory }); + testCase.add_cleanup(async () => { + await setCookieStringHttp(`cookie-name=deleted; Path=${currentDirectory}; Max-Age=0`); + await setCookieStringHttp(`cookie-name=deleted; Path=${currentDirectory}/; Max-Age=0`); + }); + const cookies = await cookieStore.getAll('cookie-name'); + assert_equals(cookies.length, 1); + assert_equals(cookies[0].name, 'cookie-name'); + assert_equals(cookies[0].value, 'cookie-new-value'); +}, 'cookieStore.set can modify a cookie set by document.cookie if document is defined'); promise_test(async testCase => { const currentUrl = new URL(self.location.href); @@ -299,7 +322,7 @@ promise_test(async testCase => { promise_test(async testCase => { await cookieStore.set('cookie-name', 'old-cookie-value'); testCase.add_cleanup(async () => { - await cookieStore.delete('cookie-name'); + await setCookieStringHttp(`cookie-name=deleted; Max-Age=0`); }); const cookie_attributes = await cookieStore.get('cookie-name'); @@ -336,3 +359,57 @@ promise_test(async testCase => { const cookie = await cookieStore.get('cookie-name'); assert_equals(cookie, null); }, 'cookieStore.set checks if the domain is too long'); + +promise_test(async testCase => { + // Cookies having a __Host- prefix are not allowed to specify a domain + await cookieStore.delete('cookie-name'); + + const currentUrl = new URL(self.location.href); + const currentDomain = currentUrl.hostname; + + await promise_rejects_js(testCase, TypeError, cookieStore.set( + { name: '__Host-cookie-name', + value: 'cookie-value', + domain: currentDomain })); + const cookie = await cookieStore.get('cookie-name'); + assert_equals(cookie, null); +}, 'cookieStore.set with a __Host- prefix should not have a domain'); + +promise_test(async testCase => { + await promise_rejects_js(testCase, TypeError, cookieStore.set( + { name: '', + value: ' ' })); + + await promise_rejects_js(testCase, TypeError, cookieStore.set( + { name: ' ', + value: '' })); + + await promise_rejects_js(testCase, TypeError, cookieStore.set( + { name: ' ', + value: ' ' })); +}, 'cookieStore.set with whitespace only name and value'); + +promise_test(async testCase => { + testCase.add_cleanup(async () => { + await cookieStore.delete('a b'); + }); + await cookieStore.set('a b', 'x y'); + const cookie = await cookieStore.get('a b'); + assert_equals(cookie.value, "x y"); + + await promise_rejects_js(testCase, TypeError, cookieStore.set( + { name: 'a ', + value: 'x' })); + + await promise_rejects_js(testCase, TypeError, cookieStore.set( + { name: ' a', + value: 'x' })); + + await promise_rejects_js(testCase, TypeError, cookieStore.set( + { name: 'a', + value: 'x ' })); + + await promise_rejects_js(testCase, TypeError, cookieStore.set( + { name: 'a', + value: 'x ' })); +}, 'cookieStore.set with whitespace at begining or end'); diff --git a/tests/wpt/tests/cookie-store/httponly_cookies.https.window.js b/tests/wpt/tests/cookie-store/httponly_cookies.https.window.js index 8a10e358ef6..605e94e6744 100644 --- a/tests/wpt/tests/cookie-store/httponly_cookies.https.window.js +++ b/tests/wpt/tests/cookie-store/httponly_cookies.https.window.js @@ -67,3 +67,33 @@ cookie_test(async t => { 'cookie1=value1; cookie2=value2; cookie3=value3', 'httpOnly is not an option for CookieStore.set()'); }, 'HttpOnly cookies can not be set by CookieStore'); + +cookie_test(async t => { + await setCookieStringHttp('HTTPONLY-cookie=value; path=/; httponly'); + assert_equals( + await getCookieString(), + undefined, + 'HttpOnly cookie we wrote using HTTP in cookie jar' + + ' is invisible to script'); + assert_equals( + await getCookieStringHttp(), + 'HTTPONLY-cookie=value', + 'HttpOnly cookie we wrote using HTTP in HTTP cookie jar'); + + try { + await cookieStore.set('HTTPONLY-cookie', 'dummy'); + } catch(e) {} + + assert_equals( + await getCookieString(), + undefined, + 'HttpOnly cookie is not overwritten'); + + try { + await cookieStore.delete('HTTPONLY-cookie'); + } catch(e) {} + + assert_equals(await getCookieString(), undefined, 'HttpOnly cookie is not overwritten'); + + assert_equals(await getCookieStringHttp(), 'HTTPONLY-cookie=value', 'HttpOnly cookie is not deleted'); +}, 'HttpOnly cookies are not deleted/overwritten'); diff --git a/tests/wpt/tests/cookie-store/resources/helper_iframe.sub.html b/tests/wpt/tests/cookie-store/resources/helper_iframe.sub.html index 3bd1843d574..d5cae23d160 100644 --- a/tests/wpt/tests/cookie-store/resources/helper_iframe.sub.html +++ b/tests/wpt/tests/cookie-store/resources/helper_iframe.sub.html @@ -40,6 +40,17 @@ } else if (opname === 'push-state') { history.pushState("foo", null, "some/path"); event.source.postMessage('pushState called'); + } else if (opname === "set-host-cookie") { + const { name, value } = event.data + await cookieStore.set({ + name, + value, + }); + event.source.postMessage('Cookie has been set', event.origin); + } else if (opname === "delete-host-cookie") { + const { name} = event.data + await cookieStore.delete({ name: name}); + event.source.postMessage('Cookie has been deleted', event.origin); } }); diff --git a/tests/wpt/tests/cookie-store/serviceworker_cookiechange_eventhandler_already_expired.https.any.js b/tests/wpt/tests/cookie-store/serviceworker_cookiechange_eventhandler_already_expired.https.any.js new file mode 100644 index 00000000000..c40fdbeb6f1 --- /dev/null +++ b/tests/wpt/tests/cookie-store/serviceworker_cookiechange_eventhandler_already_expired.https.any.js @@ -0,0 +1,86 @@ +// META: title=Cookie Store API: cookiechange event in ServiceWorker with already-expired cookie. +// META: global=serviceworker + +'use strict'; + +const kScope = '/cookie-store/does/not/exist'; + +function WorkerActivationPromise() { + return new Promise((resolve) => { + if (registration.active) { + resolve(); + return; + } + self.addEventListener('activate', () => { resolve(); }); + }); +} + +function RunOnceCookieChangeReceivedPromise() { + return new Promise(resolve => { + const listener = ev => { + resolve(ev); + self.removeEventListener('cookiechange', listener); + }; + self.addEventListener('cookiechange', listener); + }); +} + +promise_test(async t => { + await WorkerActivationPromise(); + + const subscriptions = [{url: `${kScope}/path`}]; + await registration.cookies.subscribe(subscriptions); + t.add_cleanup(() => registration.cookies.unsubscribe(subscriptions)); + + const eventPromise = RunOnceCookieChangeReceivedPromise(); + + await cookieStore.set({ + name: 'cookie-name', + value: 'already-expired', + expires: new Date(new Date() - 10_000), + }); + + await cookieStore.set('another-cookie-name', 'ignore'); + t.add_cleanup(() => cookieStore.delete('another-cookie-name')); + + const event = await eventPromise; + assert_equals(event.type, 'cookiechange'); + assert_equals(event.changed.length, 1); + assert_equals(event.changed[0].name, 'another-cookie-name'); + assert_equals(event.changed[0].value, 'ignore'); + assert_equals(event.deleted.length, 0); +}); + +promise_test(async t => { + await WorkerActivationPromise(); + + const subscriptions = [{url: `${kScope}/path`}]; + await registration.cookies.subscribe(subscriptions); + t.add_cleanup(() => registration.cookies.unsubscribe(subscriptions)); + + const eventPromise = RunOnceCookieChangeReceivedPromise(); + + await cookieStore.set({ + name: 'cookie-name', + value: 'already-expired', + expires: new Date(new Date() - 10_000), + partitioned: true, + }); + + await cookieStore.set({ + name: 'another-cookie-name', + value: 'ignore', + partitioned: true, + }); + t.add_cleanup(() => cookieStore.delete({ + name: 'another-cookie-name', + partitioned: true, + })); + + const event = await eventPromise; + assert_equals(event.type, 'cookiechange'); + assert_equals(event.changed.length, 1); + assert_equals(event.changed[0].name, 'another-cookie-name'); + assert_equals(event.changed[0].value, 'ignore'); + assert_equals(event.deleted.length, 0); +}); diff --git a/tests/wpt/tests/cookie-store/serviceworker_cookiechange_eventhandler_no_change.https.any.js b/tests/wpt/tests/cookie-store/serviceworker_cookiechange_eventhandler_no_change.https.any.js new file mode 100644 index 00000000000..fb2e2db7381 --- /dev/null +++ b/tests/wpt/tests/cookie-store/serviceworker_cookiechange_eventhandler_no_change.https.any.js @@ -0,0 +1,128 @@ +// META: title=Cookie Store API: cookiechange event in ServiceWorker with already-expired cookie. +// META: global=serviceworker + +'use strict'; + +const kScope = '/cookie-store/does/not/exist'; + +// Resolves when the service worker receives the 'activate' event. +function WorkerActivationPromise() { + return new Promise((resolve) => { + if (registration.active) { + resolve(); + return; + } + self.addEventListener('activate', () => { resolve(); }); + }); +} + +// Resolves when a cookiechange event is received. +function RunOnceCookieChangeReceivedPromise() { + return new Promise(resolve => { + const listener = ev => { + resolve(ev); + self.removeEventListener('cookiechange', listener); + }; + self.addEventListener('cookiechange', listener); + }); +} + +promise_test(async t => { + await WorkerActivationPromise(); + + const subscriptions = [{url: `${kScope}/path`}]; + await registration.cookies.subscribe(subscriptions); + t.add_cleanup(() => registration.cookies.unsubscribe(subscriptions)); + + let cookie_change_promise = RunOnceCookieChangeReceivedPromise(); + + await cookieStore.set('cookie-name', 'value'); + t.add_cleanup(async () => { + await cookieStore.delete('cookie-name'); + }); + + // Observes original cookie. + let event = await cookie_change_promise; + assert_equals(event.type, 'cookiechange'); + assert_equals(event.changed.length, 1); + assert_equals(event.changed[0].name, 'cookie-name'); + assert_equals(event.changed[0].value, 'value'); + assert_equals(event.deleted.length, 0); + + cookie_change_promise = RunOnceCookieChangeReceivedPromise(); + + // Duplicate overwrite should not be observed. + await cookieStore.set('cookie-name', 'value'); + + // This cookie should be observed instead. + await cookieStore.set('alternate-cookie-name', 'ignore'); + t.add_cleanup(async () => { + await cookieStore.delete('alternate-cookie-name'); + }); + + event = await cookie_change_promise; + assert_equals(event.type, 'cookiechange'); + assert_equals(event.changed.length, 1); + assert_equals(event.changed[0].name, 'alternate-cookie-name'); + assert_equals(event.changed[0].value, 'ignore'); + assert_equals(event.deleted.length, 0); +}); + +promise_test(async t => { + await WorkerActivationPromise(); + + const subscriptions = [{url: `${kScope}/path`}]; + await registration.cookies.subscribe(subscriptions); + t.add_cleanup(() => registration.cookies.unsubscribe(subscriptions)); + + let cookie_change_promise = RunOnceCookieChangeReceivedPromise(); + + await cookieStore.set({ + name: 'cookie-name', + value: 'value', + partitioned: true, + }); + t.add_cleanup(async () => { + await cookieStore.delete({ + name: 'cookie-name', + partitioned: true, + }); + }); + + // Observes original cookie. + let event = await cookie_change_promise; + assert_equals(event.type, 'cookiechange'); + assert_equals(event.changed.length, 1); + assert_equals(event.changed[0].name, 'cookie-name'); + assert_equals(event.changed[0].value, 'value'); + assert_equals(event.deleted.length, 0); + + cookie_change_promise = RunOnceCookieChangeReceivedPromise(); + + // Duplicate overwrite should not be observed. + await cookieStore.set({ + name: 'cookie-name', + value: 'value', + partitioned: true, + }); + + // This cookie should instead. + await cookieStore.set({ + name: 'alternate-cookie-name', + value: 'ignore', + partitioned: true, + }); + t.add_cleanup(async () => { + await cookieStore.delete({ + name: 'alternate-cookie-name', + partitioned: true, + }); + }); + + event = await cookie_change_promise; + assert_equals(event.type, 'cookiechange'); + assert_equals(event.changed.length, 1); + assert_equals(event.changed[0].name, 'alternate-cookie-name'); + assert_equals(event.changed[0].value, 'ignore'); + assert_equals(event.deleted.length, 0); +}); diff --git a/tests/wpt/tests/cookies/partitioned-cookies/partitioned-cookies-samesite-attribute.https.html b/tests/wpt/tests/cookies/partitioned-cookies/partitioned-cookies-samesite-attribute.https.html new file mode 100644 index 00000000000..323fb08236e --- /dev/null +++ b/tests/wpt/tests/cookies/partitioned-cookies/partitioned-cookies-samesite-attribute.https.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + Test SameSite attribute behavior for partitioned cookies + + + + diff --git a/tests/wpt/tests/cookies/partitioned-cookies/resources/partitioned-cookies-samesite-attributes-embed.html b/tests/wpt/tests/cookies/partitioned-cookies/resources/partitioned-cookies-samesite-attributes-embed.html new file mode 100644 index 00000000000..c6f64639eac --- /dev/null +++ b/tests/wpt/tests/cookies/partitioned-cookies/resources/partitioned-cookies-samesite-attributes-embed.html @@ -0,0 +1,31 @@ + + + +Partitioned cookie SameSite test site embedded in a cross-site context + + + + + + + diff --git a/tests/wpt/tests/css/compositing/root-element-background-image-opaque-crash.html b/tests/wpt/tests/css/compositing/root-element-background-image-opaque-crash.html new file mode 100644 index 00000000000..d380310d096 --- /dev/null +++ b/tests/wpt/tests/css/compositing/root-element-background-image-opaque-crash.html @@ -0,0 +1,9 @@ + + diff --git a/tests/wpt/tests/css/css-align/abspos/align-items-static-position-001-ref.tentative.html b/tests/wpt/tests/css/css-align/abspos/align-items-static-position-001-ref.tentative.html new file mode 100644 index 00000000000..22c1fd03e38 --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/align-items-static-position-001-ref.tentative.html @@ -0,0 +1,30 @@ + + +
+
+
diff --git a/tests/wpt/tests/css/css-align/abspos/align-items-static-position-001.tentative.html b/tests/wpt/tests/css/css-align/abspos/align-items-static-position-001.tentative.html new file mode 100644 index 00000000000..06c11e6cdb6 --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/align-items-static-position-001.tentative.html @@ -0,0 +1,39 @@ + +Align-items only applies to OOF elements of block elements when statically positioned if it would apply if not OOF. + + + + + + + + +
+
+
diff --git a/tests/wpt/tests/css/css-align/abspos/align-items-static-position-002-ref.tentative.html b/tests/wpt/tests/css/css-align/abspos/align-items-static-position-002-ref.tentative.html new file mode 100644 index 00000000000..6f53b041787 --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/align-items-static-position-002-ref.tentative.html @@ -0,0 +1,32 @@ + + + +
text +
+
text +
+ diff --git a/tests/wpt/tests/css/css-align/abspos/align-items-static-position-002.tentative.html b/tests/wpt/tests/css/css-align/abspos/align-items-static-position-002.tentative.html new file mode 100644 index 00000000000..f90004b5672 --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/align-items-static-position-002.tentative.html @@ -0,0 +1,41 @@ + +Align-items doesn't apply to OOF elements of inline elements when statically positioned because they would apply if not OOF. + + + + + + + + + +
text +
+
text +
diff --git a/tests/wpt/tests/css/css-align/abspos/align-items-static-position-ref.html b/tests/wpt/tests/css/css-align/abspos/align-items-static-position-ref.html index 1b36d26bbe1..fd7093f9874 100644 --- a/tests/wpt/tests/css/css-align/abspos/align-items-static-position-ref.html +++ b/tests/wpt/tests/css/css-align/abspos/align-items-static-position-ref.html @@ -53,21 +53,21 @@
-
+
+
-

+
+
-
+
+
-
+
+
-
+
+
-

+ +
diff --git a/tests/wpt/tests/css/css-align/abspos/align-items-static-position.html b/tests/wpt/tests/css/css-align/abspos/align-items-static-position.html index 8f3dbb7ae41..0b653ca5d6c 100644 --- a/tests/wpt/tests/css/css-align/abspos/align-items-static-position.html +++ b/tests/wpt/tests/css/css-align/abspos/align-items-static-position.html @@ -59,21 +59,21 @@
-
+
+
-

+ +
-
+
+
-
+
+
-
+
+
-

+ +
diff --git a/tests/wpt/tests/css/css-align/abspos/align-self-static-position-001-ref.html b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-001-ref.html new file mode 100644 index 00000000000..94d0b43b212 --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-001-ref.html @@ -0,0 +1,59 @@ + + + +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-align/abspos/align-self-static-position-001.html b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-001.html new file mode 100644 index 00000000000..1ff73ef116d --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-001.html @@ -0,0 +1,101 @@ + +Align-self applies to OOF elements of block elements when statically positioned. + + + + + + + + +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-align/abspos/align-self-static-position-002-ref.html b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-002-ref.html new file mode 100644 index 00000000000..d8bef63aa29 --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-002-ref.html @@ -0,0 +1,59 @@ + + + +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-align/abspos/align-self-static-position-002.html b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-002.html new file mode 100644 index 00000000000..4c794bac287 --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-002.html @@ -0,0 +1,109 @@ + +Align-self applies to OOF elements of block elements when statically positioned with different writing modes. + + + + + + + + +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-align/abspos/align-self-static-position-003-ref.html b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-003-ref.html new file mode 100644 index 00000000000..132bbb7360f --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-003-ref.html @@ -0,0 +1,59 @@ + + + + +
text +
+
+
text +
+
+
text +
+
+
+ +
text +
+
+
text +
+
+
text +
+
+
+ +
text +
+
+
text +
+
+
text +
+
diff --git a/tests/wpt/tests/css/css-align/abspos/align-self-static-position-003.html b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-003.html new file mode 100644 index 00000000000..39f871f4c2b --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-003.html @@ -0,0 +1,110 @@ + +Align-self applies to OOF elements of inline elements when statically positioned. + + + + + + + + + +
text +
+
text +
+
text +
+
text +
+
text +
+
text +
+
+ +
text +
+
text +
+
text +
+
text +
+
text +
+
text +
+
+ +
text +
+
text +
+
text +
+
text +
+
text +
+
text +
diff --git a/tests/wpt/tests/css/css-align/abspos/align-self-static-position-004-ref.html b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-004-ref.html new file mode 100644 index 00000000000..68130ee1d7e --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-004-ref.html @@ -0,0 +1,67 @@ + + + + +
text +
+
+
text +
+
+
text +
+
+
+ +
text +
+
+
text +
+
+
text +
+
+
+ +
text +
+
+
text +
+
+
text +
+
diff --git a/tests/wpt/tests/css/css-align/abspos/align-self-static-position-004.html b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-004.html new file mode 100644 index 00000000000..afba7215b11 --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-004.html @@ -0,0 +1,118 @@ + +Align-self applies to OOF elements of inline elements when statically positioned with different writing modes. + + + + + + + + + +
text +
+
text +
+
text +
+
text +
+
text +
+
text +
+
+ +
text +
+
text +
+
text +
+
text +
+
text +
+
text +
+
+ +
text +
+
text +
+
text +
+
text +
+
text +
+
text +
diff --git a/tests/wpt/tests/css/css-align/abspos/align-self-static-position-005-ref.html b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-005-ref.html new file mode 100644 index 00000000000..62597fba605 --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-005-ref.html @@ -0,0 +1,21 @@ + + + +
+ hello + hello + world + +
diff --git a/tests/wpt/tests/css/css-align/abspos/align-self-static-position-005.html b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-005.html new file mode 100644 index 00000000000..207a5443331 --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-005.html @@ -0,0 +1,29 @@ + +Align-self applies to OOF elements of inline elements when statically positioned. + + + + + + + + + +
+ hello + hello + world + +
diff --git a/tests/wpt/tests/css/css-align/abspos/align-self-static-position-006-ref.html b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-006-ref.html new file mode 100644 index 00000000000..5bbafc64845 --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-006-ref.html @@ -0,0 +1,48 @@ + + + +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-align/abspos/align-self-static-position-006.html b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-006.html new file mode 100644 index 00000000000..253b2a52f61 --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-006.html @@ -0,0 +1,64 @@ + +Align-self:self-start/end applies to OOF elements of block elements when statically positioned. + + + + + + + + +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-align/abspos/align-self-static-position-007-ref.html b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-007-ref.html new file mode 100644 index 00000000000..bc9b5f4f650 --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-007-ref.html @@ -0,0 +1,48 @@ + + + + +
text +
+
+
text +
+
+
text +
+
+
+ +
text +
+
+
text +
+
+
text +
+
diff --git a/tests/wpt/tests/css/css-align/abspos/align-self-static-position-007.html b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-007.html new file mode 100644 index 00000000000..ebe872aaab9 --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-007.html @@ -0,0 +1,71 @@ + +Align-self:self-start/end applies to OOF elements of inline elements when statically positioned. + + + + + + + + + +
text +
+
text +
+
text +
+
text +
+
text +
+
text +
+
+ +
text +
+
text +
+
text +
+
text +
+
text +
+
text +
diff --git a/tests/wpt/tests/css/css-align/abspos/align-self-static-position-008-ref.html b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-008-ref.html new file mode 100644 index 00000000000..79cf612b8f8 --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-008-ref.html @@ -0,0 +1,103 @@ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-align/abspos/align-self-static-position-008.html b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-008.html new file mode 100644 index 00000000000..187974884b4 --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/align-self-static-position-008.html @@ -0,0 +1,110 @@ + +Align-self applies to OOF elements of block elements when statically positioned. + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-align/abspos/align-self-stretch-auto-margins-aspect-ratio.html b/tests/wpt/tests/css/css-align/abspos/align-self-stretch-auto-margins-aspect-ratio.html new file mode 100644 index 00000000000..347005204e3 --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/align-self-stretch-auto-margins-aspect-ratio.html @@ -0,0 +1,30 @@ + + + + +

Test passes if there is a filled green square and no red.

+
+
+
diff --git a/tests/wpt/tests/css/css-align/abspos/align-self-stretch-auto-margins.html b/tests/wpt/tests/css/css-align/abspos/align-self-stretch-auto-margins.html new file mode 100644 index 00000000000..9d6eb230653 --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/align-self-stretch-auto-margins.html @@ -0,0 +1,29 @@ + + + + +

Test passes if there is a filled green square and no red.

+
+
+
diff --git a/tests/wpt/tests/css/css-align/abspos/justify-self-stretch-auto-margins-aspect-ratio.html b/tests/wpt/tests/css/css-align/abspos/justify-self-stretch-auto-margins-aspect-ratio.html new file mode 100644 index 00000000000..bc6120584c2 --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/justify-self-stretch-auto-margins-aspect-ratio.html @@ -0,0 +1,30 @@ + + + + +

Test passes if there is a filled green square and no red.

+
+
+
diff --git a/tests/wpt/tests/css/css-align/abspos/justify-self-stretch-auto-margins.html b/tests/wpt/tests/css/css-align/abspos/justify-self-stretch-auto-margins.html new file mode 100644 index 00000000000..675321fc2b1 --- /dev/null +++ b/tests/wpt/tests/css/css-align/abspos/justify-self-stretch-auto-margins.html @@ -0,0 +1,29 @@ + + + + +

Test passes if there is a filled green square and no red.

+
+
+
diff --git a/tests/wpt/tests/css/css-align/blocks/justify-items-anonymous.html b/tests/wpt/tests/css/css-align/blocks/justify-items-anonymous.html new file mode 100644 index 00000000000..641dea1f54b --- /dev/null +++ b/tests/wpt/tests/css/css-align/blocks/justify-items-anonymous.html @@ -0,0 +1,10 @@ + + + +

Test passes if there is a filled green square and no red.

+
+
+ +
+
+
diff --git a/tests/wpt/tests/css/css-align/blocks/justify-self-auto-margins-2.html b/tests/wpt/tests/css/css-align/blocks/justify-self-auto-margins-2.html index 33c98d6c1df..e81c4bba6f4 100644 --- a/tests/wpt/tests/css/css-align/blocks/justify-self-auto-margins-2.html +++ b/tests/wpt/tests/css/css-align/blocks/justify-self-auto-margins-2.html @@ -1,8 +1,7 @@ - +
-
-
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-anchor-position/anchor-center-visibility-change.html b/tests/wpt/tests/css/css-anchor-position/anchor-center-visibility-change.html index 0f7d805844f..e5559433206 100644 --- a/tests/wpt/tests/css/css-anchor-position/anchor-center-visibility-change.html +++ b/tests/wpt/tests/css/css-anchor-position/anchor-center-visibility-change.html @@ -1,8 +1,9 @@ -Tests 'anchor-center' value when target visibility changes +Tests 'anchor-center' value when target visibility changes (by changing 'display', 'visibility', or popover trigger) + @@ -27,27 +28,61 @@ .target { position-anchor: --anchor; - position: fixed; - background: cyan; + position: absolute; +} + +.target-inner { + width: 30px; + height: 20px; +} + +#target-1 { justify-self: anchor-center; + background: cyan; display: none; } + +#target-2 { + align-self: anchor-center; + background: blue; + visibility: hidden; +} + +#target-3 { + align-self: anchor-center; + justify-self: anchor-center; + background: magenta; + + /* Override default popover style */ + margin: 0; + padding: 0; + border: none; +}
-
-
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-anchor-position/position-area-in-grid.html b/tests/wpt/tests/css/css-anchor-position/position-area-in-grid.html index b258f57bc8a..3407fcbc351 100644 --- a/tests/wpt/tests/css/css-anchor-position/position-area-in-grid.html +++ b/tests/wpt/tests/css/css-anchor-position/position-area-in-grid.html @@ -56,6 +56,7 @@ position: relative; width: 400px; height: 400px; + outline: 1px solid; } #anchor { @@ -65,6 +66,7 @@ width: 150px; height: 75px; anchor-name: --anchor; + background: blue; } #anchored { @@ -76,6 +78,7 @@ align-self: stretch; justify-self: stretch; position-anchor: --anchor; + border: solid orange; } diff --git a/tests/wpt/tests/css/css-anchor-position/position-area-visibility-change.html b/tests/wpt/tests/css/css-anchor-position/position-area-visibility-change.html new file mode 100644 index 00000000000..61d8ab50284 --- /dev/null +++ b/tests/wpt/tests/css/css-anchor-position/position-area-visibility-change.html @@ -0,0 +1,113 @@ + + + + + + Tests that an element positioned using position-area renders when it's initially hidden, then shown + + + + + + + + + + + + +
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+ + + + + \ No newline at end of file diff --git a/tests/wpt/tests/css/css-anchor-position/position-visibility-no-overflow-without-anchor.html b/tests/wpt/tests/css/css-anchor-position/position-visibility-no-overflow-without-anchor.html new file mode 100644 index 00000000000..01f4d97947b --- /dev/null +++ b/tests/wpt/tests/css/css-anchor-position/position-visibility-no-overflow-without-anchor.html @@ -0,0 +1,36 @@ + + +CSS Anchor Positioning Test: position-visibility: no-overflow without an anchor + + + + +
+
anchor1
+ +
target1
+
diff --git a/tests/wpt/tests/css/css-anchor-position/reference/position-area-visibility-change-ref.html b/tests/wpt/tests/css/css-anchor-position/reference/position-area-visibility-change-ref.html new file mode 100644 index 00000000000..87e18049409 --- /dev/null +++ b/tests/wpt/tests/css/css-anchor-position/reference/position-area-visibility-change-ref.html @@ -0,0 +1,72 @@ + + + + + +
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+ \ No newline at end of file diff --git a/tests/wpt/tests/css/css-anchor-position/remove-position-try-rules-001.html b/tests/wpt/tests/css/css-anchor-position/remove-position-try-rules-001.html new file mode 100644 index 00000000000..56ad5cf7b32 --- /dev/null +++ b/tests/wpt/tests/css/css-anchor-position/remove-position-try-rules-001.html @@ -0,0 +1,59 @@ + +Remove current fallback @position-try rules + + + + + +
+
+ + + + + diff --git a/tests/wpt/tests/css/css-animations/crashtests/chrome-bug-405795970.html b/tests/wpt/tests/css/css-animations/crashtests/chrome-bug-405795970.html new file mode 100644 index 00000000000..5b45527fd9c --- /dev/null +++ b/tests/wpt/tests/css/css-animations/crashtests/chrome-bug-405795970.html @@ -0,0 +1,17 @@ + + + +

Pass if no crash

+
diff --git a/tests/wpt/tests/css/css-animations/crashtests/chrome-bug-415627003.html b/tests/wpt/tests/css/css-animations/crashtests/chrome-bug-415627003.html new file mode 100644 index 00000000000..af942fbcc97 --- /dev/null +++ b/tests/wpt/tests/css/css-animations/crashtests/chrome-bug-415627003.html @@ -0,0 +1,17 @@ + + + +

Pass if no crash

+
diff --git a/tests/wpt/tests/css/css-animations/parsing/keyframe-selectors.html b/tests/wpt/tests/css/css-animations/parsing/keyframe-selectors.html new file mode 100644 index 00000000000..900cba5a05a --- /dev/null +++ b/tests/wpt/tests/css/css-animations/parsing/keyframe-selectors.html @@ -0,0 +1,45 @@ + +CSS Animations Test: Parse tests for keyframe selectors + + + + + + diff --git a/tests/wpt/tests/css/css-borders/tentative/corner-shape/corner-shape-outside-left.html b/tests/wpt/tests/css/css-borders/tentative/corner-shape/corner-shape-outside-left.html new file mode 100644 index 00000000000..b38b7f3f278 --- /dev/null +++ b/tests/wpt/tests/css/css-borders/tentative/corner-shape/corner-shape-outside-left.html @@ -0,0 +1,71 @@ + +shape-outside with corner-shape: notch + + + + + + +
+
+
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/tests/wpt/tests/css/css-borders/tentative/corner-shape/corner-shape-outside-right.html b/tests/wpt/tests/css/css-borders/tentative/corner-shape/corner-shape-outside-right.html new file mode 100644 index 00000000000..96a297230bb --- /dev/null +++ b/tests/wpt/tests/css/css-borders/tentative/corner-shape/corner-shape-outside-right.html @@ -0,0 +1,71 @@ + +shape-outside with corner-shape: notch + + + + + + +
+
+
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/tests/wpt/tests/css/css-borders/tentative/corner-shape/corner-shape-overflow-clip-margin-ref.html b/tests/wpt/tests/css/css-borders/tentative/corner-shape/corner-shape-overflow-clip-margin-ref.html new file mode 100644 index 00000000000..2c1ccf86cc6 --- /dev/null +++ b/tests/wpt/tests/css/css-borders/tentative/corner-shape/corner-shape-overflow-clip-margin-ref.html @@ -0,0 +1,16 @@ + + +overflow-clip-margin with border-radius & corner-shape + +
+
diff --git a/tests/wpt/tests/css/css-borders/tentative/corner-shape/corner-shape-overflow-clip-margin.html b/tests/wpt/tests/css/css-borders/tentative/corner-shape/corner-shape-overflow-clip-margin.html new file mode 100644 index 00000000000..97371aa6c3f --- /dev/null +++ b/tests/wpt/tests/css/css-borders/tentative/corner-shape/corner-shape-overflow-clip-margin.html @@ -0,0 +1,29 @@ + + +overflow-clip-margin with border-radius & corner-shape + + + + + + + +
+
+
diff --git a/tests/wpt/tests/css/css-box/parsing/margin-trim-computed.html b/tests/wpt/tests/css/css-box/parsing/margin-trim-computed.html index ad2d17f2ef0..c38adea9a68 100644 --- a/tests/wpt/tests/css/css-box/parsing/margin-trim-computed.html +++ b/tests/wpt/tests/css/css-box/parsing/margin-trim-computed.html @@ -27,8 +27,13 @@ test_computed_value("margin-trim", "inline-start block-start", "block-start inli test_computed_value("margin-trim", "inline-end block-start", "block-start inline-end"); test_computed_value("margin-trim", "inline-end block-end", "block-end inline-end"); test_computed_value("margin-trim", "block-start block-end inline-start", "block-start inline-start block-end"); -test_computed_value("margin-trim", "inline-start block-start inline-end block-end", "block-start inline-start block-end inline-end"); -test_computed_value("margin-trim", "inline-start inline-end block-start", "block-start inline-start inline-end"); + +test_computed_value("margin-trim", "block-start inline-start block-end inline-end", "block inline"); +test_computed_value("margin-trim", "block-start block-end inline-end inline-start", "block inline"); +test_computed_value("margin-trim", "block-start block-end inline-start inline-end", "block inline"); +test_computed_value("margin-trim", "inline-start block-end block-start inline-end", "block inline"); +test_computed_value("margin-trim", "inline-start inline-end block-start block-end", "block inline"); +test_computed_value("margin-trim", "inline-end block-end inline-start block-start", "block inline"); diff --git a/tests/wpt/tests/css/css-box/parsing/margin-trim.html b/tests/wpt/tests/css/css-box/parsing/margin-trim.html index 0d92f80b9b9..e8679f9cbf3 100644 --- a/tests/wpt/tests/css/css-box/parsing/margin-trim.html +++ b/tests/wpt/tests/css/css-box/parsing/margin-trim.html @@ -21,6 +21,9 @@ test_valid_value("margin-trim", "block-end"); test_valid_value("margin-trim", "inline-start"); test_valid_value("margin-trim", "inline-end"); +test_valid_value("margin-trim", "block inline"); +test_valid_value("margin-trim", "inline block"); + // Serialize values into either block or inline test_valid_value("margin-trim", "block-start block-end", "block"); test_valid_value("margin-trim", "inline-start inline-end", "inline"); @@ -29,10 +32,15 @@ test_valid_value("margin-trim", "inline-end inline-start", "inline"); test_valid_value("margin-trim", "inline-start block-start"); test_valid_value("margin-trim", "inline-end block-start block-end"); -test_valid_value("margin-trim", "block-start inline-start block-end inline-end"); -test_valid_value("margin-trim", "inline-end block-end inline-start block-start"); -test_invalid_value("margin-trim", "block inline"); +// Serialize 4 values into "block inline" +test_valid_value("margin-trim", "block-start inline-start block-end inline-end", "block inline"); +test_valid_value("margin-trim", "block-start block-end inline-end inline-start", "block inline"); +test_valid_value("margin-trim", "block-start block-end inline-start inline-end", "block inline"); +test_valid_value("margin-trim", "inline-start block-end block-start inline-end", "block inline"); +test_valid_value("margin-trim", "inline-start inline-end block-start block-end", "block inline"); +test_valid_value("margin-trim", "inline-end block-end inline-start block-start", "block inline"); + test_invalid_value("margin-trim", "block block"); test_invalid_value("margin-trim", "inline inline"); test_invalid_value("margin-trim", "block inline-start inline-end"); diff --git a/tests/wpt/tests/css/css-break/nested-fixedpos-in-inline-003-crash.html b/tests/wpt/tests/css/css-break/nested-fixedpos-in-inline-003-crash.html new file mode 100644 index 00000000000..88a04288464 --- /dev/null +++ b/tests/wpt/tests/css/css-break/nested-fixedpos-in-inline-003-crash.html @@ -0,0 +1,13 @@ + + +
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-break/root-margin-001-print-ref.html b/tests/wpt/tests/css/css-break/root-margin-001-print-ref.html new file mode 100644 index 00000000000..320da2e0f55 --- /dev/null +++ b/tests/wpt/tests/css/css-break/root-margin-001-print-ref.html @@ -0,0 +1,6 @@ + + +
+
+This text should be on the second page. +The first page should be blank. diff --git a/tests/wpt/tests/css/css-break/root-margin-001-print.html b/tests/wpt/tests/css/css-break/root-margin-001-print.html new file mode 100644 index 00000000000..b2ec34fe079 --- /dev/null +++ b/tests/wpt/tests/css/css-break/root-margin-001-print.html @@ -0,0 +1,10 @@ + +Root block-start margin taller than the page + + + + +This text should be on the second page. +The first page should be blank. diff --git a/tests/wpt/tests/css/css-break/table/border-spacing.html b/tests/wpt/tests/css/css-break/table/border-spacing.html index fc5e87e35dd..9fd94760681 100644 --- a/tests/wpt/tests/css/css-break/table/border-spacing.html +++ b/tests/wpt/tests/css/css-break/table/border-spacing.html @@ -5,7 +5,7 @@ .cell > div { background:white; }

No red should be seen below.

-
+
1
@@ -20,15 +20,20 @@ diff --git a/tests/wpt/tests/css/css-break/table/table-parts-offsetheight.html b/tests/wpt/tests/css/css-break/table/table-parts-offsetheight.html index 5bdc33b521a..cc49f339de7 100644 --- a/tests/wpt/tests/css/css-break/table/table-parts-offsetheight.html +++ b/tests/wpt/tests/css/css-break/table/table-parts-offsetheight.html @@ -19,11 +19,11 @@ diff --git a/tests/wpt/tests/css/css-break/table/table-parts-offsets-vertical-lr.tentative.html b/tests/wpt/tests/css/css-break/table/table-parts-offsets-vertical-lr.tentative.html index bdac1f40607..77516a6146f 100644 --- a/tests/wpt/tests/css/css-break/table/table-parts-offsets-vertical-lr.tentative.html +++ b/tests/wpt/tests/css/css-break/table/table-parts-offsets-vertical-lr.tentative.html @@ -39,103 +39,103 @@ test(() => { assert_equals(table.offsetTop, 8, "offsetTop"); assert_equals(table.offsetLeft, 8, "offsetLeft"); - assert_equals(table.offsetWidth, 177, "offsetWidth"); - assert_equals(table.offsetHeight, 184, "offsetHeight"); + assert_equals(table.offsetWidth, 70, "offsetWidth"); + assert_equals(table.offsetHeight, 584, "offsetHeight"); }, "table"); test(() => { assert_equals(colgroup.offsetTop, 18, "offsetTop"); assert_equals(colgroup.offsetLeft, 18, "offsetLeft"); - assert_equals(colgroup.offsetWidth, 157, "offsetWidth"); - assert_equals(colgroup.offsetHeight, 107, "offsetHeight"); + assert_equals(colgroup.offsetWidth, 70, "offsetWidth"); + assert_equals(colgroup.offsetHeight, 507, "offsetHeight"); }, "colgroup"); test(() => { assert_equals(col.offsetTop, 18, "offsetTop"); assert_equals(col.offsetLeft, 18, "offsetLeft"); - assert_equals(col.offsetWidth, 157, "offsetWidth"); - assert_equals(col.offsetHeight, 50, "offsetHeight"); + assert_equals(col.offsetWidth, 70, "offsetWidth"); + assert_equals(col.offsetHeight, 450, "offsetHeight"); }, "col"); test(() => { assert_equals(col2.offsetTop, 75, "offsetTop"); assert_equals(col2.offsetLeft, 18, "offsetLeft"); - assert_equals(col2.offsetWidth, 157, "offsetWidth"); - assert_equals(col2.offsetHeight, 50, "offsetHeight"); + assert_equals(col2.offsetWidth, 70, "offsetWidth"); + assert_equals(col2.offsetHeight, 450, "offsetHeight"); }, "col2"); test(() => { assert_equals(colgroup2.offsetTop, 132, "offsetTop"); assert_equals(colgroup2.offsetLeft, 18, "offsetLeft"); - assert_equals(colgroup2.offsetWidth, 157, "offsetWidth"); - assert_equals(colgroup2.offsetHeight, 50, "offsetHeight"); + assert_equals(colgroup2.offsetWidth, 70, "offsetWidth"); + assert_equals(colgroup2.offsetHeight, 450, "offsetHeight"); }, "colgroup2"); test(() => { assert_equals(col3.offsetTop, 132, "offsetTop"); assert_equals(col3.offsetLeft, 18, "offsetLeft"); - assert_equals(col3.offsetWidth, 157, "offsetWidth"); - assert_equals(col3.offsetHeight, 50, "offsetHeight"); + assert_equals(col3.offsetWidth, 70, "offsetWidth"); + assert_equals(col3.offsetHeight, 450, "offsetHeight"); }, "col3"); test(() => { assert_equals(rowgroup.offsetTop, 18, "offsetTop"); assert_equals(rowgroup.offsetLeft, 18, "offsetLeft"); - assert_equals(rowgroup.offsetWidth, 157, "offsetWidth"); - assert_equals(rowgroup.offsetHeight, 164, "offsetHeight"); + assert_equals(rowgroup.offsetWidth, 70, "offsetWidth"); + assert_equals(rowgroup.offsetHeight, 564, "offsetHeight"); }, "rowgroup"); test(() => { assert_equals(row.offsetTop, 18, "offsetTop"); assert_equals(row.offsetLeft, 18, "offsetLeft"); - assert_equals(row.offsetWidth, 100, "offsetWidth"); - assert_equals(row.offsetHeight, 164, "offsetHeight"); + assert_equals(row.offsetWidth, 70, "offsetWidth"); + assert_equals(row.offsetHeight, 364, "offsetHeight"); }, "row"); test(() => { assert_equals(cell.offsetTop, 18, "offsetTop"); assert_equals(cell.offsetLeft, 18, "offsetLeft"); - assert_equals(cell.offsetWidth, 100, "offsetWidth"); - assert_equals(cell.offsetHeight, 50, "offsetHeight"); + assert_equals(cell.offsetWidth, 70, "offsetWidth"); + assert_equals(cell.offsetHeight, 250, "offsetHeight"); }, "cell"); test(() => { assert_equals(content.offsetTop, 18, "offsetTop"); assert_equals(content.offsetLeft, 18, "offsetLeft"); - assert_equals(content.offsetWidth, 100, "offsetWidth"); - assert_equals(content.offsetHeight, 50, "offsetHeight"); + assert_equals(content.offsetWidth, 70, "offsetWidth"); + assert_equals(content.offsetHeight, 250, "offsetHeight"); }, "content"); test(() => { assert_equals(cell2.offsetTop, 75, "offsetTop"); assert_equals(cell2.offsetLeft, 18, "offsetLeft"); - assert_equals(cell2.offsetWidth, 100, "offsetWidth"); - assert_equals(cell2.offsetHeight, 50, "offsetHeight"); + assert_equals(cell2.offsetWidth, 70, "offsetWidth"); + assert_equals(cell2.offsetHeight, 250, "offsetHeight"); }, "cell2"); test(() => { assert_equals(content2.offsetTop, 75, "offsetTop"); assert_equals(content2.offsetLeft, 18, "offsetLeft"); - assert_equals(content2.offsetWidth, 100, "offsetWidth"); - assert_equals(content2.offsetHeight, 50, "offsetHeight"); + assert_equals(content2.offsetWidth, 70, "offsetWidth"); + assert_equals(content2.offsetHeight, 250, "offsetHeight"); }, "content2"); test(() => { assert_equals(cell3.offsetTop, 132, "offsetTop"); assert_equals(cell3.offsetLeft, 18, "offsetLeft"); - assert_equals(cell3.offsetWidth, 100, "offsetWidth"); - assert_equals(cell3.offsetHeight, 50, "offsetHeight"); + assert_equals(cell3.offsetWidth, 70, "offsetWidth"); + assert_equals(cell3.offsetHeight, 250, "offsetHeight"); }, "cell3"); test(() => { assert_equals(content3.offsetTop, 132, "offsetTop"); assert_equals(content3.offsetLeft, 18, "offsetLeft"); - assert_equals(content3.offsetWidth, 100, "offsetWidth"); - assert_equals(content3.offsetHeight, 50, "offsetHeight"); + assert_equals(content3.offsetWidth, 70, "offsetWidth"); + assert_equals(content3.offsetHeight, 250, "offsetHeight"); }, "content3"); test(() => { assert_equals(row2.offsetTop, 218, "offsetTop"); assert_equals(row2.offsetLeft, 55, "offsetLeft"); - assert_equals(row2.offsetWidth, 50, "offsetWidth"); - assert_equals(row2.offsetHeight, 164, "offsetHeight"); + assert_equals(row2.offsetWidth, 70, "offsetWidth"); + assert_equals(row2.offsetHeight, 364, "offsetHeight"); }, "row2"); test(() => { assert_equals(cell4.offsetTop, 218, "offsetTop"); assert_equals(cell4.offsetLeft, 55, "offsetLeft"); - assert_equals(cell4.offsetWidth, 50, "offsetWidth"); - assert_equals(cell4.offsetHeight, 50, "offsetHeight"); + assert_equals(cell4.offsetWidth, 70, "offsetWidth"); + assert_equals(cell4.offsetHeight, 250, "offsetHeight"); }, "cell4"); test(() => { assert_equals(content4.offsetTop, 218, "offsetTop"); assert_equals(content4.offsetLeft, 55, "offsetLeft"); - assert_equals(content4.offsetWidth, 50, "offsetWidth"); - assert_equals(content4.offsetHeight, 50, "offsetHeight"); + assert_equals(content4.offsetWidth, 70, "offsetWidth"); + assert_equals(content4.offsetHeight, 250, "offsetHeight"); }, "content4"); diff --git a/tests/wpt/tests/css/css-break/table/table-parts-offsets-vertical-rl.tentative.html b/tests/wpt/tests/css/css-break/table/table-parts-offsets-vertical-rl.tentative.html index 9d4a472d438..ea58b145738 100644 --- a/tests/wpt/tests/css/css-break/table/table-parts-offsets-vertical-rl.tentative.html +++ b/tests/wpt/tests/css/css-break/table/table-parts-offsets-vertical-rl.tentative.html @@ -37,105 +37,105 @@ diff --git a/tests/wpt/tests/css/css-break/table/table-parts-offsets.tentative.html b/tests/wpt/tests/css/css-break/table/table-parts-offsets.tentative.html index 265d761ffb3..4bed47b6a4d 100644 --- a/tests/wpt/tests/css/css-break/table/table-parts-offsets.tentative.html +++ b/tests/wpt/tests/css/css-break/table/table-parts-offsets.tentative.html @@ -38,103 +38,103 @@ test(() => { assert_equals(table.offsetTop, 8, "offsetTop"); assert_equals(table.offsetLeft, 8, "offsetLeft"); - assert_equals(table.offsetWidth, 184, "offsetWidth"); - assert_equals(table.offsetHeight, 177, "offsetHeight"); + assert_equals(table.offsetWidth, 584, "offsetWidth"); + assert_equals(table.offsetHeight, 70, "offsetHeight"); }, "table"); test(() => { assert_equals(colgroup.offsetTop, 18, "offsetTop"); assert_equals(colgroup.offsetLeft, 18, "offsetLeft"); - assert_equals(colgroup.offsetWidth, 107, "offsetWidth"); - assert_equals(colgroup.offsetHeight, 157, "offsetHeight"); + assert_equals(colgroup.offsetWidth, 507, "offsetWidth"); + assert_equals(colgroup.offsetHeight, 70, "offsetHeight"); }, "colgroup"); test(() => { assert_equals(col.offsetTop, 18, "offsetTop"); assert_equals(col.offsetLeft, 18, "offsetLeft"); - assert_equals(col.offsetWidth, 50, "offsetWidth"); - assert_equals(col.offsetHeight, 157, "offsetHeight"); + assert_equals(col.offsetWidth, 450, "offsetWidth"); + assert_equals(col.offsetHeight, 70, "offsetHeight"); }, "col"); test(() => { assert_equals(col2.offsetTop, 18, "offsetTop"); assert_equals(col2.offsetLeft, 75, "offsetLeft"); - assert_equals(col2.offsetWidth, 50, "offsetWidth"); - assert_equals(col2.offsetHeight, 157, "offsetHeight"); + assert_equals(col2.offsetWidth, 450, "offsetWidth"); + assert_equals(col2.offsetHeight, 70, "offsetHeight"); }, "col2"); test(() => { assert_equals(colgroup2.offsetTop, 18, "offsetTop"); assert_equals(colgroup2.offsetLeft, 132, "offsetLeft"); - assert_equals(colgroup2.offsetWidth, 50, "offsetWidth"); - assert_equals(colgroup2.offsetHeight, 157, "offsetHeight"); + assert_equals(colgroup2.offsetWidth, 450, "offsetWidth"); + assert_equals(colgroup2.offsetHeight, 70, "offsetHeight"); }, "colgroup2"); test(() => { assert_equals(col3.offsetTop, 18, "offsetTop"); assert_equals(col3.offsetLeft, 132, "offsetLeft"); - assert_equals(col3.offsetWidth, 50, "offsetWidth"); - assert_equals(col3.offsetHeight, 157, "offsetHeight"); + assert_equals(col3.offsetWidth, 450, "offsetWidth"); + assert_equals(col3.offsetHeight, 70, "offsetHeight"); }, "col3"); test(() => { assert_equals(rowgroup.offsetTop, 18, "offsetTop"); assert_equals(rowgroup.offsetLeft, 18, "offsetLeft"); - assert_equals(rowgroup.offsetWidth, 164, "offsetWidth"); - assert_equals(rowgroup.offsetHeight, 157, "offsetHeight"); + assert_equals(rowgroup.offsetWidth, 564, "offsetWidth"); + assert_equals(rowgroup.offsetHeight, 70, "offsetHeight"); }, "rowgroup"); test(() => { assert_equals(row.offsetTop, 18, "offsetTop"); assert_equals(row.offsetLeft, 18, "offsetLeft"); - assert_equals(row.offsetWidth, 164, "offsetWidth"); - assert_equals(row.offsetHeight, 100, "offsetHeight"); + assert_equals(row.offsetWidth, 364, "offsetWidth"); + assert_equals(row.offsetHeight, 70, "offsetHeight"); }, "row"); test(() => { assert_equals(cell.offsetTop, 18, "offsetTop"); assert_equals(cell.offsetLeft, 18, "offsetLeft"); - assert_equals(cell.offsetWidth, 50, "offsetWidth"); - assert_equals(cell.offsetHeight, 100, "offsetHeight"); + assert_equals(cell.offsetWidth, 250, "offsetWidth"); + assert_equals(cell.offsetHeight, 70, "offsetHeight"); }, "cell"); test(() => { assert_equals(content.offsetTop, 18, "offsetTop"); assert_equals(content.offsetLeft, 18, "offsetLeft"); - assert_equals(content.offsetWidth, 50, "offsetWidth"); - assert_equals(content.offsetHeight, 100, "offsetHeight"); + assert_equals(content.offsetWidth, 250, "offsetWidth"); + assert_equals(content.offsetHeight, 70, "offsetHeight"); }, "content"); test(() => { assert_equals(cell2.offsetTop, 18, "offsetTop"); assert_equals(cell2.offsetLeft, 75, "offsetLeft"); - assert_equals(cell2.offsetWidth, 50, "offsetWidth"); - assert_equals(cell2.offsetHeight, 100, "offsetHeight"); + assert_equals(cell2.offsetWidth, 250, "offsetWidth"); + assert_equals(cell2.offsetHeight, 70, "offsetHeight"); }, "cell2"); test(() => { assert_equals(content2.offsetTop, 18, "offsetTop"); assert_equals(content2.offsetLeft, 75, "offsetLeft"); - assert_equals(content2.offsetWidth, 50, "offsetWidth"); - assert_equals(content2.offsetHeight, 100, "offsetHeight"); + assert_equals(content2.offsetWidth, 250, "offsetWidth"); + assert_equals(content2.offsetHeight, 70, "offsetHeight"); }, "content2"); test(() => { assert_equals(cell3.offsetTop, 18, "offsetTop"); assert_equals(cell3.offsetLeft, 132, "offsetLeft"); - assert_equals(cell3.offsetWidth, 50, "offsetWidth"); - assert_equals(cell3.offsetHeight, 100, "offsetHeight"); + assert_equals(cell3.offsetWidth, 250, "offsetWidth"); + assert_equals(cell3.offsetHeight, 70, "offsetHeight"); }, "cell3"); test(() => { assert_equals(content3.offsetTop, 18, "offsetTop"); assert_equals(content3.offsetLeft, 132, "offsetLeft"); - assert_equals(content3.offsetWidth, 50, "offsetWidth"); - assert_equals(content3.offsetHeight, 100, "offsetHeight"); + assert_equals(content3.offsetWidth, 250, "offsetWidth"); + assert_equals(content3.offsetHeight, 70, "offsetHeight"); }, "content3"); test(() => { assert_equals(row2.offsetTop, 55, "offsetTop"); assert_equals(row2.offsetLeft, 218, "offsetLeft"); - assert_equals(row2.offsetWidth, 164, "offsetWidth"); - assert_equals(row2.offsetHeight, 50, "offsetHeight"); + assert_equals(row2.offsetWidth, 364, "offsetWidth"); + assert_equals(row2.offsetHeight, 70, "offsetHeight"); }, "row2"); test(() => { assert_equals(cell4.offsetTop, 55, "offsetTop"); assert_equals(cell4.offsetLeft, 218, "offsetLeft"); - assert_equals(cell4.offsetWidth, 50, "offsetWidth"); - assert_equals(cell4.offsetHeight, 50, "offsetHeight"); + assert_equals(cell4.offsetWidth, 250, "offsetWidth"); + assert_equals(cell4.offsetHeight, 70, "offsetHeight"); }, "cell4"); test(() => { assert_equals(content4.offsetTop, 55, "offsetTop"); assert_equals(content4.offsetLeft, 218, "offsetLeft"); - assert_equals(content4.offsetWidth, 50, "offsetWidth"); - assert_equals(content4.offsetHeight, 50, "offsetHeight"); + assert_equals(content4.offsetWidth, 250, "offsetWidth"); + assert_equals(content4.offsetHeight, 70, "offsetHeight"); }, "content4"); diff --git a/tests/wpt/tests/css/css-cascade/inline-style-background.html b/tests/wpt/tests/css/css-cascade/inline-style-background.html new file mode 100644 index 00000000000..11451f8cefd --- /dev/null +++ b/tests/wpt/tests/css/css-cascade/inline-style-background.html @@ -0,0 +1,16 @@ + + + + + + + +
Test passes if it does not crash.
+ + diff --git a/tests/wpt/tests/css/css-color/parsing/color-computed-relative-color.html b/tests/wpt/tests/css/css-color/parsing/color-computed-relative-color.html index 9bb87459623..a5c489e40a6 100644 --- a/tests/wpt/tests/css/css-color/parsing/color-computed-relative-color.html +++ b/tests/wpt/tests/css/css-color/parsing/color-computed-relative-color.html @@ -871,6 +871,7 @@ fuzzy_test_computed_color(`lch(from var(--mycolor) l 0 h)`, `lch(62.75 0 326.96)`, 0.02); fuzzy_test_computed_color(`var(--mygray)`, `lch(62.75 0 326.96)`, 0.02); fuzzy_test_computed_color(`lch(from var(--mygray) l 30 h)`, `lch(62.75 30 326.96)`, 0.02); + fuzzy_test_computed_color(`LCH(from var(--accent) l c calc(h + 180 * sibling-index()))`, `lch(65.49 39.45 10.11)`, 0.02); diff --git a/tests/wpt/tests/css/css-color/parsing/color-valid-relative-color.html b/tests/wpt/tests/css/css-color/parsing/color-valid-relative-color.html index f1f31b93d41..142f96bcf9b 100644 --- a/tests/wpt/tests/css/css-color/parsing/color-valid-relative-color.html +++ b/tests/wpt/tests/css/css-color/parsing/color-valid-relative-color.html @@ -560,7 +560,7 @@ fuzzy_test_valid_color(`oklch(from oklch(0.7 0.2 300) calc(l - 0.2) c h)`, `oklch(from oklch(0.7 0.2 300) calc(-0.2 + l) c h)`); fuzzy_test_valid_color(`oklch(from oklch(0.7 0.2 300) l calc(c / 2) h)`, `oklch(from oklch(0.7 0.2 300) l calc(0.5 * c) h)`); fuzzy_test_valid_color(`oklch(from oklch(0.7 0.2 300) l c calc(h * 2.5))`, `oklch(from oklch(0.7 0.2 300) l c calc(2.5 * h))`); - fuzzy_test_valid_color(`oklch(from red calc(1 / l) c h)`, `oklch(from red calc((1 / l)) c h)`); + fuzzy_test_valid_color(`oklch(from red calc(1 / l) c h)`); // Testing with 'none'. fuzzy_test_valid_color(`oklch(from oklch(0.7 0.45 30) none none none)`); diff --git a/tests/wpt/tests/css/css-conditional/container-queries/position-sticky-crash.html b/tests/wpt/tests/css/css-conditional/container-queries/position-sticky-crash.html new file mode 100644 index 00000000000..aebebbdc7ac --- /dev/null +++ b/tests/wpt/tests/css/css-conditional/container-queries/position-sticky-crash.html @@ -0,0 +1,34 @@ + + + +
+ +
+
+ +
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-contain/contain-inline-size-grid-auto-fit.html b/tests/wpt/tests/css/css-contain/contain-inline-size-grid-auto-fit.html new file mode 100644 index 00000000000..b6401ac6640 --- /dev/null +++ b/tests/wpt/tests/css/css-contain/contain-inline-size-grid-auto-fit.html @@ -0,0 +1,32 @@ + + + + + + + + + + +

Test passes if there is a filled green square.

+
+
+
+
+
+
+ + diff --git a/tests/wpt/tests/css/css-contain/contain-paint-stacking-context-001-ref.html b/tests/wpt/tests/css/css-contain/contain-paint-stacking-context-001-ref.html index c7553716ab6..e2516f3d378 100644 --- a/tests/wpt/tests/css/css-contain/contain-paint-stacking-context-001-ref.html +++ b/tests/wpt/tests/css/css-contain/contain-paint-stacking-context-001-ref.html @@ -1,62 +1,13 @@ - - - - CSS Reftest Reference - - - - -
-

-
- -
-
-

-
- -
-
-
- -
-

-
- - + +Paint Containment Stacking Context Reference + + +
+Test succeeds if there is no red. diff --git a/tests/wpt/tests/css/css-contain/contain-paint-stacking-context-001a.html b/tests/wpt/tests/css/css-contain/contain-paint-stacking-context-001a.html index 71102b6c73a..5e588cb6174 100644 --- a/tests/wpt/tests/css/css-contain/contain-paint-stacking-context-001a.html +++ b/tests/wpt/tests/css/css-contain/contain-paint-stacking-context-001a.html @@ -1,66 +1,32 @@ - - - - CSS Test: 'contain: paint' with stacking contents. Z-index is defined only for siblings and children. - - - - - - - - -
-

-
- -
-
-

-
- -
-
-
- -
-

-
- - +'contain: paint' establishes stacking context. + + + + + +
+
+
+
+Test succeeds if there is no red. diff --git a/tests/wpt/tests/css/css-contain/contain-paint-stacking-context-001b.html b/tests/wpt/tests/css/css-contain/contain-paint-stacking-context-001b.html index 0c4d3323bf7..e03323782a7 100644 --- a/tests/wpt/tests/css/css-contain/contain-paint-stacking-context-001b.html +++ b/tests/wpt/tests/css/css-contain/contain-paint-stacking-context-001b.html @@ -1,66 +1,32 @@ - - - - CSS Test: 'will-change: contain' with stacking contents. Z-index is defined only for siblings and children. - - - - - - - - -
-

-
- -
-
-

-
- -
-
-
- -
-

-
- - +'will-change: contain' establishes stacking context. + + + + + +
+
+
+
+Test succeeds if there is no red. diff --git a/tests/wpt/tests/css/css-content/parsing/content-counter-valid.html b/tests/wpt/tests/css/css-content/parsing/content-counter-valid.html new file mode 100644 index 00000000000..6de60c9f1d8 --- /dev/null +++ b/tests/wpt/tests/css/css-content/parsing/content-counter-valid.html @@ -0,0 +1,13 @@ + + +CSS Content Test: counter() in alt text parsing + + + + +
+ diff --git a/tests/wpt/tests/css/css-display/display-contents-inline-002.html b/tests/wpt/tests/css/css-display/display-contents-inline-002.html new file mode 100644 index 00000000000..f40a34129f3 --- /dev/null +++ b/tests/wpt/tests/css/css-display/display-contents-inline-002.html @@ -0,0 +1,18 @@ + + +CSS Display: display:contents in inline layout should affect style of descendants + + + + + +

You should see the word PASS and no red below.

+ + P
AS
S +
diff --git a/tests/wpt/tests/css/css-display/display-contents-pseudo-click-target.html b/tests/wpt/tests/css/css-display/display-contents-pseudo-click-target.html new file mode 100644 index 00000000000..2620c97312f --- /dev/null +++ b/tests/wpt/tests/css/css-display/display-contents-pseudo-click-target.html @@ -0,0 +1,43 @@ + +Clicking a display: contents pseudo-element targets that element + + + + + + + + + + +
+ +
+ diff --git a/tests/wpt/tests/css/css-easing/linear-timing-functions-syntax.html b/tests/wpt/tests/css/css-easing/linear-timing-functions-syntax.html index 0c949e9ea7d..408264b58df 100644 --- a/tests/wpt/tests/css/css-easing/linear-timing-functions-syntax.html +++ b/tests/wpt/tests/css/css-easing/linear-timing-functions-syntax.html @@ -26,6 +26,7 @@ test_valid_value("animation-timing-function", "linear(0, 0.5 25% 75%, 1 100% 100 test_valid_value("animation-timing-function", "linear(0, 1.3, 1, 0.92, 1, 0.99, 1, 0.998, 1 100% 100%)"); test_valid_value("animation-timing-function", "linear(0, 0 40%, 1, 0.5, 1)"); test_valid_value("animation-timing-function", "linear(0, 1.3, 1, 0.92, 1, 0.99, 1, 1.004, 0.998, 1 100% 100%)"); +test_valid_value("animation-timing-function", "linear(calc(0/0), 1)", "linear(0 0%, 1 100%)"); test_invalid_value("animation-timing-function", "linear()"); test_invalid_value("animation-timing-function", "linear(0)"); diff --git a/tests/wpt/tests/css/css-easing/step-timing-functions-syntax.html b/tests/wpt/tests/css/css-easing/step-timing-functions-syntax.html index 4e8b21e4413..e8465ff7a33 100644 --- a/tests/wpt/tests/css/css-easing/step-timing-functions-syntax.html +++ b/tests/wpt/tests/css/css-easing/step-timing-functions-syntax.html @@ -29,6 +29,7 @@ test_invalid_value("animation-timing-function", "steps(0, jump-start)"); test_invalid_value("animation-timing-function", "steps(0, jump-end)"); test_invalid_value("animation-timing-function", "steps(0, jump-both)"); test_invalid_value("animation-timing-function", "steps(1, jump-none)"); +test_invalid_value("animation-timing-function", "steps(calc(0/0), jump-none)"); diff --git a/tests/wpt/tests/css/css-flexbox/flex-container-max-content-002.tentative.html b/tests/wpt/tests/css/css-flexbox/flex-container-max-content-002.tentative.html new file mode 100644 index 00000000000..77a074b153a --- /dev/null +++ b/tests/wpt/tests/css/css-flexbox/flex-container-max-content-002.tentative.html @@ -0,0 +1,169 @@ + + +Flex Container Max-Content Sizes + + + + + + + + + +
+
X
+
+
+
X
+
+
+
X
+
+ +
+
X X
+
X X
+
+
+
X X
+
X X
+
+
+
X X
+
X X
+
+ +
+ + +
+
X
+
+
+
X
+
+
+
X
+
+ +
+
X X
+
X X
+
+
+
X X
+
X X
+
+
+
X X
+
X X
+
+ +
+ + +
+
X
+
+
+
X
+
+
+
X
+
+ +
+
X X
+
X X
+
+
+
X X
+
X X
+
+
+
X X
+
X X
+
+ +
+ + +
+
X
+
+
+
X
+
+
+
X
+
+ +
+
X X
+
X X
+
+
+
X X
+
X X
+
+
+
X X
+
X X
+
+ + + + + diff --git a/tests/wpt/tests/css/css-flexbox/flex-container-min-content-002.tentative.html b/tests/wpt/tests/css/css-flexbox/flex-container-min-content-002.tentative.html new file mode 100644 index 00000000000..92d37e3b9d8 --- /dev/null +++ b/tests/wpt/tests/css/css-flexbox/flex-container-min-content-002.tentative.html @@ -0,0 +1,169 @@ + + +Flex Container Min-Content Sizes + + + + + + + + + +
+
X
+
+
+
X
+
+
+
X
+
+ +
+
X X
+
X X
+
+
+
X X
+
X X
+
+
+
X X
+
X X
+
+ +
+ + +
+
X
+
+
+
X
+
+
+
X
+
+ +
+
X X
+
X X
+
+
+
X X
+
X X
+
+
+
X X
+
X X
+
+ +
+ + +
+
X
+
+
+
X
+
+
+
X
+
+ +
+
X X
+
X X
+
+
+
X X
+
X X
+
+
+
X X
+
X X
+
+ +
+ + +
+
X
+
+
+
X
+
+
+
X
+
+ +
+
X X
+
X X
+
+
+
X X
+
X X
+
+
+
X X
+
X X
+
+ + + + + diff --git a/tests/wpt/tests/css/css-flexbox/overflow-area-001.html b/tests/wpt/tests/css/css-flexbox/overflow-area-001.html index 10685c6e84a..ac0573a7282 100644 --- a/tests/wpt/tests/css/css-flexbox/overflow-area-001.html +++ b/tests/wpt/tests/css/css-flexbox/overflow-area-001.html @@ -15,7 +15,7 @@ overflow: scroll; border: solid blue; background: aqua; - margin: 1em 0.5em; + margin: 0.5em; float: left; } div { diff --git a/tests/wpt/tests/css/css-flexbox/overflow-area-002.html b/tests/wpt/tests/css/css-flexbox/overflow-area-002.html index 1287270f1ef..24fb312397d 100644 --- a/tests/wpt/tests/css/css-flexbox/overflow-area-002.html +++ b/tests/wpt/tests/css/css-flexbox/overflow-area-002.html @@ -16,7 +16,7 @@ overflow: scroll; border: solid blue; background: aqua; - margin: 1em 0.5em; + margin: 0.5em; float: left; } div { diff --git a/tests/wpt/tests/css/css-flexbox/overflow-area-003.html b/tests/wpt/tests/css/css-flexbox/overflow-area-003.html index ff86dea30f1..382fb886339 100644 --- a/tests/wpt/tests/css/css-flexbox/overflow-area-003.html +++ b/tests/wpt/tests/css/css-flexbox/overflow-area-003.html @@ -22,7 +22,7 @@ width: 60px; height: 60px; background: teal; - margin: 1em 0.5em; + margin: 0.5em; float: left; } diff --git a/tests/wpt/tests/css/css-flexbox/reference/overflow-area-001-ref.html b/tests/wpt/tests/css/css-flexbox/reference/overflow-area-001-ref.html index 2c2a825f41e..cd13c7e0915 100644 --- a/tests/wpt/tests/css/css-flexbox/reference/overflow-area-001-ref.html +++ b/tests/wpt/tests/css/css-flexbox/reference/overflow-area-001-ref.html @@ -12,7 +12,7 @@ overflow: scroll; border: solid blue; background: aqua; - margin: 1em 0.5em; + margin: 0.5em; float: left; width: 80px; height: 80px; diff --git a/tests/wpt/tests/css/css-flexbox/reference/overflow-area-002-ref.html b/tests/wpt/tests/css/css-flexbox/reference/overflow-area-002-ref.html index f5d06c6cf3e..6422d4f7dfb 100644 --- a/tests/wpt/tests/css/css-flexbox/reference/overflow-area-002-ref.html +++ b/tests/wpt/tests/css/css-flexbox/reference/overflow-area-002-ref.html @@ -13,7 +13,7 @@ overflow: scroll; border: solid blue; background: aqua; - margin: 1em 0.5em; + margin: 0.5em; float: left; width: 80px; height: 80px; diff --git a/tests/wpt/tests/css/css-flexbox/reference/overflow-area-003-ref.html b/tests/wpt/tests/css/css-flexbox/reference/overflow-area-003-ref.html index 2d9bf19922c..40df8cb5a8b 100644 --- a/tests/wpt/tests/css/css-flexbox/reference/overflow-area-003-ref.html +++ b/tests/wpt/tests/css/css-flexbox/reference/overflow-area-003-ref.html @@ -17,7 +17,7 @@ width: 60px; height: 60px; background: teal; - margin: 1em 0.5em; + margin: 0.5em; float: left; } diff --git a/tests/wpt/tests/css/css-flexbox/stretched-child-in-nested-flexbox.html b/tests/wpt/tests/css/css-flexbox/stretched-child-in-nested-flexbox-001.html similarity index 100% rename from tests/wpt/tests/css/css-flexbox/stretched-child-in-nested-flexbox.html rename to tests/wpt/tests/css/css-flexbox/stretched-child-in-nested-flexbox-001.html diff --git a/tests/wpt/tests/css/css-flexbox/stretched-child-in-nested-flexbox-002.html b/tests/wpt/tests/css/css-flexbox/stretched-child-in-nested-flexbox-002.html new file mode 100644 index 00000000000..9a00b3c40a3 --- /dev/null +++ b/tests/wpt/tests/css/css-flexbox/stretched-child-in-nested-flexbox-002.html @@ -0,0 +1,19 @@ + + + +css-flexbox: Tests nested flex item with `align-items: stretch` + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+
+ + diff --git a/tests/wpt/tests/css/css-fonts/font-family-name-000.xht b/tests/wpt/tests/css/css-fonts/font-family-name-000.xht index 2afa0bafc04..4eee0d23233 100644 --- a/tests/wpt/tests/css/css-fonts/font-family-name-000.xht +++ b/tests/wpt/tests/css/css-fonts/font-family-name-000.xht @@ -1,26 +1,27 @@ - CSS Test: Test for prescense of test fonts + CSS Test: Test for presence of test fonts +
Test fonts must be installed for this test: FAIL
-

Test passes if FAIL does not appear below.

-

Prerequisites: these tests require that CSSTest fonts be installed.

-
FAIL
-
FAIL
+

FAIL

+

FAIL

diff --git a/tests/wpt/tests/css/css-fonts/font-family-name-024.xht b/tests/wpt/tests/css/css-fonts/font-family-name-024.xht index 20aec304fa4..6630cfd6090 100644 --- a/tests/wpt/tests/css/css-fonts/font-family-name-024.xht +++ b/tests/wpt/tests/css/css-fonts/font-family-name-024.xht @@ -9,7 +9,11 @@ +
Foo
diff --git a/tests/wpt/tests/css/css-fonts/font-shorthand-serialization-font-stretch.html b/tests/wpt/tests/css/css-fonts/font-shorthand-serialization-font-stretch.html index 3079cc06e7e..7bc591ec73d 100644 --- a/tests/wpt/tests/css/css-fonts/font-shorthand-serialization-font-stretch.html +++ b/tests/wpt/tests/css/css-fonts/font-shorthand-serialization-font-stretch.html @@ -94,7 +94,7 @@ test(function() { assert_equals(div.style.font, "ultra-condensed medium serif"); div.style.fontStretch = "calc(50% + 25% * sign(100em - 1px))"; - assert_equals(div.style.fontStretch, "calc(50% + 25% * sign(100em - 1px))"); + assert_equals(div.style.fontStretch, "calc(50% + (25% * sign(100em - 1px)))"); assert_equals(div.style.font, ""); }, "calc() transformation into keywords"); diff --git a/tests/wpt/tests/css/css-fonts/font-variant-emoji-005-ref.html b/tests/wpt/tests/css/css-fonts/font-variant-emoji-005-ref.html new file mode 100644 index 00000000000..d4275b5c951 --- /dev/null +++ b/tests/wpt/tests/css/css-fonts/font-variant-emoji-005-ref.html @@ -0,0 +1,23 @@ + + +CSS Fonts reference + + + + +

Only lines 3 and 4 should show a keycap numeral:

+ +

1. text: X

+ +

2. unicode: X

+ +

3. emoji: X3️⃣X

+ +

4. with VS16: X4️⃣X

+ +

5. emoji, with VS15: X

diff --git a/tests/wpt/tests/css/css-fonts/font-variant-emoji-005.html b/tests/wpt/tests/css/css-fonts/font-variant-emoji-005.html new file mode 100644 index 00000000000..0f5c2d7a98a --- /dev/null +++ b/tests/wpt/tests/css/css-fonts/font-variant-emoji-005.html @@ -0,0 +1,29 @@ + + +CSS Fonts: font-variant-emoji web font test + + + + + + + + +

Only lines 3 and 4 should show a keycap numeral:

+ +

1. text: X1⃣X

+ +

2. unicode: X2⃣X

+ +

3. emoji: X3⃣X

+ +

4. with VS16: X4️⃣X

+ +

5. emoji, with VS15: X5︎⃣X

diff --git a/tests/wpt/tests/css/css-fonts/parsing/font-palette-values-invalid.html b/tests/wpt/tests/css/css-fonts/parsing/font-palette-values-invalid.html index a3a0a88ba68..32c9c4c5efe 100644 --- a/tests/wpt/tests/css/css-fonts/parsing/font-palette-values-invalid.html +++ b/tests/wpt/tests/css/css-fonts/parsing/font-palette-values-invalid.html @@ -140,13 +140,18 @@ @font-palette-values --A { override-colors: 0 color-mix(in lch, red, color-mix(in lch, currentcolor, black)); } + +/* 24 */ +@font-palette-values --A { + base-palette: sibling-index(); +} diff --git a/tests/wpt/tests/css/css-fonts/test-synthetic-bold-notref.html b/tests/wpt/tests/css/css-fonts/test-synthetic-bold-notref.html new file mode 100644 index 00000000000..4a952acb46f --- /dev/null +++ b/tests/wpt/tests/css/css-fonts/test-synthetic-bold-notref.html @@ -0,0 +1,18 @@ + + + + CSS Test: Test for synthetic bold rendering + + + +
Test fonts must be installed for this test: PASS
+

Browser supports synthetic bolding if PASS appears on both lines and the second line appears bolder:

+
PASS
+
PASS
+ + diff --git a/tests/wpt/tests/css/css-fonts/test-synthetic-bold.xht b/tests/wpt/tests/css/css-fonts/test-synthetic-bold.html similarity index 84% rename from tests/wpt/tests/css/css-fonts/test-synthetic-bold.xht rename to tests/wpt/tests/css/css-fonts/test-synthetic-bold.html index 2d34f02a92a..c2337c0a3ce 100644 --- a/tests/wpt/tests/css/css-fonts/test-synthetic-bold.xht +++ b/tests/wpt/tests/css/css-fonts/test-synthetic-bold.html @@ -1,15 +1,16 @@ - - + + CSS Test: Test for synthetic bold rendering + + + +
Test fonts must be installed for this test: FAIL
+

Browser supports synthetic italics if PASS appears on both lines and the second line is slanted right:

+
FAIL
+
FAIL
+ + diff --git a/tests/wpt/tests/css/css-fonts/test-synthetic-italic.xht b/tests/wpt/tests/css/css-fonts/test-synthetic-italic.html similarity index 87% rename from tests/wpt/tests/css/css-fonts/test-synthetic-italic.xht rename to tests/wpt/tests/css/css-fonts/test-synthetic-italic.html index 05b9cf67984..3f6764ecfa3 100644 --- a/tests/wpt/tests/css/css-fonts/test-synthetic-italic.xht +++ b/tests/wpt/tests/css/css-fonts/test-synthetic-italic.html @@ -1,11 +1,12 @@ - - - + + + CSS Test: Test for synthetic italic rendering + +
+
+
+
+
+
+ +
+
diff --git a/tests/wpt/tests/css/css-gaps/agnostic/gap-decorations-003-ref.html b/tests/wpt/tests/css/css-gaps/agnostic/gap-decorations-003-ref.html new file mode 100644 index 00000000000..52e45c4f65f --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/agnostic/gap-decorations-003-ref.html @@ -0,0 +1,32 @@ + + + +
+
+
+
+
+
+
+
+ + diff --git a/tests/wpt/tests/css/css-gaps/agnostic/gap-decorations-003.html b/tests/wpt/tests/css/css-gaps/agnostic/gap-decorations-003.html new file mode 100644 index 00000000000..2ec4a45beb7 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/agnostic/gap-decorations-003.html @@ -0,0 +1,51 @@ + + + + column-rule-color invalidates and paints correctly on multi-col + + + + + + +
+
+
+
+
+
+
+
+ + + diff --git a/tests/wpt/tests/css/css-gaps/tentative/flex/flex-gap-decorations-001.html b/tests/wpt/tests/css/css-gaps/flex/flex-gap-decorations-001.html similarity index 91% rename from tests/wpt/tests/css/css-gaps/tentative/flex/flex-gap-decorations-001.html rename to tests/wpt/tests/css/css-gaps/flex/flex-gap-decorations-001.html index 0667693f38c..b32e2f13d8c 100644 --- a/tests/wpt/tests/css/css-gaps/tentative/flex/flex-gap-decorations-001.html +++ b/tests/wpt/tests/css/css-gaps/flex/flex-gap-decorations-001.html @@ -3,7 +3,7 @@ CSS Gap Decorations: column and row gaps are painted. - + +
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/flex/flex-gap-decorations-020.html b/tests/wpt/tests/css/css-gaps/flex/flex-gap-decorations-020.html new file mode 100644 index 00000000000..6c07493d14d --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/flex/flex-gap-decorations-020.html @@ -0,0 +1,49 @@ + + + CSS Gap Decorations: flex gaps are painted when container scrolls. + + + + + +
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/flex/flex-gap-decorations-021-ref.html b/tests/wpt/tests/css/css-gaps/flex/flex-gap-decorations-021-ref.html new file mode 100644 index 00000000000..1e28d73b4e2 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/flex/flex-gap-decorations-021-ref.html @@ -0,0 +1,55 @@ + + + + +
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/flex/flex-gap-decorations-021.html b/tests/wpt/tests/css/css-gaps/flex/flex-gap-decorations-021.html new file mode 100644 index 00000000000..c59db4f635b --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/flex/flex-gap-decorations-021.html @@ -0,0 +1,49 @@ + + + CSS Gap Decorations: flex gaps are painted with overflow hidden. + + + + + +
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/flex/flex-gap-decorations-022.html b/tests/wpt/tests/css/css-gaps/flex/flex-gap-decorations-022.html new file mode 100644 index 00000000000..5f3b512ef76 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/flex/flex-gap-decorations-022.html @@ -0,0 +1,46 @@ + + + CSS Gap Decorations: flex column gaps are painted with different sized gaps and row-rule-outset 0. + + + + + +
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/tentative/grid/grid-gap-decorations-001.html b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-001.html similarity index 92% rename from tests/wpt/tests/css/css-gaps/tentative/grid/grid-gap-decorations-001.html rename to tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-001.html index 5fe13a11bbf..648f3f26e56 100644 --- a/tests/wpt/tests/css/css-gaps/tentative/grid/grid-gap-decorations-001.html +++ b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-001.html @@ -3,7 +3,7 @@ CSS Gap Decorations: column and row gaps are painted. - + +
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
diff --git a/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-029.html b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-029.html new file mode 100644 index 00000000000..6da75548d94 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-029.html @@ -0,0 +1,48 @@ + + + CSS Gap Decorations: Gaps are painted when items overflow container. + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+ diff --git a/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-030-ref.html b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-030-ref.html new file mode 100644 index 00000000000..d4953ae2ddd --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-030-ref.html @@ -0,0 +1,45 @@ + + + + +
+
+
+
+
+ +
+
diff --git a/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-030.html b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-030.html new file mode 100644 index 00000000000..ef4507ef926 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-030.html @@ -0,0 +1,37 @@ + + + CSS Gap Decorations: Gaps are painted when items overflow container - no row gaps. + + + + + + + +
+
+
+
+
+ diff --git a/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-031-ref.html b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-031-ref.html new file mode 100644 index 00000000000..0615305bc9f --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-031-ref.html @@ -0,0 +1,45 @@ + + + + +
+
+
+
+
+ +
+
diff --git a/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-031.html b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-031.html new file mode 100644 index 00000000000..530b661bfe3 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-031.html @@ -0,0 +1,38 @@ + + + CSS Gap Decorations: Gaps are painted when items overflow container - no column gaps. + + + + + + + +
+
+
+
+
+ diff --git a/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-032-ref.html b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-032-ref.html new file mode 100644 index 00000000000..f90e3dd6813 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-032-ref.html @@ -0,0 +1,71 @@ + + + + +
+ +
+
+ +
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-032.html b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-032.html new file mode 100644 index 00000000000..ac2d38fdda0 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-032.html @@ -0,0 +1,39 @@ + + + CSS Gap Decorations: Gaps are painted when rows are dynamically added and overflow container. + + + + + + + +
+ + diff --git a/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-033-ref.html b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-033-ref.html new file mode 100644 index 00000000000..e26143a6b93 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-033-ref.html @@ -0,0 +1,72 @@ + + + + +
+ +
+
+ +
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-033.html b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-033.html new file mode 100644 index 00000000000..47ef35cc368 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-033.html @@ -0,0 +1,38 @@ + + + CSS Gap Decorations: Decorations are painted when rule thickness is greater than gap size. + + + + + + + +
+ + diff --git a/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-034-ref.html b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-034-ref.html new file mode 100644 index 00000000000..4e964879e79 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-034-ref.html @@ -0,0 +1,71 @@ + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-034.html b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-034.html new file mode 100644 index 00000000000..afe20ef5117 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-034.html @@ -0,0 +1,48 @@ + + + CSS Gap Decorations: Gaps are painted with container with overflow hidden, with JS scroll + + + + + + +
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-035-ref.html b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-035-ref.html new file mode 100644 index 00000000000..851360a99a4 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-035-ref.html @@ -0,0 +1,71 @@ + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-035.html b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-035.html new file mode 100644 index 00000000000..d2fa76094a9 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-035.html @@ -0,0 +1,44 @@ + + + CSS Gap Decorations: Gaps are painted when container scrolls. + + + + + + +
+
+
+
+
+
+
+
+
+
+
+ diff --git a/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-036-ref.html b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-036-ref.html new file mode 100644 index 00000000000..29ed684d85f --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-036-ref.html @@ -0,0 +1,71 @@ + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-036.html b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-036.html new file mode 100644 index 00000000000..8b1e1e2e1c2 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-036.html @@ -0,0 +1,44 @@ + + + CSS Gap Decorations: Gaps are painted with container with overflow hidden. + + + + + + +
+
+
+
+
+
+
+
+
+
+
+ diff --git a/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-037-ref.html b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-037-ref.html new file mode 100644 index 00000000000..4a2ee5bd5c6 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-037-ref.html @@ -0,0 +1,70 @@ + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-037.html b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-037.html new file mode 100644 index 00000000000..656bfee7dfc --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-037.html @@ -0,0 +1,48 @@ + + + CSS Gap Decorations: Gaps are painted with container with overflow scroll and no background. + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+ diff --git a/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-38.html b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-38.html new file mode 100644 index 00000000000..8b87bcd4860 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-38.html @@ -0,0 +1,44 @@ + + + CSS Gap Decorations: grid column gaps are painted with different sized gaps and column-rule-outset 0. + + + + + +
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-001-ref.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-001-ref.html new file mode 100644 index 00000000000..834c96ccd39 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-001-ref.html @@ -0,0 +1,49 @@ + + + + + +
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-001.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-001.html new file mode 100644 index 00000000000..c3752156c7d --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-001.html @@ -0,0 +1,45 @@ + + + CSS Gap Decorations: Multicolumn gap decorations painted with column-wrap. + + + + + + +
+

+

+

+

+

+

+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-002-ref.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-002-ref.html new file mode 100644 index 00000000000..d132f0ca677 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-002-ref.html @@ -0,0 +1,96 @@ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-002.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-002.html new file mode 100644 index 00000000000..0b632fa94ac --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-002.html @@ -0,0 +1,59 @@ + + + CSS Gap Decorations: Multicolumn gap decorations painted with column-wrap and a spanner. + + + + + + +
+

+

+

+ +

+

+

+

+

+

+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-003-ref.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-003-ref.html new file mode 100644 index 00000000000..cd26da24389 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-003-ref.html @@ -0,0 +1,59 @@ + + + + +
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-003.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-003.html new file mode 100644 index 00000000000..2f2473fd046 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-003.html @@ -0,0 +1,35 @@ + + + CSS Gap Decorations: Multicolumn gap decorations painted basic case. + + + + + + +
+

+

+

+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-004-ref.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-004-ref.html new file mode 100644 index 00000000000..002b1f2b594 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-004-ref.html @@ -0,0 +1,78 @@ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-004.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-004.html new file mode 100644 index 00000000000..0dcbeef4df8 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-004.html @@ -0,0 +1,46 @@ + + + CSS Gap Decorations: Multicolumn gap decorations painted with smaller width than gap and column-wrap. + + + + + + +
+

+

+

+

+

+

+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-005-ref.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-005-ref.html new file mode 100644 index 00000000000..6543021c979 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-005-ref.html @@ -0,0 +1,64 @@ + + + + +
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-005.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-005.html new file mode 100644 index 00000000000..daa65e904e7 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-005.html @@ -0,0 +1,64 @@ + + + CSS Gap Decorations: Multicolumn gap decorations painted with nested multicol with column wrap. + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-006-ref.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-006-ref.html new file mode 100644 index 00000000000..4cb56d207fe --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-006-ref.html @@ -0,0 +1,52 @@ + + + + +
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-006.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-006.html new file mode 100644 index 00000000000..64b3b007ab5 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-006.html @@ -0,0 +1,39 @@ + + + CSS Gap Decorations: Multicolumn gap decorations painted with nested fragmented multicol. + + + + + +
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-007-ref.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-007-ref.html new file mode 100644 index 00000000000..09973ddc690 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-007-ref.html @@ -0,0 +1,133 @@ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-007.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-007.html new file mode 100644 index 00000000000..1f23ed7f7ca --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-007.html @@ -0,0 +1,44 @@ + + + CSS Gap Decorations: Multicolumn gap decorations painted with nested fragmented multicol. + + + + + + +
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-008-ref.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-008-ref.html new file mode 100644 index 00000000000..d49c2f50f19 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-008-ref.html @@ -0,0 +1,115 @@ + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-008.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-008.html new file mode 100644 index 00000000000..ed299b5d9f6 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-008.html @@ -0,0 +1,63 @@ + + + CSS Gap Decorations: Multicolumn gap decorations painted with 0px outset and rule-break intersection. + + + + + + +
+

+

+

+ +

+

+

+

+

+

+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-009-ref.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-009-ref.html new file mode 100644 index 00000000000..124bf7eaeb1 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-009-ref.html @@ -0,0 +1,116 @@ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-009.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-009.html new file mode 100644 index 00000000000..b00c6a70f8a --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-009.html @@ -0,0 +1,63 @@ + + + CSS Gap Decorations: Multicolumn gap decorations painted with 2px outset and rule-break intersection. + + + + + + +
+

+

+

+ +

+

+

+

+

+

+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-010-ref.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-010-ref.html new file mode 100644 index 00000000000..9582ab4103d --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-010-ref.html @@ -0,0 +1,135 @@ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-010.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-010.html new file mode 100644 index 00000000000..61bb4a9b933 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-010.html @@ -0,0 +1,63 @@ + + + CSS Gap Decorations: Multicolumn gap decorations painted with -2px outset and rule-break intersection. + + + + + + +
+

+

+

+ +

+

+

+

+

+

+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-011-ref.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-011-ref.html new file mode 100644 index 00000000000..3387918d399 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-011-ref.html @@ -0,0 +1,115 @@ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-011.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-011.html new file mode 100644 index 00000000000..cf588ded41e --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-011.html @@ -0,0 +1,63 @@ + + + CSS Gap Decorations: Multicolumn gap decorations painted with 100% outset and rule-break intersection. + + + + + + +
+

+

+

+ +

+

+

+

+

+

+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-012-ref.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-012-ref.html new file mode 100644 index 00000000000..9fb5e85e920 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-012-ref.html @@ -0,0 +1,104 @@ + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-012.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-012.html new file mode 100644 index 00000000000..078b810cf8e --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-012.html @@ -0,0 +1,60 @@ + + + CSS Gap Decorations: Multicolumn gap decorations painted with 2px width and rule-break none. + + + + + +
+

+

+

+ +

+

+

+

+

+

+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-013-ref.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-013-ref.html new file mode 100644 index 00000000000..8e426d8ed32 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-013-ref.html @@ -0,0 +1,41 @@ + + + +
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-013.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-013.html new file mode 100644 index 00000000000..d4170084f06 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-013.html @@ -0,0 +1,28 @@ + + + + + + +
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-014-ref.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-014-ref.html new file mode 100644 index 00000000000..9d9674bd476 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-014-ref.html @@ -0,0 +1,62 @@ + + + + + +
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-014.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-014.html new file mode 100644 index 00000000000..1dcd393d99a --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-014.html @@ -0,0 +1,47 @@ + + + CSS Gap Decorations: Multicolumn gap decorations painted with column-wrap, rule-break-intersection, and fewer + columns in last row. + + + + + + +
+

+

+

+

+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-015-ref.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-015-ref.html new file mode 100644 index 00000000000..2fee77be84c --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-015-ref.html @@ -0,0 +1,70 @@ + + + + +
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-015.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-015.html new file mode 100644 index 00000000000..65af5112097 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-015.html @@ -0,0 +1,55 @@ + + + CSS Gap Decorations: Multicolumn gap decorations painted with different num of columns per row. + + + + + +
+
+
+ +
+
+
+ +
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-016-ref.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-016-ref.html new file mode 100644 index 00000000000..8b2458fccce --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-016-ref.html @@ -0,0 +1,70 @@ + + + + +
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-016.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-016.html new file mode 100644 index 00000000000..da205e0b88b --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-016.html @@ -0,0 +1,55 @@ + + + CSS Gap Decorations: Multicolumn gap decorations painted with different num of columns per row. + + + + + +
+
+
+
+
+ +
+
+
+ +
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-017-ref.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-017-ref.html new file mode 100644 index 00000000000..23fd089ac8e --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-017-ref.html @@ -0,0 +1,49 @@ + + + + + +
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-017.html b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-017.html new file mode 100644 index 00000000000..c7d1fe42584 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/multicol/multicol-gap-decorations-017.html @@ -0,0 +1,48 @@ + + + CSS Gap Decorations: Multicolumn gap decorations painted with column-wrap and with different sized gaps and row-rule-outset 0. + + + + + + +
+

+

+

+

+

+

+
diff --git a/tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-bidirectional-shorthands.html b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-bidirectional-shorthands.html similarity index 77% rename from tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-bidirectional-shorthands.html rename to tests/wpt/tests/css/css-gaps/parsing/gap-decorations-bidirectional-shorthands.html index 95b5bf6aea8..9ff3815c21f 100644 --- a/tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-bidirectional-shorthands.html +++ b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-bidirectional-shorthands.html @@ -19,9 +19,8 @@ column-rule-width: 10px; row-rule-width: 10px; - /* Adding `column-rule-style` property because - `column-rule-width` defaults to 0px without it */ column-rule-style: solid; + row-rule-style: solid; } #target2 { @@ -30,25 +29,31 @@ column-rule-width: 15px; row-rule-width: 20px; + + column-rule-style: double; + row-rule-style: dotted; } diff --git a/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-col-rule-width.html b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-col-rule-width.html new file mode 100644 index 00000000000..db7b97e74a5 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-col-rule-width.html @@ -0,0 +1,51 @@ + + + + +CSS Gap Decorations: Ensure getComputedStyle for column-rule-width is as specified with multiple values + + + + + + +
+
+
+ + + + diff --git a/tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-color-computed.html b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-color-computed.html similarity index 100% rename from tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-color-computed.html rename to tests/wpt/tests/css/css-gaps/parsing/gap-decorations-color-computed.html diff --git a/tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-color-invalid.html b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-color-invalid.html similarity index 100% rename from tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-color-invalid.html rename to tests/wpt/tests/css/css-gaps/parsing/gap-decorations-color-invalid.html diff --git a/tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-color-valid.html b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-color-valid.html similarity index 100% rename from tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-color-valid.html rename to tests/wpt/tests/css/css-gaps/parsing/gap-decorations-color-valid.html diff --git a/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-rule-shorthand-computed-from-longhands.html b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-rule-shorthand-computed-from-longhands.html new file mode 100644 index 00000000000..562c166e90b --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-rule-shorthand-computed-from-longhands.html @@ -0,0 +1,76 @@ + + + + CSS Gap Decorations: individual separate longhands form shorthand + + + + + +
+ diff --git a/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-rule-shorthand-computed.html b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-rule-shorthand-computed.html new file mode 100644 index 00000000000..7bb3e1858dd --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-rule-shorthand-computed.html @@ -0,0 +1,62 @@ + + + + +CSS Gap Decoration: *rule getComputedStyle() + + + + + + + + +
+
+ + + + diff --git a/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-rule-shorthand-invalid.html b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-rule-shorthand-invalid.html new file mode 100644 index 00000000000..f7c6b45b16d --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-rule-shorthand-invalid.html @@ -0,0 +1,31 @@ + + + + +CSS Gap Decorations: *-rule parsing + + + + + + + + + + + diff --git a/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-rule-shorthand.html b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-rule-shorthand.html new file mode 100644 index 00000000000..420b6757e7f --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-rule-shorthand.html @@ -0,0 +1,127 @@ + + + + +CSS Gap Decorations: *-rule sets longhands + + + + + + + + + + diff --git a/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-style-computed.html b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-style-computed.html new file mode 100644 index 00000000000..8c78bf618e3 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-style-computed.html @@ -0,0 +1,43 @@ + + + + +CSS Gap Decoration: column-rule-style getComputedStyle() + + + + + + + + +
+ + + diff --git a/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-style-invalid.html b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-style-invalid.html new file mode 100644 index 00000000000..48ac0680338 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-style-invalid.html @@ -0,0 +1,26 @@ + + + + +CSS Gap Decorations: column-rule-style parsing + + + + + + + + + + + diff --git a/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-style-valid.html b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-style-valid.html new file mode 100644 index 00000000000..1eb259a82bc --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-style-valid.html @@ -0,0 +1,47 @@ + + + + +CSS Gap Decorations: parsing column-rule-style with valid values + + + + + + + + + + + diff --git a/tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-width-computed.html b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-width-computed.html similarity index 100% rename from tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-width-computed.html rename to tests/wpt/tests/css/css-gaps/parsing/gap-decorations-width-computed.html diff --git a/tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-width-invalid.html b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-width-invalid.html similarity index 100% rename from tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-width-invalid.html rename to tests/wpt/tests/css/css-gaps/parsing/gap-decorations-width-invalid.html diff --git a/tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-width-valid.html b/tests/wpt/tests/css/css-gaps/parsing/gap-decorations-width-valid.html similarity index 100% rename from tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-width-valid.html rename to tests/wpt/tests/css/css-gaps/parsing/gap-decorations-width-valid.html diff --git a/tests/wpt/tests/css/css-gaps/tentative/parsing/rule-break-computed.html b/tests/wpt/tests/css/css-gaps/parsing/rule-break-computed.html similarity index 100% rename from tests/wpt/tests/css/css-gaps/tentative/parsing/rule-break-computed.html rename to tests/wpt/tests/css/css-gaps/parsing/rule-break-computed.html diff --git a/tests/wpt/tests/css/css-gaps/tentative/parsing/rule-break-invalid.html b/tests/wpt/tests/css/css-gaps/parsing/rule-break-invalid.html similarity index 100% rename from tests/wpt/tests/css/css-gaps/tentative/parsing/rule-break-invalid.html rename to tests/wpt/tests/css/css-gaps/parsing/rule-break-invalid.html diff --git a/tests/wpt/tests/css/css-gaps/tentative/parsing/rule-break-valid.html b/tests/wpt/tests/css/css-gaps/parsing/rule-break-valid.html similarity index 100% rename from tests/wpt/tests/css/css-gaps/tentative/parsing/rule-break-valid.html rename to tests/wpt/tests/css/css-gaps/parsing/rule-break-valid.html diff --git a/tests/wpt/tests/css/css-gaps/tentative/parsing/rule-outset-computed.html b/tests/wpt/tests/css/css-gaps/parsing/rule-outset-computed.html similarity index 100% rename from tests/wpt/tests/css/css-gaps/tentative/parsing/rule-outset-computed.html rename to tests/wpt/tests/css/css-gaps/parsing/rule-outset-computed.html diff --git a/tests/wpt/tests/css/css-gaps/tentative/parsing/rule-outset-invalid.html b/tests/wpt/tests/css/css-gaps/parsing/rule-outset-invalid.html similarity index 100% rename from tests/wpt/tests/css/css-gaps/tentative/parsing/rule-outset-invalid.html rename to tests/wpt/tests/css/css-gaps/parsing/rule-outset-invalid.html diff --git a/tests/wpt/tests/css/css-gaps/tentative/parsing/rule-outset-valid.html b/tests/wpt/tests/css/css-gaps/parsing/rule-outset-valid.html similarity index 100% rename from tests/wpt/tests/css/css-gaps/tentative/parsing/rule-outset-valid.html rename to tests/wpt/tests/css/css-gaps/parsing/rule-outset-valid.html diff --git a/tests/wpt/tests/css/css-gaps/tentative/parsing/rule-paint-order-computed.html b/tests/wpt/tests/css/css-gaps/parsing/rule-paint-order-computed.html similarity index 100% rename from tests/wpt/tests/css/css-gaps/tentative/parsing/rule-paint-order-computed.html rename to tests/wpt/tests/css/css-gaps/parsing/rule-paint-order-computed.html diff --git a/tests/wpt/tests/css/css-gaps/tentative/parsing/rule-paint-order-invalid.html b/tests/wpt/tests/css/css-gaps/parsing/rule-paint-order-invalid.html similarity index 100% rename from tests/wpt/tests/css/css-gaps/tentative/parsing/rule-paint-order-invalid.html rename to tests/wpt/tests/css/css-gaps/parsing/rule-paint-order-invalid.html diff --git a/tests/wpt/tests/css/css-gaps/tentative/parsing/rule-paint-order-valid.html b/tests/wpt/tests/css/css-gaps/parsing/rule-paint-order-valid.html similarity index 100% rename from tests/wpt/tests/css/css-gaps/tentative/parsing/rule-paint-order-valid.html rename to tests/wpt/tests/css/css-gaps/parsing/rule-paint-order-valid.html diff --git a/tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-style-computed.html b/tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-style-computed.html deleted file mode 100644 index 4de870c0984..00000000000 --- a/tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-style-computed.html +++ /dev/null @@ -1,62 +0,0 @@ - - - - -CSS Gap Decoration: column-rule-style getComputedStyle() - - - - - - - - -
- - - diff --git a/tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-style-invalid.html b/tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-style-invalid.html deleted file mode 100644 index af1d5ccae57..00000000000 --- a/tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-style-invalid.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - -CSS Gap Decorations: column-rule-style parsing - - - - - - - - - - - diff --git a/tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-style-valid.html b/tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-style-valid.html deleted file mode 100644 index 1f41238345c..00000000000 --- a/tests/wpt/tests/css/css-gaps/tentative/parsing/gap-decorations-style-valid.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - -CSS Gap Decorations: parsing column-rule-style with valid values - - - - - - - - - - - diff --git a/tests/wpt/tests/css/css-gaps/tentative/serialization/gap-decorations-properties.html b/tests/wpt/tests/css/css-gaps/tentative/serialization/gap-decorations-properties.html deleted file mode 100644 index 4985b5550f2..00000000000 --- a/tests/wpt/tests/css/css-gaps/tentative/serialization/gap-decorations-properties.html +++ /dev/null @@ -1,300 +0,0 @@ - - - - - - CSS Test: Gap Decorations - Properties exist - - - - - - - - - - - -
-
-
-
I T
-
IT
-
I
-
-
- - - - - diff --git a/tests/wpt/tests/css/css-grid/abspos/abspos-in-flexbox-in-grid-crash.html b/tests/wpt/tests/css/css-grid/abspos/abspos-in-flexbox-in-grid-crash.html new file mode 100644 index 00000000000..a2f71fb78e7 --- /dev/null +++ b/tests/wpt/tests/css/css-grid/abspos/abspos-in-flexbox-in-grid-crash.html @@ -0,0 +1,12 @@ + + + +
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-grid/grid-definition/grid-auto-fit-with-calc.html b/tests/wpt/tests/css/css-grid/grid-definition/grid-auto-fit-with-calc.html new file mode 100644 index 00000000000..4b655c1e121 --- /dev/null +++ b/tests/wpt/tests/css/css-grid/grid-definition/grid-auto-fit-with-calc.html @@ -0,0 +1,23 @@ + + + + + + + + + +

Test passes if there is a filled green square.

+
+
+
+
+ + diff --git a/tests/wpt/tests/css/css-grid/grid-items/replaced-element-016.tentative.html b/tests/wpt/tests/css/css-grid/grid-items/replaced-element-016.tentative.html new file mode 100644 index 00000000000..2d5b401b8d2 --- /dev/null +++ b/tests/wpt/tests/css/css-grid/grid-items/replaced-element-016.tentative.html @@ -0,0 +1,13 @@ + + + + + + + + + +

Test passes if there is a filled green square.

+
+ +
diff --git a/tests/wpt/tests/css/css-grid/grid-items/replaced-element-017.tentative.html b/tests/wpt/tests/css/css-grid/grid-items/replaced-element-017.tentative.html new file mode 100644 index 00000000000..575fa8c7817 --- /dev/null +++ b/tests/wpt/tests/css/css-grid/grid-items/replaced-element-017.tentative.html @@ -0,0 +1,13 @@ + + + + + + + + + +

Test passes if there is a filled green square.

+
+ +
diff --git a/tests/wpt/tests/css/css-grid/subgrid/line-names-014.html b/tests/wpt/tests/css/css-grid/subgrid/line-names-014.html new file mode 100644 index 00000000000..fe1d5d46633 --- /dev/null +++ b/tests/wpt/tests/css/css-grid/subgrid/line-names-014.html @@ -0,0 +1,36 @@ + + +CSS Grid Test: Clamping a nested subgrid's grid-template-areas + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-grid/subgrid/line-names-015.html b/tests/wpt/tests/css/css-grid/subgrid/line-names-015.html new file mode 100644 index 00000000000..6f5479abf89 --- /dev/null +++ b/tests/wpt/tests/css/css-grid/subgrid/line-names-015.html @@ -0,0 +1,34 @@ + + +CSS Grid Test: Clamping a nested subgrid's grid-template-areas in one dimension + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-images/gradient/conic-gradient-001.html b/tests/wpt/tests/css/css-images/gradient/conic-gradient-001.html index 37e41094fb2..949f955e552 100644 --- a/tests/wpt/tests/css/css-images/gradient/conic-gradient-001.html +++ b/tests/wpt/tests/css/css-images/gradient/conic-gradient-001.html @@ -1,10 +1,10 @@ - - + + Tests the maximum value of color stops in conic-gradient(). - - -
-
-
-
-
- - + .test1 { + background: linear-gradient(90deg in srgb, hsl(60deg 0 50%), yellow); + } + + .test2 { + background: linear-gradient(90deg in srgb, hsl(20deg 0 50%), yellow); + } + + .test3 { + background: linear-gradient(90deg in srgb, hsl(60deg none 50%), yellow); + } + + .test4 { + background: linear-gradient(90deg in srgb, hsl(none 0 50%), yellow); + } + + .test5 { + background: linear-gradient(90deg in srgb, hsl(none none 50%), yellow); + } + + +
+
+
+
+
diff --git a/tests/wpt/tests/css/css-images/gradient/gradient-analogous-missing-components-002.html b/tests/wpt/tests/css/css-images/gradient/gradient-analogous-missing-components-002.html index 96038650136..881ba723f91 100644 --- a/tests/wpt/tests/css/css-images/gradient/gradient-analogous-missing-components-002.html +++ b/tests/wpt/tests/css/css-images/gradient/gradient-analogous-missing-components-002.html @@ -1,40 +1,39 @@ - - - - - Gradient analogous missing components carry forward logic tests - - - - - - - - -

They should be equivalent to `background: color-mix(in srgb, color(srgb none 1 none), lime)`

-
This should be a lime background.
-
This should be a lime background.
-
This should be a lime background.
-
This should be a lime background.
- - + + +Gradient analogous missing components carry forward logic tests + + + + + + +

They should be equivalent to `background: color-mix(in srgb, color(srgb none 1 none), lime)`

+
This should be a lime background.
+
This should be a lime background.
+
This should be a lime background.
+
This should be a lime background.
diff --git a/tests/wpt/tests/css/css-images/gradient/gradient-analogous-missing-components-003.html b/tests/wpt/tests/css/css-images/gradient/gradient-analogous-missing-components-003.html index c4c8995c1ef..c97571f54b9 100644 --- a/tests/wpt/tests/css/css-images/gradient/gradient-analogous-missing-components-003.html +++ b/tests/wpt/tests/css/css-images/gradient/gradient-analogous-missing-components-003.html @@ -1,35 +1,33 @@ - - - - - Gradient analogous missing components carry forward logic tests - - - - - - - - -
This should be a lime background.
-
This should be a lime background.
-
This should be a lime background.
- - + + +Gradient analogous missing components carry forward logic tests + + + + + + + +
This should be a lime background.
+
This should be a lime background.
+
This should be a lime background.
diff --git a/tests/wpt/tests/css/css-images/gradient/gradient-single-stop-none-interpolation.html b/tests/wpt/tests/css/css-images/gradient/gradient-single-stop-none-interpolation.html index 14cd9d76302..449bde6e25a 100644 --- a/tests/wpt/tests/css/css-images/gradient/gradient-single-stop-none-interpolation.html +++ b/tests/wpt/tests/css/css-images/gradient/gradient-single-stop-none-interpolation.html @@ -1,40 +1,40 @@ - - - Gradient interpolation with single stop that has missing components - - - - - - - -
-
-
-
-
- - + +Gradient interpolation with single stop that has missing components + + + + + +
+
+
+
+
diff --git a/tests/wpt/tests/css/css-images/infinite-radial-gradient-refcrash.html b/tests/wpt/tests/css/css-images/infinite-radial-gradient-refcrash.html index a3a1e7fb069..a150b619116 100644 --- a/tests/wpt/tests/css/css-images/infinite-radial-gradient-refcrash.html +++ b/tests/wpt/tests/css/css-images/infinite-radial-gradient-refcrash.html @@ -2,6 +2,7 @@ CSS Images Test: repeating-radial-gradient with huge size crashes Chrome + + diff --git a/tests/wpt/tests/css/css-images/linear-gradient-body-sibling-index.html b/tests/wpt/tests/css/css-images/linear-gradient-body-sibling-index.html new file mode 100644 index 00000000000..299b86d464a --- /dev/null +++ b/tests/wpt/tests/css/css-images/linear-gradient-body-sibling-index.html @@ -0,0 +1,15 @@ + +CSS Images Test: Linear gradient with sibling-index() + + + + + + diff --git a/tests/wpt/tests/css/css-images/linear-gradient-calc-em-units-ref.html b/tests/wpt/tests/css/css-images/linear-gradient-calc-em-units-ref.html new file mode 100644 index 00000000000..be13be8e260 --- /dev/null +++ b/tests/wpt/tests/css/css-images/linear-gradient-calc-em-units-ref.html @@ -0,0 +1,7 @@ + +CSS Test Reference + +
+
diff --git a/tests/wpt/tests/css/css-images/linear-gradient-calc-em-units.html b/tests/wpt/tests/css/css-images/linear-gradient-calc-em-units.html new file mode 100644 index 00000000000..e764e62890d --- /dev/null +++ b/tests/wpt/tests/css/css-images/linear-gradient-calc-em-units.html @@ -0,0 +1,19 @@ + +CSS Images Test: Linear gradient with em in calc() + + + +
+
diff --git a/tests/wpt/tests/css/css-images/linear-gradient-sibling-index-ref.html b/tests/wpt/tests/css/css-images/linear-gradient-sibling-index-ref.html new file mode 100644 index 00000000000..eea8518d95b --- /dev/null +++ b/tests/wpt/tests/css/css-images/linear-gradient-sibling-index-ref.html @@ -0,0 +1,7 @@ + +CSS Test Reference + +
+
diff --git a/tests/wpt/tests/css/css-images/linear-gradient-sibling-index.html b/tests/wpt/tests/css/css-images/linear-gradient-sibling-index.html new file mode 100644 index 00000000000..b92897e0697 --- /dev/null +++ b/tests/wpt/tests/css/css-images/linear-gradient-sibling-index.html @@ -0,0 +1,19 @@ + +CSS Images Test: Linear gradient with sibling-index() + + + + +
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-images/multiple-position-color-stop-radial.html b/tests/wpt/tests/css/css-images/multiple-position-color-stop-radial.html index 4b4fd95c4ec..1cb8c5ce039 100644 --- a/tests/wpt/tests/css/css-images/multiple-position-color-stop-radial.html +++ b/tests/wpt/tests/css/css-images/multiple-position-color-stop-radial.html @@ -2,6 +2,7 @@ Radial gradient with a two position color stop + - - -
- - + +Eight Red Triangles on White Ground (with gradients) + + + + + +
\ No newline at end of file diff --git a/tests/wpt/tests/css/css-mixins/dashed-function-cycles.tentative.html b/tests/wpt/tests/css/css-mixins/dashed-function-cycles.html similarity index 96% rename from tests/wpt/tests/css/css-mixins/dashed-function-cycles.tentative.html rename to tests/wpt/tests/css/css-mixins/dashed-function-cycles.html index 11e653e9b7d..e711c9ed367 100644 --- a/tests/wpt/tests/css/css-mixins/dashed-function-cycles.tentative.html +++ b/tests/wpt/tests/css/css-mixins/dashed-function-cycles.html @@ -1,7 +1,7 @@ Custom Functions: Handling cycles - - + + @@ -40,12 +40,12 @@ -