diff options
608 files changed, 16767 insertions, 9225 deletions
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..0fe92dcf5c7 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: @@ -223,10 +224,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 +239,10 @@ 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: "Run benchmark" + run: hitrace-bench --bencher -b "org.servo.servo" -p "https://www.servo.org" -n 5 + - name: Getting bencher + uses: bencherdev/bencher@main + - 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 }}' 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 6940195ff30..36e579d2921 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", @@ -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]] @@ -844,9 +844,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.21" +version = "1.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" +checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" dependencies = [ "jobserver", "libc", @@ -964,18 +964,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", @@ -1223,7 +1223,7 @@ dependencies = [ [[package]] name = "content-security-policy" version = "0.5.4" -source = "git+https://github.com/servo/rust-content-security-policy/?branch=servo-csp#fcd91e99139ca96629e04e1a8010f96374f0370f" +source = "git+https://github.com/servo/rust-content-security-policy/?branch=servo-csp#334bfcbf0a3f503b21c90aee6aee30d4b8c9558a" dependencies = [ "base64 0.22.1", "bitflags 2.9.0", @@ -1715,6 +1715,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.1", +] + +[[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2027,9 +2037,9 @@ dependencies = [ [[package]] name = "error-code" -version = "3.3.1" +version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" [[package]] name = "etagere" @@ -2194,6 +2204,7 @@ dependencies = [ "net_traits", "num-traits", "parking_lot", + "profile_traits", "range", "serde", "servo_allocator", @@ -2227,7 +2238,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", @@ -2540,9 +2551,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "gio-sys" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "160eb5250a26998c3e1b54e6a3d4ea15c6c7762a6062a19a7b63eff6e2b33f9e" +checksum = "521e93a7e56fc89e84aea9a52cfc9436816a4b363b030260b699950ff1336c83" dependencies = [ "glib-sys", "gobject-sys", @@ -2573,9 +2584,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707b819af8059ee5395a2de9f2317d87a53dbad8846a2f089f0bb44703f37686" +checksum = "c501c495842c2b23cdacead803a5a343ca2a5d7a7ddaff14cc5f6cf22cfb92c2" dependencies = [ "bitflags 2.9.0", "futures-channel", @@ -2594,9 +2605,9 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.20.7" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715601f8f02e71baef9c1f94a657a9a77c192aea6097cf9ae7e5e177cd8cde68" +checksum = "ebe6dc9ce29887c4b3b74d78d5ba473db160a258ae7ed883d23632ac7fed7bc9" dependencies = [ "heck", "proc-macro-crate", @@ -2607,9 +2618,9 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8928869a44cfdd1fccb17d6746e4ff82c8f82e41ce705aa026a52ca8dc3aefb" +checksum = "8ab79e1ed126803a8fb827e3de0e2ff95191912b8db65cee467edb56fc4cc215" dependencies = [ "libc", "system-deps", @@ -2653,9 +2664,9 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c773a3cb38a419ad9c26c81d177d96b4b08980e8bdbbf32dace883e96e96e7e3" +checksum = "ec9aca94bb73989e3cfdbf8f2e0f1f6da04db4d291c431f444838925c4c63eda" dependencies = [ "glib-sys", "libc", @@ -2721,9 +2732,9 @@ checksum = "36119f3a540b086b4e436bb2b588cf98a68863470e0e880f4d0842f112a3183a" [[package]] name = "gstreamer" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2188fe829b0ebe12e4cf2bbcf6658470a936269daba7afae92847a2af32c9105" +checksum = "50ab4c88f731596a2511a6f14cabdd666e0d8efab62a1d58e6ddb57faa96e22e" dependencies = [ "cfg-if", "futures-channel", @@ -2731,7 +2742,7 @@ dependencies = [ "futures-util", "glib", "gstreamer-sys", - "itertools 0.13.0", + "itertools 0.14.0", "libc", "muldiv", "num-integer", @@ -2774,9 +2785,9 @@ dependencies = [ [[package]] name = "gstreamer-audio" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49118ca684e2fc42207509fcac8497d91079c2ffe8ff2b4ae99e71dbafef1ede" +checksum = "2e7ec7e0374298897e669db7c79544bc44df12011985e7dd5f38644edaf2caf4" dependencies = [ "cfg-if", "glib", @@ -2790,9 +2801,9 @@ dependencies = [ [[package]] name = "gstreamer-audio-sys" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d469526ecf30811b50a6460fd285ee40d189c46048b3d0c69b67a04b414fb51" +checksum = "2b5f3e09e7c04ec91d78c2a6ca78d50b574b9ed49fdf5e72f3693adca4306a87" dependencies = [ "glib-sys", "gobject-sys", @@ -2804,9 +2815,9 @@ dependencies = [ [[package]] name = "gstreamer-base" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad33dd444db0d215ac363164f900f800ffb93361ad8a60840e95e14b7de985e8" +checksum = "f19a74fd04ffdcb847dd322640f2cf520897129d00a7bcb92fd62a63f3e27404" dependencies = [ "atomic_refcell", "cfg-if", @@ -2818,9 +2829,9 @@ dependencies = [ [[package]] name = "gstreamer-base-sys" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "114b2a704f19a70f20c54b00e54f5d5376bbf78bd2791e6beb0776c997d8bf24" +checksum = "87f2fb0037b6d3c5b51f60dea11e667910f33be222308ca5a101450018a09840" dependencies = [ "glib-sys", "gobject-sys", @@ -2831,9 +2842,9 @@ dependencies = [ [[package]] name = "gstreamer-gl" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02818bd81028abc4ee7b0106c21625be9a2f86ba5fd41ccff58359537637db59" +checksum = "34aa19feafc4da2c7635abce0e0768892ff97ad73586bef02d9a60b251d9fe09" dependencies = [ "glib", "gstreamer", @@ -2846,9 +2857,9 @@ dependencies = [ [[package]] name = "gstreamer-gl-egl" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5fad98961d18ed5dba4be44787d4735b78a1f53b7db392be004b96f1f2430b" +checksum = "8de1f4247cf2d009b41ab5efb03e4d826b7ccaafb9a75d3ea10e68e46f65e8aa" dependencies = [ "glib", "gstreamer", @@ -2859,9 +2870,9 @@ dependencies = [ [[package]] name = "gstreamer-gl-egl-sys" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7ebdc94dc34e2b135b2610676b47d30ce88b80862f01e2acf7e29b9b42a14e4" +checksum = "dda4d852ed107cc48692af4e109e5e4775b6ce1044d13df79f6f431c195096d7" dependencies = [ "glib-sys", "gstreamer-gl-sys", @@ -2871,9 +2882,9 @@ dependencies = [ [[package]] name = "gstreamer-gl-sys" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2984f8c246407fabbecf852c4595dd1487f4cc495386e17ad31acb69db7d39" +checksum = "a832c21d4522ed5e1b8dfc676a45361969216b144fc03af413a38c471f38bcf7" dependencies = [ "glib-sys", "gobject-sys", @@ -2961,9 +2972,9 @@ dependencies = [ [[package]] name = "gstreamer-sys" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe159238834058725808cf6604a7c5d9e4a50e1eacd7b0c63bce2fe3a067dbd1" +checksum = "feea73b4d92dbf9c24a203c9cd0bcc740d584f6b5960d5faf359febf288919b2" dependencies = [ "glib-sys", "gobject-sys", @@ -2973,9 +2984,9 @@ dependencies = [ [[package]] name = "gstreamer-video" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad242d388b63c91652c8157de3b0c1f709e49c941a0aae1952455f6ee326ca2d" +checksum = "1318b599d77ca4f7702ecbdeac1672d6304cb16b7e5752fabb3ee8260449a666" dependencies = [ "cfg-if", "futures-channel", @@ -2990,9 +3001,9 @@ dependencies = [ [[package]] name = "gstreamer-video-sys" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465ff496889fb38be47f5e821163c2e83414d87c4aa55f5aae62dc7200971d4d" +checksum = "0a70f0947f12d253b9de9bc3fd92f981e4d025336c18389c7f08cdf388a99f5c" dependencies = [ "glib-sys", "gobject-sys", @@ -4010,15 +4021,6 @@ dependencies = [ [[package]] name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" @@ -4034,9 +4036,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07d8d955d798e7a4d6f9c58cd1f1916e790b42b092758a9ef6e16fef9f1b3fd" +checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806" dependencies = [ "jiff-static", "log", @@ -4047,9 +4049,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f244cfe006d98d26f859c7abd1318d85327e1882dc9cef80f62daeeb0adcf300" +checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48" dependencies = [ "proc-macro2", "quote", @@ -4247,9 +4249,9 @@ 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", @@ -4257,9 +4259,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a25169bd5913a4b437588a7e3d127cd6e90127b60e0ffbd834a38f1599e016b8" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" @@ -4644,7 +4646,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", @@ -4655,8 +4657,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", @@ -5073,6 +5075,15 @@ dependencies = [ ] [[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" @@ -5081,14 +5092,25 @@ dependencies = [ "bitflags 2.9.0", "block2", "libc", - "objc2", + "objc2 0.5.2", "objc2-core-data", "objc2-core-image", - "objc2-foundation", + "objc2-foundation 0.2.2", "objc2-quartz-core", ] [[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.1", + "objc2-foundation 0.3.1", +] + +[[package]] name = "objc2-cloud-kit" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -5096,9 +5118,9 @@ checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", + "objc2 0.5.2", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -5108,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]] @@ -5120,8 +5142,19 @@ checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags 2.9.0", + "dispatch2", + "objc2 0.6.1", ] [[package]] @@ -5131,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", ] @@ -5143,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]] @@ -5164,7 +5197,18 @@ dependencies = [ "block2", "dispatch", "libc", - "objc2", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.1", + "objc2-core-foundation", ] [[package]] @@ -5174,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]] @@ -5187,8 +5231,8 @@ checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -5199,8 +5243,8 @@ checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", "objc2-metal", ] @@ -5210,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]] @@ -5222,12 +5266,12 @@ checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", + "objc2 0.5.2", "objc2-cloud-kit", "objc2-core-data", "objc2-core-image", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", "objc2-link-presentation", "objc2-quartz-core", "objc2-symbols", @@ -5242,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]] @@ -5254,9 +5298,9 @@ checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", + "objc2 0.5.2", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -6143,9 +6187,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", @@ -6167,15 +6211,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] [[package]] name = "rustls-webpki" -version = "0.103.1" +version = "0.103.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" dependencies = [ "aws-lc-rs", "ring", @@ -6323,6 +6370,7 @@ dependencies = [ "unicode-bidi", "unicode-segmentation", "url", + "urlpattern", "utf-8", "uuid", "webdriver", @@ -6467,7 +6515,7 @@ dependencies = [ [[package]] name = "selectors" version = "0.28.0" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" dependencies = [ "bitflags 2.9.0", "cssparser", @@ -6762,7 +6810,7 @@ dependencies = [ [[package]] name = "servo_arc" version = "0.4.1" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" dependencies = [ "serde", "stable_deref_trait", @@ -6828,6 +6876,7 @@ dependencies = [ "unicode-bidi", "unicode-script", "url", + "urlpattern", "uuid", "webrender_api", "wr_malloc_size_of", @@ -6898,8 +6947,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", @@ -7222,7 +7271,7 @@ dependencies = [ [[package]] name = "stylo" version = "0.3.0" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" dependencies = [ "app_units", "arrayvec", @@ -7280,7 +7329,7 @@ dependencies = [ [[package]] name = "stylo_atoms" version = "0.3.0" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" dependencies = [ "string_cache", "string_cache_codegen", @@ -7289,12 +7338,12 @@ dependencies = [ [[package]] name = "stylo_config" version = "0.3.0" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" [[package]] name = "stylo_derive" version = "0.3.0" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" dependencies = [ "darling", "proc-macro2", @@ -7306,7 +7355,7 @@ dependencies = [ [[package]] name = "stylo_dom" version = "0.3.0" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" dependencies = [ "bitflags 2.9.0", "stylo_malloc_size_of", @@ -7315,7 +7364,7 @@ dependencies = [ [[package]] name = "stylo_malloc_size_of" version = "0.3.0" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" dependencies = [ "app_units", "cssparser", @@ -7332,12 +7381,12 @@ dependencies = [ [[package]] name = "stylo_static_prefs" version = "0.3.0" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" [[package]] name = "stylo_traits" version = "0.3.0" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" dependencies = [ "app_units", "bitflags 2.9.0", @@ -7720,7 +7769,7 @@ dependencies = [ [[package]] name = "to_shmem" version = "0.2.0" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" dependencies = [ "cssparser", "servo_arc", @@ -7733,7 +7782,7 @@ dependencies = [ [[package]] name = "to_shmem_derive" version = "0.1.0" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea" dependencies = [ "darling", "proc-macro2", @@ -7744,9 +7793,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", @@ -7944,9 +7993,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", ] @@ -8011,6 +8060,47 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -8091,6 +8181,18 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -8423,9 +8525,9 @@ dependencies = [ [[package]] name = "web_atoms" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bcbdcad8fb2e316072ba6bbe09419afdb550285668ac2534f4230a6f2da0ee" +checksum = "0b9c5f0bc545ea3b20b423e33b9b457764de0b3730cd957f6c6aa6c301785f6e" dependencies = [ "phf", "phf_codegen", @@ -8435,11 +8537,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", @@ -8498,6 +8600,7 @@ dependencies = [ "glow", "half", "ipc-channel", + "itertools 0.14.0", "log", "pixels", "snapshot", @@ -9173,9 +9276,9 @@ dependencies = [ "libc", "memmap2", "ndk", - "objc2", - "objc2-app-kit", - "objc2-foundation", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", "objc2-ui-kit", "orbclient", "percent-encoding", @@ -9204,9 +9307,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index a82fb8aea73..4045a0ffcb9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -115,7 +115,7 @@ 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-05-01" } @@ -162,8 +162,9 @@ 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.67", features = ["capture"] } diff --git a/components/canvas/backend.rs b/components/canvas/backend.rs index 53acbea8b8d..7e348fbc9b9 100644 --- a/components/canvas/backend.rs +++ b/components/canvas/backend.rs @@ -134,7 +134,18 @@ pub(crate) trait GenericPathBuilder<B: Backend> { 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<f32>, @@ -212,7 +223,23 @@ pub(crate) trait GenericPathBuilder<B: Backend> { large_arc: bool, sweep: bool, end_point: Point2D<f32>, - ); + ) { + 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; } diff --git a/components/canvas/raqote_backend.rs b/components/canvas/raqote_backend.rs index efe0ffd05b8..ecf780c36d5 100644 --- a/components/canvas/raqote_backend.rs +++ b/components/canvas/raqote_backend.rs @@ -680,26 +680,6 @@ impl PathBuilder { } impl GenericPathBuilder<RaqoteBackend> for PathBuilder { - fn arc( - &mut self, - origin: Point2D<f32>, - radius: f32, - start_angle: f32, - end_angle: f32, - anticlockwise: bool, - ) { - <PathBuilder as GenericPathBuilder<RaqoteBackend>>::ellipse( - self, - origin, - radius, - radius, - 0., - start_angle, - end_angle, - anticlockwise, - ); - } - fn bezier_curve_to( &mut self, control_point1: &Point2D<f32>, @@ -720,32 +700,6 @@ impl GenericPathBuilder<RaqoteBackend> for PathBuilder { self.0.as_mut().unwrap().close(); } - fn svg_arc( - &mut self, - radius_x: f32, - radius_y: f32, - rotation_angle: f32, - large_arc: bool, - sweep: bool, - end_point: Point2D<f32>, - ) { - 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<Point2D<f32>> { let path = self.finish(); self.0 = Some(path.clone().into()); diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index 41286a2760a..b1669277ba1 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -26,9 +26,9 @@ use crossbeam_channel::{Receiver, Sender}; use dpi::PhysicalSize; use embedder_traits::{ CompositorHitTestResult, Cursor, InputEvent, MouseButtonEvent, MouseMoveEvent, ShutdownState, - TouchEventType, UntrustedNodeAddress, ViewportDetails, + 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; @@ -646,6 +646,26 @@ impl IOCompositor { .dispatch_input_event(InputEvent::MouseMove(MouseMoveEvent { point })); }, + CompositorMsg::WebDriverWheelScrollEvent(webview_id, x, y, delta_x, delta_y) => { + let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else { + warn!("Handling input event for unknown webview: {webview_id}"); + return; + }; + let delta = WheelDelta { + x: delta_x, + y: delta_y, + z: 0.0, + mode: WheelMode::DeltaPixel, + }; + let dppx = webview_renderer.device_pixels_per_page_pixel(); + let point = dppx.transform_point(Point2D::new(x, y)); + let scroll_delta = + dppx.transform_vector(Vector2D::new(delta_x as f32, delta_y as f32)); + webview_renderer + .dispatch_input_event(InputEvent::Wheel(WheelEvent { delta, point })); + webview_renderer.on_webdriver_wheel_action(scroll_delta, point); + }, + CompositorMsg::SendInitialTransaction(pipeline) => { let mut txn = Transaction::new(); txn.set_display_list(WebRenderEpoch(0), (pipeline, Default::default())); 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 614ef0ff4c3..a51dd5f8cda 100644 --- a/components/compositing/webview_renderer.rs +++ b/components/compositing/webview_renderer.rs @@ -689,11 +689,6 @@ impl WebViewRenderer { action: MouseButtonAction::Up, point, })); - self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent { - button, - action: MouseButtonAction::Click, - point, - })); } pub(crate) fn notify_scroll_event( @@ -726,6 +721,22 @@ impl WebViewRenderer { })); } + /// Push scroll pending event when receiving wheel action from webdriver + pub(crate) fn on_webdriver_wheel_action( + &mut self, + scroll_delta: Vector2D<f32, DevicePixel>, + point: Point2D<f32, DevicePixel>, + ) { + if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown { + return; + } + + let scroll_location = + ScrollLocation::Delta(LayoutVector2D::from_untyped(scroll_delta.to_untyped())); + let cursor = DeviceIntPoint::new(point.x as i32, point.y as i32); + self.on_scroll_window_event(scroll_location, cursor) + } + pub(crate) fn process_pending_scroll_events(&mut self, compositor: &mut IOCompositor) { if self.pending_scroll_zoom_events.is_empty() { return; 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..a9ec112e3eb 100644 --- a/components/config/prefs.rs +++ b/components/config/prefs.rs @@ -236,6 +236,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 { @@ -398,6 +400,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 f3a15d7708d..5db37800c42 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -129,9 +129,10 @@ use embedder_traits::resources::{self, Resource}; use embedder_traits::user_content_manager::UserContentManager; use embedder_traits::{ AnimationState, CompositorHitTestResult, Cursor, EmbedderMsg, EmbedderProxy, - FocusSequenceNumber, ImeEvent, InputEvent, MediaSessionActionType, MediaSessionEvent, - MediaSessionPlaybackState, MouseButton, MouseButtonAction, MouseButtonEvent, Theme, - ViewportDetails, WebDriverCommandMsg, WebDriverLoadStatus, + FocusSequenceNumber, ImeEvent, InputEvent, JSValue, JavaScriptEvaluationError, + JavaScriptEvaluationId, MediaSessionActionType, MediaSessionEvent, MediaSessionPlaybackState, + MouseButton, MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, WebDriverCommandMsg, + WebDriverLoadStatus, }; use euclid::Size2D; use euclid::default::Size2D as UntypedSize2D; @@ -1477,6 +1478,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), + ); } } @@ -1817,6 +1864,9 @@ where self.mem_profiler_chan .send(mem::ProfilerMsg::Report(sender)); }, + ScriptToConstellationMessage::FinishJavaScriptEvaluation(evaluation_id, result) => { + self.handle_finish_javascript_evaluation(evaluation_id, result) + }, } } @@ -3182,6 +3232,22 @@ where feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] + fn handle_finish_javascript_evaluation( + &mut self, + evaluation_id: JavaScriptEvaluationId, + result: Result<JSValue, JavaScriptEvaluationError>, + ) { + self.embedder_proxy + .send(EmbedderMsg::FinishJavaScriptEvaluation( + evaluation_id, + result, + )); + } + + #[cfg_attr( + feature = "tracing", + tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") + )] fn handle_subframe_loaded(&mut self, pipeline_id: PipelineId) { let browsing_context_id = match self.pipelines.get(&pipeline_id) { Some(pipeline) => pipeline.browsing_context_id, @@ -4691,6 +4757,7 @@ where NavigationHistoryBehavior::Replace, ); }, + // TODO: This should use the ScriptThreadMessage::EvaluateJavaScript command WebDriverCommandMsg::ScriptCommand(browsing_context_id, cmd) => { let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) { Some(browsing_context) => browsing_context.pipeline_id, @@ -4783,6 +4850,12 @@ where self.compositor_proxy .send(CompositorMsg::WebDriverMouseMoveEvent(webview_id, x, y)); }, + 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( webview_id, 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<Process, Error let mut child_process = process::Command::new(path_to_self); setup_common(&mut child_process, token); + #[allow(clippy::zombie_processes)] let child = child_process .spawn() .expect("Failed to start unsandboxed child process!"); diff --git a/components/constellation/tracing.rs b/components/constellation/tracing.rs index eff7f755c6b..fd7fe7dd251 100644 --- a/components/constellation/tracing.rs +++ b/components/constellation/tracing.rs @@ -77,6 +77,7 @@ mod from_compositor { Self::SetWebViewThrottled(_, _) => target!("SetWebViewThrottled"), Self::SetScrollStates(..) => target!("SetScrollStates"), Self::PaintMetric(..) => target!("PaintMetric"), + Self::EvaluateJavaScript(..) => target!("EvaluateJavaScript"), } } } @@ -176,6 +177,7 @@ mod from_script { Self::TitleChanged(..) => target!("TitleChanged"), Self::IFrameSizes(..) => target!("IFrameSizes"), Self::ReportMemory(..) => target!("ReportMemory"), + Self::FinishJavaScriptEvaluation(..) => target!("FinishJavaScriptEvaluation"), } } } @@ -238,6 +240,9 @@ mod from_script { Self::ShutdownComplete => target_variant!("ShutdownComplete"), Self::ShowNotification(..) => target_variant!("ShowNotification"), Self::ShowSelectElementMenu(..) => target_variant!("ShowSelectElementMenu"), + Self::FinishJavaScriptEvaluation(..) => { + target_variant!("FinishJavaScriptEvaluation") + }, } } } diff --git a/components/devtools/actors/browsing_context.rs b/components/devtools/actors/browsing_context.rs index 5de0855df4a..81a00e82d47 100644 --- a/components/devtools/actors/browsing_context.rs +++ b/components/devtools/actors/browsing_context.rs @@ -149,10 +149,6 @@ impl ResourceAvailable for BrowsingContextActor { fn actor_name(&self) -> String { self.name.clone() } - - fn get_streams(&self) -> &RefCell<HashMap<StreamId, TcpStream>> { - &self.streams - } } impl Actor for BrowsingContextActor { diff --git a/components/devtools/actors/console.rs b/components/devtools/actors/console.rs index 3897ffa0fce..fd0bd184bfa 100644 --- a/components/devtools/actors/console.rs +++ b/components/devtools/actors/console.rs @@ -252,6 +252,7 @@ impl ConsoleActor { page_error: PageError, id: UniqueId, registry: &ActorRegistry, + stream: &mut TcpStream, ) { self.cached_events .borrow_mut() @@ -262,7 +263,11 @@ impl ConsoleActor { if let Root::BrowsingContext(bc) = &self.root { registry .find::<BrowsingContextActor>(bc) - .resource_available(PageErrorWrapper { page_error }, "error-message".into()) + .resource_available( + PageErrorWrapper { page_error }, + "error-message".into(), + stream, + ) }; } } @@ -272,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 @@ -283,7 +289,7 @@ impl ConsoleActor { if let Root::BrowsingContext(bc) = &self.root { registry .find::<BrowsingContextActor>(bc) - .resource_available(log_message, "console-message".into()) + .resource_available(log_message, "console-message".into(), stream) }; } } diff --git a/components/devtools/actors/watcher.rs b/components/devtools/actors/watcher.rs index b0b2c755fd8..061ffc92336 100644 --- a/components/devtools/actors/watcher.rs +++ b/components/devtools/actors/watcher.rs @@ -31,7 +31,7 @@ use crate::actors::watcher::thread_configuration::{ ThreadConfigurationActor, ThreadConfigurationActorMsg, }; use crate::protocol::JsonPacketStream; -use crate::resource::{ResourceAvailable, ResourceAvailableReply}; +use crate::resource::ResourceAvailable; use crate::{EmptyReplyMsg, StreamId, WorkerActor}; pub mod network_parent; @@ -291,28 +291,28 @@ impl Actor for WatcherActor { title: Some(target.title.borrow().clone()), url: Some(target.url.borrow().clone()), }; - target.resource_available(event, "document-event".into()); + target.resource_available(event, "document-event".into(), stream); } }, "source" => { let thread_actor = registry.find::<ThreadActor>(&target.thread); let sources = thread_actor.source_manager.sources(); - target.resources_available(sources.iter().collect(), "source".into()); + target.resources_available( + sources.iter().collect(), + "source".into(), + stream, + ); for worker_name in &root.workers { let worker = registry.find::<WorkerActor>(worker_name); let thread = registry.find::<ThreadActor>(&worker.thread); let worker_sources = thread.source_manager.sources(); - let msg = ResourceAvailableReply { - from: worker.name(), - type_: "resources-available-array".into(), - array: vec![( - "source".to_string(), - worker_sources.iter().cloned().collect(), - )], - }; - let _ = stream.write_json_packet(&msg); + worker.resources_available( + worker_sources.iter().collect(), + "source".into(), + stream, + ); } }, "console-message" | "error-message" => {}, diff --git a/components/devtools/actors/worker.rs b/components/devtools/actors/worker.rs index 68ff56fb3b2..f3ca4f2aed7 100644 --- a/components/devtools/actors/worker.rs +++ b/components/devtools/actors/worker.rs @@ -17,7 +17,7 @@ use servo_url::ServoUrl; use crate::StreamId; use crate::actor::{Actor, ActorMessageStatus, ActorRegistry}; use crate::protocol::JsonPacketStream; -use crate::resource::{ResourceAvailable, ResourceAvailableReply}; +use crate::resource::ResourceAvailable; #[derive(Clone, Copy)] #[allow(dead_code)] @@ -60,10 +60,6 @@ impl ResourceAvailable for WorkerActor { fn actor_name(&self) -> String { self.name.clone() } - - fn get_streams(&self) -> &RefCell<HashMap<StreamId, TcpStream>> { - &self.streams - } } impl Actor for WorkerActor { @@ -133,28 +129,6 @@ impl Actor for WorkerActor { } } -impl WorkerActor { - pub(crate) fn resource_available<T: Serialize>(&self, resource: T, resource_type: String) { - self.resources_available(vec![resource], resource_type); - } - - pub(crate) fn resources_available<T: Serialize>( - &self, - resources: Vec<T>, - resource_type: String, - ) { - let msg = ResourceAvailableReply::<T> { - 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); - } - } -} - #[derive(Serialize)] struct DetachedReply { from: String, diff --git a/components/devtools/lib.rs b/components/devtools/lib.rs index 5fb9485e9d3..d097cb25e9d 100644 --- a/components/devtools/lib.rs +++ b/components/devtools/lib.rs @@ -414,7 +414,7 @@ impl DevtoolsInstance { } fn handle_page_error( - &self, + &mut self, pipeline_id: PipelineId, worker_id: Option<WorkerId>, page_error: PageError, @@ -426,11 +426,13 @@ impl DevtoolsInstance { let actors = self.actors.lock().unwrap(); let console_actor = actors.find::<ConsoleActor>(&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<WorkerId>, console_message: ConsoleMessage, @@ -442,7 +444,9 @@ impl DevtoolsInstance { let actors = self.actors.lock().unwrap(); let console_actor = actors.find::<ConsoleActor>(&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( @@ -529,7 +533,10 @@ impl DevtoolsInstance { }; let worker_actor = actors.find::<WorkerActor>(worker_actor_name); - worker_actor.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; @@ -556,7 +563,10 @@ impl DevtoolsInstance { // Notify browsing context about the new source let browsing_context = actors.find::<BrowsingContextActor>(actor_name); - browsing_context.resource_available(source, "source".into()); + + 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 index 7cef8188cc8..4e6aa4042b8 100644 --- a/components/devtools/resource.rs +++ b/components/devtools/resource.rs @@ -2,13 +2,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use std::cell::RefCell; -use std::collections::HashMap; use std::net::TcpStream; use serde::Serialize; -use crate::StreamId; use crate::protocol::JsonPacketStream; #[derive(Serialize)] @@ -22,21 +19,27 @@ pub(crate) struct ResourceAvailableReply<T: Serialize> { pub(crate) trait ResourceAvailable { fn actor_name(&self) -> String; - fn get_streams(&self) -> &RefCell<HashMap<StreamId, TcpStream>>; - - fn resource_available<T: Serialize>(&self, resource: T, resource_type: String) { - self.resources_available(vec![resource], resource_type); + fn resource_available<T: Serialize>( + &self, + resource: T, + resource_type: String, + stream: &mut TcpStream, + ) { + self.resources_available(vec![resource], resource_type, stream); } - fn resources_available<T: Serialize>(&self, resources: Vec<T>, resource_type: String) { + fn resources_available<T: Serialize>( + &self, + resources: Vec<T>, + resource_type: String, + stream: &mut TcpStream, + ) { let msg = ResourceAvailableReply::<T> { from: self.actor_name(), type_: "resources-available-array".into(), array: vec![(resource_type, resources)], }; - for stream in self.get_streams().borrow_mut().values_mut() { - let _ = stream.write_json_packet(&msg); - } + 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<LowercaseFontFamilyName, FontTemplates>, web_fonts_loading_for_stylesheets: Vec<(DocumentStyleSheet, usize)>, @@ -134,7 +134,7 @@ impl FontStore { /// /// This optimization is taken from: /// <https://searchfox.org/mozilla-central/source/gfx/thebes/gfxFontEntry.cpp>. -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, MallocSizeOf)] struct SimpleFamily { regular: Option<FontTemplateRef>, bold: Option<FontTemplateRef>, @@ -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<FontTemplateRef>, simple_family: Option<SimpleFamily>, @@ -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<AtomicRefCell<FontTemplate>>; +#[derive(Clone, Debug, MallocSizeOf)] +pub struct FontTemplateRef(#[conditional_malloc_size_of] Arc<AtomicRefCell<FontTemplate>>); + +impl FontTemplateRef { + pub fn new(template: FontTemplate) -> Self { + Self(Arc::new(AtomicRefCell::new(template))) + } +} + +impl Deref for FontTemplateRef { + type Target = Arc<AtomicRefCell<FontTemplate>>; + 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/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<FontKey>), GetFontInstanceKey(IpcSender<FontInstanceKey>), + CollectMemoryReport(ReportsChan), Exit(IpcSender<()>), Ping, } -#[derive(Default)] +#[derive(Default, MallocSizeOf)] struct ResolvedGenericFontFamilies { default: OnceCell<LowercaseFontFamilyName>, serif: OnceCell<LowercaseFontFamilyName>, @@ -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<SystemFontServiceMessage>, 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/construct_modern.rs b/components/layout/construct_modern.rs index 22f179d146c..8f1282ec9f6 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<Node>, + info: &'a NodeAndStyleInfo<'dom>, propagated_data: PropagatedBoxTreeData, - contiguous_text_runs: Vec<ModernContainerTextRun<'dom, Node>>, + contiguous_text_runs: Vec<ModernContainerTextRun<'dom>>, /// To be run in parallel with rayon in `finish` - jobs: Vec<ModernContainerJob<'dom, Node>>, + jobs: Vec<ModernContainerJob<'dom>>, has_text_runs: bool, } -enum ModernContainerJob<'dom, Node> { +enum ModernContainerJob<'dom> { ElementOrPseudoElement { - info: NodeAndStyleInfo<Node>, + info: NodeAndStyleInfo<'dom>, display: DisplayGeneratingBox, contents: Contents, box_slot: BoxSlot<'dom>, }, - TextRuns(Vec<ModernContainerTextRun<'dom, Node>>), + TextRuns(Vec<ModernContainerTextRun<'dom>>), } -struct ModernContainerTextRun<'dom, Node> { - info: NodeAndStyleInfo<Node>, +struct ModernContainerTextRun<'dom> { + info: NodeAndStyleInfo<'dom>, text: Cow<'dom, str>, } -impl<Node> ModernContainerTextRun<'_, Node> { +impl ModernContainerTextRun<'_> { /// <https://drafts.csswg.org/css-text/#white-space> 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<Node>, 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<Node>, + 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<Node>, + 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); @@ -165,7 +159,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/display_list/mod.rs b/components/layout/display_list/mod.rs index 8799dd2da0c..f017642908d 100644 --- a/components/layout/display_list/mod.rs +++ b/components/layout/display_list/mod.rs @@ -14,6 +14,7 @@ use euclid::{Point2D, SideOffsets2D, Size2D, UnknownUnit}; use fonts::GlyphStore; use gradient::WebRenderGradient; use range::Range as ServoRange; +use servo_arc::Arc as ServoArc; use servo_geometry::MaxRect; use style::Zero; use style::color::{AbsoluteColor, ColorSpace}; @@ -153,10 +154,6 @@ pub(crate) struct DisplayListBuilder<'a> { /// 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, - /// A [LayoutContext] used to get information about the device pixel ratio /// and get handles to WebRender images. pub context: &'a LayoutContext<'a>, @@ -169,6 +166,11 @@ pub(crate) struct DisplayListBuilder<'a> { /// This data is collected during the traversal of the fragment tree and used /// to paint the highlight at the very end. inspector_highlight: Option<InspectorHighlight>, + + /// Whether or not the `<body>` element should be painted. This is false if the root `<html>` + /// element inherits the `<body>`'s background to paint the page canvas background. + /// See <https://drafts.csswg.org/css-backgrounds/#body-background>. + paint_body_background: bool, } struct InspectorHighlight { @@ -218,12 +220,12 @@ impl DisplayList { 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, inspector_highlight: context .highlighted_dom_node .map(InspectorHighlight::for_node), + paint_body_background: true, }; fragment_tree.build_display_list(&mut builder, root_stacking_context); @@ -469,18 +471,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(); @@ -543,7 +543,13 @@ impl Fragment { }, Fragment::Text(text) => { let text = &*text.borrow(); - match text.parent_style.get_inherited_box().visibility { + match text + .inline_styles + .style + .borrow() + .get_inherited_box() + .visibility + { Visibility::Visible => { self.build_display_list_for_text_fragment(text, builder, containing_block) }, @@ -603,22 +609,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 { @@ -641,7 +648,7 @@ impl Fragment { 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); + self.build_display_list_for_text_decoration(&parent_style, builder, &rect, &color); } if fragment @@ -650,7 +657,7 @@ impl Fragment { { 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); + self.build_display_list_for_text_decoration(&parent_style, builder, &rect, &color); } // TODO: This caret/text selection implementation currently does not account for vertical text @@ -677,12 +684,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, @@ -708,7 +716,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 @@ -733,7 +741,7 @@ impl Fragment { 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); + self.build_display_list_for_text_decoration(&parent_style, builder, &rect, &color); } if !shadows.0.is_empty() { @@ -743,23 +751,22 @@ impl Fragment { fn build_display_list_for_text_decoration( &self, - fragment: &TextFragment, + parent_style: &ServoArc<ComputedValues>, builder: &mut DisplayListBuilder, rect: &PhysicalRect<Au>, color: &AbsoluteColor, ) { let rect = rect.to_webrender(); let wavy_line_thickness = (0.33 * rect.size().height).ceil(); - let text_decoration_color = fragment - .parent_style + let text_decoration_color = parent_style .clone_text_decoration_color() .resolve_to_absolute(color); - let text_decoration_style = fragment.parent_style.clone_text_decoration_style(); + let text_decoration_style = parent_style.clone_text_decoration_style(); if text_decoration_style == ComputedTextDecorationStyle::MozNone { return; } builder.display_list.wr.push_line( - &builder.common_properties(rect, &fragment.parent_style), + &builder.common_properties(rect, parent_style), &rect, wavy_line_thickness, wr::LineOrientation::Horizontal, @@ -999,12 +1006,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 `<body>`'s + // background. + if flags.intersects(FragmentFlags::IS_ROOT_ELEMENT) { + return; + } + // If the `<body>` 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; } @@ -1020,7 +1032,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 diff --git a/components/layout/display_list/stacking_context.rs b/components/layout/display_list/stacking_context.rs index b044b713260..27fa73a680c 100644 --- a/components/layout/display_list/stacking_context.rs +++ b/components/layout/display_list/stacking_context.rs @@ -582,15 +582,42 @@ impl StackingContext { &self, builder: &mut DisplayListBuilder, fragment_tree: &crate::FragmentTree, - containing_block_rect: &PhysicalRect<Au>, ) { - 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 `<body>` 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 `<body>` child (either because it is + // `display: none` or `display: contents`) or if the `<body>`'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,14 +625,15 @@ 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 @@ -613,97 +641,14 @@ impl StackingContext { .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; - } - } - 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, }; diff --git a/components/layout/dom.rs b/components/layout/dom.rs index 8f2697e670a..e3a22eb5197 100644 --- a/components/layout/dom.rs +++ b/components/layout/dom.rs @@ -11,6 +11,7 @@ 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, }; @@ -18,14 +19,14 @@ use script_layout_interface::{ 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; @@ -58,7 +59,7 @@ impl InnerDOMLayoutData { /// A box that is stored in one of the `DOMLayoutData` slots. #[derive(MallocSizeOf)] pub(super) enum LayoutBox { - DisplayContents, + DisplayContents(SharedInlineStyles), BlockLevel(ArcRefCell<BlockLevelBox>), InlineLevel(Vec<ArcRefCell<InlineItem>>), FlexLevel(ArcRefCell<FlexLevelBox>), @@ -69,7 +70,7 @@ 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() }, @@ -90,7 +91,7 @@ impl LayoutBox { pub(crate) fn fragments(&self) -> Vec<Fragment> { match self { - LayoutBox::DisplayContents => vec![], + LayoutBox::DisplayContents(..) => vec![], LayoutBox::BlockLevel(block_level_box) => block_level_box.borrow().fragments(), LayoutBox::InlineLevel(inline_items) => inline_items .iter() @@ -101,6 +102,41 @@ impl LayoutBox { LayoutBox::TableLevelBox(table_box) => table_box.fragments(), } } + + fn repair_style( + &self, + context: &SharedStyleContext, + node: &ServoLayoutNode, + new_style: &ServoArc<ComputedValues>, + ) { + 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, new_style) + }, + LayoutBox::TableLevelBox(table_level_box) => { + table_level_box.repair_style(context, new_style) + }, + LayoutBox::TaffyItemBox(taffy_item_box) => { + taffy_item_box.borrow_mut().repair_style(context, new_style) + }, + } + } } /// A wrapper for [`InnerDOMLayoutData`]. This is necessary to give the entire data @@ -158,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<Arc<Image>>, PhysicalSize<f64>)>; - fn as_canvas(self) -> Option<(CanvasInfo, PhysicalSize<f64>)>; - fn as_iframe(self) -> Option<(PipelineId, BrowsingContextId)>; - fn as_video(self) -> Option<(Option<webrender_api::ImageKey>, Option<PhysicalSize<f64>>)>; - fn as_typeless_object_with_data_attribute(self) -> Option<String>; - fn style(self, context: &LayoutContext) -> ServoArc<ComputedValues>; - - fn layout_data_mut(self) -> AtomicRefMut<'dom, InnerDOMLayoutData>; - fn layout_data(self) -> Option<AtomicRef<'dom, InnerDOMLayoutData>>; + fn as_image(&self) -> Option<(Option<Arc<Image>>, PhysicalSize<f64>)>; + fn as_canvas(&self) -> Option<(CanvasInfo, PhysicalSize<f64>)>; + fn as_iframe(&self) -> Option<(PipelineId, BrowsingContextId)>; + fn as_video(&self) -> Option<(Option<webrender_api::ImageKey>, Option<PhysicalSize<f64>>)>; + fn as_typeless_object_with_data_attribute(&self) -> Option<String>; + fn style(&self, context: &SharedStyleContext) -> ServoArc<ComputedValues>; + + fn layout_data_mut(&self) -> AtomicRefMut<'dom, InnerDOMLayoutData>; + fn layout_data(&self) -> Option<AtomicRef<'dom, InnerDOMLayoutData>>; 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<PseudoElement>) -> Vec<Fragment>; - 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<Arc<Image>>, PhysicalSize<f64>)> { +impl<'dom> NodeExt<'dom> for ServoLayoutNode<'dom> { + fn as_image(&self) -> Option<(Option<Arc<Image>>, PhysicalSize<f64>)> { let node = self.to_threadsafe(); let (resource, metadata) = node.image_data()?; let (width, height) = resource @@ -201,7 +236,7 @@ where Some((resource, PhysicalSize::new(width, height))) } - fn as_video(self) -> Option<(Option<webrender_api::ImageKey>, Option<PhysicalSize<f64>>)> { + fn as_video(&self) -> Option<(Option<webrender_api::ImageKey>, Option<PhysicalSize<f64>>)> { let node = self.to_threadsafe(); let data = node.media_data()?; let natural_size = if let Some(frame) = data.current_frame { @@ -216,7 +251,7 @@ where )) } - fn as_canvas(self) -> Option<(CanvasInfo, PhysicalSize<f64>)> { + fn as_canvas(&self) -> Option<(CanvasInfo, PhysicalSize<f64>)> { let node = self.to_threadsafe(); let canvas_data = node.canvas_data()?; let source = canvas_data.source; @@ -226,7 +261,7 @@ where )) } - 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)) => { @@ -236,8 +271,10 @@ where } } - fn as_typeless_object_with_data_attribute(self) -> Option<String> { - if self.type_id() != ScriptLayoutNodeType::Element(LayoutElementType::HTMLObjectElement) { + fn as_typeless_object_with_data_attribute(&self) -> Option<String> { + if LayoutNode::type_id(self) != + ScriptLayoutNodeType::Element(LayoutElementType::HTMLObjectElement) + { return None; } @@ -253,15 +290,15 @@ where .map(|string| string.to_owned()) } - fn style(self, context: &LayoutContext) -> ServoArc<ComputedValues> { - self.to_threadsafe().style(context.shared_context()) + fn style(&self, context: &SharedStyleContext) -> ServoArc<ComputedValues> { + 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::<DOMLayoutData>(); } - LayoutNode::layout_data(&self) + LayoutNode::layout_data(self) .unwrap() .as_any() .downcast_ref::<DOMLayoutData>() @@ -270,8 +307,8 @@ where .borrow_mut() } - fn layout_data(self) -> Option<AtomicRef<'dom, InnerDOMLayoutData>> { - LayoutNode::layout_data(&self).map(|data| { + fn layout_data(&self) -> Option<AtomicRef<'dom, InnerDOMLayoutData>> { + LayoutNode::layout_data(self).map(|data| { data.as_any() .downcast_ref::<DOMLayoutData>() .unwrap() @@ -298,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, @@ -312,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; @@ -322,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(); @@ -330,7 +367,7 @@ where } fn fragments_for_pseudo(&self, pseudo_element: Option<PseudoElement>) -> Vec<Fragment> { - NodeExt::layout_data(*self) + NodeExt::layout_data(self) .and_then(|layout_data| { layout_data .for_pseudo(pseudo_element) @@ -339,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..0201d72dbe2 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<Node> { - pub node: Node, +pub(crate) struct NodeAndStyleInfo<'dom> { + pub node: ServoLayoutNode<'dom>, pub pseudo_element_type: Option<PseudoElement>, pub style: ServoArc<ComputedValues>, } -impl<'dom, Node: NodeExt<'dom>> NodeAndStyleInfo<Node> { +impl<'dom> NodeAndStyleInfo<'dom> { fn new_with_pseudo( - node: Node, + node: ServoLayoutNode<'dom>, pseudo_element_type: PseudoElement, style: ServoArc<ComputedValues>, ) -> Self { @@ -47,7 +51,7 @@ impl<'dom, Node: NodeExt<'dom>> NodeAndStyleInfo<Node> { } } - pub(crate) fn new(node: Node, style: ServoArc<ComputedValues>) -> Self { + pub(crate) fn new(node: ServoLayoutNode<'dom>, style: ServoArc<ComputedValues>) -> Self { Self { node, pseudo_element_type: None, @@ -86,11 +90,8 @@ impl<'dom, Node: NodeExt<'dom>> NodeAndStyleInfo<Node> { } } -impl<'dom, Node> From<&NodeAndStyleInfo<Node>> for BaseFragmentInfo -where - Node: NodeExt<'dom>, -{ - fn from(info: &NodeAndStyleInfo<Node>) -> 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,29 +175,30 @@ pub(super) enum PseudoElementContentItem { Replaced(ReplacedContents), } -pub(super) trait TraversalHandler<'dom, Node> -where - Node: 'dom, -{ - fn handle_text(&mut self, info: &NodeAndStyleInfo<Node>, 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<Node>, + 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!( @@ -210,7 +212,10 @@ fn traverse_children_of<'dom, Node>( ); if is_text_input_element || is_textarea_element { - let info = NodeAndStyleInfo::new(parent_element, parent_element.style(context)); + 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 @@ -229,7 +234,7 @@ fn traverse_children_of<'dom, Node>( if !is_text_input_element && !is_textarea_element { 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 +245,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 +264,15 @@ fn traverse_element<'dom, Node>( // <https://drafts.csswg.org/css-display-3/#valdef-display-contents> 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 +296,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 +325,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 +341,12 @@ fn traverse_eager_pseudo_element<'dom, Node>( } } -fn traverse_pseudo_element_contents<'dom, Node>( - info: &NodeAndStyleInfo<Node>, +fn traverse_pseudo_element_contents<'dom>( + info: &NodeAndStyleInfo<'dom>, context: &LayoutContext, - handler: &mut impl TraversalHandler<'dom, Node>, + handler: &mut impl TraversalHandler<'dom>, items: Vec<PseudoElementContentItem>, -) where - Node: NodeExt<'dom>, -{ +) { let mut anonymous_info = None; for item in items { match item { @@ -396,14 +406,12 @@ impl std::convert::TryFrom<Contents> for NonReplacedContents { } impl NonReplacedContents { - pub(crate) fn traverse<'dom, Node>( + pub(crate) fn traverse<'dom>( self, context: &LayoutContext, - info: &NodeAndStyleInfo<Node>, - 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 +435,11 @@ where } /// <https://www.w3.org/TR/CSS2/generate.html#propdef-content> -fn generate_pseudo_element_content<'dom, Node>( +fn generate_pseudo_element_content( pseudo_element_style: &ComputedValues, - element: Node, + element: ServoLayoutNode<'_>, context: &LayoutContext, -) -> Vec<PseudoElementContentItem> -where - Node: NodeExt<'dom>, -{ +) -> Vec<PseudoElementContentItem> { match &pseudo_element_style.get_counters().content { Content::Items(items) => { let mut vec = vec![]; @@ -517,18 +522,14 @@ where } } -pub enum ChildNodeIterator<Node> { +pub enum ChildNodeIterator<'dom> { /// Iterating over the children of a node - Node(Option<Node>), + Node(Option<ServoLayoutNode<'dom>>), /// Iterating over the assigned nodes of a `HTMLSlotElement` - Slottables(<Vec<Node> as IntoIterator>::IntoIter), + Slottables(<Vec<ServoLayoutNode<'dom>> as IntoIterator>::IntoIter), } -#[allow(clippy::unnecessary_to_owned)] // Clippy is wrong. -pub(crate) fn iter_child_nodes<'dom, Node>(parent: Node) -> ChildNodeIterator<Node> -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 +537,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 +546,8 @@ where ChildNodeIterator::Node(first) } -impl<'dom, Node> Iterator for ChildNodeIterator<Node> -where - Node: NodeExt<'dom>, -{ - type Item = Node; +impl<'dom> Iterator for ChildNodeIterator<'dom> { + type Item = ServoLayoutNode<'dom>; fn next(&mut self) -> Option<Self::Item> { match self { @@ -562,4 +561,4 @@ where } } -impl<'dom, Node> FusedIterator for ChildNodeIterator<Node> where Node: NodeExt<'dom> {} +impl FusedIterator for ChildNodeIterator<'_> {} diff --git a/components/layout/flexbox/layout.rs b/components/layout/flexbox/layout.rs index e69b792e272..80057af2b34 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, @@ -142,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<SpecificLayoutInfo>, } impl FlexItemLayoutResult { @@ -295,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 @@ -649,6 +655,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; @@ -670,14 +677,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 @@ -760,30 +766,23 @@ impl FlexContainer { .map(|layout| layout.line_size.cross) .sum::<Au>() + 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. // @@ -1910,6 +1909,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) => { @@ -1929,6 +1929,27 @@ impl FlexItem<'_> { } } + let lazy_block_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)); + LazySize::new( + &self.content_cross_sizes, + Direction::Block, + Size::FitContent, + Au::zero, + stretch_size, + self.is_table(), + ) + } else { + used_main_size.into() + }; + let layout = non_replaced.layout( flex_context.layout_context, &mut positioning_context, @@ -1938,12 +1959,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; @@ -1954,22 +1977,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 }; @@ -2012,6 +2020,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, }) }, } @@ -2678,6 +2687,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..91a12b31812 100644 --- a/components/layout/flexbox/mod.rs +++ b/components/layout/flexbox/mod.rs @@ -5,6 +5,7 @@ use geom::{FlexAxis, MainStartCrossStart}; use malloc_size_of_derive::MallocSizeOf; 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 +18,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 +91,6 @@ impl FlexContainerConfig { pub(crate) struct FlexContainer { children: Vec<ArcRefCell<FlexLevelBox>>, - #[conditional_malloc_size_of] style: ServoArc<ComputedValues>, /// The configuration of this [`FlexContainer`]. @@ -98,9 +98,9 @@ pub(crate) struct FlexContainer { } impl FlexContainer { - pub fn construct<'dom>( + pub fn construct( context: &LayoutContext, - info: &NodeAndStyleInfo<impl NodeExt<'dom>>, + info: &NodeAndStyleInfo<'_>, contents: NonReplacedContents, propagated_data: PropagatedBoxTreeData, ) -> Self { @@ -137,6 +137,11 @@ impl FlexContainer { config: FlexContainerConfig::new(&info.style), } } + + pub(crate) fn repair_style(&mut self, new_style: &ServoArc<ComputedValues>) { + self.config = FlexContainerConfig::new(new_style); + self.style = new_style.clone(); + } } #[derive(Debug, MallocSizeOf)] @@ -146,6 +151,22 @@ pub(crate) enum FlexLevelBox { } impl FlexLevelBox { + pub(crate) fn repair_style( + &mut self, + context: &SharedStyleContext, + new_style: &ServoArc<ComputedValues>, + ) { + match self { + FlexLevelBox::FlexItem(flex_item_box) => flex_item_box + .independent_formatting_context + .repair_style(context, new_style), + FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => positioned_box + .borrow_mut() + .context + .repair_style(context, 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 5ed567f513b..334da8ae2b0 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<Node>, + 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<Node>, +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<Node>, + 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<BlockLevelJob<'dom, Node>>, + block_level_boxes: Vec<BlockLevelJob<'dom>>, /// 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<InlineFormattingContextBuilder>, /// 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<NodeAndStyleInfo<Node>>, + /// block-level boxes, lazily initialized. + anonymous_box_info: Option<NodeAndStyleInfo<'dom>>, /// 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<AnonymousTableContent<'dom, Node>>, + anonymous_table_content: Vec<AnonymousTableContent<'dom>>, + + /// 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<SharedInlineStyles>, } impl BlockContainer { - pub fn construct<'dom, Node>( + pub fn construct( context: &LayoutContext, - info: &NodeAndStyleInfo<Node>, + 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,13 +189,10 @@ 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<Node>, + info: &'style NodeAndStyleInfo<'dom>, propagated_data: PropagatedBoxTreeData, ) -> Self { BlockContainerBuilder { @@ -203,26 +203,44 @@ where 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<InlineFormattingContext> { + 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,9 +278,7 @@ 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: @@ -274,7 +290,7 @@ where false => self.propagated_data, }; - let contents: Vec<AnonymousTableContent<'dom, Node>> = + let contents: Vec<AnonymousTableContent<'dom>> = self.anonymous_table_content.drain(..).collect(); let last_text = match contents.last() { Some(AnonymousTableContent::Text(info, text)) => Some((info.clone(), text.clone())), @@ -285,10 +301,16 @@ where Table::construct_anonymous(self.context, self.info, contents, 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(), @@ -312,13 +334,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<Node>, + info: &NodeAndStyleInfo<'dom>, display: DisplayGeneratingBox, contents: Contents, box_slot: BoxSlot<'dom>, @@ -359,7 +378,7 @@ where } } - fn handle_text(&mut self, info: &NodeAndStyleInfo<Node>, text: Cow<'dom, str>) { + fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: Cow<'dom, str>) { if text.is_empty() { return; } @@ -375,18 +394,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<Node>, - container_info: &NodeAndStyleInfo<Node>, + marker_info: &NodeAndStyleInfo<'dom>, + container_info: &NodeAndStyleInfo<'dom>, contents: Vec<crate::dom_traversal::PseudoElementContentItem>, ) { // TODO: We do not currently support saving box slots for ::marker pseudo-elements @@ -411,8 +442,8 @@ where fn handle_list_item_marker_outside( &mut self, - marker_info: &NodeAndStyleInfo<Node>, - container_info: &NodeAndStyleInfo<Node>, + marker_info: &NodeAndStyleInfo<'dom>, + container_info: &NodeAndStyleInfo<'dom>, contents: Vec<crate::dom_traversal::PseudoElementContentItem>, list_item_style: Arc<ComputedValues>, ) { @@ -439,7 +470,7 @@ where fn handle_inline_level_element( &mut self, - info: &NodeAndStyleInfo<Node>, + info: &NodeAndStyleInfo<'dom>, display_inside: DisplayInside, contents: Contents, box_slot: BoxSlot<'dom>, @@ -448,14 +479,16 @@ 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 propagaged_data = self.propagated_data.without_text_decorations(); + 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(), + propagaged_data, ), ); box_slot.set(LayoutBox::InlineLevel(vec![atomic])); @@ -464,7 +497,7 @@ where // 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. - self.inline_formatting_context_builder + self.ensure_inline_formatting_context_builder() .start_inline_box(InlineBox::new(info), None); if is_list_item { @@ -491,13 +524,16 @@ where // `InlineFormattingContextBuilder::end_inline_box()` is returning all of those box tree // items. box_slot.set(LayoutBox::InlineLevel( - self.inline_formatting_context_builder.end_inline_box(), + 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<Node>, + info: &NodeAndStyleInfo<'dom>, display_inside: DisplayInside, contents: Contents, box_slot: BoxSlot<'dom>, @@ -510,12 +546,15 @@ 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.propagated_data, + !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); } @@ -565,22 +604,23 @@ where fn handle_absolutely_positioned_element( &mut self, - info: &NodeAndStyleInfo<Node>, + 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(vec![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 { @@ -597,23 +637,23 @@ where fn handle_float_element( &mut self, - info: &NodeAndStyleInfo<Node>, + 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(vec![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 { @@ -628,18 +668,6 @@ where }); } - 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, @@ -670,10 +698,7 @@ where } } -impl<'dom, Node> BlockLevelJob<'dom, Node> -where - Node: NodeExt<'dom>, -{ +impl BlockLevelJob<'_> { fn finish(self, context: &LayoutContext) -> ArcRefCell<BlockLevelBox> { let info = &self.info; let block_level_box = match self.kind { @@ -747,14 +772,7 @@ where } impl IntermediateBlockContainer { - fn finish<'dom, Node>( - self, - context: &LayoutContext, - info: &NodeAndStyleInfo<Node>, - ) -> 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 dbc50c07603..f1ae2b4459a 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<impl NodeExt<'dom>>, + info: &NodeAndStyleInfo<'_>, display_inside: DisplayInside, contents: Contents, propagated_data: PropagatedBoxTreeData, diff --git a/components/layout/flow/inline/construct.rs b/components/layout/flow/inline/construct.rs index 61292701a9f..a99de1679a4 100644 --- a/components/layout/flow/inline/construct.rs +++ b/components/layout/flow/inline/construct.rs @@ -7,17 +7,18 @@ use std::char::{ToLowercase, ToUppercase}; use icu_segmenter::WordSegmenter; use itertools::izip; -use servo_arc::Arc; 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 super::{ + InlineBox, InlineBoxIdentifier, InlineBoxes, InlineFormattingContext, InlineItem, + SharedInlineStyles, +}; use crate::PropagatedBoxTreeData; 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; @@ -26,6 +27,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. + shared_inline_styles_stack: Vec<SharedInlineStyles>, + /// The collection of text strings that make up this [`InlineFormattingContext`] under /// construction. pub text_segments: Vec<String>, @@ -64,7 +71,7 @@ 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<InlineBoxIdentifier>, @@ -84,10 +91,17 @@ pub(crate) struct InlineFormattingContextBuilder { } 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<SharedInlineStyles>, + ) -> 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() } } @@ -101,6 +115,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. @@ -136,7 +157,7 @@ impl InlineFormattingContextBuilder { independent_formatting_context: IndependentFormattingContext, ) -> ArcRefCell<InlineItem> { 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. */ )); @@ -167,7 +188,8 @@ impl InlineFormattingContextBuilder { } pub(crate) fn push_float_box(&mut self, float_box: FloatBox) -> ArcRefCell<InlineItem> { - 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 @@ -180,6 +202,14 @@ impl InlineFormattingContextBuilder { ) { 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()); @@ -195,6 +225,8 @@ impl InlineFormattingContextBuilder { /// 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<ArcRefCell<InlineItem>> { + 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); { @@ -225,11 +257,7 @@ impl InlineFormattingContextBuilder { (identifier, block_in_inline_splits) } - pub(crate) fn push_text<'dom, Node: NodeExt<'dom>>( - &mut self, - text: Cow<'dom, str>, - info: &NodeAndStyleInfo<Node>, - ) { + 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(), @@ -277,8 +305,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 = @@ -300,14 +326,21 @@ 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, @@ -323,7 +356,8 @@ 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(); + 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) @@ -361,7 +395,7 @@ 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, @@ -372,11 +406,9 @@ 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, diff --git a/components/layout/flow/inline/inline_box.rs b/components/layout/flow/inline/inline_box.rs index de79f876340..b547f3b5935 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 super::{InlineContainerState, InlineContainerStateFlags, inline_container_needs_strut}; +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, 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,6 +27,9 @@ 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, /// Whether or not this is the first instance of an [`InlineBox`] before a possible @@ -35,9 +44,10 @@ pub(crate) struct InlineBox { } impl InlineBox { - pub(crate) fn new<'dom, Node: NodeExt<'dom>>(info: &NodeAndStyleInfo<Node>) -> 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_split: true, @@ -49,6 +59,7 @@ impl InlineBox { pub(crate) fn split_around_block(&self) -> Self { Self { base: LayoutBoxBase::new(self.base.base_fragment_info, self.base.style.clone()), + shared_inline_styles: self.shared_inline_styles.clone(), is_first_split: false, is_last_split: false, ..*self @@ -59,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<ComputedValues>, + ) { + 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)] diff --git a/components/layout/flow/inline/line.rs b/components/layout/flow/inline/line.rs index 80bab1080ed..3b92078d67d 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; @@ -21,7 +20,7 @@ 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}; @@ -568,7 +567,7 @@ 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, @@ -576,7 +575,6 @@ impl LineItemLayout<'_, '_> { 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, )); @@ -763,7 +761,7 @@ impl LineItem { pub(super) struct TextRunLineItem { pub base_fragment_info: BaseFragmentInfo, - pub parent_style: Arc<ComputedValues>, + pub inline_styles: SharedInlineStyles, pub text: Vec<std::sync::Arc<GlyphStore>>, pub font_metrics: FontMetrics, pub font_key: FontInstanceKey, @@ -771,13 +769,16 @@ pub(super) struct TextRunLineItem { /// The BiDi level of this [`TextRunLineItem`] to enable reordering. pub bidi_level: Level, pub selection_range: Option<Range<ByteIndex>>, - pub selected_style: Arc<ComputedValues>, } 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; @@ -803,7 +804,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 2023f4e7174..7e69aa1aaae 100644 --- a/components/layout/flow/inline/mod.rs +++ b/components/layout/flow/inline/mod.rs @@ -90,12 +90,13 @@ use line::{ use line_breaker::LineBreaker; use malloc_size_of_derive::MallocSizeOf; use range::Range; +use script::layout_dom::ServoLayoutNode; 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; @@ -118,6 +119,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 +133,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, PropagatedBoxTreeData, SharedStyle}; // From gfxFontConstants.h in Firefox. static FONT_SUBSCRIPT_OFFSET_RATIO: f32 = 0.20; @@ -173,6 +175,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 +211,41 @@ pub(crate) enum InlineItem { ArcRefCell<AbsolutelyPositionedBox>, usize, /* offset_in_text */ ), - OutOfFlowFloatBox(#[conditional_malloc_size_of] Arc<FloatBox>), + OutOfFlowFloatBox(ArcRefCell<FloatBox>), Atomic( - #[conditional_malloc_size_of] Arc<IndependentFormattingContext>, + ArcRefCell<IndependentFormattingContext>, usize, /* offset_in_text */ Level, /* bidi_level */ ), } impl InlineItem { + pub(crate) fn repair_style( + &self, + context: &SharedStyleContext, + node: &ServoLayoutNode, + new_style: &Arc<ComputedValues>, + ) { + 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, new_style), + InlineItem::OutOfFlowFloatBox(float_box) => float_box + .borrow_mut() + .contents + .repair_style(context, new_style), + InlineItem::Atomic(atomic, ..) => atomic.borrow_mut().repair_style(context, new_style), + } + } + pub(crate) fn invalidate_cached_fragment(&self) { match self { InlineItem::StartInlineBox(inline_box) => { @@ -212,11 +259,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 +282,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() }, } } @@ -958,6 +1010,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 +1366,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 +1451,12 @@ 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(), }, )); } @@ -1751,7 +1803,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 +1818,7 @@ impl InlineFormattingContext { )); }, InlineItem::OutOfFlowFloatBox(float_box) => { - float_box.layout_into_line_items(&mut layout); + float_box.borrow().layout_into_line_items(&mut layout); }, } } @@ -2363,8 +2415,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 @@ -2428,7 +2481,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; -/// <https://www.w3.org/TR/css-display-3/#css-text-run> -#[derive(Debug, MallocSizeOf)] -pub(crate) struct TextRun { - pub base_fragment_info: BaseFragmentInfo, - #[conditional_malloc_size_of] - pub parent_style: Arc<ComputedValues>, - pub text_range: Range<usize>, - - /// The text of this [`TextRun`] with a font selected, broken into unbreakable - /// segments, and shaped. - pub shaped_text: Vec<TextRunSegment>, - pub selection_range: Option<ServoRange<ByteIndex>>, - #[conditional_malloc_size_of] - pub selected_style: Arc<ComputedValues>, -} - // 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. +/// +/// <https://www.w3.org/TR/css-display-3/#css-text-run> +#[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<usize>, + + /// The text of this [`TextRun`] with a font selected, broken into unbreakable + /// segments, and shaped. + pub shaped_text: Vec<TextRunSegment>, + + /// The selection range for the DOM text node that originated this [`TextRun`]. This + /// comes directly from the DOM. + pub selection_range: Option<ServoRange<ByteIndex>>, +} + impl TextRun { pub(crate) fn new( base_fragment_info: BaseFragmentInfo, - parent_style: Arc<ComputedValues>, + inline_styles: SharedInlineStyles, text_range: Range<usize>, selection_range: Option<ServoRange<ByteIndex>>, - selected_style: Arc<ComputedValues>, ) -> 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<FontKeyAndMetrics>, 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<Au> = 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<FontKeyAndMetrics>, bidi_info: &BidiInfo, + parent_style: &Arc<ComputedValues>, ) -> 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 0c326c4cc6d..99b84d088e5 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 { @@ -91,6 +94,36 @@ pub(crate) enum BlockLevelBox { } impl BlockLevelBox { + pub(crate) fn repair_style( + &mut self, + context: &SharedStyleContext, + node: &ServoLayoutNode, + new_style: &Arc<ComputedValues>, + ) { + self.with_base_mut(|base| { + base.repair_style(new_style); + }); + + match self { + BlockLevelBox::Independent(independent_formatting_context) => { + independent_formatting_context.repair_style(context, new_style) + }, + BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => positioned_box + .borrow_mut() + .context + .repair_style(context, new_style), + BlockLevelBox::OutOfFlowFloatBox(float_box) => { + float_box.contents.repair_style(context, new_style) + }, + BlockLevelBox::OutsideMarker(outside_marker) => { + outside_marker.repair_style(context, node, new_style) + }, + BlockLevelBox::SameFormattingContextBlock { base, .. } => { + base.repair_style(new_style); + }, + } + } + pub(crate) fn invalidate_cached_fragment(&self) { self.with_base(LayoutBoxBase::invalidate_cached_fragment); } @@ -113,6 +146,20 @@ impl BlockLevelBox { } } + pub(crate) fn with_base_mut<T>(&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 +296,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<ComputedValues>, pub base: LayoutBoxBase, pub block_container: BlockContainer, @@ -361,6 +407,16 @@ impl OutsideMarker { None, ))) } + + fn repair_style( + &mut self, + context: &SharedStyleContext, + node: &ServoLayoutNode, + new_style: &Arc<ComputedValues>, + ) { + self.list_item_style = node.style(context); + self.base.repair_style(new_style); + } } impl BlockFormattingContext { @@ -1161,6 +1217,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,19 +1233,13 @@ 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, @@ -1313,16 +1372,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. @@ -1351,10 +1408,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), }; @@ -1416,6 +1474,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 { @@ -1429,7 +1488,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, }; @@ -2363,6 +2422,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, @@ -2370,18 +2438,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 bb9ff1d337a..a37db54065d 100644 --- a/components/layout/flow/root.rs +++ b/components/layout/flow/root.rs @@ -6,12 +6,13 @@ use app_units::Au; use atomic_refcell::AtomicRef; use compositing_traits::display_list::AxesScrollSensitivity; 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; @@ -29,7 +30,7 @@ use crate::fragment_tree::FragmentTree; use crate::geom::{LogicalVec2, PhysicalPoint, 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 +40,12 @@ pub struct BoxTree { /// There may be zero if that element has `display: none`. root: BlockFormattingContext, - /// <https://drafts.csswg.org/css-backgrounds/#special-backgrounds> - 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 +59,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 +76,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 +93,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 +123,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<BlockLevelBox>), @@ -141,12 +132,9 @@ impl BoxTree { AbsolutelyPositionedTaffyLevelBox(ArcRefCell<TaffyItemBox>), } - fn update_point<'dom, Node>( - node: Node, - ) -> Option<(Arc<ComputedValues>, DisplayInside, UpdatePoint)> - where - Node: NodeExt<'dom>, - { + fn update_point( + node: ServoLayoutNode<'_>, + ) -> Option<(Arc<ComputedValues>, DisplayInside, UpdatePoint)> { if !node.is_element() { return None; } @@ -162,7 +150,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 +174,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() => @@ -301,11 +289,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<ArcRefCell<BlockLevelBox>> { - 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) { @@ -433,69 +421,7 @@ impl BoxTree { root_fragments, scrollable_overflow, physical_containing_block, - self.canvas_background.clone(), self.viewport_scroll_sensitivity, ) } } - -/// <https://drafts.csswg.org/css-backgrounds/#root-background> -#[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 `<body>` element. - /// See <https://drafts.csswg.org/css-backgrounds/#body-background> - pub from_element: OpaqueNode, - - /// The computed styles to take background properties from. - #[conditional_malloc_size_of] - pub style: Option<Arc<ComputedValues>>, -} - -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 - }, - } - } -} diff --git a/components/layout/formatting_contexts.rs b/components/layout/formatting_contexts.rs index 4982d0dae1a..a489df2b663 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; 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>, + 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::<Node::ConcreteElement>( - &context.shared_context().guards, - &PseudoElement::ServoTableGrid, - &node_and_style_info.style, - ); + .style_for_anonymous::<ServoLayoutElement>( + &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,20 @@ impl IndependentFormattingContext { }, } } + + pub(crate) fn repair_style( + &mut self, + context: &SharedStyleContext, + new_style: &Arc<ComputedValues>, + ) { + self.base.repair_style(new_style); + match &mut self.contents { + IndependentFormattingContextContents::NonReplaced(content) => { + content.repair_style(context, new_style); + }, + IndependentFormattingContextContents::Replaced(..) => {}, + } + } } impl IndependentNonReplacedContents { @@ -227,6 +243,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 +257,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 +284,7 @@ impl IndependentNonReplacedContents { level = "trace", ) )] + #[allow(clippy::too_many_arguments)] pub fn layout( &self, layout_context: &LayoutContext, @@ -274,6 +293,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; @@ -302,6 +322,7 @@ impl IndependentNonReplacedContents { containing_block_for_children, containing_block, depends_on_block_constraints, + lazy_block_size, ); *base.cached_layout_result.borrow_mut() = Some(Box::new(CacheableLayoutResultAndInputs { @@ -334,6 +355,19 @@ impl IndependentNonReplacedContents { pub(crate) fn is_table(&self) -> bool { matches!(self, Self::Table(_)) } + + fn repair_style(&mut self, context: &SharedStyleContext, new_style: &Arc<ComputedValues>) { + match self { + IndependentNonReplacedContents::Flow(..) => {}, + 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 0cf6ee511cb..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() } } diff --git a/components/layout/fragment_tree/box_fragment.rs b/components/layout/fragment_tree/box_fragment.rs index 65ad1c4aa93..9b96b1c4fb4 100644 --- a/components/layout/fragment_tree/box_fragment.rs +++ b/components/layout/fragment_tree/box_fragment.rs @@ -16,7 +16,8 @@ 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::{ @@ -39,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<ComputedValues>, + pub style: SharedStyle, pub rect: PhysicalRect<Au>, } @@ -59,7 +58,6 @@ pub(crate) enum SpecificLayoutInfo { pub(crate) struct BoxFragment { pub base: BaseFragment, - #[conditional_malloc_size_of] pub style: ServoArc<ComputedValues>, pub children: Vec<Fragment>, @@ -238,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\ diff --git a/components/layout/fragment_tree/fragment.rs b/components/layout/fragment_tree/fragment.rs index 1c5324fa1c4..1ebc7b3c989 100644 --- a/components/layout/fragment_tree/fragment.rs +++ b/components/layout/fragment_tree/fragment.rs @@ -22,6 +22,7 @@ use super::{ Tag, }; use crate::cell::ArcRefCell; +use crate::flow::inline::SharedInlineStyles; use crate::geom::{LogicalSides, PhysicalPoint, PhysicalRect}; use crate::style_ext::ComputedValuesExt; @@ -64,8 +65,7 @@ pub(crate) struct CollapsedMargin { #[derive(MallocSizeOf)] pub(crate) struct TextFragment { pub base: BaseFragment, - #[conditional_malloc_size_of] - pub parent_style: ServoArc<ComputedValues>, + pub inline_styles: SharedInlineStyles, pub rect: PhysicalRect<Au>, pub font_metrics: FontMetrics, pub font_key: FontInstanceKey, @@ -78,14 +78,11 @@ pub(crate) struct TextFragment { /// Extra space to add for each justification opportunity. pub justification_adjustment: Au, pub selection_range: Option<ServoRange<ByteIndex>>, - #[conditional_malloc_size_of] - pub selected_style: ServoArc<ComputedValues>, } #[derive(MallocSizeOf)] pub(crate) struct ImageFragment { pub base: BaseFragment, - #[conditional_malloc_size_of] pub style: ServoArc<ComputedValues>, pub rect: PhysicalRect<Au>, pub clip: PhysicalRect<Au>, @@ -97,7 +94,6 @@ pub(crate) struct IFrameFragment { pub base: BaseFragment, pub pipeline_id: PipelineId, pub rect: PhysicalRect<Au>, - #[conditional_malloc_size_of] pub style: ServoArc<ComputedValues>, } @@ -308,6 +304,25 @@ impl Fragment { _ => None, } } + + pub(crate) fn repair_style(&self, style: &ServoArc<ComputedValues>) { + 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 1499a50dacf..979bd0090fc 100644 --- a/components/layout/fragment_tree/fragment_tree.rs +++ b/components/layout/fragment_tree/fragment_tree.rs @@ -11,10 +11,10 @@ use malloc_size_of_derive::MallocSizeOf; use style::animation::AnimationSetKey; use webrender_api::units; -use super::{ContainingBlockManager, Fragment}; +use super::{BoxFragment, ContainingBlockManager, Fragment}; +use crate::ArcRefCell; use crate::context::LayoutContext; use crate::display_list::StackingContext; -use crate::flow::CanvasBackground; use crate::geom::PhysicalRect; #[derive(MallocSizeOf)] @@ -36,9 +36,6 @@ pub struct FragmentTree { /// The containing block used in the layout of this fragment tree. pub(crate) initial_containing_block: PhysicalRect<Au>, - /// <https://drafts.csswg.org/css-backgrounds/#special-backgrounds> - pub(crate) canvas_background: CanvasBackground, - /// Whether or not the viewport is sensitive to scroll input events. pub viewport_scroll_sensitivity: AxesScrollSensitivity, } @@ -49,14 +46,12 @@ impl FragmentTree { root_fragments: Vec<Fragment>, scrollable_overflow: PhysicalRect<Au>, initial_containing_block: PhysicalRect<Au>, - canvas_background: CanvasBackground, viewport_scroll_sensitivity: AxesScrollSensitivity, ) -> Self { let fragment_tree = Self { root_fragments, scrollable_overflow, initial_containing_block, - canvas_background, viewport_scroll_sensitivity, }; @@ -102,11 +97,7 @@ impl FragmentTree { 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_canvas_background_display_list(builder, self); root_stacking_context.build_display_list(builder); } @@ -160,4 +151,45 @@ impl FragmentTree { scroll_area } } + + /// Find the `<body>` element's [`Fragment`], if it exists in this [`FragmentTree`]. + pub(crate) fn body_fragment(&self) -> Option<ArcRefCell<BoxFragment>> { + fn find_body(children: &[Fragment]) -> Option<ArcRefCell<BoxFragment>> { + 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 `<body>` 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 `<body>` 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 `<body>` 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 0cf525a3479..e45a6137bff 100644 --- a/components/layout/fragment_tree/positioning_fragment.rs +++ b/components/layout/fragment_tree/positioning_fragment.rs @@ -24,9 +24,8 @@ pub(crate) struct PositioningFragment { /// The scrollable overflow of this anonymous fragment's children. pub scrollable_overflow: PhysicalRect<Au>, - /// If this fragment was created with a style, the style of the fragment. - #[conditional_malloc_size_of] - pub style: Option<ServoArc<ComputedValues>>, + /// The style of the fragment. + pub style: ServoArc<ComputedValues>, /// This [`PositioningFragment`]'s containing block rectangle in coordinates relative to /// the initial containing block, but not taking into account any transforms. @@ -34,8 +33,12 @@ pub(crate) struct PositioningFragment { } impl PositioningFragment { - pub fn new_anonymous(rect: PhysicalRect<Au>, children: Vec<Fragment>) -> ArcRefCell<Self> { - Self::new_with_base_fragment(BaseFragment::anonymous(), None, rect, children) + pub fn new_anonymous( + style: ServoArc<ComputedValues>, + rect: PhysicalRect<Au>, + children: Vec<Fragment>, + ) -> ArcRefCell<Self> { + Self::new_with_base_fragment(BaseFragment::anonymous(), style, rect, children) } pub fn new_empty( @@ -43,12 +46,12 @@ impl PositioningFragment { rect: PhysicalRect<Au>, style: ServoArc<ComputedValues>, ) -> ArcRefCell<Self> { - 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<ServoArc<ComputedValues>>, + style: ServoArc<ComputedValues>, rect: PhysicalRect<Au>, children: Vec<Fragment>, ) -> ArcRefCell<Self> { diff --git a/components/layout/geom.rs b/components/layout/geom.rs index 6a09519b7ed..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}; @@ -1102,3 +1102,87 @@ impl Sizes { ) } } + +struct LazySizeData<'a> { + sizes: &'a Sizes, + axis: Direction, + automatic_size: Size<Au>, + get_automatic_minimum_size: fn() -> Au, + stretch_size: Option<Au>, + 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<Au>, + data: Option<LazySizeData<'a>>, +} + +impl<'a> LazySize<'a> { + pub(crate) fn new( + sizes: &'a Sizes, + axis: Direction, + automatic_size: Size<Au>, + get_automatic_minimum_size: fn() -> Au, + stretch_size: Option<Au>, + 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<Au> 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<ComputedValues>, pub cached_inline_content_size: AtomicRefCell<Option<Box<(SizeConstraint, InlineContentSizesResult)>>>, @@ -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<ComputedValues>) { + 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 b8d91c38027..fcf658036b2 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}; @@ -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::{ @@ -84,7 +83,7 @@ use crate::query::{ 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,10 +94,6 @@ 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); -thread_local!(static SEEN_POINTERS: LazyCell<RefCell<HashSet<*const c_void>>> = const { - LazyCell::new(|| RefCell::new(HashSet::new())) -}); - /// A CSS file to style the user agent stylesheet. static USER_AGENT_CSS: &[u8] = include_bytes!("./stylesheets/user-agent.css"); @@ -770,6 +765,12 @@ impl LayoutThread { driver::traverse_dom(&recalc_style_traversal, token, rayon_pool).as_node(); let root_node = root_element.as_node(); + let damage = compute_damage_and_repair_style(layout_context.shared_context(), root_node); + if damage == RestyleDamage::REPAINT { + layout_context.style_context.stylist.rule_tree().maybe_gc(); + return; + } + let mut box_tree = self.box_tree.borrow_mut(); let box_tree = &mut *box_tree; let mut build_box_tree = || { diff --git a/components/layout/lib.rs b/components/layout/lib.rs index af7d432c4d8..cd992387277 100644 --- a/components/layout/lib.rs +++ b/components/layout/lib.rs @@ -38,6 +38,7 @@ 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; @@ -45,6 +46,16 @@ 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<ServoArc<ComputedValues>>; + /// 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 { 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; /// <https://drafts.csswg.org/css-lists/#content-property> -pub(crate) fn make_marker<'dom, Node>( +pub(crate) fn make_marker<'dom>( context: &LayoutContext, - info: &NodeAndStyleInfo<Node>, -) -> Option<(NodeAndStyleInfo<Node>, Vec<PseudoElementContentItem>)> -where - Node: NodeExt<'dom>, -{ + info: &NodeAndStyleInfo<'dom>, +) -> Option<(NodeAndStyleInfo<'dom>, Vec<PseudoElementContentItem>)> { 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 ff361396fa3..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,12 +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::layout_box_base::LayoutBoxBase; -use crate::sizing::ContentSizes; use crate::style_ext::{Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, DisplayInside}; use crate::{ ConstraintSpace, ContainingBlock, ContainingBlockSize, DefiniteContainingBlock, @@ -57,9 +55,9 @@ impl AbsolutelyPositionedBox { Self { context } } - pub fn construct<'dom>( + pub fn construct( context: &LayoutContext, - node_info: &NodeAndStyleInfo<impl NodeExt<'dom>>, + node_info: &NodeAndStyleInfo, display_inside: DisplayInside, contents: Contents, ) -> Self { @@ -474,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, @@ -497,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, @@ -512,52 +510,6 @@ 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::default(); let mut new_fragment = { let content_size: LogicalVec2<Au>; @@ -567,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, @@ -580,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, }; @@ -595,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, @@ -602,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, @@ -630,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; @@ -716,12 +724,6 @@ impl LogicalRect<Au> { } } -struct AxisResult { - size: SizeConstraint, - margin_start: Au, - margin_end: Au, -} - struct AbsoluteAxisSolver<'a> { axis: Direction, containing_size: Au, @@ -764,101 +766,56 @@ impl AbsoluteAxisSolver<'_> { } } - /// This unifies some of the parts in common in: - /// - /// * <https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-width> - /// * <https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-height> - /// - /// … and: - /// - /// * <https://drafts.csswg.org/css2/visudet.html#abs-replaced-width> - /// * <https://drafts.csswg.org/css2/visudet.html#abs-replaced-height> - /// - /// In the replaced case, `size` is never `Auto`. - fn solve(&self, get_content_size: Option<impl FnOnce() -> 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<Au> { + 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::<fn() -> 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<Au> { + 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( diff --git a/components/layout/query.rs b/components/layout/query.rs index e78acdd0ca8..ca9db9ceaf1 100644 --- a/components/layout/query.rs +++ b/components/layout/query.rs @@ -9,6 +9,7 @@ use app_units::Au; use euclid::default::{Point2D, Rect}; 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, @@ -46,7 +47,7 @@ use crate::fragment_tree::{ }; use crate::taffy::SpecificTaffyGridInfo; -pub fn process_content_box_request<'dom>(node: impl LayoutNode<'dom> + 'dom) -> Option<Rect<Au>> { +pub fn process_content_box_request(node: ServoLayoutNode<'_>) -> Option<Rect<Au>> { let rects: Vec<_> = node .fragments_for_pseudo(None) .iter() @@ -61,7 +62,7 @@ pub fn process_content_box_request<'dom>(node: impl LayoutNode<'dom> + 'dom) -> })) } -pub fn process_content_boxes_request<'dom>(node: impl LayoutNode<'dom> + 'dom) -> Vec<Rect<Au>> { +pub fn process_content_boxes_request(node: ServoLayoutNode<'_>) -> Vec<Rect<Au>> { node.fragments_for_pseudo(None) .iter() .filter_map(Fragment::cumulative_border_box_rect) @@ -69,7 +70,7 @@ pub fn process_content_boxes_request<'dom>(node: impl LayoutNode<'dom> + 'dom) - .collect() } -pub fn process_client_rect_request<'dom>(node: impl LayoutNode<'dom> + 'dom) -> Rect<i32> { +pub fn process_client_rect_request(node: ServoLayoutNode<'_>) -> Rect<i32> { node.fragments_for_pseudo(None) .first() .map(Fragment::client_rect) @@ -77,8 +78,8 @@ pub fn process_client_rect_request<'dom>(node: impl LayoutNode<'dom> + 'dom) -> } /// <https://drafts.csswg.org/cssom-view/#scrolling-area> -pub fn process_node_scroll_area_request<'dom>( - requested_node: Option<impl LayoutNode<'dom> + 'dom>, +pub fn process_node_scroll_area_request( + requested_node: Option<ServoLayoutNode<'_>>, fragment_tree: Option<Arc<FragmentTree>>, ) -> Rect<i32> { let Some(tree) = fragment_tree else { @@ -105,9 +106,9 @@ pub fn process_node_scroll_area_request<'dom>( /// Return the resolved value of property for a given (pseudo)element. /// <https://drafts.csswg.org/cssom/#resolved-value> -pub fn process_resolved_style_request<'dom>( +pub fn process_resolved_style_request( context: &SharedStyleContext, - node: impl LayoutNode<'dom> + 'dom, + node: ServoLayoutNode<'_>, pseudo: &Option<PseudoElement>, property: &PropertyId, ) -> String { @@ -361,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<PseudoElement>, property: &PropertyId, ) -> String { @@ -434,9 +435,7 @@ struct OffsetParentFragments { } /// <https://www.w3.org/TR/2016/WD-cssom-view-1-20160317/#dom-htmlelement-offsetparent> -fn offset_parent_fragments<'dom>( - node: impl LayoutNode<'dom> + 'dom, -) -> Option<OffsetParentFragments> { +fn offset_parent_fragments(node: ServoLayoutNode<'_>) -> Option<OffsetParentFragments> { // 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. @@ -498,9 +497,7 @@ fn offset_parent_fragments<'dom>( } #[inline] -pub fn process_offset_parent_query<'dom>( - node: impl LayoutNode<'dom> + 'dom, -) -> Option<OffsetParentResponse> { +pub fn process_offset_parent_query(node: ServoLayoutNode<'_>) -> Option<OffsetParentResponse> { // 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 @@ -580,7 +577,7 @@ pub fn process_offset_parent_query<'dom>( } /// <https://html.spec.whatwg.org/multipage/#get-the-text-steps> -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 @@ -668,8 +665,8 @@ impl Default for RenderedTextCollectionState { } /// <https://html.spec.whatwg.org/multipage/#rendered-text-collection-steps> -fn rendered_text_collection_steps<'dom>( - node: impl LayoutNode<'dom>, +fn rendered_text_collection_steps( + node: ServoLayoutNode<'_>, state: &mut RenderedTextCollectionState, ) -> Vec<InnerOrOuterTextItem> { // 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 bbebc57aa97..0cc95c1a87d 100644 --- a/components/layout/replaced.rs +++ b/components/layout/replaced.rs @@ -13,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; @@ -120,7 +122,7 @@ pub(crate) enum ReplacedContentKind { } impl ReplacedContents { - pub fn for_element<'dom>(element: impl NodeExt<'dom>, context: &LayoutContext) -> Option<Self> { + pub fn for_element(element: ServoLayoutNode<'_>, context: &LayoutContext) -> Option<Self> { 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( @@ -184,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<Self> { @@ -213,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<Self> { diff --git a/components/layout/table/construct.rs b/components/layout/table/construct.rs index 56e11320be4..0c238073df2 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<Node>, Cow<'dom, str>), +pub(crate) enum AnonymousTableContent<'dom> { + Text(NodeAndStyleInfo<'dom>, Cow<'dom, str>), Element { - info: NodeAndStyleInfo<Node>, + info: NodeAndStyleInfo<'dom>, display: DisplayGeneratingBox, contents: Contents, box_slot: BoxSlot<'dom>, }, } -impl<Node> AnonymousTableContent<'_, Node> { +impl AnonymousTableContent<'_> { fn is_whitespace_only(&self) -> bool { match self { Self::Element { .. } => false, @@ -74,9 +74,9 @@ impl<Node> AnonymousTableContent<'_, Node> { } impl Table { - pub(crate) fn construct<'dom>( + pub(crate) fn construct( context: &LayoutContext, - info: &NodeAndStyleInfo<impl NodeExt<'dom>>, + info: &NodeAndStyleInfo, grid_style: Arc<ComputedValues>, contents: NonReplacedContents, propagated_data: PropagatedBoxTreeData, @@ -91,15 +91,12 @@ impl Table { traversal.finish() } - pub(crate) fn construct_anonymous<'dom, Node>( + pub(crate) fn construct_anonymous<'dom>( context: &LayoutContext, - parent_info: &NodeAndStyleInfo<Node>, - contents: Vec<AnonymousTableContent<'dom, Node>>, + parent_info: &NodeAndStyleInfo<'dom>, + contents: Vec<AnonymousTableContent<'dom>>, propagated_data: PropagatedBoxTreeData, - ) -> (NodeAndStyleInfo<Node>, 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 +642,9 @@ impl TableBuilder { } } -pub(crate) struct TableBuilderTraversal<'style, 'dom, Node> { +pub(crate) struct TableBuilderTraversal<'style, 'dom> { context: &'style LayoutContext<'style>, - info: &'style NodeAndStyleInfo<Node>, + 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 +654,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<AnonymousTableContent<'dom, Node>>, + current_anonymous_row_content: Vec<AnonymousTableContent<'dom>>, /// The index of the current row group, if there is one. current_row_group_index: Option<usize>, } -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<Node>, + info: &'style NodeAndStyleInfo<'dom>, grid_style: Arc<ComputedValues>, propagated_data: PropagatedBoxTreeData, ) -> Self { @@ -728,9 +722,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 +740,8 @@ where } } -impl<'dom, Node: 'dom> TraversalHandler<'dom, Node> for TableBuilderTraversal<'_, 'dom, Node> -where - Node: NodeExt<'dom>, -{ - fn handle_text(&mut self, info: &NodeAndStyleInfo<Node>, 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 +749,7 @@ where /// <https://html.spec.whatwg.org/multipage/#forming-a-table> fn handle_element( &mut self, - info: &NodeAndStyleInfo<Node>, + info: &NodeAndStyleInfo<'dom>, display: DisplayGeneratingBox, contents: Contents, box_slot: BoxSlot<'dom>, @@ -775,6 +767,7 @@ 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()); @@ -817,6 +810,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 +856,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 +911,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<Node>, + info: &'a NodeAndStyleInfo<'dom>, - current_anonymous_cell_content: Vec<AnonymousTableContent<'dom, Node>>, + current_anonymous_cell_content: Vec<AnonymousTableContent<'dom>>, /// 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<Node>, + table_traversal: &'builder mut TableBuilderTraversal<'style, 'dom>, + info: &'a NodeAndStyleInfo<'dom>, propagated_data: PropagatedBoxTreeData, ) -> Self { table_traversal.builder.start_row(); @@ -996,11 +988,8 @@ where } } -impl<'dom, Node: 'dom> TraversalHandler<'dom, Node> for TableRowBuilder<'_, '_, 'dom, '_, Node> -where - Node: NodeExt<'dom>, -{ - fn handle_text(&mut self, info: &NodeAndStyleInfo<Node>, 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 +997,7 @@ where /// <https://html.spec.whatwg.org/multipage/#algorithm-for-processing-rows> fn handle_element( &mut self, - info: &NodeAndStyleInfo<Node>, + info: &NodeAndStyleInfo<'dom>, display: DisplayGeneratingBox, contents: Contents, box_slot: BoxSlot<'dom>, @@ -1091,14 +1080,11 @@ struct TableColumnGroupBuilder { columns: Vec<ArcRefCell<TableTrack>>, } -impl<'dom, Node: 'dom> TraversalHandler<'dom, Node> for TableColumnGroupBuilder -where - Node: NodeExt<'dom>, -{ - fn handle_text(&mut self, _info: &NodeAndStyleInfo<Node>, _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<Node>, + info: &NodeAndStyleInfo<'dom>, display: DisplayGeneratingBox, _contents: Contents, box_slot: BoxSlot<'dom>, @@ -1134,9 +1120,9 @@ impl From<DisplayLayoutInternal> for TableTrackGroupType { } } -fn add_column<'dom, Node: NodeExt<'dom>>( +fn add_column( collection: &mut Vec<ArcRefCell<TableTrack>>, - column_info: &NodeAndStyleInfo<Node>, + column_info: &NodeAndStyleInfo, group_index: Option<usize>, is_anonymous: bool, ) -> ArcRefCell<TableTrack> { @@ -1153,6 +1139,7 @@ fn add_column<'dom, Node: NodeExt<'dom>>( 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 as usize)); column diff --git a/components/layout/table/layout.rs b/components/layout/table/layout.rs index 2efe339837e..5b7e79d7fb0 100644 --- a/components/layout/table/layout.rs +++ b/components/layout/table/layout.rs @@ -2063,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, }) } @@ -2072,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, }) } @@ -2085,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, }) } @@ -2093,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, }) } @@ -2867,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 fe7f90437b8..72b67863e7d 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; 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<ComputedValues>, /// 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<ComputedValues>, /// 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<ComputedValues>, + ) { + self.style = new_style.clone(); + self.grid_style = context.stylist.style_for_anonymous::<ServoLayoutElement>( + &context.guards, + &PseudoElement::ServoTableGrid, + new_style, + ); + } } type TableSlotCoordinates = Point2D<usize, UnknownUnit>; @@ -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<ComputedValues>) { + 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 `<colgroup>`. 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<ComputedValues>) { + 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<usize>, + + /// 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<ComputedValues>) { + self.base.repair_style(new_style); + self.shared_background_style = SharedStyle::new(new_style.clone()); + } } #[derive(Debug, MallocSizeOf)] @@ -380,4 +421,22 @@ impl TableLevelBox { TableLevelBox::Track(track) => track.borrow().base.fragments(), } } + + pub(crate) fn repair_style( + &self, + context: &SharedStyleContext<'_>, + new_style: &Arc<ComputedValues>, + ) { + match self { + TableLevelBox::Caption(caption) => caption + .borrow_mut() + .context + .repair_style(context, 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 a5838c1bd65..61c4a0508e9 100644 --- a/components/layout/taffy/layout.rs +++ b/components/layout/taffy/layout.rs @@ -23,8 +23,8 @@ 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}; @@ -251,6 +251,12 @@ impl taffy::LayoutPartialTree for TaffyContainerContext<'_> { style, }; + 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, @@ -258,13 +264,16 @@ impl taffy::LayoutPartialTree for TaffyContainerContext<'_> { &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, diff --git a/components/layout/taffy/mod.rs b/components/layout/taffy/mod.rs index b1ff753ea78..ba80824fa99 100644 --- a/components/layout/taffy/mod.rs +++ b/components/layout/taffy/mod.rs @@ -8,6 +8,7 @@ use std::fmt; use app_units::Au; use malloc_size_of_derive::MallocSizeOf; use servo_arc::Arc; +use style::context::SharedStyleContext; use style::properties::ComputedValues; use stylo_taffy::TaffyStyloStyle; @@ -15,7 +16,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,14 +25,13 @@ use crate::positioned::{AbsolutelyPositionedBox, PositioningContext}; #[derive(Debug, MallocSizeOf)] pub(crate) struct TaffyContainer { children: Vec<ArcRefCell<TaffyItemBox>>, - #[conditional_malloc_size_of] style: Arc<ComputedValues>, } impl TaffyContainer { - pub fn construct<'dom>( + pub fn construct( context: &LayoutContext, - info: &NodeAndStyleInfo<impl NodeExt<'dom>>, + info: &NodeAndStyleInfo, contents: NonReplacedContents, propagated_data: PropagatedBoxTreeData, ) -> Self { @@ -69,6 +69,10 @@ impl TaffyContainer { style: info.style.clone(), } } + + pub(crate) fn repair_style(&mut self, new_style: &Arc<ComputedValues>) { + 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<Fragment>, pub(crate) positioning_context: PositioningContext, - #[conditional_malloc_size_of] pub(crate) style: Arc<ComputedValues>, pub(crate) taffy_level_box: TaffyItemBoxInner, } @@ -145,6 +148,23 @@ impl TaffyItemBox { }, } } + + pub(crate) fn repair_style( + &mut self, + context: &SharedStyleContext, + new_style: &Arc<ComputedValues>, + ) { + self.style = new_style.clone(); + match &mut self.taffy_level_box { + TaffyItemBoxInner::InFlowBox(independent_formatting_context) => { + independent_formatting_context.repair_style(context, new_style) + }, + TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(positioned_box) => positioned_box + .borrow_mut() + .context + .repair_style(context, new_style), + } + } } /// Details from Taffy grid layout that will be stored diff --git a/components/layout/traversal.rs b/components/layout/traversal.rs index bf60c41d6ba..17c3d0b1c20 100644 --- a/components/layout/traversal.rs +++ b/components/layout/traversal.rs @@ -2,14 +2,18 @@ * 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: &'a LayoutContext<'a>, @@ -40,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::<DOMLayoutData>(); - 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(); } } @@ -68,3 +91,48 @@ 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(); + + if let Some(ref style) = element_data.styles.primary { + if style.get_box().display == Display::None { + return parent_restyle_damage; + } + } + + original_damage = std::mem::take(&mut element_data.damage); + element_data.damage |= parent_restyle_damage; + element_data.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 52523af7cb1..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<T: MallocSizeOf> MallocConditionalSizeOf for Arc<T> { } } +impl<T> MallocUnconditionalShallowSizeOf for Rc<T> { + fn unconditional_shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + unsafe { ops.malloc_size_of(Rc::as_ptr(self)) } + } +} + +impl<T: MallocSizeOf> MallocUnconditionalSizeOf for Rc<T> { + fn unconditional_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.unconditional_shallow_size_of(ops) + (**self).size_of(ops) + } +} + +impl<T: MallocSizeOf> MallocConditionalSizeOf for Rc<T> { + 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<T> MallocSizeOf for ipc_channel::ipc::IpcSender<T> { } } +impl<T> MallocSizeOf for ipc_channel::ipc::IpcReceiver<T> { + 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<T: MallocSizeOf> MallocSizeOf for accountable_refcell::RefCell<T> { } } +impl MallocSizeOf for servo_arc::Arc<ComputedValues> { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.conditional_size_of(ops) + } +} + malloc_size_of_hash_map!(indexmap::IndexMap<K, V, S>); malloc_size_of_hash_set!(indexmap::IndexSet<T, S>); @@ -749,10 +785,14 @@ 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),+) => ( @@ -772,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); @@ -816,6 +857,14 @@ where } } +impl<T> MallocSizeOf for style::shared_lock::Locked<T> { + fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { + // TODO: fix this implementation when Locked derives MallocSizeOf. + 0 + //<style::shared_lock::Locked<T> as stylo_malloc_size_of::MallocSizeOf>::size_of(self, ops) + } +} + impl<T: MallocSizeOf> MallocSizeOf for atomic_refcell::AtomicRefCell<T> { fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { self.borrow().size_of(ops) diff --git a/components/net/hsts.rs b/components/net/hsts.rs index d74794ce60a..be955980d2b 100644 --- a/components/net/hsts.rs +++ b/components/net/hsts.rs @@ -4,26 +4,37 @@ 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 headers::{HeaderMapExt, StrictTransportSecurity}; use http::HeaderMap; -use log::{error, info}; +use log::{debug, error, info}; 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<Duration>, - pub timestamp: Option<CrossProcessInstant>, + // Nonzero to allow for memory optimization + pub expires_at: Option<NonZeroU64>, +} + +// 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,43 +43,59 @@ impl HstsEntry { subdomains: IncludeSubdomains, max_age: Option<Duration>, ) -> Option<HstsEntry> { + let expires_at = max_age.map(|duration| { + unix_timestamp_to_nonzerou64((UtcDateTime::now() + duration).unix_timestamp()) + }); if host.parse::<Ipv4Addr>().is_ok() || host.parse::<Ipv6Addr>().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<String, Vec<HstsEntry>>, } -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. +#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, Serialize)] +pub struct HstsPreloadList { + pub entries_map: HashMap<String, Vec<HstsEntry>>, +} + +pub static PRELOAD_LIST_ENTRIES: LazyLock<HstsPreloadList> = + LazyLock::new(HstsPreloadList::from_servo_preload); + +impl HstsPreloadList { /// Create an `HstsList` from the bytes of a JSON preload file. - pub fn from_preload(preload_content: &str) -> Option<HstsList> { + pub fn from_preload(preload_content: &str) -> Option<HstsPreloadList> { #[derive(Deserialize)] struct HstsEntries { entries: Vec<HstsEntry>, @@ -77,7 +104,7 @@ impl HstsList { let hsts_entries: Option<HstsEntries> = serde_json::from_str(preload_content).ok(); hsts_entries.map(|hsts_entries| { - let mut hsts_list: HstsList = HstsList::default(); + let mut hsts_list: HstsPreloadList = HstsPreloadList::default(); for hsts_entry in hsts_entries.entries { hsts_list.push(hsts_entry); @@ -87,17 +114,21 @@ impl HstsList { }) } - pub fn from_servo_preload() -> HstsList { + pub fn from_servo_preload() -> HstsPreloadList { + debug!("Intializing HSTS Preload list"); let list = resources::read_string(Resource::HstsPreloadList); - HstsList::from_preload(&list).unwrap_or_else(|| { + HstsPreloadList::from_preload(&list).unwrap_or_else(|| { error!("HSTS preload file is invalid. Setting HSTS list to default values"); - HstsList::default() + HstsPreloadList { + entries_map: Default::default(), + } }) } pub fn is_host_secure(&self, host: &str) -> bool { let base_domain = reg_suffix(host); self.entries_map.get(base_domain).is_some_and(|entries| { + // No need to check for expiration in the preload list entries.iter().any(|e| { if e.include_subdomains { e.matches_subdomain(host) || e.matches_domain(host) @@ -108,16 +139,74 @@ impl HstsList { }) } - fn has_domain(&self, host: &str, base_domain: &str) -> bool { + pub fn has_domain(&self, host: &str, base_domain: &str) -> bool { self.entries_map .get(base_domain) .is_some_and(|entries| entries.iter().any(|e| e.matches_domain(host))) } - fn has_subdomain(&self, host: &str, base_domain: &str) -> bool { + pub 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.include_subdomains && e.matches_subdomain(host)) + }) + } + + pub fn push(&mut self, entry: HstsEntry) { + let host = entry.host.clone(); + let base_domain = reg_suffix(&host); + let have_domain = self.has_domain(&entry.host, base_domain); + let have_subdomain = self.has_subdomain(&entry.host, base_domain); + + let entries = self.entries_map.entry(base_domain.to_owned()).or_default(); + if !have_domain && !have_subdomain { + entries.push(entry); + } else if !have_subdomain { + for e in entries { + if e.matches_domain(&entry.host) { + e.include_subdomains = entry.include_subdomains; + // TODO(sebsebmc): We could shrink the the HSTS preload memory use further by using a type + // that doesn't store an expiry since all preload entries should be "forever" + e.expires_at = entry.expires_at; + } + } + } + } +} + +impl HstsList { + pub fn is_host_secure(&self, host: &str) -> bool { + debug!("HSTS: is {host} secure?"); + 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().filter(|e| !e.is_expired()).any(|e| { + if e.include_subdomains { + e.matches_subdomain(host) || e.matches_domain(host) + } else { + e.matches_domain(host) + } + }) + }) + } + + fn has_domain(&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))) + .is_some_and(|entries| entries.iter().any(|e| e.matches_domain(host))) + } + + 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.include_subdomains && e.matches_subdomain(host)) + }) } pub fn push(&mut self, entry: HstsEntry) { @@ -130,13 +219,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 <https://fetch.spec.whatwg.org/#concept-main-fetch>. diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index e0867b8d07f..704901f6940 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<u8>), + Chunk(IpcSharedMemory), /// Body is done. Done, } @@ -489,12 +489,14 @@ enum BodySink { } impl BodySink { - fn transmit_bytes(&self, bytes: Vec<u8>) { + fn transmit_bytes(&self, bytes: IpcSharedMemory) { match self { BodySink::Chunked(sender) => { let sender = sender.clone(); HANDLE.spawn(async move { - let _ = sender.send(Ok(Frame::data(bytes.into()))).await; + 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<u8> = 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."), diff --git a/components/net/image_cache.rs b/components/net/image_cache.rs index e3d31b11736..46a2a4ea111 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}; @@ -675,6 +675,20 @@ impl ImageCache for ImageCacheImpl { } } +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 { /// Require self.store.lock() before calling. fn add_listener_with_store(&self, store: &mut ImageCacheStore, listener: ImageResponder) { diff --git a/components/net/resource_thread.rs b/components/net/resource_thread.rs index 5d1ede28c32..d361d63f44a 100644 --- a/components/net/resource_thread.rs +++ b/components/net/resource_thread.rs @@ -21,6 +21,7 @@ use embedder_traits::EmbedderProxy; use hyper_serde::Serde; use ipc_channel::ipc::{self, IpcReceiver, IpcReceiverSet, IpcSender}; use log::{debug, trace, warn}; +use malloc_size_of::MallocSizeOf; use net_traits::blob_url_store::parse_blob_url; use net_traits::filemanager_thread::FileTokenCheck; use net_traits::request::{Destination, RequestBuilder, RequestId}; @@ -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; @@ -176,7 +179,7 @@ fn create_http_states( ignore_certificate_errors: bool, embedder_proxy: EmbedderProxy, ) -> (Arc<HttpState>, Arc<HttpState>) { - 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 +208,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 +287,11 @@ 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.push(Report { + path: path!["hsts-preload-list"], + kind: ReportKind::ExplicitJemallocHeapSize, + size: hsts::PRELOAD_LIST_ENTRIES.size_of(ops), + }); msg.send(ProcessReports::new(reports)); }) } diff --git a/components/net/tests/hsts.rs b/components/net/tests/hsts.rs index 863cbc56fe1..e1e754beb3c 100644 --- a/components/net/tests/hsts.rs +++ b/components/net/tests/hsts.rs @@ -3,32 +3,18 @@ * 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 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(), 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()); @@ -39,8 +25,7 @@ fn test_hsts_entry_is_expired_when_it_has_reached_its_max_age() { let entry = HstsEntry { host: "mozilla.org".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()); @@ -102,7 +87,7 @@ fn test_base_domain_in_entries_map() { } #[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(), @@ -131,6 +116,36 @@ fn test_push_entry_with_0_max_age_evicts_entry_from_list() { assert_eq!(list.is_host_secure("mozilla.org"), false) } +fn test_push_entry_with_0_max_age_evicts_entry_from_list() { + let mut entries_map = HashMap::new(); + entries_map.insert( + "mozilla.org".to_owned(), + vec![ + HstsEntry::new( + "mozilla.org".to_owned(), + IncludeSubdomains::NotIncluded, + Some(StdDuration::from_secs(500000)), + ) + .unwrap(), + ], + ); + let mut list = HstsList { + entries_map: entries_map, + }; + + assert_eq!(list.entries_map.get("mozilla.org").unwrap().len(), 1); + + list.push( + HstsEntry::new( + "mozilla.org".to_owned(), + IncludeSubdomains::NotIncluded, + Some(StdDuration::ZERO), + ) + .unwrap(), + ); + assert_eq!(list.entries_map.get("mozilla.org").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(); @@ -155,6 +170,36 @@ fn test_push_entry_to_hsts_list_should_not_add_subdomains_whose_superdomain_is_a } #[test] +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::NotIncluded, + None, + ) + .unwrap(), + ], + ); + let mut list = HstsList { + entries_map: entries_map, + }; + + list.push( + HstsEntry::new( + "servo.mozilla.org".to_owned(), + IncludeSubdomains::NotIncluded, + None, + ) + .unwrap(), + ); + + assert_eq!(list.entries_map.get("mozilla.org").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( @@ -244,7 +289,7 @@ fn test_push_entry_to_hsts_list_should_add_an_entry() { fn test_parse_hsts_preload_should_return_none_when_json_invalid() { let mock_preload_content = "derp"; assert!( - HstsList::from_preload(mock_preload_content).is_none(), + HstsPreloadList::from_preload(mock_preload_content).is_none(), "invalid preload list should not have parsed" ) } @@ -253,7 +298,7 @@ fn test_parse_hsts_preload_should_return_none_when_json_invalid() { 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" ) } @@ -266,7 +311,7 @@ fn test_parse_hsts_preload_should_decode_host_and_includes_subdomains() { \"include_subdomains\": false}\ ]\ }"; - let hsts_list = HstsList::from_preload(mock_preload_content); + let hsts_list = HstsPreloadList::from_preload(mock_preload_content); let entries_map = hsts_list.unwrap().entries_map; assert_eq!( @@ -378,8 +423,7 @@ fn test_hsts_list_with_expired_entry_is_not_is_host_secure() { vec![HstsEntry { host: "mozilla.org".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 { @@ -391,6 +435,6 @@ fn test_hsts_list_with_expired_entry_is_not_is_host_secure() { #[test] fn test_preload_hsts_domains_well_formed() { - let hsts_list = HstsList::from_servo_preload(); + let hsts_list = HstsPreloadList::from_servo_preload(); assert!(!hsts_list.entries_map.is_empty()); } diff --git a/components/net/tests/http_loader.rs b/components/net/tests/http_loader.rs index 1fc2d1b662d..b1e90276472 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<u8>) -> 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/pixels/lib.rs b/components/pixels/lib.rs index 24386ff830a..c1f57875c6d 100644 --- a/components/pixels/lib.rs +++ b/components/pixels/lib.rs @@ -294,9 +294,10 @@ pub fn generic_transform_inplace< match MULTIPLY { 1 => { let a = rgba[3]; - multiply_u8_color(rgba[0], a); - multiply_u8_color(rgba[1], a); - multiply_u8_color(rgba[2], a); + + 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; diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index 1aa821cdbd3..ea1ec52f911 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -132,6 +132,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/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<IpcSender<BodyChunkResponse>>, control_sender: IpcSender<BodyChunkRequest>, - in_memory: Option<Vec<u8>>, + in_memory: Option<IpcSharedMemory>, in_memory_done: bool, source: BodySource, } @@ -83,7 +83,7 @@ impl TransmitBodyConnectHandler { stream: Trusted<ReadableStream>, task_source: SendableTaskSource, control_sender: IpcSender<BodyChunkRequest>, - in_memory: Option<Vec<u8>>, + in_memory: Option<IpcSharedMemory>, 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 7dfdf48e3f5..ec388e039f1 100644 --- a/components/script/canvas_context.rs +++ b/components/script/canvas_context.rs @@ -125,7 +125,11 @@ impl CanvasContext for RenderingContext { fn resize(&self) { match self { - RenderingContext::Placeholder(context) => (*context.context().unwrap()).resize(), + 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(), 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 { + // <https://svgwg.org/svg2-draft/linking.html#XLinkHrefAttribute> + 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<Heap<*mut JSObject>>), } -pub(crate) fn new_initialized_heap_buffer_source<T>( - init: HeapTypedArrayInit, +pub(crate) fn create_heap_buffer_source_with_length<T>( + cx: JSContext, + len: u32, can_gc: CanGc, -) -> Result<HeapBufferSource<T>, ()> +) -> Fallible<HeapBufferSource<T>> 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::<JSObject>()); - let typed_array_result = - create_buffer_source_with_length::<T>(cx, len as usize, array.handle_mut(), can_gc); - if typed_array_result.is_err() { - return Err(()); - } - - HeapBufferSource::<T>::new(BufferSource::ArrayBufferView(Heap::boxed(*array.handle()))) - }, - }; - Ok(heap_buffer_source) -} + rooted!(in (*cx) let mut array = ptr::null_mut::<JSObject>()); + let typed_array_result = + create_buffer_source_with_length::<T>(cx, len as usize, array.handle_mut(), can_gc); + if typed_array_result.is_err() { + return Err(Error::JSFailed); + } -pub(crate) enum HeapTypedArrayInit { - Buffer(BufferSource), - Info { len: u32, cx: JSContext }, + Ok(HeapBufferSource::<T>::new(BufferSource::ArrayBufferView( + Heap::boxed(*array.handle()), + ))) } pub(crate) struct HeapBufferSource<T> { @@ -131,11 +120,11 @@ where } pub(crate) fn from_view( - chunk: CustomAutoRooterGuard<ArrayBufferView>, - ) -> HeapBufferSource<ArrayBufferViewU8> { - HeapBufferSource::<ArrayBufferViewU8>::new(BufferSource::ArrayBufferView(Heap::boxed( - unsafe { *chunk.underlying_object() }, - ))) + chunk: CustomAutoRooterGuard<TypedArray<T, *mut JSObject>>, + ) -> HeapBufferSource<T> { + HeapBufferSource::<T>::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..7e7e752bc0c 100644 --- a/components/script/dom/bindings/cell.rs +++ b/components/script/dom/bindings/cell.rs @@ -10,6 +10,7 @@ pub(crate) use std::cell::{Ref, RefCell, RefMut}; #[cfg(feature = "refcell_backtrace")] pub(crate) use accountable_refcell::{Ref, RefCell, RefMut, ref_filter_map}; +use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOfOps}; #[cfg(not(feature = "refcell_backtrace"))] pub(crate) use ref_filter_map::ref_filter_map; @@ -24,6 +25,12 @@ pub(crate) struct DomRefCell<T> { value: RefCell<T>, } +impl<T: MallocConditionalSizeOf> MallocConditionalSizeOf for DomRefCell<T> { + 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/structuredclone.rs b/components/script/dom/bindings/structuredclone.rs index c9a49ba00c9..70638238123 100644 --- a/components/script/dom/bindings/structuredclone.rs +++ b/components/script/dom/bindings/structuredclone.rs @@ -44,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}; @@ -65,6 +65,7 @@ pub(super) enum StructuredCloneTags { ReadableStream = 0xFFFF8006, DomException = 0xFFFF8007, WritableStream = 0xFFFF8008, + TransformStream = 0xFFFF8009, Max = 0xFFFFFFFF, } @@ -85,6 +86,7 @@ impl From<TransferrableInterface> for StructuredCloneTags { TransferrableInterface::MessagePort => StructuredCloneTags::MessagePort, TransferrableInterface::ReadableStream => StructuredCloneTags::ReadableStream, TransferrableInterface::WritableStream => StructuredCloneTags::WritableStream, + TransferrableInterface::TransformStream => StructuredCloneTags::TransformStream, } } } @@ -265,6 +267,7 @@ fn receiver_for_type( TransferrableInterface::MessagePort => receive_object::<MessagePort>, TransferrableInterface::ReadableStream => receive_object::<ReadableStream>, TransferrableInterface::WritableStream => receive_object::<WritableStream>, + TransferrableInterface::TransformStream => receive_object::<TransformStream>, } } @@ -390,6 +393,7 @@ fn transfer_for_type(val: TransferrableInterface) -> TransferOperation { TransferrableInterface::MessagePort => try_transfer::<MessagePort>, TransferrableInterface::ReadableStream => try_transfer::<ReadableStream>, TransferrableInterface::WritableStream => try_transfer::<WritableStream>, + TransferrableInterface::TransformStream => try_transfer::<TransformStream>, } } @@ -438,6 +442,7 @@ unsafe fn can_transfer_for_type( TransferrableInterface::MessagePort => can_transfer::<MessagePort>(obj, cx), TransferrableInterface::ReadableStream => can_transfer::<ReadableStream>(obj, cx), TransferrableInterface::WritableStream => can_transfer::<WritableStream>(obj, cx), + TransferrableInterface::TransformStream => can_transfer::<TransformStream>(obj, cx), } } 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 +/// <https://w3c.github.io/clipboard-apis/#dom-clipboard-readtext>. +#[derive(Clone, JSTraceable, MallocSizeOf)] +struct RepresentationDataPromiseFulfillmentHandler { + #[ignore_malloc_size_of = "Rc are hard"] + promise: Rc<Promise>, +} + +impl Callback for RepresentationDataPromiseFulfillmentHandler { + /// The fulfillment case of Step 3.4.1.1.4.3 of + /// <https://w3c.github.io/clipboard-apis/#dom-clipboard-readtext>. + 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 +/// <https://w3c.github.io/clipboard-apis/#dom-clipboard-readtext>. +#[derive(Clone, JSTraceable, MallocSizeOf)] +struct RepresentationDataPromiseRejectionHandler { + #[ignore_malloc_size_of = "Rc are hard"] + promise: Rc<Promise>, +} + +impl Callback for RepresentationDataPromiseRejectionHandler { + /// The rejection case of Step 3.4.1.1.4.3 of + /// <https://w3c.github.io/clipboard-apis/#dom-clipboard-readtext>. + 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<crate::DomTypeHolder> for Clipboard { + /// <https://w3c.github.io/clipboard-apis/#dom-clipboard-readtext> + fn ReadText(&self, can_gc: CanGc) -> Rc<Promise> { + // 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 + } + /// <https://w3c.github.io/clipboard-apis/#dom-clipboard-writetext> fn WriteText(&self, data: DOMString, can_gc: CanGc) -> Rc<Promise> { // Step 1 Let realm be this's relevant realm. @@ -95,6 +174,71 @@ impl ClipboardMethods<crate::DomTypeHolder> for Clipboard { } } +impl RoutedPromiseListener<Result<String, String>> for Clipboard { + fn handle_response( + &self, + response: Result<String, String>, + promise: &Rc<Promise>, + 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 + } +} + /// <https://w3c.github.io/clipboard-apis/#write-blobs-and-option-to-the-clipboard> 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 "; /// <https://w3c.github.io/clipboard-apis/#representation> #[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<Promise>, + pub data: Rc<Promise>, } #[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/document.rs b/components/script/dom/document.rs index ae48fa1fb2f..78cb2c33075 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -4307,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 } @@ -6569,9 +6567,6 @@ impl DocumentMethods<crate::DomTypeHolder> 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); diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index b2168846fad..ed58548a3e5 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; @@ -62,8 +63,9 @@ 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::attr::{Attr, AttrHelpersForLayout, is_relevant_attribute}; use crate::dom::bindings::cell::{DomRefCell, Ref, RefMut, ref_filter_map}; use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; @@ -79,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}; @@ -124,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; @@ -141,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; @@ -150,6 +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::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 { /// <https://dom.spec.whatwg.org/#concept-element-is-value> #[no_trace] is: DomRefCell<Option<LocalName>>, - #[ignore_malloc_size_of = "Arc"] + #[conditional_malloc_size_of] #[no_trace] style_attribute: DomRefCell<Option<Arc<Locked<PropertyDeclarationBlock>>>>, attr_list: MutNullableDom<NamedNodeMap>, @@ -353,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(); } } @@ -1699,7 +1705,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); } } @@ -1841,7 +1847,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()) @@ -1896,7 +1902,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, @@ -1990,6 +1996,15 @@ impl Element { .unwrap_or_else(|_| TrustedScriptURLOrUSVString::USVString(USVString(value.to_owned()))) } + pub(crate) fn get_trusted_html_attribute(&self, local_name: &LocalName) -> TrustedHTMLOrString { + assert_eq!(*local_name, local_name.to_ascii_lowercase()); + let value = match self.get_attribute(&ns!(), local_name) { + Some(attr) => (&**attr.value()).into(), + None => "".into(), + }; + TrustedHTMLOrString::String(value) + } + pub(crate) fn get_string_attribute(&self, local_name: &LocalName) -> DOMString { match self.get_attribute(&ns!(), local_name) { Some(x) => x.Value(), @@ -2120,6 +2135,130 @@ impl Element { node.owner_doc().element_attr_will_change(self, attr); } + /// <https://html.spec.whatwg.org/multipage/#the-style-attribute> + fn update_style_attribute(&self, attr: &Attr, mutation: AttributeMutation) { + let doc = self.upcast::<Node>().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, + }; + } + + /// <https://html.spec.whatwg.org/multipage/#nonce-attributes> + pub(crate) fn update_nonce_internal_slot(&self, nonce: String) { + self.ensure_rare_data().cryptographic_nonce = nonce; + } + + /// <https://html.spec.whatwg.org/multipage/#nonce-attributes> + pub(crate) fn nonce_value(&self) -> String { + match self.rare_data().as_ref() { + None => String::new(), + Some(rare_data) => rare_data.cryptographic_nonce.clone(), + } + } + + /// <https://html.spec.whatwg.org/multipage/#nonce-attributes> + 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::<Node>().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); + } + + /// <https://www.w3.org/TR/CSP/#is-element-nonceable> + pub(crate) fn nonce_value_if_nonceable(&self) -> Option<String> { + // 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::<HTMLScriptElement>().is_some() { + for attr in self.attrs().iter() { + // Step 2.1: If attribute’s name contains an ASCII case-insensitive match + // for "<script" or "<style", return "Not Nonceable". + let attr_name = attr.name().to_ascii_lowercase(); + if attr_name.contains("<script") || attr_name.contains("<style") { + return None; + } + // Step 2.2: If attribute’s value contains an ASCII case-insensitive match + // for "<script" or "<style", return "Not Nonceable". + let attr_value = attr.value().to_ascii_lowercase(); + if attr_value.contains("<script") || attr_value.contains("<style") { + return None; + } + } + } + // Step 3: If element had a duplicate-attribute parse error during tokenization, return "Not Nonceable". + // TODO(https://github.com/servo/servo/issues/4577 and https://github.com/whatwg/html/issues/3257): + // Figure out how to retrieve this information from the parser + // Step 4: Return "Nonceable". + Some(self.nonce_value().trim().to_owned()) + } + // https://dom.spec.whatwg.org/#insert-adjacent pub(crate) fn insert_adjacent( &self, @@ -2239,18 +2378,25 @@ impl Element { Ok(fragment) } + /// Step 4 of <https://html.spec.whatwg.org/multipage/#dom-element-insertadjacenthtml> pub(crate) fn fragment_parsing_context( owner_doc: &Document, element: Option<&Self>, can_gc: CanGc, ) -> DomRoot<Self> { + // 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, @@ -2363,6 +2509,13 @@ impl Element { Dom::from_ref(&*ElementInternals::new(elem, can_gc)) })) } + + pub(crate) fn outer_html(&self, can_gc: CanGc) -> Fallible<DOMString> { + match self.GetOuterHTML(can_gc)? { + TrustedHTMLOrNullIsEmptyString::NullIsEmptyString(str) => Ok(str), + TrustedHTMLOrNullIsEmptyString::TrustedHTML(_) => unreachable!(), + } + } } impl ElementMethods<crate::DomTypeHolder> for Element { @@ -2621,7 +2774,7 @@ impl ElementMethods<crate::DomTypeHolder> 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())), @@ -3017,7 +3170,17 @@ impl ElementMethods<crate::DomTypeHolder> for Element { } /// <https://html.spec.whatwg.org/multipage/#dom-element-sethtmlunsafe> - 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::<HTMLTemplateElement>() { DomRoot::upcast(template.Content(can_gc)) @@ -3027,6 +3190,7 @@ impl ElementMethods<crate::DomTypeHolder> for Element { // Step 3. Unsafely set HTML given target, this, and compliantHTML Node::unsafely_set_html(&target, self, html, can_gc); + Ok(()) } /// <https://html.spec.whatwg.org/multipage/#dom-element-gethtml> @@ -3042,7 +3206,7 @@ impl ElementMethods<crate::DomTypeHolder> for Element { } /// <https://html.spec.whatwg.org/multipage/#dom-element-innerhtml> - fn GetInnerHTML(&self, can_gc: CanGc) -> Fallible<DOMString> { + fn GetInnerHTML(&self, can_gc: CanGc) -> Fallible<TrustedHTMLOrNullIsEmptyString> { let qname = QualName::new( self.prefix().clone(), self.namespace().clone(), @@ -3059,16 +3223,28 @@ impl ElementMethods<crate::DomTypeHolder> for Element { .xml_serialize(XmlChildrenOnly(Some(qname))) }; - Ok(result) + Ok(TrustedHTMLOrNullIsEmptyString::NullIsEmptyString(result)) } /// <https://html.spec.whatwg.org/multipage/#dom-element-innerhtml> - 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::<HTMLTemplateElement>() { + // 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()) }; @@ -3085,15 +3261,17 @@ impl ElementMethods<crate::DomTypeHolder> 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(()) } /// <https://html.spec.whatwg.org/multipage/#dom-element-outerhtml> - fn GetOuterHTML(&self, can_gc: CanGc) -> Fallible<DOMString> { + fn GetOuterHTML(&self, can_gc: CanGc) -> Fallible<TrustedHTMLOrNullIsEmptyString> { // 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() { @@ -3103,27 +3281,39 @@ impl ElementMethods<crate::DomTypeHolder> for Element { self.upcast::<Node>().xml_serialize(XmlIncludeNode) }; - Ok(result) + Ok(TrustedHTMLOrNullIsEmptyString::NullIsEmptyString(result)) } /// <https://html.spec.whatwg.org/multipage/#dom-element-outerhtml> - 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::<Node>(); - // 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")), @@ -3139,9 +3329,10 @@ impl ElementMethods<crate::DomTypeHolder> 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(()) } @@ -3308,38 +3499,57 @@ impl ElementMethods<crate::DomTypeHolder> 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::<AdjacentPosition>()?; + // 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::<Node>().GetParentNode() { + // Step 3.2: If context is null or a Document, throw a "NoModificationAllowedError" DOMException. Some(ref node) if node.is::<Document>() => { 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::<Node>()) }, }; - // Step 2. + // Step 4. let context = Element::fragment_parsing_context( &context.owner_doc(), context.downcast::<Element>(), 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(|_| ()) } @@ -3800,43 +4010,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(); @@ -4066,6 +4240,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(); + } + + /// <https://html.spec.whatwg.org/multipage/#nonce-attributes%3Aconcept-node-clone-ext> + 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::<Element>().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<bool> for EventCancelable { } impl From<EventCancelable> 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<bool> for EventComposed { + fn from(boolean: bool) -> Self { + if boolean { + EventComposed::Composed + } else { + EventComposed::NotComposed + } + } +} + +impl From<EventComposed> 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 273abda0a02..7cf7bd6106f 100644 --- a/components/script/dom/eventsource.rs +++ b/components/script/dom/eventsource.rs @@ -448,7 +448,7 @@ impl FetchResponseListener for EventSourceContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index 98c4c3ed53d..55db2e4d248 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -106,6 +106,7 @@ 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; @@ -115,6 +116,7 @@ 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; @@ -2930,7 +2932,7 @@ impl GlobalScope { let (is_js_evaluation_allowed, violations) = csp_list.is_js_evaluation_allowed(source); - self.report_csp_violations(violations); + self.report_csp_violations(violations, None); is_js_evaluation_allowed == CheckResult::Allowed } @@ -2957,7 +2959,7 @@ impl GlobalScope { let (result, violations) = csp_list.should_navigation_request_be_blocked(&request, NavigationCheckType::Other); - self.report_csp_violations(violations); + self.report_csp_violations(violations, None); result == CheckResult::Blocked } @@ -3444,8 +3446,13 @@ impl GlobalScope { unreachable!(); } + /// <https://www.w3.org/TR/CSP/#report-violation> #[allow(unsafe_code)] - pub(crate) fn report_csp_violations(&self, violations: Vec<Violation>) { + pub(crate) fn report_csp_violations( + &self, + violations: Vec<Violation>, + element: Option<&Element>, + ) { let scripted_caller = unsafe { describe_scripted_caller(*GlobalScope::get_cx()) }.unwrap_or_default(); for violation in violations { @@ -3471,7 +3478,38 @@ impl GlobalScope { .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::<Window>() { + if !window + .Document() + .upcast::<Node>() + .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::<Window>() { + 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); @@ -3524,10 +3562,6 @@ impl GlobalScopeHelpers<crate::DomTypeHolder> for GlobalScope { GlobalScope::from_reflector(reflector, realm) } - unsafe fn from_object_maybe_wrapped(obj: *mut JSObject, cx: *mut JSContext) -> DomRoot<Self> { - GlobalScope::from_object_maybe_wrapped(obj, cx) - } - fn origin(&self) -> &MutableOrigin { GlobalScope::origin(self) } 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::<EventTarget>(); // 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 56e008839ba..c2bfc9c2d7f 100644 --- a/components/script/dom/htmlcanvaselement.rs +++ b/components/script/dom/htmlcanvaselement.rs @@ -103,10 +103,14 @@ impl EncodedImageType { } } +/// <https://html.spec.whatwg.org/multipage/#htmlcanvaselement> #[dom_struct] pub(crate) struct HTMLCanvasElement { htmlelement: HTMLElement, - context: DomRefCell<Option<RenderingContext>>, + + /// <https://html.spec.whatwg.org/multipage/#concept-canvas-context-mode> + context_mode: DomRefCell<Option<RenderingContext>>, + // This id and hashmap are used to keep track of ongoing toBlob() calls. callback_id: Cell<u32>, #[ignore_malloc_size_of = "not implemented for webidl callbacks"] @@ -121,7 +125,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()), } @@ -146,7 +150,7 @@ impl HTMLCanvasElement { } fn recreate_contexts_after_resize(&self) { - if let Some(ref context) = *self.context.borrow() { + if let Some(ref context) = *self.context_mode.borrow() { context.resize() } } @@ -156,14 +160,14 @@ impl HTMLCanvasElement { } pub(crate) fn origin_is_clean(&self) -> bool { - match *self.context.borrow() { + 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() { + if let Some(ref context) = *self.context_mode.borrow() { context.mark_as_dirty() } } @@ -193,7 +197,7 @@ 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() { + match self.unsafe_get().context_mode.borrow_for_layout().as_ref() { Some(RenderingContext::Context2d(context)) => { context.to_layout().canvas_data_source() }, @@ -221,7 +225,7 @@ impl LayoutHTMLCanvasElementHelpers for LayoutDom<'_, HTMLCanvasElement> { impl HTMLCanvasElement { pub(crate) fn context(&self) -> Option<Ref<RenderingContext>> { - ref_filter_map(self.context.borrow(), |ctx| ctx.as_ref()) + ref_filter_map(self.context_mode.borrow(), |ctx| ctx.as_ref()) } fn get_or_init_2d_context(&self, can_gc: CanGc) -> Option<DomRoot<CanvasRenderingContext2D>> { @@ -235,7 +239,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(RenderingContext::Context2d(Dom::from_ref(&*context))); + *self.context_mode.borrow_mut() = + Some(RenderingContext::Context2d(Dom::from_ref(&*context))); Some(context) } @@ -263,7 +268,7 @@ impl HTMLCanvasElement { attrs, can_gc, )?; - *self.context.borrow_mut() = Some(RenderingContext::WebGL(Dom::from_ref(&*context))); + *self.context_mode.borrow_mut() = Some(RenderingContext::WebGL(Dom::from_ref(&*context))); Some(context) } @@ -288,7 +293,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(RenderingContext::WebGL2(Dom::from_ref(&*context))); + *self.context_mode.borrow_mut() = Some(RenderingContext::WebGL2(Dom::from_ref(&*context))); Some(context) } @@ -315,7 +320,7 @@ impl HTMLCanvasElement { .expect("Failed to get WebGPU channel") .map(|channel| { let context = GPUCanvasContext::new(&global_scope, self, channel, can_gc); - *self.context.borrow_mut() = + *self.context_mode.borrow_mut() = Some(RenderingContext::WebGPU(Dom::from_ref(&*context))); context }) @@ -323,7 +328,7 @@ impl HTMLCanvasElement { /// Gets the base WebGLRenderingContext for WebGL or WebGL 2, if exists. pub(crate) fn get_base_webgl_context(&self) -> Option<DomRoot<WebGLRenderingContext>> { - match *self.context.borrow() { + 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, @@ -352,7 +357,7 @@ impl HTMLCanvasElement { } pub(crate) fn get_image_data(&self) -> Option<Snapshot> { - match self.context.borrow().as_ref() { + match self.context_mode.borrow().as_ref() { Some(context) => context.get_image_data(), None => { let size = self.get_size(); @@ -431,12 +436,12 @@ impl HTMLCanvasElementMethods<crate::DomTypeHolder> 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. + /// <https://html.spec.whatwg.org/multipage/#dom-canvas-width> fn SetWidth(&self, value: u32, can_gc: CanGc) -> Fallible<()> { - if let Some(RenderingContext::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); } @@ -453,9 +458,12 @@ impl HTMLCanvasElementMethods<crate::DomTypeHolder> 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 + /// <https://html.spec.whatwg.org/multipage/#dom-canvas-height> fn SetHeight(&self, value: u32, can_gc: CanGc) -> Fallible<()> { - if let Some(RenderingContext::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); } @@ -478,7 +486,7 @@ impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement { can_gc: CanGc, ) -> Fallible<Option<RootedRenderingContext>> { // Always throw an InvalidState exception when the canvas is in Placeholder mode (See table in the spec). - if let Some(RenderingContext::Placeholder(_)) = *self.context.borrow() { + if let Some(RenderingContext::Placeholder(_)) = *self.context_mode.borrow() { return Err(Error::InvalidState); } @@ -622,7 +630,7 @@ impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement { /// <https://html.spec.whatwg.org/multipage/#dom-canvas-transfercontroltooffscreen> fn TransferControlToOffscreen(&self, can_gc: CanGc) -> Fallible<DomRoot<OffscreenCanvas>> { - 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); @@ -641,8 +649,9 @@ impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement { Some(&Dom::from_ref(self)), can_gc, ); + // Step 4. Set this canvas element's context mode to placeholder. - *self.context.borrow_mut() = + *self.context_mode.borrow_mut() = Some(RenderingContext::Placeholder(offscreen_canvas.as_traced())); // Step 5. Return offscreenCanvas. diff --git a/components/script/dom/htmlelement.rs b/components/script/dom/htmlelement.rs index 59b71543d6d..f41370386e9 100644 --- a/components/script/dom/htmlelement.rs +++ b/components/script/dom/htmlelement.rs @@ -191,9 +191,6 @@ impl HTMLElementMethods<crate::DomTypeHolder> 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<DOMStringMap> { self.dataset.or_init(|| DOMStringMap::new(self, can_gc)) @@ -648,13 +645,16 @@ impl HTMLElementMethods<crate::DomTypeHolder> 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 { @@ -1141,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/htmliframeelement.rs b/components/script/dom/htmliframeelement.rs index 0fbff86e44a..18116eee8ae 100644 --- a/components/script/dom/htmliframeelement.rs +++ b/components/script/dom/htmliframeelement.rs @@ -27,6 +27,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 +42,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; @@ -595,10 +598,29 @@ impl HTMLIFrameElementMethods<crate::DomTypeHolder> 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>(); + 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::<Element>(); + 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<DOMTokenList> { 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<ImageSource>, 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<u32>, pub density: Option<f64>, @@ -145,7 +146,7 @@ struct ImageRequest { parsed_url: Option<ServoUrl>, source_url: Option<USVString>, blocker: DomRefCell<Option<LoadBlocker>>, - #[ignore_malloc_size_of = "Arc"] + #[conditional_malloc_size_of] #[no_trace] image: Option<Arc<Image>>, #[no_trace] @@ -162,7 +163,6 @@ pub(crate) struct HTMLImageElement { pending_request: DomRefCell<ImageRequest>, form_owner: MutNullableDom<HTMLFormElement>, generation: Cell<u32>, - #[ignore_malloc_size_of = "SourceSet"] source_set: DomRefCell<SourceSet>, last_selected_source: DomRefCell<Option<USVString>>, #[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<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } 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<LinkRelations>, - #[ignore_malloc_size_of = "Arc"] + #[conditional_malloc_size_of] #[no_trace] stylesheet: DomRefCell<Option<Arc<Stylesheet>>>, cssom_stylesheet: MutNullableDom<CSSStyleSheet>, @@ -344,7 +343,7 @@ impl HTMLLinkElement { destination: Some(destination), integrity: String::new(), link_type: String::new(), - cryptographic_nonce_metadata: self.upcast::<HTMLElement>().Nonce().into(), + cryptographic_nonce_metadata: self.upcast::<Element>().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<csp::Violation>) { 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 361c22c1250..391da272ef3 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -2951,7 +2951,7 @@ impl FetchResponseListener for HTMLMediaElementFetchListener { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { 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::<Element>() + .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 9d0ca807748..d1b3cfd3467 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -281,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<DOMString>, } -#[derive(JSTraceable)] +#[derive(JSTraceable, MallocSizeOf)] pub(crate) enum SourceCode { - Text(Rc<DOMString>), + Text(#[conditional_malloc_size_of] Rc<DOMString>), Compiled(CompiledSourceCode), } #[derive(JSTraceable, MallocSizeOf)] pub(crate) struct ScriptOrigin { - #[ignore_malloc_size_of = "Rc is hard"] code: SourceCode, #[no_trace] url: ServoUrl, @@ -547,7 +546,8 @@ impl FetchResponseListener for ClassicContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + let elem = self.elem.root(); + global.report_csp_violations(violations, Some(elem.upcast::<Element>())); } } @@ -781,7 +781,7 @@ impl HTMLScriptElement { }; // Step 24. Let cryptographic nonce be el's [[CryptographicNonce]] internal slot's value. - let cryptographic_nonce = self.upcast::<HTMLElement>().Nonce().into(); + let cryptographic_nonce = self.upcast::<Element>().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. diff --git a/components/script/dom/htmlselectelement.rs b/components/script/dom/htmlselectelement.rs index f4a62abe8b4..56fac20e841 100644 --- a/components/script/dom/htmlselectelement.rs +++ b/components/script/dom/htmlselectelement.rs @@ -153,7 +153,7 @@ impl HTMLSelectElement { n } - // https://html.spec.whatwg.org/multipage/#concept-select-option-list + /// <https://html.spec.whatwg.org/multipage/#concept-select-option-list> pub(crate) fn list_of_options( &self, ) -> impl Iterator<Item = DomRoot<HTMLOptionElement>> + use<'_> { @@ -353,8 +353,10 @@ impl HTMLSelectElement { .fire_bubbling_event(atom!("change"), can_gc); } - fn selected_option(&self) -> Option<DomRoot<HTMLOptionElement>> { - self.list_of_options().find(|opt_elem| opt_elem.Selected()) + pub(crate) fn selected_option(&self) -> Option<DomRoot<HTMLOptionElement>> { + 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<usize> { @@ -539,7 +541,8 @@ impl HTMLSelectElementMethods<crate::DomTypeHolder> for HTMLSelectElement { /// <https://html.spec.whatwg.org/multipage/#dom-select-value> 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<Option<Arc<Stylesheet>>>, cssom_stylesheet: MutNullableDom<CSSStyleSheet>, @@ -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/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<csp::Violation>) { 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::<JSObject>()); if let Some(ref mut d) = data { d.resize(len as usize, 0); + + let typed_array = + create_buffer_source::<ClampedU8>(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<HandleObject>, width: u32, opt_height: Option<u32>, - jsobject: *mut JSObject, + data: CustomAutoRooterGuard<Uint8ClampedArray>, can_gc: CanGc, ) -> Fallible<DomRoot<ImageData>> { - let heap_typed_array = match new_initialized_heap_buffer_source::<ClampedU8>( - 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::<ClampedU8>::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<HandleObject>, width: u32, height: u32, can_gc: CanGc, ) -> Fallible<DomRoot<ImageData>> { + // 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::<ClampedU8>( - 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::<ClampedU8>(cx, len, can_gc)?; + let imagedata = Box::new(ImageData { reflector_: Reflector::new(), width, @@ -198,20 +194,19 @@ impl ImageDataMethods<crate::DomTypeHolder> for ImageData { width: u32, height: u32, ) -> Fallible<DomRoot<Self>> { - Self::new_without_jsobject(global, proto, width, height, can_gc) + Self::new_without_data(global, proto, width, height, can_gc) } /// <https://html.spec.whatwg.org/multipage/#dom-imagedata-with-data> fn Constructor_( - _cx: JSContext, global: &GlobalScope, proto: Option<HandleObject>, can_gc: CanGc, - jsobject: *mut JSObject, + data: CustomAutoRooterGuard<Uint8ClampedArray>, width: u32, opt_height: Option<u32>, ) -> Fallible<DomRoot<Self>> { - Self::new_with_jsobject(global, proto, width, opt_height, jsobject, can_gc) + Self::new_with_data(global, proto, width, opt_height, data, can_gc) } /// <https://html.spec.whatwg.org/multipage/#dom-imagedata-width> 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 cc44497d0b9..564fe810db0 100644 --- a/components/script/dom/macros.rs +++ b/components/script/dom/macros.rs @@ -523,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); @@ -561,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); @@ -585,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); ) ); @@ -605,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); @@ -633,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); @@ -646,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) => ( diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 4bc272db8dd..91a4e1b1359 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -547,6 +547,7 @@ pub(crate) mod submitevent; pub(crate) mod subtlecrypto; pub(crate) mod svgelement; pub(crate) mod svggraphicselement; +pub(crate) mod svgimageelement; pub(crate) mod svgsvgelement; pub(crate) mod testbinding; pub(crate) mod testbindingiterable; @@ -631,6 +632,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 e9d36a01426..5f08abce354 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -3110,11 +3110,15 @@ impl NodeMethods<crate::DomTypeHolder> for Node { /// <https://dom.spec.whatwg.org/#dom-node-childnodes> fn ChildNodes(&self, can_gc: CanGc) -> DomRoot<NodeList> { - self.ensure_rare_data().child_list.or_init(|| { - let doc = self.owner_doc(); - let window = doc.window(); - NodeList::new_child_list(window, self, can_gc) - }) + if let Some(list) = self.ensure_rare_data().child_list.get() { + return list; + } + + let doc = self.owner_doc(); + let window = doc.window(); + let list = NodeList::new_child_list(window, self, can_gc); + self.ensure_rare_data().child_list.set(Some(&list)); + list } /// <https://dom.spec.whatwg.org/#dom-node-firstchild> @@ -4203,6 +4207,9 @@ impl From<ElementTypeIdWrapper> for LayoutElementType { LayoutElementType::HTMLTextAreaElement }, ElementTypeId::SVGElement(SVGElementTypeId::SVGGraphicsElement( + SVGGraphicsElementTypeId::SVGImageElement, + )) => LayoutElementType::SVGImageElement, + ElementTypeId::SVGElement(SVGElementTypeId::SVGGraphicsElement( SVGGraphicsElementTypeId::SVGSVGElement, )) => LayoutElementType::SVGSVGElement, _ => LayoutElementType::Element, diff --git a/components/script/dom/notification.rs b/components/script/dom/notification.rs index 4dbb97430e5..1aecb785475 100644 --- a/components/script/dom/notification.rs +++ b/components/script/dom/notification.rs @@ -795,7 +795,7 @@ impl FetchResponseListener for ResourceFetchListener { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/dom/offscreencanvas.rs b/components/script/dom/offscreencanvas.rs index 9947d35f4e0..bceed49ac7d 100644 --- a/components/script/dom/offscreencanvas.rs +++ b/components/script/dom/offscreencanvas.rs @@ -24,12 +24,20 @@ use crate::dom::htmlcanvaselement::HTMLCanvasElement; use crate::dom::offscreencanvasrenderingcontext2d::OffscreenCanvasRenderingContext2D; use crate::script_runtime::{CanGc, JSContext}; +/// <https://html.spec.whatwg.org/multipage/#offscreencanvas> #[dom_struct] pub(crate) struct OffscreenCanvas { eventtarget: EventTarget, width: Cell<u64>, height: Cell<u64>, + + /// Represents both the [bitmap] and the [context mode] of the canvas. + /// + /// [bitmap]: https://html.spec.whatwg.org/multipage/#offscreencanvas-bitmap + /// [context mode]: https://html.spec.whatwg.org/multipage/#offscreencanvas-context-mode context: DomRefCell<Option<OffscreenRenderingContext>>, + + /// <https://html.spec.whatwg.org/multipage/#offscreencanvas-placeholder> placeholder: Option<Dom<HTMLCanvasElement>>, } @@ -119,7 +127,7 @@ impl OffscreenCanvas { } impl OffscreenCanvasMethods<crate::DomTypeHolder> for OffscreenCanvas { - // https://html.spec.whatwg.org/multipage/#dom-offscreencanvas + /// <https://html.spec.whatwg.org/multipage/#dom-offscreencanvas> fn Constructor( global: &GlobalScope, proto: Option<HandleObject>, @@ -131,7 +139,7 @@ impl OffscreenCanvasMethods<crate::DomTypeHolder> for OffscreenCanvas { Ok(offscreencanvas) } - // https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-getcontext + /// <https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-getcontext> fn GetContext( &self, _cx: JSContext, @@ -155,12 +163,12 @@ impl OffscreenCanvasMethods<crate::DomTypeHolder> for OffscreenCanvas { } } - // https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-width + /// <https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-width> fn Width(&self) -> u64 { self.width.get() } - // https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-width + /// <https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-width> fn SetWidth(&self, value: u64, can_gc: CanGc) { self.width.set(value); @@ -173,12 +181,12 @@ impl OffscreenCanvasMethods<crate::DomTypeHolder> for OffscreenCanvas { } } - // https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-height + /// <https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-height> fn Height(&self) -> u64 { self.height.get() } - // https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-height + /// <https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-height> fn SetHeight(&self, value: u64, can_gc: CanGc) { self.height.set(value); diff --git a/components/script/dom/promise.rs b/components/script/dom/promise.rs index 80b62f161bc..0efffbe6fe2 100644 --- a/components/script/dom/promise.rs +++ b/components/script/dom/promise.rs @@ -29,7 +29,6 @@ use js::rust::wrappers::{ ResolvePromise, SetAnyPromiseIsHandled, SetPromiseUserInputEventHandlingState, }; use js::rust::{HandleObject, HandleValue, MutableHandleObject, Runtime}; -use script_bindings::interfaces::PromiseHelpers; use crate::dom::bindings::conversions::root_from_object; use crate::dom::bindings::error::{Error, ErrorToJsval}; @@ -388,16 +387,6 @@ fn create_native_handler_function( } } -impl PromiseHelpers<crate::DomTypeHolder> for Promise { - fn new_resolved( - global: &GlobalScope, - cx: SafeJSContext, - value: impl ToJSValConvertible, - ) -> Rc<Promise> { - Promise::new_resolved(global, cx, value, CanGc::note()) - } -} - impl FromJSValConvertibleRc for Promise { #[allow(unsafe_code)] unsafe fn from_jsval( @@ -407,16 +396,12 @@ impl FromJSValConvertibleRc for Promise { if value.get().is_null() { return Ok(ConversionResult::Failure("null not allowed".into())); } - if !value.get().is_object() { - return Ok(ConversionResult::Failure("not an object".into())); - } - rooted!(in(cx) let obj = value.get().to_object()); let cx = SafeJSContext::from_ptr(cx); let in_realm_proof = AlreadyInRealm::assert_for_cx(cx); let global_scope = GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)); - let promise = Promise::new_resolved(&global_scope, cx, *obj, CanGc::note()); + let promise = Promise::new_resolved(&global_scope, cx, value, CanGc::note()); Ok(ConversionResult::Success(promise)) } } diff --git a/components/script/dom/raredata.rs b/components/script/dom/raredata.rs index 3afa000511e..0c048956217 100644 --- a/components/script/dom/raredata.rs +++ b/components/script/dom/raredata.rs @@ -75,4 +75,5 @@ pub(crate) struct ElementRareData { /// > Element objects have an internal [[RegisteredIntersectionObservers]] slot, /// > which is initialized to an empty list. This list holds IntersectionObserverRegistration records, which have: pub(crate) registered_intersection_observers: Vec<IntersectionObserverRegistration>, + pub(crate) cryptographic_nonce: String, } diff --git a/components/script/dom/readablestream.rs b/components/script/dom/readablestream.rs index 4982bfa32e3..d631a01e1e7 100644 --- a/components/script/dom/readablestream.rs +++ b/components/script/dom/readablestream.rs @@ -11,6 +11,7 @@ use std::rc::Rc; use base::id::{MessagePortId, MessagePortIndex}; use constellation_traits::MessagePortImpl; use dom_struct::dom_struct; +use ipc_channel::ipc::IpcSharedMemory; use js::conversions::ToJSValConvertible; use js::jsapi::{Heap, JSObject}; use js::jsval::{JSVal, NullValue, ObjectValue, UndefinedValue}; @@ -1131,12 +1132,14 @@ impl ReadableStream { /// Return bytes for synchronous use, if the stream has all data in memory. /// Useful for native source integration only. - pub(crate) fn get_in_memory_bytes(&self) -> Option<Vec<u8>> { + pub(crate) fn get_in_memory_bytes(&self) -> Option<IpcSharedMemory> { match self.controller.borrow().as_ref() { Some(ControllerType::Default(controller)) => controller .get() .expect("Stream should have controller.") - .get_in_memory_bytes(), + .get_in_memory_bytes() + .as_deref() + .map(IpcSharedMemory::from_bytes), _ => { unreachable!("Getting in-memory bytes for a stream with a non-default controller") }, diff --git a/components/script/dom/readablestreamdefaultcontroller.rs b/components/script/dom/readablestreamdefaultcontroller.rs index c52fb712a03..80c800d1bbe 100644 --- a/components/script/dom/readablestreamdefaultcontroller.rs +++ b/components/script/dom/readablestreamdefaultcontroller.rs @@ -383,7 +383,6 @@ impl ReadableStreamDefaultController { } /// <https://streams.spec.whatwg.org/#set-up-readable-stream-default-controller> - #[allow(unsafe_code)] pub(crate) fn setup( &self, stream: DomRoot<ReadableStream>, @@ -866,7 +865,6 @@ impl ReadableStreamDefaultController { } /// <https://streams.spec.whatwg.org/#rs-default-controller-has-backpressure> - #[allow(unused)] pub(crate) fn has_backpressure(&self) -> bool { // If ! ReadableStreamDefaultControllerShouldCallPull(controller) is true, return false. // Otherwise, return true. diff --git a/components/script/dom/readablestreamgenericreader.rs b/components/script/dom/readablestreamgenericreader.rs index 8ba1149bcb5..d2a109ee692 100644 --- a/components/script/dom/readablestreamgenericreader.rs +++ b/components/script/dom/readablestreamgenericreader.rs @@ -80,7 +80,6 @@ pub(crate) trait ReadableStreamGenericReader { } /// <https://streams.spec.whatwg.org/#readable-stream-reader-generic-release> - #[allow(unsafe_code)] fn generic_release(&self, can_gc: CanGc) -> Fallible<()> { // Let stream be reader.[[stream]]. diff --git a/components/script/dom/securitypolicyviolationevent.rs b/components/script/dom/securitypolicyviolationevent.rs index 3580e525e55..3c528cc5814 100644 --- a/components/script/dom/securitypolicyviolationevent.rs +++ b/components/script/dom/securitypolicyviolationevent.rs @@ -15,7 +15,7 @@ use crate::dom::bindings::codegen::Bindings::SecurityPolicyViolationEventBinding use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::{DOMString, USVString}; -use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::event::{Event, EventBubbles, EventCancelable, EventComposed}; use crate::dom::globalscope::GlobalScope; use crate::script_runtime::CanGc; @@ -70,12 +70,14 @@ impl SecurityPolicyViolationEvent { ) } + #[allow(clippy::too_many_arguments)] fn new_with_proto( global: &GlobalScope, proto: Option<HandleObject>, type_: Atom, bubbles: EventBubbles, cancelable: EventCancelable, + composed: EventComposed, init: &SecurityPolicyViolationEventInit, can_gc: CanGc, ) -> DomRoot<Self> { @@ -83,6 +85,7 @@ impl SecurityPolicyViolationEvent { { let event = ev.upcast::<Event>(); event.init_event(type_, bool::from(bubbles), bool::from(cancelable)); + event.set_composed(bool::from(composed)); } ev } @@ -92,10 +95,13 @@ impl SecurityPolicyViolationEvent { type_: Atom, bubbles: EventBubbles, cancelable: EventCancelable, + composed: EventComposed, init: &SecurityPolicyViolationEventInit, can_gc: CanGc, ) -> DomRoot<Self> { - Self::new_with_proto(global, None, type_, bubbles, cancelable, init, can_gc) + Self::new_with_proto( + global, None, type_, bubbles, cancelable, composed, init, can_gc, + ) } } @@ -115,6 +121,7 @@ impl SecurityPolicyViolationEventMethods<crate::DomTypeHolder> for SecurityPolic Atom::from(type_), EventBubbles::from(init.parent.bubbles), EventCancelable::from(init.parent.cancelable), + EventComposed::from(init.parent.composed), init, can_gc, ) diff --git a/components/script/dom/servoparser/html.rs b/components/script/dom/servoparser/html.rs index 07848c87678..9dfbeda4030 100644 --- a/components/script/dom/servoparser/html.rs +++ b/components/script/dom/servoparser/html.rs @@ -16,6 +16,7 @@ use html5ever::{QualName, local_name, ns}; use markup5ever::TokenizerResult; use script_bindings::trace::CustomTraceable; use servo_url::ServoUrl; +use style::attr::AttrValue; use style::context::QuirksMode as StyleContextQuirksMode; use xml5ever::LocalName; @@ -116,18 +117,34 @@ impl Tokenizer { } } -fn start_element<S: Serializer>(node: &Element, serializer: &mut S) -> io::Result<()> { - let name = QualName::new(None, node.namespace().clone(), node.local_name().clone()); - let attrs = node - .attrs() - .iter() - .map(|attr| { - let qname = QualName::new(None, attr.namespace().clone(), attr.local_name().clone()); - let value = attr.value().clone(); - (qname, value) - }) - .collect::<Vec<_>>(); - let attr_refs = attrs.iter().map(|(qname, value)| { +/// <https://html.spec.whatwg.org/multipage/#html-fragment-serialisation-algorithm> +fn start_element<S: Serializer>(element: &Element, serializer: &mut S) -> io::Result<()> { + let name = QualName::new( + None, + element.namespace().clone(), + element.local_name().clone(), + ); + + let mut attributes = vec![]; + + // The "is" value of an element is treated as if it was an attribute and it is serialized before all + // other attributes. If the element already has an "is" attribute then the "is" value is ignored. + if !element.has_attribute(&LocalName::from("is")) { + if let Some(is_value) = element.get_is() { + let qualified_name = QualName::new(None, ns!(), LocalName::from("is")); + + attributes.push((qualified_name, AttrValue::String(is_value.to_string()))); + } + } + + // Collect all the "normal" attributes + attributes.extend(element.attrs().iter().map(|attr| { + let qname = QualName::new(None, attr.namespace().clone(), attr.local_name().clone()); + let value = attr.value().clone(); + (qname, value) + })); + + let attr_refs = attributes.iter().map(|(qname, value)| { let ar: AttrRef = (qname, &**value); ar }); @@ -302,11 +319,10 @@ pub(crate) fn serialize_html_fragment<S: Serializer>( serializer.write_processing_instruction(pi.target(), &data)?; }, - NodeTypeId::DocumentFragment(_) => {}, + NodeTypeId::DocumentFragment(_) | NodeTypeId::Attr => {}, NodeTypeId::Document(_) => panic!("Can't serialize Document node itself"), NodeTypeId::Element(_) => panic!("Element shouldn't appear here"), - NodeTypeId::Attr => panic!("Attr shouldn't appear here"), }, SerializationCommand::SerializeShadowRoot(shadow_root) => { // Shadow roots are serialized as template elements with a fixed set of diff --git a/components/script/dom/servoparser/mod.rs b/components/script/dom/servoparser/mod.rs index 3a1efdfb291..9e45124522a 100644 --- a/components/script/dom/servoparser/mod.rs +++ b/components/script/dom/servoparser/mod.rs @@ -1075,7 +1075,8 @@ impl FetchResponseListener for ParserContext { }; let document = &parser.document; let global = &document.global(); - global.report_csp_violations(violations); + // TODO(https://github.com/w3c/webappsec-csp/issues/687): Update after spec is resolved + global.report_csp_violations(violations, None); } } diff --git a/components/script/dom/svgelement.rs b/components/script/dom/svgelement.rs index 9c8b990826d..a380dcff5ac 100644 --- a/components/script/dom/svgelement.rs +++ b/components/script/dom/svgelement.rs @@ -8,12 +8,13 @@ use js::rust::HandleObject; use script_bindings::str::DOMString; use stylo_dom::ElementState; +use crate::dom::attr::Attr; use crate::dom::bindings::codegen::Bindings::SVGElementBinding::SVGElementMethods; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; use crate::dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner}; use crate::dom::document::Document; -use crate::dom::element::Element; +use crate::dom::element::{AttributeMutation, Element}; use crate::dom::node::{Node, NodeTraits}; use crate::dom::virtualmethods::VirtualMethods; use crate::script_runtime::CanGc; @@ -59,11 +60,33 @@ impl SVGElement { can_gc, ) } + + fn as_element(&self) -> &Element { + self.upcast::<Element>() + } } impl VirtualMethods for SVGElement { fn super_type(&self) -> Option<&dyn VirtualMethods> { - Some(self.upcast::<Element>() as &dyn VirtualMethods) + Some(self.as_element() as &dyn VirtualMethods) + } + + fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) { + self.super_type() + .unwrap() + .attribute_mutated(attr, mutation, can_gc); + let element = self.as_element(); + if let (&local_name!("nonce"), mutation) = (attr.local_name(), mutation) { + match mutation { + AttributeMutation::Set(_) => { + let nonce = &**attr.value(); + element.update_nonce_internal_slot(nonce.to_owned()); + }, + AttributeMutation::Removed => { + element.update_nonce_internal_slot(String::new()); + }, + } + } } } @@ -82,13 +105,19 @@ impl SVGElementMethods<crate::DomTypeHolder> for SVGElement { }) } - // 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/#globaleventhandlers> + global_event_handlers!(); + // 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 { diff --git a/components/script/dom/svgimageelement.rs b/components/script/dom/svgimageelement.rs new file mode 100644 index 00000000000..17a5a9149d8 --- /dev/null +++ b/components/script/dom/svgimageelement.rs @@ -0,0 +1,96 @@ +/* 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 dom_struct::dom_struct; +use html5ever::{LocalName, Prefix, local_name, ns}; +use js::rust::HandleObject; +use style::attr::AttrValue; + +use crate::dom::attr::Attr; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::AttributeMutation; +use crate::dom::node::{Node, NodeTraits}; +use crate::dom::svggraphicselement::SVGGraphicsElement; +use crate::dom::virtualmethods::VirtualMethods; +use crate::script_runtime::CanGc; + +/// <https://svgwg.org/svg2-draft/embedded.html#Placement> +const DEFAULT_WIDTH: u32 = 300; +const DEFAULT_HEIGHT: u32 = 150; + +#[dom_struct] +pub(crate) struct SVGImageElement { + svggraphicselement: SVGGraphicsElement, +} + +impl SVGImageElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> SVGImageElement { + SVGImageElement { + svggraphicselement: SVGGraphicsElement::new_inherited(local_name, prefix, document), + } + } + + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + pub(crate) fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + proto: Option<HandleObject>, + can_gc: CanGc, + ) -> DomRoot<SVGImageElement> { + Node::reflect_node_with_proto( + Box::new(SVGImageElement::new_inherited(local_name, prefix, document)), + document, + proto, + can_gc, + ) + } + + /// <https://svgwg.org/svg2-draft/linking.html#processingURL> + fn fetch_image_resource(&self) { + // TODO: Process and fetch the image resource (as HTMLImageElement). + // Reject any resource fetching request immediately. + self.owner_global() + .task_manager() + .dom_manipulation_task_source() + .queue_simple_event(self.upcast(), atom!("error")); + } +} + +impl VirtualMethods for SVGImageElement { + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<SVGGraphicsElement>() as &dyn VirtualMethods) + } + + fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) { + self.super_type() + .unwrap() + .attribute_mutated(attr, mutation, can_gc); + if attr.local_name() == &local_name!("href") && + matches!(attr.namespace(), &ns!() | &ns!(xlink)) + { + if let AttributeMutation::Set(_) = mutation { + self.fetch_image_resource(); + } + } + } + + fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { + match *name { + local_name!("width") => AttrValue::from_u32(value.into(), DEFAULT_WIDTH), + local_name!("height") => AttrValue::from_u32(value.into(), DEFAULT_HEIGHT), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(name, value), + } + } +} diff --git a/components/script/dom/transformstream.rs b/components/script/dom/transformstream.rs new file mode 100644 index 00000000000..0251498980d --- /dev/null +++ b/components/script/dom/transformstream.rs @@ -0,0 +1,1105 @@ +/* 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::Cell; +use std::collections::HashMap; +use std::ptr::{self}; +use std::rc::Rc; + +use base::id::{MessagePortId, MessagePortIndex}; +use constellation_traits::MessagePortImpl; +use dom_struct::dom_struct; +use js::jsapi::{Heap, IsPromiseObject, JSObject}; +use js::jsval::{JSVal, ObjectValue, UndefinedValue}; +use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue, IntoHandle}; +use script_bindings::callback::ExceptionHandling; +use script_bindings::realms::InRealm; + +use super::bindings::codegen::Bindings::QueuingStrategyBinding::QueuingStrategySize; +use super::bindings::structuredclone::StructuredData; +use super::bindings::transferable::Transferable; +use super::messageport::MessagePort; +use super::promisenativehandler::Callback; +use super::types::{TransformStreamDefaultController, WritableStream}; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::QueuingStrategyBinding::QueuingStrategy; +use crate::dom::bindings::codegen::Bindings::TransformStreamBinding::TransformStreamMethods; +use crate::dom::bindings::codegen::Bindings::TransformerBinding::Transformer; +use crate::dom::bindings::conversions::ConversionResult; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::countqueuingstrategy::{extract_high_water_mark, extract_size_algorithm}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::dom::readablestream::{ReadableStream, create_readable_stream}; +use crate::dom::types::PromiseNativeHandler; +use crate::dom::underlyingsourcecontainer::UnderlyingSourceType; +use crate::dom::writablestream::create_writable_stream; +use crate::dom::writablestreamdefaultcontroller::UnderlyingSinkType; +use crate::realms::enter_realm; +use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; + +impl js::gc::Rootable for TransformBackPressureChangePromiseFulfillment {} + +/// Reacting to backpressureChangePromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-write-algorithm> +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct TransformBackPressureChangePromiseFulfillment { + /// The result of reacting to backpressureChangePromise. + #[ignore_malloc_size_of = "Rc is hard"] + result_promise: Rc<Promise>, + + #[ignore_malloc_size_of = "mozjs"] + chunk: Box<Heap<JSVal>>, + + /// The writable used in the fulfillment steps + writable: Dom<WritableStream>, + + controller: Dom<TransformStreamDefaultController>, +} + +impl Callback for TransformBackPressureChangePromiseFulfillment { + /// Reacting to backpressureChangePromise with the following fulfillment steps: + fn callback(&self, cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // Let writable be stream.[[writable]]. + // Let state be writable.[[state]]. + // If state is "erroring", throw writable.[[storedError]]. + if self.writable.is_erroring() { + rooted!(in(*cx) let mut error = UndefinedValue()); + self.writable.get_stored_error(error.handle_mut()); + self.result_promise.reject(cx, error.handle(), can_gc); + return; + } + + // Assert: state is "writable". + assert!(self.writable.is_writable()); + + // Return ! TransformStreamDefaultControllerPerformTransform(controller, chunk). + rooted!(in(*cx) let mut chunk = UndefinedValue()); + chunk.set(self.chunk.get()); + let transform_result = self + .controller + .transform_stream_default_controller_perform_transform( + cx, + &self.writable.global(), + chunk.handle(), + can_gc, + ) + .expect("perform transform failed"); + + // PerformTransformFulfillment and PerformTransformRejection do not need + // to be rooted because they only contain an Rc. + let handler = PromiseNativeHandler::new( + &self.writable.global(), + Some(Box::new(PerformTransformFulfillment { + result_promise: self.result_promise.clone(), + })), + Some(Box::new(PerformTransformRejection { + result_promise: self.result_promise.clone(), + })), + can_gc, + ); + + let realm = enter_realm(&*self.writable.global()); + let comp = InRealm::Entered(&realm); + transform_result.append_native_handler(&handler, comp, can_gc); + } +} + +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +/// Reacting to fulfillment of performTransform as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-write-algorithm> +struct PerformTransformFulfillment { + #[ignore_malloc_size_of = "Rc is hard"] + result_promise: Rc<Promise>, +} + +impl Callback for PerformTransformFulfillment { + fn callback(&self, _cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // Fulfilled: resolve the outer promise + self.result_promise.resolve_native(&(), can_gc); + } +} + +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +/// Reacting to rejection of performTransform as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-write-algorithm> +struct PerformTransformRejection { + #[ignore_malloc_size_of = "Rc is hard"] + result_promise: Rc<Promise>, +} + +impl Callback for PerformTransformRejection { + fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // Stream already errored in perform_transform, just reject result_promise + self.result_promise.reject(cx, v, can_gc); + } +} + +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +/// Reacting to rejection of backpressureChangePromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-write-algorithm> +struct BackpressureChangeRejection { + #[ignore_malloc_size_of = "Rc is hard"] + result_promise: Rc<Promise>, +} + +impl Callback for BackpressureChangeRejection { + fn callback(&self, cx: SafeJSContext, reason: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + self.result_promise.reject(cx, reason, can_gc); + } +} + +impl js::gc::Rootable for CancelPromiseFulfillment {} + +/// Reacting to fulfillment of the cancelpromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-abort-algorithm> +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct CancelPromiseFulfillment { + readable: Dom<ReadableStream>, + controller: Dom<TransformStreamDefaultController>, + #[ignore_malloc_size_of = "mozjs"] + reason: Box<Heap<JSVal>>, +} + +impl Callback for CancelPromiseFulfillment { + /// Reacting to backpressureChangePromise with the following fulfillment steps: + fn callback(&self, cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // If readable.[[state]] is "errored", reject controller.[[finishPromise]] with readable.[[storedError]]. + if self.readable.is_errored() { + rooted!(in(*cx) let mut error = UndefinedValue()); + self.readable.get_stored_error(error.handle_mut()); + self.controller + .get_finish_promise() + .expect("finish promise is not set") + .reject_native(&error.handle(), can_gc); + } else { + // Otherwise: + // Perform ! ReadableStreamDefaultControllerError(readable.[[controller]], reason). + rooted!(in(*cx) let mut reason = UndefinedValue()); + reason.set(self.reason.get()); + self.readable + .get_default_controller() + .error(reason.handle(), can_gc); + + // Resolve controller.[[finishPromise]] with undefined. + self.controller + .get_finish_promise() + .expect("finish promise is not set") + .resolve_native(&(), can_gc); + } + } +} + +impl js::gc::Rootable for CancelPromiseRejection {} + +/// Reacting to rejection of cancelpromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-abort-algorithm> +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct CancelPromiseRejection { + readable: Dom<ReadableStream>, + controller: Dom<TransformStreamDefaultController>, +} + +impl Callback for CancelPromiseRejection { + /// Reacting to backpressureChangePromise with the following fulfillment steps: + fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // Perform ! ReadableStreamDefaultControllerError(readable.[[controller]], r). + self.readable.get_default_controller().error(v, can_gc); + + // Reject controller.[[finishPromise]] with r. + self.controller + .get_finish_promise() + .expect("finish promise is not set") + .reject(cx, v, can_gc); + } +} + +impl js::gc::Rootable for SourceCancelPromiseFulfillment {} + +/// Reacting to fulfillment of the cancelpromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-source-cancel> +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct SourceCancelPromiseFulfillment { + writeable: Dom<WritableStream>, + controller: Dom<TransformStreamDefaultController>, + stream: Dom<TransformStream>, + #[ignore_malloc_size_of = "mozjs"] + reason: Box<Heap<JSVal>>, +} + +impl Callback for SourceCancelPromiseFulfillment { + /// Reacting to backpressureChangePromise with the following fulfillment steps: + fn callback(&self, cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // If cancelPromise was fulfilled, then: + let finish_promise = self + .controller + .get_finish_promise() + .expect("finish promise is not set"); + + let global = &self.writeable.global(); + // If writable.[[state]] is "errored", reject controller.[[finishPromise]] with writable.[[storedError]]. + if self.writeable.is_errored() { + rooted!(in(*cx) let mut error = UndefinedValue()); + self.writeable.get_stored_error(error.handle_mut()); + finish_promise.reject(cx, error.handle(), can_gc); + } else { + // Otherwise: + // Perform ! WritableStreamDefaultControllerErrorIfNeeded(writable.[[controller]], reason). + rooted!(in(*cx) let mut reason = UndefinedValue()); + reason.set(self.reason.get()); + self.writeable.get_default_controller().error_if_needed( + cx, + reason.handle(), + global, + can_gc, + ); + + // Perform ! TransformStreamUnblockWrite(stream). + self.stream.unblock_write(global, can_gc); + + // Resolve controller.[[finishPromise]] with undefined. + finish_promise.resolve_native(&(), can_gc); + } + } +} + +impl js::gc::Rootable for SourceCancelPromiseRejection {} + +/// Reacting to rejection of cancelpromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-source-cancel> +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct SourceCancelPromiseRejection { + writeable: Dom<WritableStream>, + controller: Dom<TransformStreamDefaultController>, + stream: Dom<TransformStream>, +} + +impl Callback for SourceCancelPromiseRejection { + /// Reacting to backpressureChangePromise with the following fulfillment steps: + fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // Perform ! WritableStreamDefaultControllerErrorIfNeeded(writable.[[controller]], r). + let global = &self.writeable.global(); + + self.writeable + .get_default_controller() + .error_if_needed(cx, v, global, can_gc); + + // Perform ! TransformStreamUnblockWrite(stream). + self.stream.unblock_write(global, can_gc); + + // Reject controller.[[finishPromise]] with r. + self.controller + .get_finish_promise() + .expect("finish promise is not set") + .reject(cx, v, can_gc); + } +} + +impl js::gc::Rootable for FlushPromiseFulfillment {} + +/// Reacting to fulfillment of the flushpromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-close-algorithm> +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct FlushPromiseFulfillment { + readable: Dom<ReadableStream>, + controller: Dom<TransformStreamDefaultController>, +} + +impl Callback for FlushPromiseFulfillment { + /// Reacting to flushpromise with the following fulfillment steps: + fn callback(&self, cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // If flushPromise was fulfilled, then: + let finish_promise = self + .controller + .get_finish_promise() + .expect("finish promise is not set"); + + // If readable.[[state]] is "errored", reject controller.[[finishPromise]] with readable.[[storedError]]. + if self.readable.is_errored() { + rooted!(in(*cx) let mut error = UndefinedValue()); + self.readable.get_stored_error(error.handle_mut()); + finish_promise.reject(cx, error.handle(), can_gc); + } else { + // Otherwise: + // Perform ! ReadableStreamDefaultControllerClose(readable.[[controller]]). + self.readable.get_default_controller().close(can_gc); + + // Resolve controller.[[finishPromise]] with undefined. + finish_promise.resolve_native(&(), can_gc); + } + } +} + +impl js::gc::Rootable for FlushPromiseRejection {} +/// Reacting to rejection of flushpromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-close-algorithm> + +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct FlushPromiseRejection { + readable: Dom<ReadableStream>, + controller: Dom<TransformStreamDefaultController>, +} + +impl Callback for FlushPromiseRejection { + /// Reacting to flushpromise with the following fulfillment steps: + fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // If flushPromise was rejected with reason r, then: + // Perform ! ReadableStreamDefaultControllerError(readable.[[controller]], r). + self.readable.get_default_controller().error(v, can_gc); + + // Reject controller.[[finishPromise]] with r. + self.controller + .get_finish_promise() + .expect("finish promise is not set") + .reject(cx, v, can_gc); + } +} + +/// <https://streams.spec.whatwg.org/#ts-class> +#[dom_struct] +pub struct TransformStream { + reflector_: Reflector, + + /// <https://streams.spec.whatwg.org/#transformstream-backpressure> + backpressure: Cell<bool>, + + /// <https://streams.spec.whatwg.org/#transformstream-backpressurechangepromise> + #[ignore_malloc_size_of = "Rc is hard"] + backpressure_change_promise: DomRefCell<Option<Rc<Promise>>>, + + /// <https://streams.spec.whatwg.org/#transformstream-controller> + controller: MutNullableDom<TransformStreamDefaultController>, + + /// <https://streams.spec.whatwg.org/#transformstream-detached> + detached: Cell<bool>, + + /// <https://streams.spec.whatwg.org/#transformstream-readable> + readable: MutNullableDom<ReadableStream>, + + /// <https://streams.spec.whatwg.org/#transformstream-writable> + writable: MutNullableDom<WritableStream>, +} + +impl TransformStream { + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + /// <https://streams.spec.whatwg.org/#initialize-transform-stream> + fn new_inherited() -> TransformStream { + TransformStream { + reflector_: Reflector::new(), + backpressure: Default::default(), + backpressure_change_promise: DomRefCell::new(None), + controller: MutNullableDom::new(None), + detached: Cell::new(false), + readable: MutNullableDom::new(None), + writable: MutNullableDom::new(None), + } + } + + pub(crate) fn new_with_proto( + global: &GlobalScope, + proto: Option<SafeHandleObject>, + can_gc: CanGc, + ) -> DomRoot<TransformStream> { + reflect_dom_object_with_proto( + Box::new(TransformStream::new_inherited()), + global, + proto, + can_gc, + ) + } + + pub(crate) fn get_controller(&self) -> DomRoot<TransformStreamDefaultController> { + self.controller.get().expect("controller is not set") + } + + pub(crate) fn get_writable(&self) -> DomRoot<WritableStream> { + self.writable.get().expect("writable stream is not set") + } + + pub(crate) fn get_readable(&self) -> DomRoot<ReadableStream> { + self.readable.get().expect("readable stream is not set") + } + + pub(crate) fn get_backpressure(&self) -> bool { + self.backpressure.get() + } + + /// <https://streams.spec.whatwg.org/#initialize-transform-stream> + #[allow(clippy::too_many_arguments)] + fn initialize( + &self, + cx: SafeJSContext, + global: &GlobalScope, + start_promise: Rc<Promise>, + writable_high_water_mark: f64, + writable_size_algorithm: Rc<QueuingStrategySize>, + readable_high_water_mark: f64, + readable_size_algorithm: Rc<QueuingStrategySize>, + can_gc: CanGc, + ) -> Fallible<()> { + // Let startAlgorithm be an algorithm that returns startPromise. + // Let writeAlgorithm be the following steps, taking a chunk argument: + // Return ! TransformStreamDefaultSinkWriteAlgorithm(stream, chunk). + // Let abortAlgorithm be the following steps, taking a reason argument: + // Return ! TransformStreamDefaultSinkAbortAlgorithm(stream, reason). + // Let closeAlgorithm be the following steps: + // Return ! TransformStreamDefaultSinkCloseAlgorithm(stream). + // Set stream.[[writable]] to ! CreateWritableStream(startAlgorithm, writeAlgorithm, + // closeAlgorithm, abortAlgorithm, writableHighWaterMark, writableSizeAlgorithm). + // Note: Those steps are implemented using UnderlyingSinkType::Transform. + + let writable = create_writable_stream( + cx, + global, + writable_high_water_mark, + writable_size_algorithm, + UnderlyingSinkType::Transform(Dom::from_ref(self), start_promise.clone()), + can_gc, + )?; + self.writable.set(Some(&writable)); + + // Let pullAlgorithm be the following steps: + + // Return ! TransformStreamDefaultSourcePullAlgorithm(stream). + + // Let cancelAlgorithm be the following steps, taking a reason argument: + + // Return ! TransformStreamDefaultSourceCancelAlgorithm(stream, reason). + + // Set stream.[[readable]] to ! CreateReadableStream(startAlgorithm, pullAlgorithm, + // cancelAlgorithm, readableHighWaterMark, readableSizeAlgorithm). + + let readable = create_readable_stream( + global, + UnderlyingSourceType::Transform(Dom::from_ref(self), start_promise.clone()), + Some(readable_size_algorithm), + Some(readable_high_water_mark), + can_gc, + ); + self.readable.set(Some(&readable)); + + // Set stream.[[backpressure]] and stream.[[backpressureChangePromise]] to undefined. + // Note: This is done in the constructor. + + // Perform ! TransformStreamSetBackpressure(stream, true). + self.set_backpressure(global, true, can_gc); + + // Set stream.[[controller]] to undefined. + self.controller.set(None); + + Ok(()) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-set-backpressure> + pub(crate) fn set_backpressure(&self, global: &GlobalScope, backpressure: bool, can_gc: CanGc) { + // Assert: stream.[[backpressure]] is not backpressure. + assert!(self.backpressure.get() != backpressure); + + // If stream.[[backpressureChangePromise]] is not undefined, resolve + // stream.[[backpressureChangePromise]] with undefined. + if let Some(promise) = self.backpressure_change_promise.borrow_mut().take() { + promise.resolve_native(&(), can_gc); + } + + // Set stream.[[backpressureChangePromise]] to a new promise.; + *self.backpressure_change_promise.borrow_mut() = Some(Promise::new(global, can_gc)); + + // Set stream.[[backpressure]] to backpressure. + self.backpressure.set(backpressure); + } + + /// <https://streams.spec.whatwg.org/#set-up-transform-stream-default-controller> + fn set_up_transform_stream_default_controller( + &self, + controller: &TransformStreamDefaultController, + ) { + // Assert: stream implements TransformStream. + // Note: this is checked with type. + + // Assert: stream.[[controller]] is undefined. + assert!(self.controller.get().is_none()); + + // Set controller.[[stream]] to stream. + controller.set_stream(self); + + // Set stream.[[controller]] to controller. + self.controller.set(Some(controller)); + + // Set controller.[[transformAlgorithm]] to transformAlgorithm. + // Set controller.[[flushAlgorithm]] to flushAlgorithm. + // Set controller.[[cancelAlgorithm]] to cancelAlgorithm. + // Note: These are set in the constructor. + } + + /// <https://streams.spec.whatwg.org/#set-up-transform-stream-default-controller-from-transformer> + fn set_up_transform_stream_default_controller_from_transformer( + &self, + global: &GlobalScope, + transformer_obj: SafeHandleObject, + transformer: &Transformer, + can_gc: CanGc, + ) { + // Let controller be a new TransformStreamDefaultController. + let controller = TransformStreamDefaultController::new(global, transformer, can_gc); + + // Let transformAlgorithm be the following steps, taking a chunk argument: + // Let result be TransformStreamDefaultControllerEnqueue(controller, chunk). + // If result is an abrupt completion, return a promise rejected with result.[[Value]]. + // Otherwise, return a promise resolved with undefined. + + // Let flushAlgorithm be an algorithm which returns a promise resolved with undefined. + // Let cancelAlgorithm be an algorithm which returns a promise resolved with undefined. + + // If transformerDict["transform"] exists, set transformAlgorithm to an algorithm which + // takes an argument + // chunk and returns the result of invoking transformerDict["transform"] with argument + // list « chunk, controller » + // and callback this value transformer. + + // If transformerDict["flush"] exists, set flushAlgorithm to an algorithm which returns + // the result + // of invoking transformerDict["flush"] with argument list « controller » and callback + // this value transformer. + + // If transformerDict["cancel"] exists, set cancelAlgorithm to an algorithm which takes an argument + // reason and returns the result of invoking transformerDict["cancel"] with argument list « reason » + // and callback this value transformer. + controller.set_transform_obj(transformer_obj); + + // Perform ! SetUpTransformStreamDefaultController(stream, controller, + // transformAlgorithm, flushAlgorithm, cancelAlgorithm). + self.set_up_transform_stream_default_controller(&controller); + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-sink-write-algorithm> + pub(crate) fn transform_stream_default_sink_write_algorithm( + &self, + cx: SafeJSContext, + global: &GlobalScope, + chunk: SafeHandleValue, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // Assert: stream.[[writable]].[[state]] is "writable". + assert!(self.writable.get().is_some()); + + // Let controller be stream.[[controller]]. + let controller = self.controller.get().expect("controller is not set"); + + // If stream.[[backpressure]] is true, + if self.backpressure.get() { + // Let backpressureChangePromise be stream.[[backpressureChangePromise]]. + let backpressure_change_promise = self.backpressure_change_promise.borrow(); + + // Assert: backpressureChangePromise is not undefined. + assert!(backpressure_change_promise.is_some()); + + // Return the result of reacting to backpressureChangePromise with the following fulfillment steps: + let result_promise = Promise::new(global, can_gc); + rooted!(in(*cx) let mut fulfillment_handler = Some(TransformBackPressureChangePromiseFulfillment { + controller: Dom::from_ref(&controller), + writable: Dom::from_ref(&self.writable.get().expect("writable stream")), + chunk: Heap::boxed(chunk.get()), + result_promise: result_promise.clone(), + })); + + let handler = PromiseNativeHandler::new( + global, + fulfillment_handler.take().map(|h| Box::new(h) as Box<_>), + Some(Box::new(BackpressureChangeRejection { + result_promise: result_promise.clone(), + })), + can_gc, + ); + let realm = enter_realm(global); + let comp = InRealm::Entered(&realm); + backpressure_change_promise + .as_ref() + .expect("Promise must be some by now.") + .append_native_handler(&handler, comp, can_gc); + + return Ok(result_promise); + } + + // Return ! TransformStreamDefaultControllerPerformTransform(controller, chunk). + controller.transform_stream_default_controller_perform_transform(cx, global, chunk, can_gc) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-sink-abort-algorithm> + pub(crate) fn transform_stream_default_sink_abort_algorithm( + &self, + cx: SafeJSContext, + global: &GlobalScope, + reason: SafeHandleValue, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // Let controller be stream.[[controller]]. + let controller = self.controller.get().expect("controller is not set"); + + // If controller.[[finishPromise]] is not undefined, return controller.[[finishPromise]]. + if let Some(finish_promise) = controller.get_finish_promise() { + return Ok(finish_promise); + } + + // Let readable be stream.[[readable]]. + let readable = self.readable.get().expect("readable stream is not set"); + + // Let controller.[[finishPromise]] be a new promise. + controller.set_finish_promise(Promise::new(global, can_gc)); + + // Let cancelPromise be the result of performing controller.[[cancelAlgorithm]], passing reason. + let cancel_promise = controller.perform_cancel(cx, global, reason, can_gc)?; + + // Perform ! TransformStreamDefaultControllerClearAlgorithms(controller). + controller.clear_algorithms(); + + // React to cancelPromise: + let handler = PromiseNativeHandler::new( + global, + Some(Box::new(CancelPromiseFulfillment { + readable: Dom::from_ref(&readable), + controller: Dom::from_ref(&controller), + reason: Heap::boxed(reason.get()), + })), + Some(Box::new(CancelPromiseRejection { + readable: Dom::from_ref(&readable), + controller: Dom::from_ref(&controller), + })), + can_gc, + ); + let realm = enter_realm(global); + let comp = InRealm::Entered(&realm); + cancel_promise.append_native_handler(&handler, comp, can_gc); + + // Return controller.[[finishPromise]]. + let finish_promise = controller + .get_finish_promise() + .expect("finish promise is not set"); + Ok(finish_promise) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-sink-close-algorithm> + pub(crate) fn transform_stream_default_sink_close_algorithm( + &self, + cx: SafeJSContext, + global: &GlobalScope, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // Let controller be stream.[[controller]]. + let controller = self + .controller + .get() + .ok_or(Error::Type("controller is not set".to_string()))?; + + // If controller.[[finishPromise]] is not undefined, return controller.[[finishPromise]]. + if let Some(finish_promise) = controller.get_finish_promise() { + return Ok(finish_promise); + } + + // Let readable be stream.[[readable]]. + let readable = self + .readable + .get() + .ok_or(Error::Type("readable stream is not set".to_string()))?; + + // Let controller.[[finishPromise]] be a new promise. + controller.set_finish_promise(Promise::new(global, can_gc)); + + // Let flushPromise be the result of performing controller.[[flushAlgorithm]]. + let flush_promise = controller.perform_flush(cx, global, can_gc)?; + + // Perform ! TransformStreamDefaultControllerClearAlgorithms(controller). + controller.clear_algorithms(); + + // React to flushPromise: + let handler = PromiseNativeHandler::new( + global, + Some(Box::new(FlushPromiseFulfillment { + readable: Dom::from_ref(&readable), + controller: Dom::from_ref(&controller), + })), + Some(Box::new(FlushPromiseRejection { + readable: Dom::from_ref(&readable), + controller: Dom::from_ref(&controller), + })), + can_gc, + ); + + let realm = enter_realm(global); + let comp = InRealm::Entered(&realm); + flush_promise.append_native_handler(&handler, comp, can_gc); + // Return controller.[[finishPromise]]. + let finish_promise = controller + .get_finish_promise() + .expect("finish promise is not set"); + Ok(finish_promise) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-source-cancel> + pub(crate) fn transform_stream_default_source_cancel( + &self, + cx: SafeJSContext, + global: &GlobalScope, + reason: SafeHandleValue, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // Let controller be stream.[[controller]]. + let controller = self + .controller + .get() + .ok_or(Error::Type("controller is not set".to_string()))?; + + // If controller.[[finishPromise]] is not undefined, return controller.[[finishPromise]]. + if let Some(finish_promise) = controller.get_finish_promise() { + return Ok(finish_promise); + } + + // Let writable be stream.[[writable]]. + let writable = self + .writable + .get() + .ok_or(Error::Type("writable stream is not set".to_string()))?; + + // Let controller.[[finishPromise]] be a new promise. + controller.set_finish_promise(Promise::new(global, can_gc)); + + // Let cancelPromise be the result of performing controller.[[cancelAlgorithm]], passing reason. + let cancel_promise = controller.perform_cancel(cx, global, reason, can_gc)?; + + // Perform ! TransformStreamDefaultControllerClearAlgorithms(controller). + controller.clear_algorithms(); + + // React to cancelPromise: + let handler = PromiseNativeHandler::new( + global, + Some(Box::new(SourceCancelPromiseFulfillment { + writeable: Dom::from_ref(&writable), + controller: Dom::from_ref(&controller), + stream: Dom::from_ref(self), + reason: Heap::boxed(reason.get()), + })), + Some(Box::new(SourceCancelPromiseRejection { + writeable: Dom::from_ref(&writable), + controller: Dom::from_ref(&controller), + stream: Dom::from_ref(self), + })), + can_gc, + ); + + // Return controller.[[finishPromise]]. + let finish_promise = controller + .get_finish_promise() + .expect("finish promise is not set"); + let realm = enter_realm(global); + let comp = InRealm::Entered(&realm); + cancel_promise.append_native_handler(&handler, comp, can_gc); + Ok(finish_promise) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-source-pull> + pub(crate) fn transform_stream_default_source_pull( + &self, + global: &GlobalScope, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // Assert: stream.[[backpressure]] is true. + assert!(self.backpressure.get()); + + // Assert: stream.[[backpressureChangePromise]] is not undefined. + assert!(self.backpressure_change_promise.borrow().is_some()); + + // Perform ! TransformStreamSetBackpressure(stream, false). + self.set_backpressure(global, false, can_gc); + + // Return stream.[[backpressureChangePromise]]. + Ok(self + .backpressure_change_promise + .borrow() + .clone() + .expect("Promise must be some by now.")) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-error-writable-and-unblock-write> + pub(crate) fn error_writable_and_unblock_write( + &self, + cx: SafeJSContext, + global: &GlobalScope, + error: SafeHandleValue, + can_gc: CanGc, + ) { + // Perform ! TransformStreamDefaultControllerClearAlgorithms(stream.[[controller]]). + self.get_controller().clear_algorithms(); + + // Perform ! WritableStreamDefaultControllerErrorIfNeeded(stream.[[writable]].[[controller]], e). + self.get_writable() + .get_default_controller() + .error_if_needed(cx, error, global, can_gc); + + // Perform ! TransformStreamUnblockWrite(stream). + self.unblock_write(global, can_gc) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-unblock-write> + pub(crate) fn unblock_write(&self, global: &GlobalScope, can_gc: CanGc) { + // If stream.[[backpressure]] is true, perform ! TransformStreamSetBackpressure(stream, false). + if self.backpressure.get() { + self.set_backpressure(global, false, can_gc); + } + } + + /// <https://streams.spec.whatwg.org/#transform-stream-error> + pub(crate) fn error( + &self, + cx: SafeJSContext, + global: &GlobalScope, + error: SafeHandleValue, + can_gc: CanGc, + ) { + // Perform ! ReadableStreamDefaultControllerError(stream.[[readable]].[[controller]], e). + self.get_readable() + .get_default_controller() + .error(error, can_gc); + + // Perform ! TransformStreamErrorWritableAndUnblockWrite(stream, e). + self.error_writable_and_unblock_write(cx, global, error, can_gc); + } +} + +impl TransformStreamMethods<crate::DomTypeHolder> for TransformStream { + /// <https://streams.spec.whatwg.org/#ts-constructor> + #[allow(unsafe_code)] + fn Constructor( + cx: SafeJSContext, + global: &GlobalScope, + proto: Option<SafeHandleObject>, + can_gc: CanGc, + transformer: Option<*mut JSObject>, + writable_strategy: &QueuingStrategy, + readable_strategy: &QueuingStrategy, + ) -> Fallible<DomRoot<TransformStream>> { + // If transformer is missing, set it to null. + rooted!(in(*cx) let transformer_obj = transformer.unwrap_or(ptr::null_mut())); + + // Let underlyingSinkDict be underlyingSink, + // converted to an IDL value of type UnderlyingSink. + let transformer_dict = if !transformer_obj.is_null() { + rooted!(in(*cx) let obj_val = ObjectValue(transformer_obj.get())); + match Transformer::new(cx, obj_val.handle()) { + Ok(ConversionResult::Success(val)) => val, + Ok(ConversionResult::Failure(error)) => return Err(Error::Type(error.to_string())), + _ => { + return Err(Error::JSFailed); + }, + } + } else { + Transformer::empty() + }; + + // If transformerDict["readableType"] exists, throw a RangeError exception. + if !transformer_dict.readableType.handle().is_undefined() { + return Err(Error::Range("readableType is set".to_string())); + } + + // If transformerDict["writableType"] exists, throw a RangeError exception. + if !transformer_dict.writableType.handle().is_undefined() { + return Err(Error::Range("writableType is set".to_string())); + } + + // Let readableHighWaterMark be ? ExtractHighWaterMark(readableStrategy, 0). + let readable_high_water_mark = extract_high_water_mark(readable_strategy, 0.0)?; + + // Let readableSizeAlgorithm be ! ExtractSizeAlgorithm(readableStrategy). + let readable_size_algorithm = extract_size_algorithm(readable_strategy, can_gc); + + // Let writableHighWaterMark be ? ExtractHighWaterMark(writableStrategy, 1). + let writable_high_water_mark = extract_high_water_mark(writable_strategy, 1.0)?; + + // Let writableSizeAlgorithm be ! ExtractSizeAlgorithm(writableStrategy). + let writable_size_algorithm = extract_size_algorithm(writable_strategy, can_gc); + + // Let startPromise be a new promise. + let start_promise = Promise::new(global, can_gc); + + // Perform ! InitializeTransformStream(this, startPromise, writableHighWaterMark, + // writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm). + let stream = TransformStream::new_with_proto(global, proto, can_gc); + stream.initialize( + cx, + global, + start_promise.clone(), + writable_high_water_mark, + writable_size_algorithm, + readable_high_water_mark, + readable_size_algorithm, + can_gc, + )?; + + // Perform ? SetUpTransformStreamDefaultControllerFromTransformer(this, transformer, transformerDict). + stream.set_up_transform_stream_default_controller_from_transformer( + global, + transformer_obj.handle(), + &transformer_dict, + can_gc, + ); + + // If transformerDict["start"] exists, then resolve startPromise with the + // result of invoking transformerDict["start"] + // with argument list « this.[[controller]] » and callback this value transformer. + if let Some(start) = &transformer_dict.start { + rooted!(in(*cx) let mut result_object = ptr::null_mut::<JSObject>()); + rooted!(in(*cx) let mut result: JSVal); + rooted!(in(*cx) let this_object = transformer_obj.get()); + start.Call_( + &this_object.handle(), + &stream.get_controller(), + result.handle_mut(), + ExceptionHandling::Rethrow, + can_gc, + )?; + let is_promise = unsafe { + if result.is_object() { + result_object.set(result.to_object()); + IsPromiseObject(result_object.handle().into_handle()) + } else { + false + } + }; + let promise = if is_promise { + let promise = Promise::new_with_js_promise(result_object.handle(), cx); + promise + } else { + Promise::new_resolved(global, cx, result.get(), can_gc) + }; + start_promise.resolve_native(&promise, can_gc); + } else { + // Otherwise, resolve startPromise with undefined. + start_promise.resolve_native(&(), can_gc); + }; + + Ok(stream) + } + + /// <https://streams.spec.whatwg.org/#ts-readable> + fn Readable(&self) -> DomRoot<ReadableStream> { + // Return this.[[readable]]. + self.readable.get().expect("readable stream is not set") + } + + /// <https://streams.spec.whatwg.org/#ts-writable> + fn Writable(&self) -> DomRoot<WritableStream> { + // Return this.[[writable]]. + self.writable.get().expect("writable stream is not set") + } +} + +/// <https://streams.spec.whatwg.org/#ts-transfer> +impl Transferable for TransformStream { + type Index = MessagePortIndex; + type Data = MessagePortImpl; + + fn transfer(&self) -> Result<(MessagePortId, MessagePortImpl), ()> { + let global = self.global(); + let realm = enter_realm(&*global); + let comp = InRealm::Entered(&realm); + let cx = GlobalScope::get_cx(); + let can_gc = CanGc::note(); + + // Let readable be value.[[readable]]. + let readable = self.get_readable(); + + // Let writable be value.[[writable]]. + let writable = self.get_writable(); + + // If ! IsReadableStreamLocked(readable) is true, throw a "DataCloneError" DOMException. + if readable.is_locked() { + return Err(()); + } + + // If ! IsWritableStreamLocked(writable) is true, throw a "DataCloneError" DOMException. + if writable.is_locked() { + return Err(()); + } + + // Create the shared port pair + let port_1 = MessagePort::new(&global, can_gc); + global.track_message_port(&port_1, None); + let port_2 = MessagePort::new(&global, can_gc); + global.track_message_port(&port_2, None); + global.entangle_ports(*port_1.message_port_id(), *port_2.message_port_id()); + + // Create a proxy WritableStream wired to port_1 + let proxy_writable = WritableStream::new_with_proto(&global, None, can_gc); + proxy_writable.setup_cross_realm_transform_writable(cx, &port_1, can_gc); + + // Pipe readable into the proxy writable (→ port_1) + let pipe1 = readable.pipe_to( + cx, + &global, + &proxy_writable, + false, + false, + false, + comp, + can_gc, + ); + pipe1.set_promise_is_handled(); + + // Create a proxy ReadableStream wired to port_1 + let proxy_readable = ReadableStream::new_with_proto(&global, None, can_gc); + proxy_readable.setup_cross_realm_transform_readable(cx, &port_1, can_gc); + + // Pipe proxy readable (← port_1) into writable + let pipe2 = + proxy_readable.pipe_to(cx, &global, &writable, false, false, false, comp, can_gc); + pipe2.set_promise_is_handled(); + + // Set dataHolder.[[readable]] to ! StructuredSerializeWithTransfer(readable, « readable »). + // Set dataHolder.[[writable]] to ! StructuredSerializeWithTransfer(writable, « writable »). + port_2.transfer() + } + + fn transfer_receive( + owner: &GlobalScope, + id: MessagePortId, + port_impl: MessagePortImpl, + ) -> Result<DomRoot<Self>, ()> { + let can_gc = CanGc::note(); + + // Let readableRecord be ! StructuredDeserializeWithTransfer(dataHolder.[[readable]], the current Realm). + // Set value.[[readable]] to readableRecord.[[Deserialized]]. + let readable = ReadableStream::transfer_receive(owner, id, port_impl.clone())?; + + // Let writableRecord be ! StructuredDeserializeWithTransfer(dataHolder.[[writable]], the current Realm). + let writable = WritableStream::transfer_receive(owner, id, port_impl)?; + + // Set value.[[readable]] to readableRecord.[[Deserialized]]. + // Set value.[[writable]] to writableRecord.[[Deserialized]]. + // Set value.[[backpressure]], value.[[backpressureChangePromise]], and value.[[controller]] to undefined. + let stream = TransformStream::new_with_proto(owner, None, can_gc); + stream.readable.set(Some(&readable)); + stream.writable.set(Some(&writable)); + + Ok(stream) + } + + fn serialized_storage<'a>( + data: StructuredData<'a, '_>, + ) -> &'a mut Option<HashMap<MessagePortId, Self::Data>> { + match data { + StructuredData::Reader(r) => &mut r.port_impls, + StructuredData::Writer(w) => &mut w.ports, + } + } +} diff --git a/components/script/dom/transformstreamdefaultcontroller.rs b/components/script/dom/transformstreamdefaultcontroller.rs new file mode 100644 index 00000000000..41813ef6f96 --- /dev/null +++ b/components/script/dom/transformstreamdefaultcontroller.rs @@ -0,0 +1,420 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::cell::RefCell; +use std::rc::Rc; + +use dom_struct::dom_struct; +use js::jsapi::{ + ExceptionStackBehavior, Heap, JS_IsExceptionPending, JS_SetPendingException, JSObject, +}; +use js::jsval::UndefinedValue; +use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue}; + +use super::bindings::cell::DomRefCell; +use super::bindings::codegen::Bindings::TransformerBinding::{ + Transformer, TransformerCancelCallback, TransformerFlushCallback, TransformerTransformCallback, +}; +use super::types::TransformStream; +use crate::dom::bindings::callback::ExceptionHandling; +use crate::dom::bindings::codegen::Bindings::TransformStreamDefaultControllerBinding::TransformStreamDefaultControllerMethods; +use crate::dom::bindings::error::{Error, ErrorToJsval, Fallible}; +use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler}; +use crate::realms::{InRealm, enter_realm}; +use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; + +impl js::gc::Rootable for TransformTransformPromiseRejection {} + +/// Reacting to transformPromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-controller-perform-transform> +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct TransformTransformPromiseRejection { + controller: Dom<TransformStreamDefaultController>, +} + +impl Callback for TransformTransformPromiseRejection { + /// Reacting to transformPromise with the following fulfillment steps: + #[allow(unsafe_code)] + fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // Perform ! TransformStreamError(controller.[[stream]], r). + self.controller + .error(cx, &self.controller.global(), v, can_gc); + + // Throw r. + // Note: this is done part of perform_transform(). + } +} + +/// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller> +#[dom_struct] +pub struct TransformStreamDefaultController { + reflector_: Reflector, + + /// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-cancelalgorithm> + #[ignore_malloc_size_of = "Rc is hard"] + cancel: RefCell<Option<Rc<TransformerCancelCallback>>>, + + /// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-flushalgorithm> + #[ignore_malloc_size_of = "Rc is hard"] + flush: RefCell<Option<Rc<TransformerFlushCallback>>>, + + /// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-transformalgorithm> + #[ignore_malloc_size_of = "Rc is hard"] + transform: RefCell<Option<Rc<TransformerTransformCallback>>>, + + /// The JS object used as `this` when invoking sink algorithms. + #[ignore_malloc_size_of = "mozjs"] + transform_obj: Heap<*mut JSObject>, + + /// <https://streams.spec.whatwg.org/#TransformStreamDefaultController-stream> + stream: MutNullableDom<TransformStream>, + + /// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-finishpromise> + #[ignore_malloc_size_of = "Rc is hard"] + finish_promise: DomRefCell<Option<Rc<Promise>>>, +} + +impl TransformStreamDefaultController { + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + fn new_inherited(transformer: &Transformer) -> TransformStreamDefaultController { + TransformStreamDefaultController { + reflector_: Reflector::new(), + cancel: RefCell::new(transformer.cancel.clone()), + flush: RefCell::new(transformer.flush.clone()), + transform: RefCell::new(transformer.transform.clone()), + finish_promise: DomRefCell::new(None), + stream: MutNullableDom::new(None), + transform_obj: Default::default(), + } + } + + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + pub(crate) fn new( + global: &GlobalScope, + transformer: &Transformer, + can_gc: CanGc, + ) -> DomRoot<TransformStreamDefaultController> { + reflect_dom_object( + Box::new(TransformStreamDefaultController::new_inherited(transformer)), + global, + can_gc, + ) + } + + /// Setting the JS object after the heap has settled down. + pub(crate) fn set_transform_obj(&self, this_object: SafeHandleObject) { + self.transform_obj.set(*this_object); + } + + pub(crate) fn set_stream(&self, stream: &TransformStream) { + self.stream.set(Some(stream)); + } + + pub(crate) fn get_finish_promise(&self) -> Option<Rc<Promise>> { + self.finish_promise.borrow().clone() + } + + pub(crate) fn set_finish_promise(&self, promise: Rc<Promise>) { + *self.finish_promise.borrow_mut() = Some(promise); + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-controller-perform-transform> + pub(crate) fn transform_stream_default_controller_perform_transform( + &self, + cx: SafeJSContext, + global: &GlobalScope, + chunk: SafeHandleValue, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // Let transformPromise be the result of performing controller.[[transformAlgorithm]], passing chunk. + let transform_promise = self.perform_transform(cx, global, chunk, can_gc)?; + + // Return the result of reacting to transformPromise with the following rejection steps given the argument r: + rooted!(in(*cx) let mut reject_handler = Some(TransformTransformPromiseRejection { + controller: Dom::from_ref(self), + })); + + let handler = PromiseNativeHandler::new( + global, + None, + reject_handler.take().map(|h| Box::new(h) as Box<_>), + can_gc, + ); + let realm = enter_realm(global); + let comp = InRealm::Entered(&realm); + transform_promise.append_native_handler(&handler, comp, can_gc); + + Ok(transform_promise) + } + + pub(crate) fn perform_transform( + &self, + cx: SafeJSContext, + global: &GlobalScope, + chunk: SafeHandleValue, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // If transformerDict["transform"] exists, set transformAlgorithm to an algorithm which + // takes an argument chunk and returns the result of invoking transformerDict["transform"] with argument list + // « chunk, controller » and callback this value transformer. + let algo = self.transform.borrow().clone(); + let result = if let Some(transform) = algo { + rooted!(in(*cx) let this_object = self.transform_obj.get()); + let call_result = transform.Call_( + &this_object.handle(), + chunk, + self, + ExceptionHandling::Rethrow, + can_gc, + ); + match call_result { + Ok(p) => p, + Err(e) => { + let p = Promise::new(global, can_gc); + p.reject_error(e, can_gc); + p + }, + } + } else { + // Let transformAlgorithm be the following steps, taking a chunk argument: + // Let result be TransformStreamDefaultControllerEnqueue(controller, chunk). + // If result is an abrupt completion, return a promise rejected with result.[[Value]]. + let promise = if let Err(error) = self.enqueue(cx, global, chunk, can_gc) { + rooted!(in(*cx) let mut error_val = UndefinedValue()); + error.to_jsval(cx, global, error_val.handle_mut(), can_gc); + Promise::new_rejected(global, cx, error_val.handle(), can_gc) + } else { + // Otherwise, return a promise resolved with undefined. + Promise::new_resolved(global, cx, (), can_gc) + }; + + promise + }; + + Ok(result) + } + + pub(crate) fn perform_cancel( + &self, + cx: SafeJSContext, + global: &GlobalScope, + chunk: SafeHandleValue, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // If transformerDict["cancel"] exists, set cancelAlgorithm to an algorithm which takes an argument + // reason and returns the result of invoking transformerDict["cancel"] with argument list « reason » + // and callback this value transformer. + let algo = self.cancel.borrow().clone(); + let result = if let Some(cancel) = algo { + rooted!(in(*cx) let this_object = self.transform_obj.get()); + let call_result = cancel.Call_( + &this_object.handle(), + chunk, + ExceptionHandling::Rethrow, + can_gc, + ); + match call_result { + Ok(p) => p, + Err(e) => { + let p = Promise::new(global, can_gc); + p.reject_error(e, can_gc); + p + }, + } + } else { + // Let cancelAlgorithm be an algorithm which returns a promise resolved with undefined. + Promise::new_resolved(global, cx, (), can_gc) + }; + + Ok(result) + } + + pub(crate) fn perform_flush( + &self, + cx: SafeJSContext, + global: &GlobalScope, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // If transformerDict["flush"] exists, set flushAlgorithm to an algorithm which returns the result of + // invoking transformerDict["flush"] with argument list « controller » and callback this value transformer. + let algo = self.flush.borrow().clone(); + let result = if let Some(flush) = algo { + rooted!(in(*cx) let this_object = self.transform_obj.get()); + let call_result = flush.Call_( + &this_object.handle(), + self, + ExceptionHandling::Rethrow, + can_gc, + ); + match call_result { + Ok(p) => p, + Err(e) => { + let p = Promise::new(global, can_gc); + p.reject_error(e, can_gc); + p + }, + } + } else { + // Let flushAlgorithm be an algorithm which returns a promise resolved with undefined. + Promise::new_resolved(global, cx, (), can_gc) + }; + + Ok(result) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-controller-enqueue> + #[allow(unsafe_code)] + pub(crate) fn enqueue( + &self, + cx: SafeJSContext, + global: &GlobalScope, + chunk: SafeHandleValue, + can_gc: CanGc, + ) -> Fallible<()> { + // Let stream be controller.[[stream]]. + let stream = self.stream.get().expect("stream is null"); + + // Let readableController be stream.[[readable]].[[controller]]. + let readable = stream.get_readable(); + let readable_controller = readable.get_default_controller(); + + // If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(readableController) + // is false, throw a TypeError exception. + if !readable_controller.can_close_or_enqueue() { + return Err(Error::Type( + "ReadableStreamDefaultControllerCanCloseOrEnqueue is false".to_owned(), + )); + } + + // Let enqueueResult be ReadableStreamDefaultControllerEnqueue(readableController, chunk). + // If enqueueResult is an abrupt completion, + if let Err(error) = readable_controller.enqueue(cx, chunk, can_gc) { + // Perform ! TransformStreamErrorWritableAndUnblockWrite(stream, enqueueResult.[[Value]]). + rooted!(in(*cx) let mut rooted_error = UndefinedValue()); + error + .clone() + .to_jsval(cx, global, rooted_error.handle_mut(), can_gc); + stream.error_writable_and_unblock_write(cx, global, rooted_error.handle(), can_gc); + + // Throw stream.[[readable]].[[storedError]]. + unsafe { + if !JS_IsExceptionPending(*cx) { + rooted!(in(*cx) let mut stored_error = UndefinedValue()); + readable.get_stored_error(stored_error.handle_mut()); + + JS_SetPendingException( + *cx, + stored_error.handle().into(), + ExceptionStackBehavior::Capture, + ); + } + } + return Err(error); + } + + // Let backpressure be ! ReadableStreamDefaultControllerHasBackpressure(readableController). + let backpressure = readable_controller.has_backpressure(); + + // If backpressure is not stream.[[backpressure]], + if backpressure != stream.get_backpressure() { + // Assert: backpressure is true. + assert!(backpressure); + + // Perform ! TransformStreamSetBackpressure(stream, true). + stream.set_backpressure(global, true, can_gc); + } + Ok(()) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-controller-error> + pub(crate) fn error( + &self, + cx: SafeJSContext, + global: &GlobalScope, + reason: SafeHandleValue, + can_gc: CanGc, + ) { + // Perform ! TransformStreamError(controller.[[stream]], e). + self.stream + .get() + .expect("stream is undefined") + .error(cx, global, reason, can_gc); + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-controller-clear-algorithms> + pub(crate) fn clear_algorithms(&self) { + // Set controller.[[transformAlgorithm]] to undefined. + self.transform.replace(None); + + // Set controller.[[flushAlgorithm]] to undefined. + self.flush.replace(None); + + // Set controller.[[cancelAlgorithm]] to undefined. + self.cancel.replace(None); + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-controller-terminate> + pub(crate) fn terminate(&self, cx: SafeJSContext, global: &GlobalScope, can_gc: CanGc) { + // Let stream be controller.[[stream]]. + let stream = self.stream.get().expect("stream is null"); + + // Let readableController be stream.[[readable]].[[controller]]. + let readable = stream.get_readable(); + let readable_controller = readable.get_default_controller(); + + // Perform ! ReadableStreamDefaultControllerClose(readableController). + readable_controller.close(can_gc); + + // Let error be a TypeError exception indicating that the stream has been terminated. + let error = Error::Type("stream has been terminated".to_owned()); + + // Perform ! TransformStreamErrorWritableAndUnblockWrite(stream, error). + rooted!(in(*cx) let mut rooted_error = UndefinedValue()); + error.to_jsval(cx, global, rooted_error.handle_mut(), can_gc); + stream.error_writable_and_unblock_write(cx, global, rooted_error.handle(), can_gc); + } +} + +impl TransformStreamDefaultControllerMethods<crate::DomTypeHolder> + for TransformStreamDefaultController +{ + /// <https://streams.spec.whatwg.org/#ts-default-controller-desired-size> + fn GetDesiredSize(&self) -> Option<f64> { + // Let readableController be this.[[stream]].[[readable]].[[controller]]. + let readable_controller = self + .stream + .get() + .expect("stream is null") + .get_readable() + .get_default_controller(); + + // Return ! ReadableStreamDefaultControllerGetDesiredSize(readableController). + readable_controller.get_desired_size() + } + + /// <https://streams.spec.whatwg.org/#ts-default-controller-enqueue> + fn Enqueue(&self, cx: SafeJSContext, chunk: SafeHandleValue, can_gc: CanGc) -> Fallible<()> { + // Perform ? TransformStreamDefaultControllerEnqueue(this, chunk). + self.enqueue(cx, &self.global(), chunk, can_gc) + } + + /// <https://streams.spec.whatwg.org/#ts-default-controller-error> + fn Error(&self, cx: SafeJSContext, reason: SafeHandleValue, can_gc: CanGc) -> Fallible<()> { + // Perform ? TransformStreamDefaultControllerError(this, e). + self.error(cx, &self.global(), reason, can_gc); + Ok(()) + } + + /// <https://streams.spec.whatwg.org/#ts-default-controller-terminate> + fn Terminate(&self, can_gc: CanGc) -> Fallible<()> { + // Perform ? TransformStreamDefaultControllerTerminate(this). + self.terminate(GlobalScope::get_cx(), &self.global(), can_gc); + Ok(()) + } +} diff --git a/components/script/dom/trustedhtml.rs b/components/script/dom/trustedhtml.rs index 8508f28c150..d1ca3cd5e71 100644 --- a/components/script/dom/trustedhtml.rs +++ b/components/script/dom/trustedhtml.rs @@ -6,8 +6,11 @@ use std::fmt; use dom_struct::dom_struct; +use crate::conversions::Convert; use crate::dom::bindings::codegen::Bindings::TrustedHTMLBinding::TrustedHTMLMethods; -use crate::dom::bindings::codegen::UnionTypes::TrustedHTMLOrString; +use crate::dom::bindings::codegen::UnionTypes::{ + TrustedHTMLOrNullIsEmptyString, TrustedHTMLOrString, +}; use crate::dom::bindings::error::Fallible; use crate::dom::bindings::reflector::{Reflector, reflect_dom_object}; use crate::dom::bindings::root::DomRoot; @@ -80,3 +83,16 @@ impl TrustedHTMLMethods<crate::DomTypeHolder> for TrustedHTML { DOMString::from(&*self.data) } } + +impl Convert<TrustedHTMLOrString> for TrustedHTMLOrNullIsEmptyString { + fn convert(self) -> TrustedHTMLOrString { + match self { + TrustedHTMLOrNullIsEmptyString::TrustedHTML(trusted_html) => { + TrustedHTMLOrString::TrustedHTML(trusted_html) + }, + TrustedHTMLOrNullIsEmptyString::NullIsEmptyString(str) => { + TrustedHTMLOrString::String(str) + }, + } + } +} diff --git a/components/script/dom/trustedtypepolicyfactory.rs b/components/script/dom/trustedtypepolicyfactory.rs index 0927446b904..284fa7045eb 100644 --- a/components/script/dom/trustedtypepolicyfactory.rs +++ b/components/script/dom/trustedtypepolicyfactory.rs @@ -66,7 +66,7 @@ impl TrustedTypePolicyFactory { (CheckResult::Allowed, Vec::new()) }; - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); // Step 2: If allowedByCSP is "Blocked", throw a TypeError and abort further steps. if allowed_by_csp == CheckResult::Blocked { @@ -230,7 +230,7 @@ impl TrustedTypePolicyFactory { .should_sink_type_mismatch_violation_be_blocked_by_csp( sink, sink_group, &input, ); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); // Step 6.2: If disposition is “Allowed”, return stringified input and abort further steps. if disposition == CheckResult::Allowed { Ok(input) diff --git a/components/script/dom/underlyingsourcecontainer.rs b/components/script/dom/underlyingsourcecontainer.rs index 4acb58bafef..dd4b867df45 100644 --- a/components/script/dom/underlyingsourcecontainer.rs +++ b/components/script/dom/underlyingsourcecontainer.rs @@ -20,6 +20,7 @@ use crate::dom::defaultteeunderlyingsource::DefaultTeeUnderlyingSource; use crate::dom::globalscope::GlobalScope; use crate::dom::messageport::MessagePort; use crate::dom::promise::Promise; +use crate::dom::transformstream::TransformStream; use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; /// <https://streams.spec.whatwg.org/#underlying-source-api> @@ -46,8 +47,7 @@ pub(crate) enum UnderlyingSourceType { /// A struct representing a JS object as underlying source, /// and the actual JS object for use as `thisArg` in callbacks. /// This is used for the `TransformStream` API. - #[allow(unused)] - Transform(/* Dom<TransformStream>, Rc<Promise>*/), + Transform(Dom<TransformStream>, Rc<Promise>), } impl UnderlyingSourceType { @@ -139,9 +139,9 @@ impl UnderlyingSourceContainer { // Call the cancel algorithm for the appropriate branch. tee_underlying_source.cancel_algorithm(cx, global, reason, can_gc) }, - UnderlyingSourceType::Transform() => { + UnderlyingSourceType::Transform(stream, _) => { // Return ! TransformStreamDefaultSourceCancelAlgorithm(stream, reason). - todo!(); + Some(stream.transform_stream_default_source_cancel(cx, global, reason, can_gc)) }, UnderlyingSourceType::Transfer(port) => { // Let cancelAlgorithm be the following steps, taking a reason argument: @@ -213,9 +213,9 @@ impl UnderlyingSourceContainer { Some(Ok(promise)) }, // Note: other source type have no pull steps for now. - UnderlyingSourceType::Transform() => { + UnderlyingSourceType::Transform(stream, _) => { // Return ! TransformStreamDefaultSourcePullAlgorithm(stream). - todo!(); + Some(stream.transform_stream_default_source_pull(&self.global(), can_gc)) }, _ => None, } @@ -280,9 +280,9 @@ impl UnderlyingSourceContainer { // from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable None }, - UnderlyingSourceType::Transform() => { - // Some(transform_underlying_source.start_algorithm()) - todo!(); + UnderlyingSourceType::Transform(_, start_promise) => { + // Let startAlgorithm be an algorithm that returns startPromise. + Some(Ok(start_promise.clone())) }, _ => None, } diff --git a/components/script/dom/urlpattern.rs b/components/script/dom/urlpattern.rs new file mode 100644 index 00000000000..c811d3a9a70 --- /dev/null +++ b/components/script/dom/urlpattern.rs @@ -0,0 +1,204 @@ +/* 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 dom_struct::dom_struct; +use js::rust::HandleObject; +use script_bindings::codegen::GenericUnionTypes::USVStringOrURLPatternInit; +use script_bindings::error::{Error, Fallible}; +use script_bindings::reflector::Reflector; +use script_bindings::root::DomRoot; +use script_bindings::script_runtime::CanGc; +use script_bindings::str::USVString; + +use crate::dom::bindings::codegen::Bindings::URLPatternBinding; +use crate::dom::bindings::codegen::Bindings::URLPatternBinding::URLPatternMethods; +use crate::dom::bindings::reflector::reflect_dom_object_with_proto; +use crate::dom::globalscope::GlobalScope; + +/// <https://urlpattern.spec.whatwg.org/#urlpattern> +#[dom_struct] +pub(crate) struct URLPattern { + reflector: Reflector, + + /// <https://urlpattern.spec.whatwg.org/#urlpattern-associated-url-pattern> + #[no_trace] + associated_url_pattern: urlpattern::UrlPattern, +} + +impl URLPattern { + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + fn new_inherited(associated_url_pattern: urlpattern::UrlPattern) -> URLPattern { + URLPattern { + reflector: Reflector::new(), + associated_url_pattern, + } + } + + /// <https://urlpattern.spec.whatwg.org/#urlpattern-initialize> + pub(crate) fn initialize( + global: &GlobalScope, + proto: Option<HandleObject>, + input: USVStringOrURLPatternInit, + base_url: Option<USVString>, + options: &URLPatternBinding::URLPatternOptions, + can_gc: CanGc, + ) -> Fallible<DomRoot<URLPattern>> { + // The section below converts from servos types to the types used in the urlpattern crate + let base_url = base_url.map(|usv_string| usv_string.0); + let input = bindings_to_third_party::map_urlpattern_input(input, base_url.clone()); + let options = urlpattern::UrlPatternOptions { + ignore_case: options.ignoreCase, + }; + + // Parse and initialize the URL pattern. + let pattern_init = + urlpattern::quirks::process_construct_pattern_input(input, base_url.as_deref()) + .map_err(|error| Error::Type(format!("{error}")))?; + + let pattern = urlpattern::UrlPattern::parse(pattern_init, options) + .map_err(|error| Error::Type(format!("{error}")))?; + + let url_pattern = reflect_dom_object_with_proto( + Box::new(URLPattern::new_inherited(pattern)), + global, + proto, + can_gc, + ); + Ok(url_pattern) + } +} + +impl URLPatternMethods<crate::DomTypeHolder> for URLPattern { + // <https://urlpattern.spec.whatwg.org/#dom-urlpattern-urlpattern> + fn Constructor( + global: &GlobalScope, + proto: Option<HandleObject>, + can_gc: CanGc, + input: USVStringOrURLPatternInit, + base_url: USVString, + options: &URLPatternBinding::URLPatternOptions, + ) -> Fallible<DomRoot<URLPattern>> { + URLPattern::initialize(global, proto, input, Some(base_url), options, can_gc) + } + + /// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-urlpattern-input-options> + fn Constructor_( + global: &GlobalScope, + proto: Option<HandleObject>, + can_gc: CanGc, + input: USVStringOrURLPatternInit, + options: &URLPatternBinding::URLPatternOptions, + ) -> Fallible<DomRoot<URLPattern>> { + // Step 1. Run initialize given this, input, null, and options. + URLPattern::initialize(global, proto, input, None, options, can_gc) + } + + /// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-protocol> + fn Protocol(&self) -> USVString { + // Step 1. Return this’s associated URL pattern’s protocol component’s pattern string. + USVString(self.associated_url_pattern.protocol().to_owned()) + } + + /// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-username> + fn Username(&self) -> USVString { + // Step 1. Return this’s associated URL pattern’s username component’s pattern string. + USVString(self.associated_url_pattern.username().to_owned()) + } + + /// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-password> + fn Password(&self) -> USVString { + // Step 1. Return this’s associated URL pattern’s password component’s pattern string. + USVString(self.associated_url_pattern.password().to_owned()) + } + + /// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-hostname> + fn Hostname(&self) -> USVString { + // Step 1. Return this’s associated URL pattern’s hostname component’s pattern string. + USVString(self.associated_url_pattern.hostname().to_owned()) + } + + /// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-port> + fn Port(&self) -> USVString { + // Step 1. Return this’s associated URL pattern’s port component’s pattern string. + USVString(self.associated_url_pattern.port().to_owned()) + } + + /// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-pathname> + fn Pathname(&self) -> USVString { + // Step 1. Return this’s associated URL pattern’s pathname component’s pattern string. + USVString(self.associated_url_pattern.pathname().to_owned()) + } + + /// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-search> + fn Search(&self) -> USVString { + // Step 1. Return this’s associated URL pattern’s search component’s pattern string. + USVString(self.associated_url_pattern.search().to_owned()) + } + + /// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-hash> + fn Hash(&self) -> USVString { + // Step 1. Return this’s associated URL pattern’s hash component’s pattern string. + USVString(self.associated_url_pattern.hash().to_owned()) + } + + /// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-hasregexpgroups> + fn HasRegExpGroups(&self) -> bool { + // Step 1. If this’s associated URL pattern’s has regexp groups, then return true. + // Step 2. Return false. + self.associated_url_pattern.has_regexp_groups() + } +} + +mod bindings_to_third_party { + use crate::dom::urlpattern::USVStringOrURLPatternInit; + + pub(super) fn map_urlpattern_input( + input: USVStringOrURLPatternInit, + base_url: Option<String>, + ) -> urlpattern::quirks::StringOrInit { + match input { + USVStringOrURLPatternInit::USVString(usv_string) => { + urlpattern::quirks::StringOrInit::String(usv_string.0) + }, + USVStringOrURLPatternInit::URLPatternInit(pattern_init) => { + let pattern_init = urlpattern::quirks::UrlPatternInit { + protocol: pattern_init + .protocol + .as_ref() + .map(|usv_string| usv_string.to_string()), + username: pattern_init + .username + .as_ref() + .map(|usv_string| usv_string.to_string()), + password: pattern_init + .password + .as_ref() + .map(|usv_string| usv_string.to_string()), + hostname: pattern_init + .hostname + .as_ref() + .map(|usv_string| usv_string.to_string()), + port: pattern_init + .port + .as_ref() + .map(|usv_string| usv_string.to_string()), + pathname: pattern_init + .pathname + .as_ref() + .map(|usv_string| usv_string.to_string()), + search: pattern_init + .search + .as_ref() + .map(|usv_string| usv_string.to_string()), + hash: pattern_init + .hash + .as_ref() + .map(|usv_string| usv_string.to_string()), + base_url, + }; + urlpattern::quirks::StringOrInit::Init(pattern_init) + }, + } + } +} diff --git a/components/script/dom/urlpattern/mod.rs b/components/script/dom/urlpattern/mod.rs deleted file mode 100644 index e92963c672b..00000000000 --- a/components/script/dom/urlpattern/mod.rs +++ /dev/null @@ -1,810 +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/. */ - -mod pattern_parser; -mod preprocessing; -mod tokenizer; - -use std::ptr; - -use dom_struct::dom_struct; -use js::jsapi::{Heap, JSObject, RegExpFlag_IgnoreCase, RegExpFlag_UnicodeSets, RegExpFlags}; -use js::rust::HandleObject; -use pattern_parser::parse_a_pattern_string; -use preprocessing::{ - canonicalize_a_hash, canonicalize_a_hostname, canonicalize_a_password, canonicalize_a_pathname, - canonicalize_a_port, canonicalize_a_protocol, canonicalize_a_search, canonicalize_a_username, - escape_a_regexp_string, process_a_url_pattern_init, -}; -use script_bindings::error::{Error, Fallible}; -use script_bindings::reflector::Reflector; -use script_bindings::root::DomRoot; -use script_bindings::script_runtime::CanGc; -use script_bindings::str::USVString; - -use crate::dom::bindings::cell::RefCell; -use crate::dom::bindings::codegen::Bindings::URLPatternBinding::{ - URLPatternInit, URLPatternMethods, URLPatternOptions, -}; -use crate::dom::bindings::reflector::reflect_dom_object_with_proto; -use crate::dom::globalscope::GlobalScope; -use crate::dom::htmlinputelement::new_js_regex; - -/// <https://urlpattern.spec.whatwg.org/#full-wildcard-regexp-value> -const FULL_WILDCARD_REGEXP_VALUE: &str = ".*"; - -/// <https://urlpattern.spec.whatwg.org/#urlpattern> -#[dom_struct] -pub(crate) struct URLPattern { - reflector: Reflector, - - /// <https://urlpattern.spec.whatwg.org/#urlpattern-associated-url-pattern> - associated_url_pattern: RefCell<URLPatternInternal>, -} - -#[derive(JSTraceable, MallocSizeOf)] -#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] -struct URLPatternInternal { - /// <https://urlpattern.spec.whatwg.org/#url-pattern-protocol-component> - protocol: Component, - - /// <https://urlpattern.spec.whatwg.org/#url-pattern-username-component> - username: Component, - - /// <https://urlpattern.spec.whatwg.org/#url-pattern-password-component> - password: Component, - - /// <https://urlpattern.spec.whatwg.org/#url-pattern-hostname-component> - hostname: Component, - - /// <https://urlpattern.spec.whatwg.org/#url-pattern-port-component> - port: Component, - - /// <https://urlpattern.spec.whatwg.org/#url-pattern-pathname-component> - pathname: Component, - - /// <https://urlpattern.spec.whatwg.org/#url-pattern-search-component> - search: Component, - - /// <https://urlpattern.spec.whatwg.org/#url-pattern-hash-component> - hash: Component, -} - -/// <https://urlpattern.spec.whatwg.org/#component> -#[derive(JSTraceable, MallocSizeOf)] -#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] -struct Component { - /// <https://urlpattern.spec.whatwg.org/#component-pattern-string> - pattern_string: USVString, - - /// <https://urlpattern.spec.whatwg.org/#component-regular-expression> - #[ignore_malloc_size_of = "mozjs"] - regular_expression: Box<Heap<*mut JSObject>>, - - /// <https://urlpattern.spec.whatwg.org/#component-group-name-list> - group_name_list: Vec<USVString>, - - /// <https://urlpattern.spec.whatwg.org/#component-has-regexp-groups> - has_regexp_groups: bool, -} - -/// <https://urlpattern.spec.whatwg.org/#part> -#[derive(Debug)] -struct Part { - /// <https://urlpattern.spec.whatwg.org/#part-type> - part_type: PartType, - - /// <https://urlpattern.spec.whatwg.org/#part-value> - value: String, - - /// <https://urlpattern.spec.whatwg.org/#part-modifier> - modifier: PartModifier, - - /// <https://urlpattern.spec.whatwg.org/#part-name> - name: String, - - /// <https://urlpattern.spec.whatwg.org/#part-prefix> - prefix: String, - - /// <https://urlpattern.spec.whatwg.org/#part-suffix> - suffix: String, -} - -/// <https://urlpattern.spec.whatwg.org/#part-type> -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum PartType { - /// <https://urlpattern.spec.whatwg.org/#part-type-fixed-text> - FixedText, - - /// <https://urlpattern.spec.whatwg.org/#part-type-regexp> - Regexp, - - /// <https://urlpattern.spec.whatwg.org/#part-type-segment-wildcard> - SegmentWildcard, - - /// <https://urlpattern.spec.whatwg.org/#part-type-full-wildcard> - FullWildcard, -} - -/// <https://urlpattern.spec.whatwg.org/#part-modifier> -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -#[allow(dead_code)] // Parser is not implemented yet -enum PartModifier { - /// <https://urlpattern.spec.whatwg.org/#part-modifier-none> - None, - - /// <https://urlpattern.spec.whatwg.org/#part-modifier-optional> - Optional, - - /// <https://urlpattern.spec.whatwg.org/#part-modifier-zero-or-more> - ZeroOrMore, - - /// <https://urlpattern.spec.whatwg.org/#part-modifier-one-or-more> - OneOrMore, -} - -/// <https://urlpattern.spec.whatwg.org/#options> -#[derive(Clone, Copy, Default)] -#[allow(dead_code)] // Parser is not fully implemented yet -struct Options { - /// <https://urlpattern.spec.whatwg.org/#options-delimiter-code-point> - delimiter_code_point: Option<char>, - - /// <https://urlpattern.spec.whatwg.org/#options-prefix-code-point> - prefix_code_point: Option<char>, - - /// <https://urlpattern.spec.whatwg.org/#options-ignore-case> - ignore_case: bool, -} - -impl Component { - fn new_unrooted() -> Self { - Self { - pattern_string: Default::default(), - regular_expression: Heap::boxed(ptr::null_mut()), - group_name_list: Default::default(), - has_regexp_groups: false, - } - } -} - -impl URLPattern { - #[cfg_attr(crown, allow(crown::unrooted_must_root))] - fn new_inherited() -> URLPattern { - let associated_url_pattern = URLPatternInternal { - protocol: Component::new_unrooted(), - username: Component::new_unrooted(), - password: Component::new_unrooted(), - hostname: Component::new_unrooted(), - port: Component::new_unrooted(), - pathname: Component::new_unrooted(), - search: Component::new_unrooted(), - hash: Component::new_unrooted(), - }; - - URLPattern { - reflector: Reflector::new(), - associated_url_pattern: RefCell::new(associated_url_pattern), - } - } - - #[cfg_attr(crown, allow(crown::unrooted_must_root))] - pub(crate) fn new_with_proto( - global: &GlobalScope, - proto: Option<HandleObject>, - can_gc: CanGc, - ) -> DomRoot<URLPattern> { - reflect_dom_object_with_proto(Box::new(URLPattern::new_inherited()), global, proto, can_gc) - } - - /// <https://urlpattern.spec.whatwg.org/#urlpattern-initialize> - fn initialize( - global: &GlobalScope, - proto: Option<HandleObject>, - input: &URLPatternInit, - options: &URLPatternOptions, - can_gc: CanGc, - ) -> Fallible<DomRoot<URLPattern>> { - // Step 1. Set this’s associated URL pattern to the result of create given input, baseURL, and options. - let pattern = URLPattern::new_with_proto(global, proto, can_gc); - URLPatternInternal::create( - input, - options, - &mut pattern.associated_url_pattern.borrow_mut(), - )?; - - Ok(pattern) - } -} - -impl URLPatternMethods<crate::DomTypeHolder> for URLPattern { - /// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-urlpattern-input-options> - fn Constructor( - global: &GlobalScope, - proto: Option<HandleObject>, - can_gc: CanGc, - input: &URLPatternInit, - options: &URLPatternOptions, - ) -> Fallible<DomRoot<URLPattern>> { - // Step 1. Run initialize given this, input, null, and options. - URLPattern::initialize(global, proto, input, options, can_gc) - } - - /// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-protocol> - fn Protocol(&self) -> USVString { - // Step 1. Return this’s associated URL pattern’s protocol component’s pattern string. - self.associated_url_pattern - .borrow() - .protocol - .pattern_string - .clone() - } - - /// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-username> - fn Username(&self) -> USVString { - // Step 1. Return this’s associated URL pattern’s username component’s pattern string. - self.associated_url_pattern - .borrow() - .username - .pattern_string - .clone() - } - - /// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-password> - fn Password(&self) -> USVString { - // Step 1. Return this’s associated URL pattern’s password component’s pattern string. - self.associated_url_pattern - .borrow() - .password - .pattern_string - .clone() - } - - /// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-hostname> - fn Hostname(&self) -> USVString { - // Step 1. Return this’s associated URL pattern’s hostname component’s pattern string. - self.associated_url_pattern - .borrow() - .hostname - .pattern_string - .clone() - } - - /// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-port> - fn Port(&self) -> USVString { - // Step 1. Return this’s associated URL pattern’s port component’s pattern string. - self.associated_url_pattern - .borrow() - .port - .pattern_string - .clone() - } - - /// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-pathname> - fn Pathname(&self) -> USVString { - // Step 1. Return this’s associated URL pattern’s pathname component’s pattern string. - self.associated_url_pattern - .borrow() - .pathname - .pattern_string - .clone() - } - - /// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-search> - fn Search(&self) -> USVString { - // Step 1. Return this’s associated URL pattern’s search component’s pattern string. - self.associated_url_pattern - .borrow() - .search - .pattern_string - .clone() - } - - /// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-hash> - fn Hash(&self) -> USVString { - // Step 1. Return this’s associated URL pattern’s hash component’s pattern string. - self.associated_url_pattern - .borrow() - .hash - .pattern_string - .clone() - } - - /// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-hasregexpgroups> - fn HasRegExpGroups(&self) -> bool { - // Step 1. If this’s associated URL pattern’s has regexp groups, then return true. - // Step 2. Return false. - self.associated_url_pattern.borrow().has_regexp_groups() - } -} - -impl URLPatternInternal { - /// <https://urlpattern.spec.whatwg.org/#url-pattern-create> - fn create(input: &URLPatternInit, options: &URLPatternOptions, out: &mut Self) -> Fallible<()> { - // Step 1. Let init be null. - // Step 2. If input is a scalar value string then: - // NOTE: We don't support strings as input yet - // Step 3. Otherwise: - // Step 3.1 Assert: input is a URLPatternInit. - // Step 3.2 If baseURL is not null, then throw a TypeError. - if input.baseURL.is_some() { - return Err(Error::Type("baseURL must be none".into())); - } - - // Step 3.3 Set init to input. - let init = input; - - // Step 4. Let processedInit be the result of process a URLPatternInit given init, "pattern", null, null, - // null, null, null, null, null, and null. - let mut processed_init = process_a_url_pattern_init(init, PatternInitType::Pattern)?; - - // Step 5. For each componentName of « "protocol", "username", "password", "hostname", "port", - // "pathname", "search", "hash" »: - // Step 5.1 If processedInit[componentName] does not exist, then set processedInit[componentName] to "*". - // NOTE: We do this later on - - // Step 6. If processedInit["protocol"] is a special scheme and processedInit["port"] is a string - // which represents its corresponding default port in radix-10 using ASCII digits then set - // processedInit["port"] to the empty string. - let default_port = processed_init - .protocol - .as_deref() - .and_then(default_port_for_special_scheme); - let given_port = processed_init - .port - .as_deref() - .map(str::parse) - .transpose() - .ok() - .flatten(); - if default_port.is_some() && default_port == given_port { - processed_init.port = Some(Default::default()); - } - - // Step 7. Let urlPattern be a new URL pattern. - // NOTE: We construct the pattern provided as the out parameter. - - // Step 8. Set urlPattern’s protocol component to the result of compiling a component given - // processedInit["protocol"], canonicalize a protocol, and default options. - Component::compile( - processed_init.protocol.as_deref().unwrap_or("*"), - Box::new(canonicalize_a_protocol), - Options::default(), - &mut out.protocol, - )?; - - // Step 9. Set urlPattern’s username component to the result of compiling a component given - // processedInit["username"], canonicalize a username, and default options. - Component::compile( - processed_init.username.as_deref().unwrap_or("*"), - Box::new(|i| Ok(canonicalize_a_username(i))), - Options::default(), - &mut out.username, - )?; - - // Step 10. Set urlPattern’s password component to the result of compiling a component given - // processedInit["password"], canonicalize a password, and default options. - Component::compile( - processed_init.password.as_deref().unwrap_or("*"), - Box::new(|i| Ok(canonicalize_a_password(i))), - Options::default(), - &mut out.password, - )?; - - // FIXME: Steps 11 and 12: Compile host pattern correctly - Component::compile( - processed_init.hostname.as_deref().unwrap_or("*"), - Box::new(canonicalize_a_hostname), - Options::HOSTNAME, - &mut out.hostname, - )?; - - // Step 13. Set urlPattern’s port component to the result of compiling a component given - // processedInit["port"], canonicalize a port, and default options. - Component::compile( - processed_init.port.as_deref().unwrap_or("*"), - Box::new(|i| canonicalize_a_port(i, None)), - Options::default(), - &mut out.port, - )?; - - // FIXME: Step 14: respect ignore case option from here on out - let _ = options; - - // FIXME: Steps 15-16: Compile path pattern correctly - Component::compile( - processed_init.pathname.as_deref().unwrap_or("*"), - Box::new(|i| Ok(canonicalize_a_pathname(i))), - Options::PATHNAME, - &mut out.pathname, - )?; - - // Step 17. Set urlPattern’s search component to the result of compiling a component given - // processedInit["search"], canonicalize a search, and compileOptions. - Component::compile( - processed_init.search.as_deref().unwrap_or("*"), - Box::new(|i| Ok(canonicalize_a_search(i))), - Options::default(), - &mut out.search, - )?; - - // Step 18. Set urlPattern’s hash component to the result of compiling a component given - // processedInit["hash"], canonicalize a hash, and compileOptions. - Component::compile( - processed_init.hash.as_deref().unwrap_or("*"), - Box::new(|i| Ok(canonicalize_a_hash(i))), - Options::default(), - &mut out.hash, - )?; - - // Step 19. Return urlPattern. - // NOTE: not necessary since we use an out parameter - Ok(()) - } - - /// <https://urlpattern.spec.whatwg.org/#url-pattern-has-regexp-groups> - fn has_regexp_groups(&self) -> bool { - self.protocol.has_regexp_groups || - self.username.has_regexp_groups || - self.password.has_regexp_groups || - self.hostname.has_regexp_groups || - self.port.has_regexp_groups || - self.pathname.has_regexp_groups || - self.search.has_regexp_groups || - self.hash.has_regexp_groups - } -} - -impl Component { - /// <https://urlpattern.spec.whatwg.org/#compile-a-component> - fn compile( - input: &str, - encoding_callback: EncodingCallback, - options: Options, - out: &mut Self, - ) -> Fallible<()> { - // Step 1. Let part list be the result of running parse a pattern string given input, options, - // and encoding callback. - let part_list = parse_a_pattern_string(input, options, encoding_callback)?; - - // Step 2. Let (regular expression string, name list) be the result of running generate a regular expression and - // name list given part list and options. - let (regular_expression_string, name_list) = - generate_a_regular_expression_and_name_list(&part_list, options); - - log::debug!("Compiled {input:?} (URLPattern) to {regular_expression_string:?} (Regex)"); - - // Step 3. Let flags be an empty string. - // Step 4. If options’s ignore case is true then set flags to "vi". - let flags = if options.ignore_case { - RegExpFlags { - flags_: RegExpFlag_UnicodeSets | RegExpFlag_IgnoreCase, - } - } - // Step 5. Otherwise set flags to "v" - else { - RegExpFlags { - flags_: RegExpFlag_UnicodeSets, - } - }; - - // Step 6. Let regular expression be RegExpCreate(regular expression string, flags). - // If this throws an exception, catch it, and throw a TypeError. - let cx = GlobalScope::get_cx(); - rooted!(in(*cx) let mut regular_expression: *mut JSObject = ptr::null_mut()); - let succeeded = new_js_regex( - cx, - ®ular_expression_string, - flags, - regular_expression.handle_mut(), - ); - if !succeeded { - return Err(Error::Type(format!( - "Failed to compile {regular_expression_string:?} as a regular expression" - ))); - } - - // TODO Step 7. Let pattern string be the result of running generate a pattern string given - // part list and options. - let pattern_string = Default::default(); - - // Step 8. Let has regexp groups be false. - // Step 9. For each part of part list: - // Step 9.1 If part’s type is "regexp", then set has regexp groups to true. - let has_regexp_groups = part_list - .iter() - .any(|part| part.part_type == PartType::Regexp); - - // Step 10. Return a new component whose pattern string is pattern string, regular expression - // is regular expression, group name list is name list, and has regexp groups is has regexp groups. - out.pattern_string = pattern_string; - out.regular_expression.set(*regular_expression.handle()); - out.group_name_list = name_list; - out.has_regexp_groups = has_regexp_groups; - - Ok(()) - } -} - -/// <https://urlpattern.spec.whatwg.org/#generate-a-regular-expression-and-name-list> -fn generate_a_regular_expression_and_name_list( - part_list: &[Part], - options: Options, -) -> (String, Vec<USVString>) { - // Step 1. Let result be "^". - let mut result = String::from("^"); - - // Step 2. Let name list be a new list. - let mut name_list = vec![]; - - // Step 3. For each part of part list: - for part in part_list { - // Step 3.1 If part’s type is "fixed-text": - if part.part_type == PartType::FixedText { - // Step 3.1.1 If part’s modifier is "none", then append the result of running escape a regexp string given - // part’s value to the end of result. - if part.modifier == PartModifier::None { - result.push_str(&escape_a_regexp_string(&part.value)); - } - // Step 3.1.2 Otherwise: - else { - // Step 3.1.2.1 Append "(?:" to the end of result. - result.push_str("(?:"); - - // Step 3.1.2.2 Append the result of running escape a regexp string given part’s value - // to the end of result. - result.push_str(&escape_a_regexp_string(&part.value)); - - // Step 3.1.2.3 Append ")" to the end of result. - result.push(')'); - - // Step 3.1.2.4 Append the result of running convert a modifier to a string given part’s - // modifier to the end of result. - result.push_str(part.modifier.convert_to_string()); - } - - // Step 3.1.3 Continue. - continue; - } - - // Step 3.2 Assert: part’s name is not the empty string. - debug_assert!(!part.name.is_empty()); - - // Step 3.3 Append part’s name to name list. - name_list.push(USVString(part.name.to_string())); - - // Step 3.4 Let regexp value be part’s value. - let mut regexp_value = part.value.clone(); - - // Step 3.5 If part’s type is "segment-wildcard", then set regexp value to the result of running - // generate a segment wildcard regexp given options. - if part.part_type == PartType::SegmentWildcard { - regexp_value = generate_a_segment_wildcard_regexp(options); - } - // Step 3.6 Otherwise if part’s type is "full-wildcard", then set regexp value to full wildcard regexp value. - else if part.part_type == PartType::FullWildcard { - regexp_value = FULL_WILDCARD_REGEXP_VALUE.into(); - } - - // Step 3.7 If part’s prefix is the empty string and part’s suffix is the empty string: - if part.prefix.is_empty() && part.suffix.is_empty() { - // Step 3.7.1 If part’s modifier is "none" or "optional", then: - if matches!(part.modifier, PartModifier::None | PartModifier::Optional) { - // Step 3.7.1.1 Append "(" to the end of result. - result.push('('); - - // Step 3.7.1.2 Append regexp value to the end of result. - result.push_str(®exp_value); - - // Step 3.7.1.3 Append ")" to the end of result. - result.push(')'); - - // Step 3.7.1.4 Append the result of running convert a modifier to a string given part’s modifier - // to the end of result. - result.push_str(part.modifier.convert_to_string()); - } - // Step 3.7.2 Otherwise: - else { - // Step 3.7.2.1 Append "((?:" to the end of result. - result.push_str("((?:"); - - // Step 3.7.2.2 Append regexp value to the end of result. - result.push_str(®exp_value); - - // Step 3.7.2.3 Append ")" to the end of result. - result.push(')'); - - // Step 3.7.2.4 Append the result of running convert a modifier to a string given part’s modifier - // to the end of result. - result.push_str(part.modifier.convert_to_string()); - - // Step 3.7.2.5 Append ")" to the end of result. - result.push(')'); - } - - // Step 3.7.3 Continue. - continue; - } - - // Step 3.8 If part’s modifier is "none" or "optional": - if matches!(part.modifier, PartModifier::None | PartModifier::Optional) { - // Step 3.8.1 Append "(?:" to the end of result. - result.push_str("(?:"); - - // Step 3.8.2 Append the result of running escape a regexp string given part’s prefix - // to the end of result. - result.push_str(&escape_a_regexp_string(&part.prefix)); - - // Step 3.8.3 Append "(" to the end of result. - result.push('('); - - // Step 3.8.4 Append regexp value to the end of result. - result.push_str(®exp_value); - - // Step 3.8.5 Append ")" to the end of result. - result.push(')'); - - // Step 3.8.6 Append the result of running escape a regexp string given part’s suffix - // to the end of result. - result.push_str(&escape_a_regexp_string(&part.suffix)); - - // Step 3.8.7 Append ")" to the end of result. - result.push(')'); - - // Step 3.8.8 Append the result of running convert a modifier to a string given part’s modifier to - // the end of result. - result.push_str(part.modifier.convert_to_string()); - - // Step 3.8.9 Continue. - continue; - } - - // Step 3.9 Assert: part’s modifier is "zero-or-more" or "one-or-more". - debug_assert!(matches!( - part.modifier, - PartModifier::ZeroOrMore | PartModifier::OneOrMore - )); - - // Step 3.10 Assert: part’s prefix is not the empty string or part’s suffix is not the empty string. - debug_assert!(!part.prefix.is_empty() || !part.suffix.is_empty()); - - // Step 3.11 Append "(?:" to the end of result. - result.push_str("(?:"); - - // Step 3.12 Append the result of running escape a regexp string given part’s prefix to the end of result. - result.push_str(&escape_a_regexp_string(&part.prefix)); - - // Step 3.13 Append "((?:" to the end of result. - result.push_str("((?:"); - - // Step 3.14 Append regexp value to the end of result. - result.push_str(®exp_value); - - // Step 3.15 Append ")(?:" to the end of result. - result.push_str(")(?:"); - - // Step 3.16 Append the result of running escape a regexp string given part’s suffix to the end of result. - result.push_str(&escape_a_regexp_string(&part.suffix)); - - // Step 3.17 Append the result of running escape a regexp string given part’s prefix to the end of result. - result.push_str(&escape_a_regexp_string(&part.prefix)); - - // Step 3.18 Append "(?:" to the end of result. - result.push_str("(?:"); - - // Step 3.19 Append regexp value to the end of result. - result.push_str(®exp_value); - - // Step 3.20 Append "))*)" to the end of result. - result.push_str("))*)"); - - // Step 3.21 Append the result of running escape a regexp string given part’s suffix to the end of result. - result.push_str(&escape_a_regexp_string(&part.suffix)); - - // Step 3.22 Append ")" to the end of result. - result.push(')'); - - // Step 3.23 If part’s modifier is "zero-or-more" then append "?" to the end of result. - if part.modifier == PartModifier::ZeroOrMore { - result.push('?'); - } - } - - // Step 4. Append "$" to the end of result. - result.push('$'); - - // Step 5. Return (result, name list). - (result, name_list) -} - -/// <https://urlpattern.spec.whatwg.org/#encoding-callback> -type EncodingCallback = Box<dyn Fn(&str) -> Fallible<String>>; - -// FIXME: Deduplicate this with the url crate -/// <https://url.spec.whatwg.org/#special-scheme> -fn default_port_for_special_scheme(scheme: &str) -> Option<u16> { - match scheme { - "ftp" => Some(21), - "http" | "ws" => Some(80), - "https" | "wss" => Some(443), - _ => None, - } -} - -/// <https://url.spec.whatwg.org/#special-scheme> -fn is_special_scheme(scheme: &str) -> bool { - matches!(scheme, "ftp" | "http" | "https" | "ws" | "wss") -} - -/// <https://urlpattern.spec.whatwg.org/#generate-a-segment-wildcard-regexp> -fn generate_a_segment_wildcard_regexp(options: Options) -> String { - // Step 1. Let result be "[^". - let mut result = String::from("[^"); - - // Step 2. Append the result of running escape a regexp string given options’s - // delimiter code point to the end of result. - result.push_str(&escape_a_regexp_string( - &options - .delimiter_code_point - .map(|c| c.to_string()) - .unwrap_or_default(), - )); - - // Step 3. Append "]+?" to the end of result. - result.push_str("]+?"); - - // Step 4. Return result. - result -} - -impl PartModifier { - /// <https://urlpattern.spec.whatwg.org/#convert-a-modifier-to-a-string> - fn convert_to_string(&self) -> &'static str { - match self { - // Step 1. If modifier is "zero-or-more", then return "*". - Self::ZeroOrMore => "*", - // Step 2. If modifier is "optional", then return "?". - Self::Optional => "?", - // Step 3. If modifier is "one-or-more", then return "+". - Self::OneOrMore => "+", - // Step 4. Return the empty string. - _ => "", - } - } -} - -impl Options { - /// <https://urlpattern.spec.whatwg.org/#hostname-options> - const HOSTNAME: Self = Self { - delimiter_code_point: Some('.'), - prefix_code_point: None, - ignore_case: false, - }; - - /// <https://urlpattern.spec.whatwg.org/#pathname-options> - const PATHNAME: Self = Self { - delimiter_code_point: Some('/'), - prefix_code_point: Some('/'), - ignore_case: false, - }; -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum PatternInitType { - Pattern, - Url, -} - -impl Part { - fn new(part_type: PartType, value: String, modifier: PartModifier) -> Self { - Self { - part_type, - value, - modifier, - name: String::new(), - prefix: String::new(), - suffix: String::new(), - } - } -} diff --git a/components/script/dom/urlpattern/pattern_parser.rs b/components/script/dom/urlpattern/pattern_parser.rs deleted file mode 100644 index 3147c5649f4..00000000000 --- a/components/script/dom/urlpattern/pattern_parser.rs +++ /dev/null @@ -1,473 +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 script_bindings::error::{Error, Fallible}; - -use crate::dom::urlpattern::tokenizer::{Token, TokenType, TokenizePolicy, tokenize}; -use crate::dom::urlpattern::{ - EncodingCallback, FULL_WILDCARD_REGEXP_VALUE, Options, Part, PartModifier, PartType, - generate_a_segment_wildcard_regexp, -}; - -/// <https://urlpattern.spec.whatwg.org/#parse-a-pattern-string> -pub(super) fn parse_a_pattern_string( - input: &str, - options: Options, - encoding_callback: EncodingCallback, -) -> Fallible<Vec<Part>> { - // Step 1. Let parser be a new pattern parser whose encoding callback is encoding callback and - // segment wildcard regexp is the result of running generate a segment wildcard regexp given options. - let mut parser = PatternParser::new( - generate_a_segment_wildcard_regexp(options), - encoding_callback, - ); - - // Step 2. Set parser’s token list to the result of running tokenize given input and "strict". - parser.token_list = tokenize(input, TokenizePolicy::Strict)?; - - // Step 3. While parser’s index is less than parser’s token list’s size: - while parser.index < parser.token_list.len() { - // Step 3.1 Let char token be the result of running try to consume a token given parser and "char". - let char_token = parser.try_to_consume_a_token(TokenType::Char); - - // Step 3.2 Let name token be the result of running try to consume a token given parser and "name". - let mut name_token = parser.try_to_consume_a_token(TokenType::Name); - - // Step 3.3 Let regexp or wildcard token be the result of running try to consume a - // regexp or wildcard token given parser and name token. - let mut regexp_or_wildcard_token = - parser.try_to_consume_a_regexp_or_wildcard_token(name_token); - - // Step 3.4 If name token is not null or regexp or wildcard token is not null: - if name_token.is_some() || regexp_or_wildcard_token.is_some() { - // Step 3.4.1 Let prefix be the empty string. - let mut prefix = ""; - - // Step 3.4.2 If char token is not null then set prefix to char token’s value. - if let Some(char_token) = char_token { - prefix = char_token.value; - } - - // Step 3.4.3 If prefix is not the empty string and not options’s prefix code point: - let prefix_is_prefix_code_point = options.prefix_code_point.is_some_and(|c| { - let mut buffer = [0; 4]; - prefix == c.encode_utf8(&mut buffer) - }); - if !prefix.is_empty() && !prefix_is_prefix_code_point { - // Step 3.4.3.1 Append prefix to the end of parser’s pending fixed value. - parser.pending_fixed_value.push_str(prefix); - - // Step 3.4.3.2 Set prefix to the empty string. - prefix = ""; - } - - // Step 3.4.4 Run maybe add a part from the pending fixed value given parser. - parser.maybe_add_a_part_from_the_pending_fixed_value()?; - - // Step 3.4.5 Let modifier token be the result of running try to consume a modifier token given parser. - let modifier_token = parser.try_to_consume_a_modifier_token(); - - // Step 3.4.6 Run add a part given parser, prefix, name token, regexp or wildcard token, - // the empty string, and modifier token. - parser.add_a_part( - prefix, - name_token, - regexp_or_wildcard_token, - "", - modifier_token, - )?; - - // Step 3.4.7 Continue. - continue; - } - - // Step 3.5 Let fixed token be char token. - let mut fixed_token = char_token; - - // Step 3.6 If fixed token is null, then set fixed token to the result of running - // try to consume a token given parser and "escaped-char". - if fixed_token.is_none() { - fixed_token = parser.try_to_consume_a_token(TokenType::EscapedChar); - } - - // Step 3.7 If fixed token is not null: - if let Some(fixed_token) = fixed_token { - // Step 3.7.1 Append fixed token’s value to parser’s pending fixed value. - parser.pending_fixed_value.push_str(fixed_token.value); - - // Step 3.7.2 Continue. - continue; - } - - // Step 3.8 Let open token be the result of running try to consume a token given parser and "open". - let open_token = parser.try_to_consume_a_token(TokenType::Open); - - // Step 3.9 If open token is not null: - if open_token.is_some() { - // Step 3.9.1 Let prefix be the result of running consume text given parser. - let prefix = parser.consume_text(); - - // Step 3.9.2 Set name token to the result of running try to consume a token given parser and "name". - name_token = parser.try_to_consume_a_token(TokenType::Name); - - // Step 3.9.3 Set regexp or wildcard token to the result of running try to consume a regexp or wildcard - // token given parser and name token. - regexp_or_wildcard_token = parser.try_to_consume_a_regexp_or_wildcard_token(name_token); - - // Step 3.9.4 Let suffix be the result of running consume text given parser. - let suffix = parser.consume_text(); - - // Step 3.9.5 Run consume a required token given parser and "close". - parser.consume_a_required_token(TokenType::Close)?; - - // Step 3.9.6 Let modifier token be the result of running try to consume a modifier token given parser. - let modifier_token = parser.try_to_consume_a_modifier_token(); - - // Step 3.9.7 Run add a part given parser, prefix, name token, regexp or wildcard token, - // suffix, and modifier token. - parser.add_a_part( - &prefix, - name_token, - regexp_or_wildcard_token, - &suffix, - modifier_token, - )?; - - // Step 3.9.8 Continue. - continue; - } - - // Step 3.10 Run maybe add a part from the pending fixed value given parser. - parser.maybe_add_a_part_from_the_pending_fixed_value()?; - - // Step 3.11 Run consume a required token given parser and "end". - parser.consume_a_required_token(TokenType::End)?; - } - - Ok(parser.part_list) -} - -/// <https://urlpattern.spec.whatwg.org/#pattern-parser> -struct PatternParser<'a> { - /// <https://urlpattern.spec.whatwg.org/#pattern-parser-token-list> - token_list: Vec<Token<'a>>, - - /// <https://urlpattern.spec.whatwg.org/#pattern-parser-encoding-callback> - encoding_callback: EncodingCallback, - - /// <https://urlpattern.spec.whatwg.org/#pattern-parser-segment-wildcard-regexp> - segment_wildcard_regexp: String, - - /// <https://urlpattern.spec.whatwg.org/#pattern-parser-part-list> - part_list: Vec<Part>, - - /// <https://urlpattern.spec.whatwg.org/#pattern-parser-pending-fixed-value> - pending_fixed_value: String, - - /// <https://urlpattern.spec.whatwg.org/#pattern-parser-index> - index: usize, - - /// <https://urlpattern.spec.whatwg.org/#pattern-parser-next-numeric-name> - next_numeric_name: usize, -} - -impl<'a> PatternParser<'a> { - fn new(segment_wildcard_regexp: String, encoding_callback: EncodingCallback) -> Self { - Self { - token_list: vec![], - segment_wildcard_regexp, - part_list: vec![], - pending_fixed_value: String::new(), - index: 0, - next_numeric_name: 0, - encoding_callback, - } - } - - /// <https://urlpattern.spec.whatwg.org/#try-to-consume-a-token> - fn try_to_consume_a_token(&mut self, token_type: TokenType) -> Option<Token<'a>> { - // Step 1. Assert: parser’s index is less than parser’s token list size. - debug_assert!(self.index < self.token_list.len()); - - // Step 2. Let next token be parser’s token list[parser’s index]. - let next_token = self.token_list[self.index]; - - // Step 3. If next token’s type is not type return null. - if next_token.token_type != token_type { - return None; - } - - // Step 4. Increment parser’s index by 1. - self.index += 1; - - // Step 5. Return next token. - Some(next_token) - } - - /// <https://urlpattern.spec.whatwg.org/#try-to-consume-a-modifier-token> - fn try_to_consume_a_modifier_token(&mut self) -> Option<Token<'a>> { - // Step 1. Let token be the result of running try to consume a token given parser and "other-modifier". - let token = self.try_to_consume_a_token(TokenType::OtherModifier); - - // Step 2. If token is not null, then return token. - if token.is_some() { - return token; - } - - // Step 3. Set token to the result of running try to consume a token given parser and "asterisk". - let token = self.try_to_consume_a_token(TokenType::Asterisk); - - // Step 4. Return token. - token - } - - /// <https://urlpattern.spec.whatwg.org/#consume-a-required-token> - fn consume_a_required_token(&mut self, token_type: TokenType) -> Fallible<Token<'a>> { - // Step 1. Let result be the result of running try to consume a token given parser and type. - let result = self.try_to_consume_a_token(token_type); - - // Step 2. If result is null, then throw a TypeError. - let Some(result) = result else { - return Err(Error::Type(format!( - "Missing required token {token_type:?}" - ))); - }; - - // Step 3. Return result. - Ok(result) - } - - /// <https://urlpattern.spec.whatwg.org/#try-to-consume-a-regexp-or-wildcard-token> - fn try_to_consume_a_regexp_or_wildcard_token( - &mut self, - name_token: Option<Token<'a>>, - ) -> Option<Token<'a>> { - // Step 1. Let token be the result of running try to consume a token given parser and "regexp". - let mut token = self.try_to_consume_a_token(TokenType::Regexp); - - // Step 2. If name token is null and token is null, then set token to the result of running - // try to consume a token given parser and "asterisk". - if name_token.is_none() && token.is_none() { - token = self.try_to_consume_a_token(TokenType::Asterisk); - } - - // Step 3. Return token. - token - } - - /// <https://urlpattern.spec.whatwg.org/#maybe-add-a-part-from-the-pending-fixed-value> - fn maybe_add_a_part_from_the_pending_fixed_value(&mut self) -> Fallible<()> { - // Step 1. If parser’s pending fixed value is the empty string, then return. - if self.pending_fixed_value.is_empty() { - return Ok(()); - } - - // Step 2. Let encoded value be the result of running parser’s encoding callback - // given parser’s pending fixed value. - let encoded_value = (self.encoding_callback)(&self.pending_fixed_value)?; - - // Step 3. Set parser’s pending fixed value to the empty string. - self.pending_fixed_value.clear(); - - // Step 4. Let part be a new part whose type is "fixed-text", value is encoded value, and modifier is "none". - let part = Part::new(PartType::FixedText, encoded_value, PartModifier::None); - - // Step 5. Append part to parser’s part list. - self.part_list.push(part); - - Ok(()) - } - - /// <https://urlpattern.spec.whatwg.org/#add-a-part> - fn add_a_part( - &mut self, - prefix: &str, - name_token: Option<Token<'a>>, - regexp_or_wildcard_token: Option<Token<'a>>, - suffix: &str, - modifier_token: Option<Token<'a>>, - ) -> Fallible<()> { - // Step 1. Let modifier be "none". - let mut modifier = PartModifier::None; - - // Step 2. If modifier token is not null: - if let Some(modifier_token) = modifier_token { - // Step 2.1 If modifier token’s value is "?" then set modifier to "optional". - if modifier_token.value == "?" { - modifier = PartModifier::Optional; - } - // Step 2.2 Otherwise if modifier token’s value is "*" then set modifier to "zero-or-more". - else if modifier_token.value == "*" { - modifier = PartModifier::ZeroOrMore; - } - // Step 2.3 Otherwise if modifier token’s value is "+" then set modifier to "one-or-more". - else if modifier_token.value == "+" { - modifier = PartModifier::OneOrMore; - } - } - - // Step 3. If name token is null and regexp or wildcard token is null and modifier is "none": - if name_token.is_none() && - regexp_or_wildcard_token.is_none() && - modifier == PartModifier::None - { - // Step 3.1 Append prefix to the end of parser’s pending fixed value. - self.pending_fixed_value.push_str(prefix); - - // Step 3.2 Return - return Ok(()); - } - - // Step 4. Run maybe add a part from the pending fixed value given parser. - self.maybe_add_a_part_from_the_pending_fixed_value()?; - - // Step 5. If name token is null and regexp or wildcard token is null: - if name_token.is_none() && regexp_or_wildcard_token.is_none() { - // Step 5.1 Assert: suffix is the empty string. - debug_assert!(suffix.is_empty()); - - // Step 5.2 If prefix is the empty string, then return. - if prefix.is_empty() { - return Ok(()); - } - - // Step 5.3 Let encoded value be the result of running parser’s encoding callback given prefix. - let encoded_value = (self.encoding_callback)(prefix)?; - - // Step 5.4 Let part be a new part whose type is "fixed-text", - // value is encoded value, and modifier is modifier. - let part = Part::new(PartType::FixedText, encoded_value, modifier); - - // Step 5.5 Append part to parser’s part list. - self.part_list.push(part); - - // Step 6. Return. - return Ok(()); - } - - // Step 6. Let regexp value be the empty string. - let mut regexp_value = { - // Step 7. If regexp or wildcard token is null, then set regexp value to parser’s segment wildcard regexp. - match regexp_or_wildcard_token { - None => self.segment_wildcard_regexp.clone(), - Some(token) => { - // Step 8. Otherwise if regexp or wildcard token’s type is "asterisk", - // then set regexp value to the full wildcard regexp value. - if token.token_type == TokenType::Asterisk { - FULL_WILDCARD_REGEXP_VALUE.into() - } - // Step 9. Otherwise set regexp value to regexp or wildcard token’s value. - else { - token.value.to_owned() - } - }, - } - }; - - // Step 10. Let type be "regexp". - let mut part_type = PartType::Regexp; - - // Step 11. If regexp value is parser’s segment wildcard regexp: - if regexp_value == self.segment_wildcard_regexp { - // Step 11.1 Set type to "segment-wildcard". - part_type = PartType::SegmentWildcard; - - // Step 11.2 Set regexp value to the empty string. - regexp_value.clear(); - } - // Step 12. Otherwise if regexp value is the full wildcard regexp value: - else if regexp_value == FULL_WILDCARD_REGEXP_VALUE { - // Step 12.1 Set type to "full-wildcard". - part_type = PartType::FullWildcard; - - // Step 12.2 Set regexp value to the empty string. - regexp_value.clear(); - } - - // Step 13. Let name be the empty string. - let mut name = String::new(); - - // Step 14. If name token is not null, then set name to name token’s value. - if let Some(name_token) = name_token { - name = name_token.value.to_owned(); - } - // Step 15. Otherwise if regexp or wildcard token is not null: - else if regexp_or_wildcard_token.is_some() { - // Step 15.1 Set name to parser’s next numeric name, serialized. - name = self.next_numeric_name.to_string(); - - // Step 15.2 Increment parser’s next numeric name by 1. - self.next_numeric_name = self.next_numeric_name.wrapping_add(1); - } - - // Step 16. If the result of running is a duplicate name given parser and name is true, then throw a TypeError. - if self.is_a_duplicate_name(&name) { - return Err(Error::Type(format!("Duplicate part name: {name:?}"))); - } - - // Step 17. Let encoded prefix be the result of running parser’s encoding callback given prefix. - let encoded_prefix = (self.encoding_callback)(prefix)?; - - // Step 18. Let encoded suffix be the result of running parser’s encoding callback given suffix. - let encoded_suffix = (self.encoding_callback)(suffix)?; - - // Step 19. Let part be a new part whose type is type, value is regexp value, modifier is modifier, - // name is name, prefix is encoded prefix, and suffix is encoded suffix. - let part = Part { - part_type, - value: regexp_value, - modifier, - name, - prefix: encoded_prefix, - suffix: encoded_suffix, - }; - - // Step 20. Append part to parser’s part list. - self.part_list.push(part); - - Ok(()) - } - - // <https://urlpattern.spec.whatwg.org/#is-a-duplicate-name> - fn is_a_duplicate_name(&self, name: &str) -> bool { - // Step 1. For each part of parser’s part list: - for part in &self.part_list { - // Step 1.1 If part’s name is name, then return true. - if part.name == name { - return true; - } - } - - // Step 2. Return false. - false - } - - /// <https://urlpattern.spec.whatwg.org/#consume-text> - fn consume_text(&mut self) -> String { - // Step 1. Let result be the empty string. - let mut result = String::new(); - - // Step 2. While true: - loop { - // Step 2.1 Let token be the result of running try to consume a token given parser and "char". - let mut token = self.try_to_consume_a_token(TokenType::Char); - - // Step 2.2 If token is null, then set token to the result of running - // try to consume a token given parser and "escaped-char". - if token.is_none() { - token = self.try_to_consume_a_token(TokenType::EscapedChar); - } - - // Step 2.3 If token is null, then break. - let Some(token) = token else { - break; - }; - - // Step 2.4 Append token’s value to the end of result. - result.push_str(token.value); - } - - result - } -} diff --git a/components/script/dom/urlpattern/preprocessing.rs b/components/script/dom/urlpattern/preprocessing.rs deleted file mode 100644 index 7fc3c136315..00000000000 --- a/components/script/dom/urlpattern/preprocessing.rs +++ /dev/null @@ -1,659 +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 script_bindings::error::{Error, Fallible}; -use script_bindings::str::USVString; -use url::Url; - -use crate::dom::bindings::codegen::Bindings::URLPatternBinding::URLPatternInit; -use crate::dom::urlpattern::{PatternInitType, default_port_for_special_scheme, is_special_scheme}; - -/// <https://urlpattern.spec.whatwg.org/#process-a-urlpatterninit> -pub(super) fn process_a_url_pattern_init( - init: &URLPatternInit, - init_type: PatternInitType, -) -> Fallible<URLPatternInit> { - // Step 1. Let result be the result of creating a new URLPatternInit. - let mut result = URLPatternInit::default(); - - // TODO Step 2. If protocol is not null, set result["protocol"] to protocol. - // TODO Step 3. If username is not null, set result["username"] to username. - // TODO Step 4. If password is not null, set result["password"] to password. - // TODO Step 5. If hostname is not null, set result["hostname"] to hostname. - // TODO Step 6. If port is not null, set result["port"] to port. - // TODO Step 7. If pathname is not null, set result["pathname"] to pathname. - // TODO Step 8. If search is not null, set result["search"] to search. - // TODO Step 9. If hash is not null, set result["hash"] to hash. - - // Step 10. Let baseURL be null. - let mut base_url: Option<Url> = None; - - // Step 11. If init["baseURL"] exists: - if let Some(init_base_url) = init.baseURL.as_ref() { - // Step 11.1 Set baseURL to the result of running the basic URL parser on init["baseURL"]. - let Ok(parsed_base_url) = init_base_url.0.parse() else { - // Step 11.2 If baseURL is failure, then throw a TypeError. - return Err(Error::Type(format!( - "Failed to parse {:?} as URL", - init_base_url.0 - ))); - }; - let base_url = base_url.insert(parsed_base_url); - - // Step 11.3 If init["protocol"] does not exist, then set result["protocol"] to the result of - // processing a base URL string given baseURL’s scheme and type. - if init.protocol.is_none() { - result.protocol = Some(USVString(process_a_base_url_string( - base_url.scheme(), - init_type, - ))); - } - - // Step 11.4. If type is not "pattern" and init contains none of "protocol", "hostname", - // "port" and "username", then set result["username"] to the result of processing a base URL string - // given baseURL’s username and type. - if init_type != PatternInitType::Pattern && - init.protocol.is_none() && - init.hostname.is_none() && - init.port.is_none() && - init.username.is_none() - { - result.username = Some(USVString(process_a_base_url_string( - base_url.username(), - init_type, - ))); - } - - // Step 11.5 If type is not "pattern" and init contains none of "protocol", "hostname", "port", - // "username" and "password", then set result["password"] to the result of processing a base URL string - // given baseURL’s password and type. - if init_type != PatternInitType::Pattern && - init.protocol.is_none() && - init.hostname.is_none() && - init.port.is_none() && - init.username.is_none() && - init.password.is_none() - { - result.password = Some(USVString(process_a_base_url_string( - base_url.password().unwrap_or_default(), - init_type, - ))); - } - - // Step 11.6 If init contains neither "protocol" nor "hostname", then: - if init.protocol.is_none() && init.hostname.is_none() { - // Step 11.6.1 Let baseHost be the empty string. - // Step 11.6.2 If baseURL’s host is not null, then set baseHost to its serialization. - let base_host = base_url - .host() - .map(|host| host.to_string()) - .unwrap_or_default(); - - // Step 11.6.3 Set result["hostname"] to the result of processing a base URL string given baseHost and type. - result.hostname = Some(USVString(process_a_base_url_string(&base_host, init_type))); - } - - // Step 11.7 If init contains none of "protocol", "hostname", and "port", then: - if init.protocol.is_none() && init.hostname.is_none() && init.port.is_none() { - match base_url.port() { - // Step 11.7.1 If baseURL’s port is null, then set result["port"] to the empty string. - None => { - result.port = Some(USVString(String::new())); - }, - // Step 11.7.2 Otherwise, set result["port"] to baseURL’s port, serialized. - Some(port) => { - result.port = Some(USVString(port.to_string())); - }, - } - } - - // Step 11.8 If init contains none of "protocol", "hostname", "port", and "pathname", then set - // result["pathname"] to the result of processing a base URL string given the result of - // URL path serializing baseURL and type. - if init.protocol.is_none() && - init.hostname.is_none() && - init.port.is_none() && - init.pathname.is_none() - { - result.pathname = Some(USVString(process_a_base_url_string( - base_url.path(), - init_type, - ))); - } - - // Step 11.9 If init contains none of "protocol", "hostname", "port", "pathname", - // and "search", then: - if init.protocol.is_none() && - init.hostname.is_none() && - init.port.is_none() && - init.pathname.is_none() && - init.search.is_none() - { - // Step 11.9.1 Let baseQuery be baseURL’s query. - let base_query = base_url.query(); - - // Step 11.9.2 If baseQuery is null, then set baseQuery to the empty string. - let base_query = base_query.unwrap_or_default(); - - // Step 11.9.3 Set result["search"] to the result of processing a base URL string given baseQuery and type. - result.search = Some(USVString(process_a_base_url_string(base_query, init_type))); - } - - // Step 11.10 If init contains none of "protocol", "hostname", - // "port", "pathname", "search", and "hash", then: - if init.protocol.is_none() && - init.hostname.is_none() && - init.port.is_none() && - init.pathname.is_none() && - init.search.is_none() && - init.hash.is_none() - { - // Step 11.10.1 Let baseFragment be baseURL’s fragment. - let base_fragment = base_url.fragment(); - - // Step 11.10.2 If baseFragment is null, then set baseFragment to the empty string. - let base_fragment = base_fragment.unwrap_or_default(); - - // Step 11.10.3 Set result["hash"] to the result of processing a base URL string - // given baseFragment and type. - result.hash = Some(USVString(process_a_base_url_string( - base_fragment, - init_type, - ))); - } - } - - // Step 12. If init["protocol"] exists, then set result["protocol"] to the result of process protocol for init - // given init["protocol"] and type. - if let Some(protocol) = &init.protocol { - result.protocol = Some(USVString(process_a_protocol_for_init(protocol, init_type)?)); - } - - // Step 13. If init["username"] exists, then set result["username"] to the result of - // process username for init given init["username"] and type. - if let Some(username) = &init.username { - result.username = Some(USVString(process_username_for_init(username, init_type))); - } - - // Step 14. If init["password"] exists, then set result["password"] to the result of - // process password for init given init["password"] and type. - if let Some(password) = &init.password { - result.password = Some(USVString(process_password_for_init(password, init_type))); - } - - // Step 15. If init["hostname"] exists, then set result["hostname"] to the result of - // process hostname for init given init["hostname"] and type. - if let Some(hostname) = &init.hostname { - result.hostname = Some(USVString(process_hostname_for_init(hostname, init_type)?)); - } - - // Step 16. Let resultProtocolString be result["protocol"] if it exists; otherwise the empty string. - let result_protocol_string = result.protocol.as_deref().unwrap_or_default(); - - // Step 17. If init["port"] exists, then set result["port"] to the result of process port for init - // given init["port"], resultProtocolString, and type. - if let Some(port) = &init.port { - result.port = Some(USVString(process_port_for_init( - port, - result_protocol_string, - init_type, - )?)); - } - - // Step 18. If init["pathname"] exists: - if let Some(path_name) = &init.pathname { - // Step 18.1 Set result["pathname"] to init["pathname"]. - // NOTE: This is not necessary - the spec uses result["pathname"] in the following section, - // but it could just as well use init["pathname"]. Storing the string in an intermediate - // variable makes the code simpler - let mut result_pathname = path_name.to_string(); - - // Step 18.2 If the following are all true: - // * baseURL is not null; - // * baseURL does not have an opaque path; and - // * the result of running is an absolute pathname given result["pathname"] and type is false, - if let Some(base_url) = base_url { - if !base_url.cannot_be_a_base() && !is_an_absolute_pathname(path_name, init_type) { - // Step 18.2.1 Let baseURLPath be the result of running process a base URL string given the result - // of URL path serializing baseURL and type. - let base_url_path = process_a_base_url_string(base_url.path(), init_type); - - // Step 18.2.2 Let slash index be the index of the last U+002F (/) code point found in baseURLPath, - // interpreted as a sequence of code points, or null if there are no instances of the code point. - let slash_index = base_url_path.rfind('/'); - - // Step 18.2.3 If slash index is not null: - if let Some(slash_index) = slash_index { - // Step 18.2.3.1 Let new pathname be the code point substring from 0 to slash index + 1 - // within baseURLPath. - let mut new_pathname = base_url_path[..=slash_index].to_owned(); - - // Step 18.2.3.2 Append result["pathname"] to the end of new pathname. - new_pathname.push_str(path_name); - - // Step 18.2.3.3 Set result["pathname"] to new pathname. - result_pathname = new_pathname; - } - } - } - - // Step 18.3 Set result["pathname"] to the result of process pathname for init given result["pathname"], - // resultProtocolString, and type. - result.pathname = Some(USVString(process_pathname_for_init( - &result_pathname, - result_protocol_string, - init_type, - )?)); - } - - // Step 19. If init["search"] exists then set result["search"] to the result of - // process search for init given init["search"] and type. - if let Some(search) = &init.search { - result.search = Some(USVString(process_search_for_init(search, init_type))); - } - - // Step 20. If init["hash"] exists then set result["hash"] to the result of - // process hash for init given init["hash"] and type. - if let Some(hash) = &init.hash { - result.hash = Some(USVString(process_hash_for_init(hash, init_type))); - } - - // Step 21. Return result. - Ok(result) -} - -/// <https://urlpattern.spec.whatwg.org/#process-protocol-for-init> -fn process_a_protocol_for_init(input: &str, init_type: PatternInitType) -> Fallible<String> { - // Step 1. Let strippedValue be the given value with a single trailing U+003A (:) removed, if any. - let stripped_value = input.strip_suffix(':').unwrap_or(input); - - // Step 2. If type is "pattern" then return strippedValue. - if init_type == PatternInitType::Pattern { - return Ok(stripped_value.to_owned()); - } - - // Step 3. Return the result of running canonicalize a protocol given strippedValue. - canonicalize_a_protocol(stripped_value) -} - -/// <https://urlpattern.spec.whatwg.org/#process-username-for-init> -fn process_username_for_init(value: &str, init_type: PatternInitType) -> String { - // Step 1. If type is "pattern" then return value. - if init_type == PatternInitType::Pattern { - return value.to_owned(); - } - - // Step 2. Return the result of running canonicalize a username given value. - canonicalize_a_username(value) -} - -/// <https://urlpattern.spec.whatwg.org/#process-password-for-init> -fn process_password_for_init(value: &str, init_type: PatternInitType) -> String { - // Step 1. If type is "pattern" then return value. - if init_type == PatternInitType::Pattern { - return value.to_owned(); - } - - // Step 2. Return the result of running canonicalize a password given value. - canonicalize_a_password(value) -} - -/// <https://urlpattern.spec.whatwg.org/#process-hostname-for-init> -fn process_hostname_for_init(value: &str, init_type: PatternInitType) -> Fallible<String> { - // Step 1. If type is "pattern" then return value. - if init_type == PatternInitType::Pattern { - return Ok(value.to_owned()); - } - - // Step 2. Return the result of running canonicalize a hostname given value. - canonicalize_a_hostname(value) -} - -/// <https://urlpattern.spec.whatwg.org/#process-port-for-init> -fn process_port_for_init( - port_value: &str, - protocol_value: &str, - init_type: PatternInitType, -) -> Fallible<String> { - // Step 1. If type is "pattern" then return portValue. - if init_type == PatternInitType::Pattern { - return Ok(port_value.to_owned()); - } - - // Step 2. Return the result of running canonicalize a port given portValue and protocolValue. - canonicalize_a_port(port_value, Some(protocol_value)) -} - -/// <https://urlpattern.spec.whatwg.org/#process-pathname-for-init> -fn process_pathname_for_init( - path_name_value: &str, - protocol_value: &str, - init_type: PatternInitType, -) -> Fallible<String> { - // Step 1. If type is "pattern" then return pathnameValue. - if init_type == PatternInitType::Pattern { - return Ok(path_name_value.to_owned()); - } - - // Step 2. If protocolValue is a special scheme or the empty string, then return the result of - // running canonicalize a pathname given pathnameValue. - if is_special_scheme(protocol_value) || protocol_value.is_empty() { - return Ok(canonicalize_a_pathname(path_name_value)); - } - - // Step 2. Return the result of running canonicalize an opaque pathname given pathnameValue. - canonicalize_an_opaque_pathname(path_name_value) -} - -/// <https://urlpattern.spec.whatwg.org/#process-search-for-init> -fn process_search_for_init(value: &str, init_type: PatternInitType) -> String { - // Step 1. Let strippedValue be the given value with a single leading U+003F (?) removed, if any. - let stripped_value = value.strip_prefix('?').unwrap_or(value); - - // Step 2. If type is "pattern" then return strippedValue. - if init_type == PatternInitType::Pattern { - return stripped_value.to_owned(); - } - - // Step 3. Return the result of running canonicalize a search given strippedValue. - canonicalize_a_search(stripped_value) -} - -/// <https://urlpattern.spec.whatwg.org/#process-hash-for-init> -fn process_hash_for_init(value: &str, init_type: PatternInitType) -> String { - // Step 1. Let strippedValue be the given value with a single leading U+0023 (#) removed, if any. - let stripped_value = value.strip_prefix('#').unwrap_or(value); - - // Step 2. If type is "pattern" then return strippedValue. - if init_type == PatternInitType::Pattern { - return stripped_value.to_owned(); - } - - // Step 3. Return the result of running canonicalize a hash given strippedValue. - canonicalize_a_hash(stripped_value) -} - -/// <https://urlpattern.spec.whatwg.org/#url-pattern-create-a-dummy-url> -fn create_a_dummy_url() -> Url { - // Step 1. Let dummyInput be "https://dummy.invalid/". - let dummy_input = "https://dummy.invalid/"; - - // Step 2. Return the result of running the basic URL parser on dummyInput. - dummy_input - .parse() - .expect("parsing dummy input cannot fail") -} - -/// <https://urlpattern.spec.whatwg.org/#canonicalize-a-protocol> -pub(super) fn canonicalize_a_protocol(value: &str) -> Fallible<String> { - // Step 1. If value is the empty string, return value. - if value.is_empty() { - return Ok(String::new()); - } - - // Step 2. Let parseResult be the result of running the basic URL parser - // given value followed by "://dummy.invalid/". - let Ok(parse_result) = Url::parse(&format!("{value}://dummy.invalid/")) else { - // Step 3. If parseResult is failure, then throw a TypeError. - return Err(Error::Type(format!( - "Failed to canonicalize {value:?} as a protocol" - ))); - }; - - // Step 4. Return parseResult’s scheme. - Ok(parse_result.scheme().to_owned()) -} - -/// <https://urlpattern.spec.whatwg.org/#canonicalize-a-username> -pub(super) fn canonicalize_a_username(input: &str) -> String { - // Step 1. If value is the empty string, return value. - if input.is_empty() { - return input.to_owned(); - } - - // Step 2. Let dummyURL be the result of creating a dummy URL. - let mut dummy_url = create_a_dummy_url(); - - // Step 3. Set the username given dummyURL and value. - dummy_url.set_username(input).unwrap(); - - // Step 4. Return dummyURL’s username. - dummy_url.username().to_owned() -} - -/// <https://urlpattern.spec.whatwg.org/#canonicalize-a-password> -pub(super) fn canonicalize_a_password(input: &str) -> String { - // Step 1. If value is the empty string, return value. - if input.is_empty() { - return input.to_owned(); - } - - // Step 2. Let dummyURL be the result of creating a dummy URL. - let mut dummy_url = create_a_dummy_url(); - - // Step 3. Set the password given dummyURL and value. - dummy_url.set_password(Some(input)).unwrap(); - - // Step 4. Return dummyURL’s password. - dummy_url.password().unwrap().to_owned() -} - -/// <https://urlpattern.spec.whatwg.org/#canonicalize-a-hostname> -pub(super) fn canonicalize_a_hostname(input: &str) -> Fallible<String> { - // Step 1. If value is the empty string, return value. - if input.is_empty() { - return Ok(String::new()); - } - - // Step 2. Let dummyURL be the result of creating a dummy URL. - let mut dummy_url = create_a_dummy_url(); - - // FIXME: The rest of the algorithm needs functionality that the url crate - // does not expose. We need to figure out if there's a way around that or - // if we want to reimplement that functionality here - - if dummy_url.set_host(Some(input)).is_err() { - return Err(Error::Type(format!( - "Failed to canonicalize hostname: {input:?}" - ))); - } - - Ok(dummy_url.host_str().unwrap().to_owned()) -} - -/// <https://urlpattern.spec.whatwg.org/#canonicalize-a-port> -pub(super) fn canonicalize_a_port( - port_value: &str, - protocol_value: Option<&str>, -) -> Fallible<String> { - // Step 1. If portValue is the empty string, return portValue. - if port_value.is_empty() { - return Ok(String::new()); - } - - // Step 2. Let dummyURL be the result of creating a dummy URL. - let mut dummy_url = create_a_dummy_url(); - - // Step 3. If protocolValue was given, then set dummyURL’s scheme to protocolValue. - if let Some(protocol_value) = protocol_value { - dummy_url.set_scheme(protocol_value).unwrap(); - } - - // Step 4. Let parseResult be the result of running basic URL parser given portValue - // with dummyURL as url and port state as state override. - // NOTE: The url crate does not expose these parsing concepts, so we try - // to recreate the parsing step here. - let port_value = port_value.trim(); - let Ok(port) = port_value.parse::<u16>() else { - // Step 5. If parseResult is failure, then throw a TypeError. - return Err(Error::Type(format!( - "{port_value:?} is not a valid port number" - ))); - }; - - // Step 6. Return dummyURL’s port, serialized, or empty string if it is null. - if let Some(scheme) = protocol_value { - if default_port_for_special_scheme(scheme) == Some(port) { - return Ok(String::new()); - } - } - Ok(port.to_string()) -} - -/// <https://urlpattern.spec.whatwg.org/#canonicalize-a-pathname> -pub(super) fn canonicalize_a_pathname(value: &str) -> String { - // Step 1. If value is the empty string, then return value. - if value.is_empty() { - return String::new(); - } - - // NOTE: This is not what the spec says, but the url crate does not expose the required functionality. - // TODO: Investigate whether this is different in practice - let mut dummy_url = create_a_dummy_url(); - dummy_url.set_path(value); - - dummy_url.path().to_owned() -} - -/// <https://urlpattern.spec.whatwg.org/#canonicalize-an-opaque-pathname> -pub(super) fn canonicalize_an_opaque_pathname(value: &str) -> Fallible<String> { - // NOTE: The url crate doesn't expose the functionality needed by this algorithm. - // Instead we create a url with an opaque path that is value and then return that opaque path, - // which should be equivalent. - let Ok(url) = Url::parse(&format!("foo:{value}")) else { - return Err(Error::Type(format!( - "Could not parse {value:?} as opaque path" - ))); - }; - - Ok(url.path().to_owned()) -} - -/// <https://urlpattern.spec.whatwg.org/#canonicalize-a-search> -pub(super) fn canonicalize_a_search(value: &str) -> String { - if value.is_empty() { - return String::new(); - } - - let Ok(url) = Url::parse(&format!("http://example.com?{value}")) else { - log::warn!("canonicalizing a search should never fail"); - return String::new(); - }; - - url.query().unwrap_or_default().to_owned() -} - -/// <https://urlpattern.spec.whatwg.org/#canonicalize-a-hash> -pub(super) fn canonicalize_a_hash(value: &str) -> String { - if value.is_empty() { - return String::new(); - } - - let Ok(url) = Url::parse(&format!("http://example.com#{value}")) else { - log::warn!("canonicalizing a hash should never fail"); - return String::new(); - }; - - url.fragment().unwrap_or_default().to_owned() -} - -/// <https://urlpattern.spec.whatwg.org/#is-an-absolute-pathname> -fn is_an_absolute_pathname(input: &str, init_type: PatternInitType) -> bool { - let mut chars = input.chars(); - - // Step 1. If input is the empty string, then return false. - let Some(first_char) = chars.next() else { - return false; - }; - - // Step 2. If input[0] is U+002F (/), then return true. - if first_char == '/' { - return true; - } - - // Step 3. If type is "url", then return false. - if init_type == PatternInitType::Url { - return false; - } - - // Step 4. If input’s code point length is less than 2, then return false. - let Some(second_char) = chars.next() else { - return false; - }; - - // Step 5. If input[0] is U+005C (\) and input[1] is U+002F (/), then return true. - if first_char == '\\' && second_char == '/' { - return true; - } - - // Step 6. If input[0] is U+007B ({) and input[1] is U+002F (/), then return true. - if first_char == '{' && second_char == '/' { - return true; - } - - // Step 7. Return false. - false -} - -/// <https://urlpattern.spec.whatwg.org/#process-a-base-url-string> -fn process_a_base_url_string(input: &str, init_type: PatternInitType) -> String { - // Step 1. Assert: input is not null. - // NOTE: The type system ensures that already - - // Step 2. If type is not "pattern" return input. - if init_type != PatternInitType::Pattern { - return input.to_owned(); - } - - // Step 3. Return the result of escaping a pattern string given input. - escape_a_pattern_string(input) -} - -/// Implements functionality that is shared between <https://urlpattern.spec.whatwg.org/#escape-a-pattern-string> -/// and <https://urlpattern.spec.whatwg.org/#escape-a-regexp-string>. -/// -/// These two algorithms are identical except for the set of characters that they escape, so implementing them -/// seperately does not make sense. -fn escape_a_string(input: &str, to_escape: &[char]) -> String { - // Step 1. Assert: input is an ASCII string. - debug_assert!( - input.is_ascii(), - "Expected input to be ASCII, got {input:?}" - ); - - // Step 2. Let result be the empty string. - let mut result = String::with_capacity(input.len()); - - // Step 3. Let index be 0. - // Step 4. While index is less than input’s length: - // Step 4.1 Let c be input[index]. - // Step 4.2 Increment index by 1. - for c in input.chars() { - // Step 4.3 If c is one of: [..] then append "\" to the end of result. - if to_escape.contains(&c) { - result.push('\\'); - } - - // Step 4.4 Append c to the end of result. - result.push(c); - } - - // Step 5. Return result. - result -} - -/// <https://urlpattern.spec.whatwg.org/#escape-a-pattern-string> -fn escape_a_pattern_string(input: &str) -> String { - escape_a_string(input, &['+', '*', '?', ':', '{', '}', '(', ')', '\\']) -} - -/// <https://urlpattern.spec.whatwg.org/#escape-a-regexp-string> -pub(super) fn escape_a_regexp_string(input: &str) -> String { - escape_a_string( - input, - &[ - '.', '+', '*', '?', '^', '$', '{', '}', '(', ')', '[', ']', '|', '/', '\\', - ], - ) -} diff --git a/components/script/dom/urlpattern/tokenizer.rs b/components/script/dom/urlpattern/tokenizer.rs deleted file mode 100644 index e2d70217c3f..00000000000 --- a/components/script/dom/urlpattern/tokenizer.rs +++ /dev/null @@ -1,524 +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 script_bindings::error::{Error, Fallible}; - -/// <https://urlpattern.spec.whatwg.org/#tokenize> -pub(super) fn tokenize(input: &str, policy: TokenizePolicy) -> Fallible<Vec<Token>> { - // Step 1. Let tokenizer be a new tokenizer. - // Step 2. Set tokenizer’s input to input. - // Step 3. Set tokenizer’s policy to policy. - let mut tokenizer = Tokenizer { - input, - policy, - index: 0, - next_index: 0, - token_list: vec![], - code_point: char::MIN, - }; - - // Step 4. While tokenizer’s index is less than tokenizer’s input’s code point length: - while tokenizer.index < tokenizer.input.len() { - // Step 4.1 Run seek and get the next code point given tokenizer and tokenizer’s index. - tokenizer.seek_and_get_the_next_code_point(tokenizer.index); - - match tokenizer.code_point { - // Step 4.2 If tokenizer’s code point is U+002A (*): - '*' => { - // Step 4.2.1 Run add a token with default position and length given tokenizer and "asterisk". - tokenizer.add_a_token_with_default_position_and_length(TokenType::Asterisk); - - // Step 4.2.2 Continue. - continue; - }, - // Step 4.3 If tokenizer’s code point is U+002B (+) or U+003F (?): - '+' | '?' => { - // Step 4.3.1 Run add a token with default position and length given tokenizer and "other-modifier". - tokenizer.add_a_token_with_default_position_and_length(TokenType::OtherModifier); - - // Step 4.3.2 Continue. - continue; - }, - // Step 4.4 If tokenizer’s code point is U+005C (\): - '\\' => { - // Step 4.4.1 If tokenizer’s index is equal to tokenizer’s input’s code point length − 1: - if tokenizer.is_done() { - // Step 4.4.1.1 Run process a tokenizing error given tokenizer, tokenizer’s next index, - // and tokenizer’s index. - tokenizer.process_a_tokenizing_error(tokenizer.next_index, tokenizer.index)?; - - // Step 4.4.1.2 Continue. - continue; - } - - // Step 4.4.2 Let escaped index be tokenizer’s next index. - let escaped_index = tokenizer.index; - - // Step 4.4.3 Run get the next code point given tokenizer. - tokenizer.get_the_next_code_point(); - - // Step 4.4.4 Run add a token with default length given tokenizer, "escaped-char", - // tokenizer’s next index, and escaped index. - tokenizer.add_a_token_with_default_length( - TokenType::EscapedChar, - tokenizer.next_index, - escaped_index, - ); - - // Step 4.4.5 Continue. - continue; - }, - // Step 4.5 If tokenizer’s code point is U+007B ({): - '{' => { - // Step 4.5.1 Run add a token with default position and length given tokenizer and "open". - tokenizer.add_a_token_with_default_position_and_length(TokenType::Open); - - // Step 4.5.2 Continue. - continue; - }, - // Step 4.6 If tokenizer’s code point is U+007D (}): - '}' => { - // Step 4.6.1 Run add a token with default position and length given tokenizer and "close". - tokenizer.add_a_token_with_default_position_and_length(TokenType::Close); - - // Step 4.6.2 Continue. - continue; - }, - // Step 4.7 If tokenizer’s code point is U+003A (:): - ':' => { - // Step 4.7.1 Let name position be tokenizer’s next index. - let mut name_position = tokenizer.next_index; - - // Step 4.7.2 Let name start be name position. - let name_start = name_position; - - // Step 4.7.3 While name position is less than tokenizer’s input’s code point length: - while name_position < tokenizer.input.len() { - // Step 4.7.3.1 Run seek and get the next code point given tokenizer and name position. - tokenizer.seek_and_get_the_next_code_point(name_position); - - // Step 4.7.3.2 Let first code point be true if name position equals name start - // and false otherwise. - let first_code_point = name_position == name_start; - - // Step 4.7.3.3 Let valid code point be the result of running is a valid name - // code point given tokenizer’s code point and first code point. - let valid_code_point = - is_a_valid_name_code_point(tokenizer.code_point, first_code_point); - - // Step 4.7.3.4 If valid code point is false break. - if !valid_code_point { - break; - } - - // Step 4.6.3.5 Set name position to tokenizer’s next index. - name_position = tokenizer.next_index; - } - - // Step 4.7.4 If name position is less than or equal to name start: - if name_position <= name_start { - // Step 4.7.4.1 Run process a tokenizing error given tokenizer, name start, and tokenizer’s index. - tokenizer.process_a_tokenizing_error(name_start, tokenizer.index)?; - - // Step 4.7.4.2 Continue. - continue; - } - - // Step 4.7.5 Run add a token with default length given tokenizer, "name", name position, - // and name start. - tokenizer.add_a_token_with_default_length( - TokenType::Name, - name_position, - name_start, - ); - - // Step 4.7.6 Continue. - continue; - }, - // Step 4.8 If tokenizer’s code point is U+0028 ((): - '(' => { - // Step 4.8.1 Let depth be 1. - let mut depth = 1; - - // Step 4.8.2 Let regexp position be tokenizer’s next index. - let mut regexp_position = tokenizer.next_index; - - // Step 4.8.3 Let regexp start be regexp position. - let regexp_start = regexp_position; - - // Step 4.8.4 Let error be false. - let mut error = false; - - // Step 4.8.5 While regexp position is less than tokenizer’s input’s code point length: - while regexp_position < tokenizer.input.len() { - // Step 4.8.5.1 Run seek and get the next code point given tokenizer and regexp position. - tokenizer.seek_and_get_the_next_code_point(regexp_position); - - // Step 4.8.5.2 If tokenizer’s code point is not an ASCII code point: - if !tokenizer.code_point.is_ascii() { - // Step 4.8.5.1.1 Run process a tokenizing error given tokenizer, regexp start, - // and tokenizer’s index. - tokenizer.process_a_tokenizing_error(regexp_start, tokenizer.index)?; - - // Step 4.8.5.1.2 Set error to true. - error = true; - - // Step 4.8.5.1.2 Break. - break; - } - - // Step 4.8.5.3 If regexp position equals regexp start and tokenizer’s code point is U+003F (?): - if regexp_position == regexp_start && tokenizer.code_point == '?' { - // Step 4.8.5.3.1 Run process a tokenizing error given tokenizer, regexp start, - // and tokenizer’s index. - tokenizer.process_a_tokenizing_error(regexp_start, tokenizer.index)?; - - // Step 4.8.5.3.2 Set error to true. - error = true; - - // Step 4.8.5.3.3 Break. - break; - } - - // Step 4.8.5.4 If tokenizer’s code point is U+005C (\): - if tokenizer.code_point == '\\' { - // Step 4.8.5.4.1 If regexp position equals tokenizer’s input’s code point length − 1: - if tokenizer.is_last_character(regexp_position) { - // Step 4.8.5.4.1.1 Run process a tokenizing error given tokenizer, regexp start, - // and tokenizer’s index. - tokenizer.process_a_tokenizing_error(regexp_start, tokenizer.index)?; - - // Step 4.8.5.4.1.2 Set error to true. - error = true; - - // Step 4.8.5.4.1.3 Break - break; - } - - // Step 4.8.5.4.2 Run get the next code point given tokenizer. - tokenizer.get_the_next_code_point(); - - // Step 4.8.5.4.3 If tokenizer’s code point is not an ASCII code point: - if !tokenizer.code_point.is_ascii() { - // Step 4.8.5.4.3.1 Run process a tokenizing error given tokenizer, regexp start, - // and tokenizer’s index. - tokenizer.process_a_tokenizing_error(regexp_start, tokenizer.index)?; - - // Step 4.8.5.4.3.2 Set error to true. - error = true; - - // Step 4.8.5.4.3.3 Break - break; - } - - // Step 4.8.5.4.4 Set regexp position to tokenizer’s next index. - regexp_position = tokenizer.next_index; - - // Step 4.8.5.4.5 Continue. - continue; - } - - // Step 4.8.5.5 If tokenizer’s code point is U+0029 ()): - if tokenizer.code_point == ')' { - // Step 4.8.5.5.1 Decrement depth by 1. - depth -= 1; - - // Step 4.8.5.5.2 If depth is 0: - if depth == 0 { - // Step 4.8.5.5.2.1 Set regexp position to tokenizer’s next index. - regexp_position = tokenizer.next_index; - - // Step 4.8.5.5.2.2 Break. - break; - } - } - // Step 4.8.5.6 Otherwise if tokenizer’s code point is U+0028 ((): - else if tokenizer.code_point == '(' { - // Step 4.8.5.6.1 Increment depth by 1. - depth += 1; - - // Step 4.8.5.6.2 If regexp position equals tokenizer’s input’s code point length − 1: - if tokenizer.is_last_character(regexp_position) { - // Step 4.8.5.6.2.1 Run process a tokenizing error given tokenizer, regexp start, - // and tokenizer’s index. - tokenizer.process_a_tokenizing_error(regexp_start, tokenizer.index)?; - - // Step 4.8.5.6.2.2 Set error to true. - error = true; - - // Step 4.8.5.6.2.3 Break - break; - } - - // Step 4.8.5.6.3 Let temporary position be tokenizer’s next index. - let temporary_position = tokenizer.next_index; - - // Step 4.8.5.6.4 Run get the next code point given tokenizer. - tokenizer.get_the_next_code_point(); - - // Step 4.8.5.6.5 If tokenizer’s code point is not U+003F (?): - if tokenizer.code_point != '?' { - // Step 4.8.5.6.5.1 Run process a tokenizing error given tokenizer, regexp start, - // and tokenizer’s index. - tokenizer.process_a_tokenizing_error(regexp_start, tokenizer.index)?; - - // Step 4.8.5.6.5.2 Set error to true. - error = true; - - // Step 4.8.5.6.5.3 Break. - break; - } - - // Step 4.8.5.6.6 Set tokenizer’s next index to temporary position. - tokenizer.next_index = temporary_position; - } - - // Step 4.8.5.7 Set regexp position to tokenizer’s next index. - regexp_position = tokenizer.next_index; - } - - // Step 4.8.6 If error is true continue. - if error { - continue; - } - - // Step 4.8.7 If depth is not zero: - if depth != 0 { - // Step 4.8.7.1 Run process a tokenizing error given tokenizer, regexp start, - // and tokenizer’s index - tokenizer.process_a_tokenizing_error(regexp_start, tokenizer.index)?; - - // Step 4.8.7.2 Continue. - continue; - } - - // Step 4.8.8 Let regexp length be regexp position − regexp start − 1. - let regexp_length = regexp_position - regexp_start - 1; - - // Step 4.8.9 If regexp length is zero: - if regexp_length == 0 { - // Step 4.8.9.1 Run process a tokenizing error given tokenizer, regexp start, - // and tokenizer’s index. - tokenizer.process_a_tokenizing_error(regexp_start, tokenizer.index)?; - - // Step 4.8.9.2 Continue. - continue; - } - - // Step 4.8.10 Run add a token given tokenizer, "regexp", regexp position, - // regexp start, and regexp length. - tokenizer.add_a_token( - TokenType::Regexp, - regexp_position, - regexp_start, - regexp_length, - ); - - // Step 4.8.11 Continue. - continue; - }, - _ => { - // Step 4.9 Run add a token with default position and length given tokenizer and "char". - tokenizer.add_a_token_with_default_position_and_length(TokenType::Char); - }, - } - } - - // Step 5. Run add a token with default length given tokenizer, "end", tokenizer’s index, and tokenizer’s index. - tokenizer.add_a_token_with_default_length(TokenType::End, tokenizer.index, tokenizer.index); - - // Step 6.Return tokenizer’s token list. - Ok(tokenizer.token_list) -} - -/// <https://urlpattern.spec.whatwg.org/#tokenizer> -struct Tokenizer<'a> { - /// <https://urlpattern.spec.whatwg.org/#tokenizer-input> - input: &'a str, - - /// <https://urlpattern.spec.whatwg.org/#tokenizer-policy> - policy: TokenizePolicy, - - /// <https://urlpattern.spec.whatwg.org/#tokenizer-index> - /// - /// Note that we deviate the from the spec and index bytes, not code points. - index: usize, - - /// <https://urlpattern.spec.whatwg.org/#tokenizer-next-index> - /// - /// Note that we deviate the from the spec and index bytes, not code points. - next_index: usize, - - /// <https://urlpattern.spec.whatwg.org/#tokenizer-token-list> - token_list: Vec<Token<'a>>, - - /// <https://urlpattern.spec.whatwg.org/#tokenizer-code-point> - code_point: char, -} - -/// <https://urlpattern.spec.whatwg.org/#token> -#[derive(Clone, Copy, Debug)] -#[allow(dead_code)] // index isn't used yet, because constructor strings aren't parsed -pub(super) struct Token<'a> { - /// <https://urlpattern.spec.whatwg.org/#token-index> - pub(super) index: usize, - - /// <https://urlpattern.spec.whatwg.org/#token-value> - pub(super) value: &'a str, - - /// <https://urlpattern.spec.whatwg.org/#token-type> - pub(super) token_type: TokenType, -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub(super) enum TokenType { - /// <https://urlpattern.spec.whatwg.org/#token-type-open> - Open, - - /// <https://urlpattern.spec.whatwg.org/#token-type-close> - Close, - - /// <https://urlpattern.spec.whatwg.org/#token-type-regexp> - Regexp, - - /// <https://urlpattern.spec.whatwg.org/#token-type-name> - Name, - - /// <https://urlpattern.spec.whatwg.org/#token-type-char> - Char, - - /// <https://urlpattern.spec.whatwg.org/#token-type-escaped-char> - EscapedChar, - - /// <https://urlpattern.spec.whatwg.org/#token-type-other-modifier> - OtherModifier, - - /// <https://urlpattern.spec.whatwg.org/#token-type-asterisk> - Asterisk, - - /// <https://urlpattern.spec.whatwg.org/#token-type-end> - End, - - /// <https://urlpattern.spec.whatwg.org/#token-type-invalid-char> - InvalidChar, -} - -/// <https://urlpattern.spec.whatwg.org/#tokenize-policy> -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub(super) enum TokenizePolicy { - /// <https://urlpattern.spec.whatwg.org/#tokenize-policy-strict> - Strict, - - /// <https://urlpattern.spec.whatwg.org/#tokenize-policy-lenient> - Lenient, -} - -impl Tokenizer<'_> { - fn is_last_character(&self, position: usize) -> bool { - self.input[position..].chars().count() == 1 - } - - fn is_done(&self) -> bool { - self.input[self.next_index..].is_empty() - } - - /// <https://urlpattern.spec.whatwg.org/#get-the-next-code-point> - fn get_the_next_code_point(&mut self) { - // Step 1. Set tokenizer’s code point to the Unicode code point in tokenizer’s - // input at the position indicated by tokenizer’s next index. - self.code_point = self.input[self.next_index..] - .chars() - .next() - .expect("URLPattern tokenizer is trying to read out of bounds"); - - // Step 2. Increment tokenizer’s next index by 1. - // NOTE: Because our next_index is indexing bytes (not code points) we use - // the utf8 length of the code point instead. - self.next_index = self.next_index.wrapping_add(self.code_point.len_utf8()); - } - - /// <https://urlpattern.spec.whatwg.org/#seek-and-get-the-next-code-point> - fn seek_and_get_the_next_code_point(&mut self, index: usize) { - // Step 1. Set tokenizer’s next index to index. - self.next_index = index; - - // Step 2. Run get the next code point given tokenizer. - self.get_the_next_code_point(); - } - - /// <https://urlpattern.spec.whatwg.org/#add-a-token> - fn add_a_token( - &mut self, - token_type: TokenType, - next_position: usize, - value_position: usize, - value_length: usize, - ) { - // Step 1. Let token be a new token. - // Step 2. Set token’s type to type. - // Step 3. Set token’s index to tokenizer’s index. - // Step 4. Set token’s value to the code point substring from value position - // with length value length within tokenizer’s input. - let token = Token { - token_type, - index: self.index, - value: &self.input[value_position..][..value_length], - }; - - // Step 5. Append token to the back of tokenizer’s token list. - self.token_list.push(token); - - // Step 6. Set tokenizer’s index to next position. - self.index = next_position; - } - - /// <https://urlpattern.spec.whatwg.org/#add-a-token-with-default-position-and-length> - fn add_a_token_with_default_position_and_length(&mut self, token_type: TokenType) { - // Step 1. Run add a token with default length given tokenizer, type, - // tokenizer’s next index, and tokenizer’s index. - self.add_a_token_with_default_length(token_type, self.next_index, self.index); - } - - /// <https://urlpattern.spec.whatwg.org/#add-a-token-with-default-length> - fn add_a_token_with_default_length( - &mut self, - token_type: TokenType, - next_position: usize, - value_position: usize, - ) { - // Step 1. Let computed length be next position − value position. - let computed_length = next_position - value_position; - - // Step 2. Run add a token given tokenizer, type, next position, value position, and computed length. - self.add_a_token(token_type, next_position, value_position, computed_length); - } - - /// <https://urlpattern.spec.whatwg.org/#process-a-tokenizing-error> - fn process_a_tokenizing_error( - &mut self, - next_position: usize, - value_position: usize, - ) -> Fallible<()> { - // Step 1. If tokenizer’s policy is "strict", then throw a TypeError. - if self.policy == TokenizePolicy::Strict { - return Err(Error::Type("Failed to tokenize URL pattern".into())); - } - - // Step 2. Assert: tokenizer’s policy is "lenient". - debug_assert_eq!(self.policy, TokenizePolicy::Lenient); - - // Step 3. Run add a token with default length given tokenizer, "invalid-char", - // next position, and value position. - self.add_a_token_with_default_length(TokenType::InvalidChar, next_position, value_position); - - Ok(()) - } -} - -/// <https://urlpattern.spec.whatwg.org/#is-a-valid-name-code-point> -fn is_a_valid_name_code_point(code_point: char, first: bool) -> bool { - // FIXME: implement this check - _ = first; - code_point.is_alphabetic() -} diff --git a/components/script/dom/virtualmethods.rs b/components/script/dom/virtualmethods.rs index 57ecba7b172..1d992b1f301 100644 --- a/components/script/dom/virtualmethods.rs +++ b/components/script/dom/virtualmethods.rs @@ -61,6 +61,7 @@ use crate::dom::htmlvideoelement::HTMLVideoElement; use crate::dom::node::{BindContext, ChildrenMutation, CloneChildrenFlag, Node, UnbindContext}; use crate::dom::shadowroot::ShadowRoot; use crate::dom::svgelement::SVGElement; +use crate::dom::svgimageelement::SVGImageElement; use crate::dom::svgsvgelement::SVGSVGElement; /// Trait to allow DOM nodes to opt-in to overriding (or adding to) common @@ -299,6 +300,9 @@ pub(crate) fn vtable_for(node: &Node) -> &dyn VirtualMethods { node.downcast::<HTMLTitleElement>().unwrap() as &dyn VirtualMethods }, NodeTypeId::Element(ElementTypeId::SVGElement(SVGElementTypeId::SVGGraphicsElement( + SVGGraphicsElementTypeId::SVGImageElement, + ))) => node.downcast::<SVGImageElement>().unwrap() as &dyn VirtualMethods, + NodeTypeId::Element(ElementTypeId::SVGElement(SVGElementTypeId::SVGGraphicsElement( SVGGraphicsElementTypeId::SVGSVGElement, ))) => node.downcast::<SVGSVGElement>().unwrap() as &dyn VirtualMethods, NodeTypeId::Element(ElementTypeId::SVGElement(SVGElementTypeId::SVGElement)) => { diff --git a/components/script/dom/websocket.rs b/components/script/dom/websocket.rs index b9b1b50c901..bbb637dfe28 100644 --- a/components/script/dom/websocket.rs +++ b/components/script/dom/websocket.rs @@ -471,7 +471,7 @@ struct ReportCSPViolationTask { impl TaskOnce for ReportCSPViolationTask { fn run_once(self) { let global = self.websocket.root().global(); - global.report_csp_violations(self.violations); + global.report_csp_violations(self.violations, None); } } diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index d888cc8d917..24e694b4f06 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -65,6 +65,7 @@ use profile_traits::time::ProfilerChan as TimeProfilerChan; use script_bindings::codegen::GenericBindings::NavigatorBinding::NavigatorMethods; use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods; use script_bindings::interfaces::WindowHelpers; +use script_bindings::root::Root; use script_layout_interface::{ FragmentType, Layout, PendingImageState, QueryMsg, Reflow, ReflowGoal, ReflowRequest, TrustedNodeAddress, combine_id_with_fragment_type, @@ -146,6 +147,7 @@ use crate::dom::performance::Performance; use crate::dom::promise::Promise; use crate::dom::screen::Screen; use crate::dom::selection::Selection; +use crate::dom::shadowroot::ShadowRoot; use crate::dom::storage::Storage; #[cfg(feature = "bluetooth")] use crate::dom::testrunner::TestRunner; @@ -165,7 +167,7 @@ use crate::script_runtime::{CanGc, JSContext, Runtime}; use crate::script_thread::ScriptThread; use crate::timers::{IsInterval, TimerCallback}; use crate::unminify::unminified_path; -use crate::webdriver_handlers::jsval_to_webdriver; +use crate::webdriver_handlers::{find_node_by_unique_id_in_document, jsval_to_webdriver}; use crate::{fetch, window_named_properties}; /// A callback to call when a response comes back from the `ImageCache`. @@ -1394,6 +1396,30 @@ impl WindowMethods<crate::DomTypeHolder> for Window { } } + fn WebdriverElement(&self, id: DOMString) -> Option<DomRoot<Element>> { + find_node_by_unique_id_in_document(&self.Document(), id.into()) + .ok() + .and_then(Root::downcast) + } + + fn WebdriverFrame(&self, id: DOMString) -> Option<DomRoot<Element>> { + find_node_by_unique_id_in_document(&self.Document(), id.into()) + .ok() + .and_then(Root::downcast::<HTMLIFrameElement>) + .map(Root::upcast::<Element>) + } + + fn WebdriverWindow(&self, _id: DOMString) -> Option<DomRoot<Window>> { + warn!("Window references are not supported in webdriver yet"); + None + } + + fn WebdriverShadowRoot(&self, id: DOMString) -> Option<DomRoot<ShadowRoot>> { + find_node_by_unique_id_in_document(&self.Document(), id.into()) + .ok() + .and_then(Root::downcast) + } + // https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle fn GetComputedStyle( &self, diff --git a/components/script/dom/writablestream.rs b/components/script/dom/writablestream.rs index 1b029f592de..f528f4fbde2 100644 --- a/components/script/dom/writablestream.rs +++ b/components/script/dom/writablestream.rs @@ -962,14 +962,13 @@ impl WritableStream { /// <https://streams.spec.whatwg.org/#create-writable-stream> #[cfg_attr(crown, allow(crown::unrooted_must_root))] -#[allow(unused)] pub(crate) fn create_writable_stream( cx: SafeJSContext, global: &GlobalScope, - can_gc: CanGc, writable_high_water_mark: f64, writable_size_algorithm: Rc<QueuingStrategySize>, underlying_sink_type: UnderlyingSinkType, + can_gc: CanGc, ) -> Fallible<DomRoot<WritableStream>> { // Assert: ! IsNonNegativeNumber(highWaterMark) is true. assert!(writable_high_water_mark >= 0.0); diff --git a/components/script/dom/writablestreamdefaultcontroller.rs b/components/script/dom/writablestreamdefaultcontroller.rs index 084165a6892..135ee6faa59 100644 --- a/components/script/dom/writablestreamdefaultcontroller.rs +++ b/components/script/dom/writablestreamdefaultcontroller.rs @@ -12,6 +12,7 @@ use js::jsval::{JSVal, UndefinedValue}; use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue, IntoHandle}; use super::bindings::codegen::Bindings::QueuingStrategyBinding::QueuingStrategySize; +use super::types::TransformStream; use crate::dom::bindings::callback::ExceptionHandling; use crate::dom::bindings::codegen::Bindings::UnderlyingSinkBinding::{ UnderlyingSinkAbortCallback, UnderlyingSinkCloseCallback, UnderlyingSinkStartCallback, @@ -290,8 +291,7 @@ pub enum UnderlyingSinkType { port: Dom<MessagePort>, }, /// Algorithms supporting transform streams are implemented in Rust. - #[allow(unused)] - Transform(/*Dom<TransformStream>, Rc<Promise>*/), + Transform(Dom<TransformStream>, Rc<Promise>), } impl UnderlyingSinkType { @@ -413,7 +413,7 @@ impl WritableStreamDefaultController { } => { backpressure_promise.borrow_mut().take(); }, - UnderlyingSinkType::Transform() => { + UnderlyingSinkType::Transform(_, _) => { return; }, } @@ -423,7 +423,6 @@ impl WritableStreamDefaultController { } /// <https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller> - #[allow(unsafe_code)] pub(crate) fn setup( &self, cx: SafeJSContext, @@ -560,9 +559,9 @@ impl WritableStreamDefaultController { // Let startAlgorithm be an algorithm that returns undefined. Ok(Promise::new_resolved(global, cx, (), can_gc)) }, - UnderlyingSinkType::Transform() => { + UnderlyingSinkType::Transform(_, start_promise) => { // Let startAlgorithm be an algorithm that returns startPromise. - todo!() + Ok(start_promise.clone()) }, } } @@ -622,9 +621,11 @@ impl WritableStreamDefaultController { } promise }, - UnderlyingSinkType::Transform() => { + UnderlyingSinkType::Transform(stream, _) => { // Return ! TransformStreamDefaultSinkAbortAlgorithm(stream, reason). - todo!() + stream + .transform_stream_default_sink_abort_algorithm(cx, global, reason, can_gc) + .expect("Transform stream default sink abort algorithm should not fail.") }, }; @@ -707,9 +708,11 @@ impl WritableStreamDefaultController { .append_native_handler(&handler, comp, can_gc); result_promise }, - UnderlyingSinkType::Transform() => { + UnderlyingSinkType::Transform(stream, _) => { // Return ! TransformStreamDefaultSinkWriteAlgorithm(stream, chunk). - todo!() + stream + .transform_stream_default_sink_write_algorithm(cx, global, chunk, can_gc) + .expect("Transform stream default sink write algorithm should not fail.") }, } } @@ -757,9 +760,11 @@ impl WritableStreamDefaultController { // Return a promise resolved with undefined. Promise::new_resolved(global, cx, (), can_gc) }, - UnderlyingSinkType::Transform() => { + UnderlyingSinkType::Transform(stream, _) => { // Return ! TransformStreamDefaultSinkCloseAlgorithm(stream). - todo!() + stream + .transform_stream_default_sink_close_algorithm(cx, global, can_gc) + .expect("Transform stream default sink close algorithm should not fail.") }, } } @@ -1038,7 +1043,7 @@ impl WritableStreamDefaultController { } /// <https://streams.spec.whatwg.org/#writable-stream-default-controller-error> - fn error( + pub(crate) fn error( &self, stream: &WritableStream, cx: SafeJSContext, diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs index 9cef58acc9a..ca5bb72a1dc 100644 --- a/components/script/dom/xmlhttprequest.rs +++ b/components/script/dom/xmlhttprequest.rs @@ -148,7 +148,7 @@ impl FetchResponseListener for XHRContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/fetch.rs b/components/script/fetch.rs index 9192a030b66..989cdba862a 100644 --- a/components/script/fetch.rs +++ b/components/script/fetch.rs @@ -313,7 +313,7 @@ impl FetchResponseListener for FetchContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/layout_image.rs b/components/script/layout_image.rs index df542b4b759..546e758e38c 100644 --- a/components/script/layout_image.rs +++ b/components/script/layout_image.rs @@ -81,7 +81,7 @@ impl FetchResponseListener for LayoutImageContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/messaging.rs b/components/script/messaging.rs index e0ea9e30af2..08d6fc841cf 100644 --- a/components/script/messaging.rs +++ b/components/script/messaging.rs @@ -91,6 +91,7 @@ impl MixedMessage { #[cfg(feature = "webgpu")] ScriptThreadMessage::SetWebGPUPort(..) => None, ScriptThreadMessage::SetScrollStates(id, ..) => Some(*id), + ScriptThreadMessage::EvaluateJavaScript(id, _, _) => Some(*id), }, MixedMessage::FromScript(inner_msg) => match inner_msg { MainThreadScriptMsg::Common(CommonScriptMsg::Task(_, _, pipeline_id, _)) => { diff --git a/components/script/script_module.rs b/components/script/script_module.rs index 0aa35a2eda8..449f17901ed 100644 --- a/components/script/script_module.rs +++ b/components/script/script_module.rs @@ -1277,7 +1277,7 @@ impl FetchResponseListener for ModuleContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/script_runtime.rs b/components/script/script_runtime.rs index 1f05c15d74e..c407f9cfc73 100644 --- a/components/script/script_runtime.rs +++ b/components/script/script_runtime.rs @@ -390,7 +390,7 @@ unsafe extern "C" fn content_security_policy_allows( RuntimeCode::WASM => csp_list.is_wasm_evaluation_allowed(), }; - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); allowed = is_evaluation_allowed == CheckResult::Allowed; }); allowed diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 54cf89a213f..4815e6feae1 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -50,9 +50,9 @@ use devtools_traits::{ }; use embedder_traits::user_content_manager::UserContentManager; use embedder_traits::{ - CompositorHitTestResult, EmbedderMsg, FocusSequenceNumber, InputEvent, MediaSessionActionType, - MouseButton, MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, - WebDriverScriptCommand, + CompositorHitTestResult, EmbedderMsg, FocusSequenceNumber, InputEvent, + JavaScriptEvaluationError, JavaScriptEvaluationId, MediaSessionActionType, MouseButton, + MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, WebDriverScriptCommand, }; use euclid::Point2D; use euclid::default::Rect; @@ -156,6 +156,7 @@ use crate::script_runtime::{ }; use crate::task_queue::TaskQueue; use crate::task_source::{SendableTaskSource, TaskSourceName}; +use crate::webdriver_handlers::jsval_to_webdriver; use crate::{devtools, webdriver_handlers}; thread_local!(static SCRIPT_THREAD_ROOT: Cell<Option<*const ScriptThread>> = const { Cell::new(None) }); @@ -1878,6 +1879,9 @@ impl ScriptThread { ScriptThreadMessage::SetScrollStates(pipeline_id, scroll_states) => { self.handle_set_scroll_states(pipeline_id, scroll_states) }, + ScriptThreadMessage::EvaluateJavaScript(pipeline_id, evaluation_id, script) => { + self.handle_evaluate_javascript(pipeline_id, evaluation_id, script, can_gc); + }, } } @@ -3606,7 +3610,8 @@ impl ScriptThread { fn handle_csp_violations(&self, id: PipelineId, _: RequestId, violations: Vec<csp::Violation>) { if let Some(global) = self.documents.borrow().find_global(id) { - global.report_csp_violations(violations); + // TODO(https://github.com/w3c/webappsec-csp/issues/687): Update after spec is resolved + global.report_csp_violations(violations, None); } } @@ -3814,6 +3819,53 @@ impl ScriptThread { ) } } + + fn handle_evaluate_javascript( + &self, + pipeline_id: PipelineId, + evaluation_id: JavaScriptEvaluationId, + script: String, + can_gc: CanGc, + ) { + let Some(window) = self.documents.borrow().find_window(pipeline_id) else { + let _ = self.senders.pipeline_to_constellation_sender.send(( + pipeline_id, + ScriptToConstellationMessage::FinishJavaScriptEvaluation( + evaluation_id, + Err(JavaScriptEvaluationError::WebViewNotReady), + ), + )); + return; + }; + + let global_scope = window.as_global_scope(); + let realm = enter_realm(global_scope); + let context = window.get_cx(); + + rooted!(in(*context) let mut return_value = UndefinedValue()); + global_scope.evaluate_js_on_global_with_result( + &script, + return_value.handle_mut(), + ScriptFetchOptions::default_classic_script(global_scope), + global_scope.api_base_url(), + can_gc, + ); + let result = match jsval_to_webdriver( + context, + global_scope, + return_value.handle(), + (&realm).into(), + can_gc, + ) { + Ok(ref value) => Ok(value.into()), + Err(_) => Err(JavaScriptEvaluationError::SerializationError), + }; + + let _ = self.senders.pipeline_to_constellation_sender.send(( + pipeline_id, + ScriptToConstellationMessage::FinishJavaScriptEvaluation(evaluation_id, result), + )); + } } impl Drop for ScriptThread { diff --git a/components/script/security_manager.rs b/components/script/security_manager.rs index ee320206de2..ee062594eb8 100644 --- a/components/script/security_manager.rs +++ b/components/script/security_manager.rs @@ -14,8 +14,7 @@ use crate::dom::bindings::codegen::Bindings::SecurityPolicyViolationEventBinding }; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::refcounted::Trusted; -use crate::dom::bindings::reflector::DomGlobal; -use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::event::{Event, EventBubbles, EventCancelable, EventComposed}; use crate::dom::eventtarget::EventTarget; use crate::dom::securitypolicyviolationevent::SecurityPolicyViolationEvent; use crate::dom::types::GlobalScope; @@ -23,6 +22,7 @@ use crate::script_runtime::CanGc; use crate::task::TaskOnce; pub(crate) struct CSPViolationReportTask { + global: Trusted<GlobalScope>, event_target: Trusted<EventTarget>, violation_report: SecurityPolicyViolationReport, } @@ -159,28 +159,31 @@ impl CSPViolationReportBuilder { impl CSPViolationReportTask { pub fn new( - global: &GlobalScope, - report: SecurityPolicyViolationReport, + global: Trusted<GlobalScope>, + event_target: Trusted<EventTarget>, + violation_report: SecurityPolicyViolationReport, ) -> CSPViolationReportTask { CSPViolationReportTask { - violation_report: report, - event_target: Trusted::new(global.upcast::<EventTarget>()), + global, + event_target, + violation_report, } } fn fire_violation_event(self, can_gc: CanGc) { - let target = self.event_target.root(); - let global = &target.global(); let event = SecurityPolicyViolationEvent::new( - global, + &self.global.root(), Atom::from("securitypolicyviolation"), EventBubbles::Bubbles, EventCancelable::Cancelable, + EventComposed::Composed, &self.violation_report.convert(), can_gc, ); - event.upcast::<Event>().fire(&target, can_gc); + event + .upcast::<Event>() + .fire(&self.event_target.root(), can_gc); } } diff --git a/components/script/stylesheet_loader.rs b/components/script/stylesheet_loader.rs index 67e186c7f6a..a18d63e323b 100644 --- a/components/script/stylesheet_loader.rs +++ b/components/script/stylesheet_loader.rs @@ -286,7 +286,7 @@ impl FetchResponseListener for StylesheetContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/webdriver_handlers.rs b/components/script/webdriver_handlers.rs index 781ac53f415..6b4264d945e 100644 --- a/components/script/webdriver_handlers.rs +++ b/components/script/webdriver_handlers.rs @@ -53,6 +53,7 @@ use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::{DomGlobal, DomObject}; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; use crate::dom::element::Element; use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; @@ -77,12 +78,27 @@ fn find_node_by_unique_id( pipeline: PipelineId, node_id: String, ) -> Result<DomRoot<Node>, ErrorStatus> { - match documents.find_document(pipeline).and_then(|document| { - document - .upcast::<Node>() - .traverse_preorder(ShadowIncluding::Yes) - .find(|node| node.unique_id() == node_id) - }) { + match documents.find_document(pipeline) { + Some(doc) => find_node_by_unique_id_in_document(&doc, node_id), + None => { + if ScriptThread::has_node_id(&node_id) { + Err(ErrorStatus::StaleElementReference) + } else { + Err(ErrorStatus::NoSuchElement) + } + }, + } +} + +pub(crate) fn find_node_by_unique_id_in_document( + document: &Document, + node_id: String, +) -> Result<DomRoot<Node>, ErrorStatus> { + match document + .upcast::<Node>() + .traverse_preorder(ShadowIncluding::Yes) + .find(|node| node.unique_id() == node_id) + { Some(node) => Ok(node), None => { if ScriptThread::has_node_id(&node_id) { @@ -183,7 +199,7 @@ unsafe fn is_arguments_object(cx: *mut JSContext, value: HandleValue) -> bool { jsstring_to_str(cx, class_name) == "[object Arguments]" } -#[derive(Eq, Hash, PartialEq)] +#[derive(Clone, Eq, Hash, PartialEq)] struct HashableJSVal(u64); impl From<HandleValue<'_>> for HashableJSVal { @@ -193,6 +209,7 @@ impl From<HandleValue<'_>> for HashableJSVal { } #[allow(unsafe_code)] +/// <https://w3c.github.io/webdriver/#dfn-json-deserialize> pub(crate) fn jsval_to_webdriver( cx: SafeJSContext, global_scope: &GlobalScope, @@ -215,12 +232,6 @@ unsafe fn jsval_to_webdriver_inner( val: HandleValue, seen: &mut HashSet<HashableJSVal>, ) -> WebDriverJSResult { - let hashable = val.into(); - if seen.contains(&hashable) { - return Err(WebDriverJSError::JSError); - } - seen.insert(hashable); - let _ac = enter_realm(global_scope); if val.get().is_undefined() { Ok(WebDriverJSValue::Undefined) @@ -252,14 +263,26 @@ unsafe fn jsval_to_webdriver_inner( _ => unreachable!(), }; Ok(WebDriverJSValue::String(String::from(string))) - } else if val.get().is_object() { + } + // https://w3c.github.io/webdriver/#dfn-clone-an-object + else if val.get().is_object() { + let hashable = val.into(); + // Step 1. If value is in `seen`, return error with error code javascript error. + if seen.contains(&hashable) { + return Err(WebDriverJSError::JSError); + } + //Step 2. Append value to `seen`. + seen.insert(hashable.clone()); + rooted!(in(cx) let object = match FromJSValConvertible::from_jsval(cx, val, ()).unwrap() { ConversionResult::Success(object) => object, _ => unreachable!(), }); let _ac = JSAutoRealm::new(cx, *object); - if is_array_like::<crate::DomTypeHolder>(cx, val) || is_arguments_object(cx, val) { + let return_val = if is_array_like::<crate::DomTypeHolder>(cx, val) || + is_arguments_object(cx, val) + { let mut result: Vec<WebDriverJSValue> = Vec::new(); let length = match get_property::<u32>( @@ -282,7 +305,7 @@ unsafe fn jsval_to_webdriver_inner( return Err(WebDriverJSError::JSError); }, }; - + // Step 4. For each enumerable property in value, run the following substeps: for i in 0..length { rooted!(in(cx) let mut item = UndefinedValue()); match get_property_jsval(cx, object.handle(), &i.to_string(), item.handle_mut()) { @@ -303,7 +326,6 @@ unsafe fn jsval_to_webdriver_inner( }, } } - Ok(WebDriverJSValue::ArrayLike(result)) } else if let Ok(element) = root_from_object::<Element>(*object, cx) { Ok(WebDriverJSValue::Element(WebElement( @@ -312,7 +334,7 @@ unsafe fn jsval_to_webdriver_inner( } else if let Ok(window) = root_from_object::<Window>(*object, cx) { let window_proxy = window.window_proxy(); if window_proxy.is_browsing_context_discarded() { - Err(WebDriverJSError::StaleElementReference) + return Err(WebDriverJSError::StaleElementReference); } else if window_proxy.browsing_context_id() == window_proxy.webview_id() { Ok(WebDriverJSValue::Window(WebWindow( window.Document().upcast::<Node>().unique_id(), @@ -332,7 +354,12 @@ unsafe fn jsval_to_webdriver_inner( &HandleValueArray::empty(), value.handle_mut(), ) { - jsval_to_webdriver_inner(cx, global_scope, value.handle(), seen) + Ok(jsval_to_webdriver_inner( + cx, + global_scope, + value.handle(), + seen, + )?) } else { throw_dom_exception( SafeJSContext::from_ptr(cx), @@ -340,7 +367,7 @@ unsafe fn jsval_to_webdriver_inner( Error::JSFailed, CanGc::note(), ); - Err(WebDriverJSError::JSError) + return Err(WebDriverJSError::JSError); } } else { let mut result = HashMap::new(); @@ -392,9 +419,12 @@ unsafe fn jsval_to_webdriver_inner( } } } - Ok(WebDriverJSValue::Object(result)) - } + }; + // Step 5. Remove the last element of `seen`. + seen.remove(&hashable); + // Step 6. Return success with data `result`. + return_val } else { Err(WebDriverJSError::UnknownType) } @@ -872,7 +902,7 @@ pub(crate) fn handle_get_page_source( .find_document(pipeline) .ok_or(ErrorStatus::UnknownError) .and_then(|document| match document.GetDocumentElement() { - Some(element) => match element.GetOuterHTML(can_gc) { + Some(element) => match element.outer_html(can_gc) { Ok(source) => Ok(source.to_string()), Err(_) => { match XMLSerializer::new(document.window(), None, can_gc) diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index cb33188804f..92871bc54aa 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -88,7 +88,7 @@ DOMInterfaces = { }, 'Clipboard': { - 'canGc': ['WriteText'] + 'canGc': ['ReadText', 'WriteText'] }, 'ClipboardItem': { @@ -371,7 +371,7 @@ DOMInterfaces = { }, 'HTMLIFrameElement': { - 'canGc': ['Sandbox'], + 'canGc': ['Sandbox', 'SetSrcdoc'], }, 'HTMLImageElement': { @@ -538,7 +538,7 @@ DOMInterfaces = { 'Promise': { 'spiderMonkeyInterface': True, - 'additionalTraits': ["crate::interfaces::PromiseHelpers<Self>", "js::conversions::FromJSValConvertibleRc"] + 'additionalTraits': ["js::conversions::FromJSValConvertibleRc"] }, 'Range': { diff --git a/components/script_bindings/codegen/CodegenRust.py b/components/script_bindings/codegen/CodegenRust.py index 48f024be70f..458aa7508b0 100644 --- a/components/script_bindings/codegen/CodegenRust.py +++ b/components/script_bindings/codegen/CodegenRust.py @@ -742,6 +742,19 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, "}") return templateBody + # A helper function for types that implement FromJSValConvertible trait + def fromJSValTemplate(config, errorHandler, exceptionCode): + return f"""match FromJSValConvertible::from_jsval(*cx, ${{val}}, {config}) {{ + Ok(ConversionResult::Success(value)) => value, + Ok(ConversionResult::Failure(error)) => {{ + {errorHandler} + }} + _ => {{ + {exceptionCode} + }}, +}} +""" + assert not (isEnforceRange and isClamp) # These are mutually exclusive if type.isSequence() or type.isRecord(): @@ -755,13 +768,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, if type.nullable(): declType = CGWrapper(declType, pre="Option<", post=" >") - templateBody = (f"match FromJSValConvertible::from_jsval(*cx, ${{val}}, {config}) {{\n" - " Ok(ConversionResult::Success(value)) => value,\n" - " Ok(ConversionResult::Failure(error)) => {\n" - f"{indent(failOrPropagate, 8)}\n" - " }\n" - f" _ => {{ {exceptionCode} }},\n" - "}") + templateBody = fromJSValTemplate(config, failOrPropagate, exceptionCode) return handleOptional(templateBody, declType, handleDefault("None")) @@ -770,13 +777,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, if type.nullable(): declType = CGWrapper(declType, pre="Option<", post=" >") - templateBody = ("match FromJSValConvertible::from_jsval(*cx, ${val}, ()) {\n" - " Ok(ConversionResult::Success(value)) => value,\n" - " Ok(ConversionResult::Failure(error)) => {\n" - f"{indent(failOrPropagate, 8)}\n" - " }\n" - f" _ => {{ {exceptionCode} }},\n" - "}") + templateBody = fromJSValTemplate("()", failOrPropagate, exceptionCode) dictionaries = [ memberType @@ -836,21 +837,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, # once again be providing a Promise to signal completion of an # operation, which would then not be exposed to anyone other than # our own implementation code. - templateBody = fill( - """ - { // Scope for our JSAutoRealm. - - rooted!(in(*cx) let globalObj = CurrentGlobalOrNull(*cx)); - let promiseGlobal = D::GlobalScope::from_object_maybe_wrapped(globalObj.handle().get(), *cx); - - rooted!(in(*cx) let mut valueToResolve = $${val}.get()); - if !JS_WrapValue(*cx, valueToResolve.handle_mut()) { - $*{exceptionCode} - } - D::Promise::new_resolved(&promiseGlobal, cx, valueToResolve.handle()) - } - """, - exceptionCode=exceptionCode) + templateBody = fromJSValTemplate("()", failOrPropagate, exceptionCode) if isArgument: declType = CGGeneric("&D::Promise") @@ -960,14 +947,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, if type.isDOMString(): nullBehavior = getConversionConfigForType(type, isEnforceRange, isClamp, treatNullAs) - conversionCode = ( - f"match FromJSValConvertible::from_jsval(*cx, ${{val}}, {nullBehavior}) {{\n" - " Ok(ConversionResult::Success(strval)) => strval,\n" - " Ok(ConversionResult::Failure(error)) => {\n" - f"{indent(failOrPropagate, 8)}\n" - " }\n" - f" _ => {{ {exceptionCode} }},\n" - "}") + conversionCode = fromJSValTemplate(nullBehavior, failOrPropagate, exceptionCode) if defaultValue is None: default = None @@ -989,14 +969,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, if type.isUSVString(): assert not isEnforceRange and not isClamp - conversionCode = ( - "match FromJSValConvertible::from_jsval(*cx, ${val}, ()) {\n" - " Ok(ConversionResult::Success(strval)) => strval,\n" - " Ok(ConversionResult::Failure(error)) => {\n" - f"{indent(failOrPropagate, 8)}\n" - " }\n" - f" _ => {{ {exceptionCode} }},\n" - "}") + conversionCode = fromJSValTemplate("()", failOrPropagate, exceptionCode) if defaultValue is None: default = None @@ -1018,14 +991,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, if type.isByteString(): assert not isEnforceRange and not isClamp - conversionCode = ( - "match FromJSValConvertible::from_jsval(*cx, ${val}, ()) {\n" - " Ok(ConversionResult::Success(strval)) => strval,\n" - " Ok(ConversionResult::Failure(error)) => {\n" - f"{indent(failOrPropagate, 8)}\n" - " }\n" - f" _ => {{ {exceptionCode} }},\n" - "}") + conversionCode = fromJSValTemplate("()", failOrPropagate, exceptionCode) if defaultValue is None: default = None @@ -1056,12 +1022,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, else: handleInvalidEnumValueCode = "return true;" - template = ( - "match FromJSValConvertible::from_jsval(*cx, ${val}, ()) {" - f" Err(_) => {{ {exceptionCode} }},\n" - " Ok(ConversionResult::Success(v)) => v,\n" - f" Ok(ConversionResult::Failure(error)) => {{ {handleInvalidEnumValueCode} }},\n" - "}") + template = fromJSValTemplate("()", handleInvalidEnumValueCode, exceptionCode) if defaultValue is not None: assert defaultValue.type.tag() == IDLType.Tags.domstring @@ -1192,14 +1153,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, if type_needs_tracing(type): declType = CGTemplatedType("RootedTraceableBox", declType) - template = ( - "match FromJSValConvertible::from_jsval(*cx, ${val}, ()) {\n" - " Ok(ConversionResult::Success(dictionary)) => dictionary,\n" - " Ok(ConversionResult::Failure(error)) => {\n" - f"{indent(failOrPropagate, 8)}\n" - " }\n" - f" _ => {{ {exceptionCode} }},\n" - "}") + template = fromJSValTemplate("()", failOrPropagate, exceptionCode) return handleOptional(template, declType, handleDefault(empty)) @@ -1220,14 +1174,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, if type.nullable(): declType = CGWrapper(declType, pre="Option<", post=">") - template = ( - f"match FromJSValConvertible::from_jsval(*cx, ${{val}}, {conversionBehavior}) {{\n" - " Ok(ConversionResult::Success(v)) => v,\n" - " Ok(ConversionResult::Failure(error)) => {\n" - f"{indent(failOrPropagate, 8)}\n" - " }\n" - f" _ => {{ {exceptionCode} }}\n" - "}") + template = fromJSValTemplate(conversionBehavior, failOrPropagate, exceptionCode) if defaultValue is not None: if isinstance(defaultValue, IDLNullValue): diff --git a/components/script_bindings/conversions.rs b/components/script_bindings/conversions.rs index 4d8e7aa595c..953dca889a8 100644 --- a/components/script_bindings/conversions.rs +++ b/components/script_bindings/conversions.rs @@ -532,7 +532,7 @@ where /// Returns whether `value` is an array-like object (Array, FileList, /// HTMLCollection, HTMLFormControlsCollection, HTMLOptionsCollection, -/// NodeList). +/// NodeList, DOMTokenList). /// /// # Safety /// `cx` must point to a valid, non-null JSContext. @@ -548,6 +548,10 @@ pub unsafe fn is_array_like<D: crate::DomTypes>(cx: *mut JSContext, value: Handl _ => return false, }; + // TODO: HTMLAllCollection + if root_from_object::<D::DOMTokenList>(object, cx).is_ok() { + return true; + } if root_from_object::<D::FileList>(object, cx).is_ok() { return true; } diff --git a/components/script_bindings/import.rs b/components/script_bindings/import.rs index 16cc92f07bf..25bd6c38669 100644 --- a/components/script_bindings/import.rs +++ b/components/script_bindings/import.rs @@ -11,12 +11,12 @@ pub(crate) mod base { }; pub(crate) use js::error::throw_type_error; pub(crate) use js::jsapi::{ - CurrentGlobalOrNull, HandleValue as RawHandleValue, HandleValueArray, Heap, IsCallable, - JS_NewObject, JSContext, JSObject, + HandleValue as RawHandleValue, HandleValueArray, Heap, IsCallable, JS_NewObject, JSContext, + JSObject, }; pub(crate) use js::jsval::{JSVal, NullValue, ObjectOrNullValue, ObjectValue, UndefinedValue}; pub(crate) use js::panic::maybe_resume_unwind; - pub(crate) use js::rust::wrappers::{Call, JS_WrapValue}; + pub(crate) use js::rust::wrappers::Call; pub(crate) use js::rust::{HandleObject, HandleValue, MutableHandleObject, MutableHandleValue}; pub(crate) use crate::callback::{ diff --git a/components/script_bindings/interfaces.rs b/components/script_bindings/interfaces.rs index b289737143e..58917283170 100644 --- a/components/script_bindings/interfaces.rs +++ b/components/script_bindings/interfaces.rs @@ -3,10 +3,8 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cell::RefCell; -use std::rc::Rc; use std::thread::LocalKey; -use js::conversions::ToJSValConvertible; use js::glue::JSPrincipalsCallbacks; use js::jsapi::{CallArgs, HandleObject as RawHandleObject, JSContext as RawJSContext, JSObject}; use js::rust::{HandleObject, MutableHandleObject}; @@ -78,14 +76,6 @@ pub trait GlobalScopeHelpers<D: DomTypes> { unsafe fn from_object(obj: *mut JSObject) -> DomRoot<D::GlobalScope>; fn from_reflector(reflector: &impl DomObject, realm: InRealm) -> DomRoot<D::GlobalScope>; - /// # Safety - /// `obj` must point to a valid, non-null JSObject. - /// `cx` must point to a valid, non-null RawJSContext. - unsafe fn from_object_maybe_wrapped( - obj: *mut JSObject, - cx: *mut RawJSContext, - ) -> DomRoot<D::GlobalScope>; - fn origin(&self) -> &MutableOrigin; fn incumbent() -> Option<DomRoot<D::GlobalScope>>; @@ -101,15 +91,6 @@ pub trait DocumentHelpers { fn ensure_safe_to_run_script_or_layout(&self); } -/// Operations that must be invoked from the generated bindings. -pub trait PromiseHelpers<D: crate::DomTypes> { - fn new_resolved( - global: &D::GlobalScope, - cx: JSContext, - value: impl ToJSValConvertible, - ) -> Rc<D::Promise>; -} - pub trait ServoInternalsHelpers { fn is_servo_internal(cx: JSContext, global: HandleObject) -> bool; } diff --git a/components/script_bindings/webidls/CanvasRenderingContext2D.webidl b/components/script_bindings/webidls/CanvasRenderingContext2D.webidl index 0c4960fe6ad..46d91683577 100644 --- a/components/script_bindings/webidls/CanvasRenderingContext2D.webidl +++ b/components/script_bindings/webidls/CanvasRenderingContext2D.webidl @@ -254,7 +254,7 @@ interface CanvasPattern { Serializable] interface ImageData { [Throws] constructor(unsigned long sw, unsigned long sh/*, optional ImageDataSettings settings = {}*/); - [Throws] constructor(/* Uint8ClampedArray */ object data, unsigned long sw, optional unsigned long sh + [Throws] constructor(Uint8ClampedArray data, unsigned long sw, optional unsigned long sh /*, optional ImageDataSettings settings = {}*/); readonly attribute unsigned long width; diff --git a/components/script_bindings/webidls/Clipboard.webidl b/components/script_bindings/webidls/Clipboard.webidl index 7562adbfa38..b77e975917e 100644 --- a/components/script_bindings/webidls/Clipboard.webidl +++ b/components/script_bindings/webidls/Clipboard.webidl @@ -9,7 +9,7 @@ typedef sequence<ClipboardItem> ClipboardItems; [SecureContext, Exposed=Window, Pref="dom_async_clipboard_enabled"] interface Clipboard : EventTarget { // Promise<ClipboardItems> read(); - // Promise<DOMString> readText(); + Promise<DOMString> readText(); // Promise<undefined> write(ClipboardItems data); Promise<undefined> writeText(DOMString data); }; diff --git a/components/script_bindings/webidls/Document.webidl b/components/script_bindings/webidls/Document.webidl index 737e74d3bf2..e878b1642e4 100644 --- a/components/script_bindings/webidls/Document.webidl +++ b/components/script_bindings/webidls/Document.webidl @@ -154,7 +154,6 @@ partial /*sealed*/ interface Document { // also has obsolete members }; Document includes GlobalEventHandlers; -Document includes DocumentAndElementEventHandlers; // https://html.spec.whatwg.org/multipage/#Document-partial partial interface Document { diff --git a/components/script_bindings/webidls/Element.webidl b/components/script_bindings/webidls/Element.webidl index 0d2e204ae52..42733b91929 100644 --- a/components/script_bindings/webidls/Element.webidl +++ b/components/script_bindings/webidls/Element.webidl @@ -82,7 +82,7 @@ interface Element : Node { [Throws] undefined insertAdjacentText(DOMString where_, DOMString data); [CEReactions, Throws] - undefined insertAdjacentHTML(DOMString position, DOMString html); + undefined insertAdjacentHTML(DOMString position, (TrustedHTML or DOMString) string); [Throws, Pref="dom_shadowdom_enabled"] ShadowRoot attachShadow(ShadowRootInit init); readonly attribute ShadowRoot? shadowRoot; @@ -122,11 +122,11 @@ partial interface Element { // https://html.spec.whatwg.org/multipage/#dom-parsing-and-serialization partial interface Element { - [CEReactions] undefined setHTMLUnsafe(DOMString html); + [CEReactions, Throws] undefined setHTMLUnsafe((TrustedHTML or DOMString) html); DOMString getHTML(optional GetHTMLOptions options = {}); - [CEReactions, Throws] attribute [LegacyNullToEmptyString] DOMString innerHTML; - [CEReactions, Throws] attribute [LegacyNullToEmptyString] DOMString outerHTML; + [CEReactions, Throws] attribute (TrustedHTML or [LegacyNullToEmptyString] DOMString) innerHTML; + [CEReactions, Throws] attribute (TrustedHTML or [LegacyNullToEmptyString] DOMString) outerHTML; }; dictionary GetHTMLOptions { diff --git a/components/script_bindings/webidls/EventHandler.webidl b/components/script_bindings/webidls/EventHandler.webidl index f597ce237d3..d32302f4b37 100644 --- a/components/script_bindings/webidls/EventHandler.webidl +++ b/components/script_bindings/webidls/EventHandler.webidl @@ -28,6 +28,10 @@ typedef OnBeforeUnloadEventHandlerNonNull? OnBeforeUnloadEventHandler; [Exposed=Window] interface mixin GlobalEventHandlers { attribute EventHandler onabort; + attribute EventHandler onauxclick; + attribute EventHandler onbeforeinput; + attribute EventHandler onbeforematch; + attribute EventHandler onbeforetoggle; attribute EventHandler onblur; attribute EventHandler oncancel; attribute EventHandler oncanplay; @@ -35,13 +39,17 @@ interface mixin GlobalEventHandlers { attribute EventHandler onchange; attribute EventHandler onclick; attribute EventHandler onclose; + attribute EventHandler oncommand; + attribute EventHandler oncontextlost; attribute EventHandler oncontextmenu; + attribute EventHandler oncontextrestored; + attribute EventHandler oncopy; attribute EventHandler oncuechange; + attribute EventHandler oncut; attribute EventHandler ondblclick; attribute EventHandler ondrag; attribute EventHandler ondragend; attribute EventHandler ondragenter; - attribute EventHandler ondragexit; attribute EventHandler ondragleave; attribute EventHandler ondragover; attribute EventHandler ondragstart; @@ -68,7 +76,7 @@ interface mixin GlobalEventHandlers { attribute EventHandler onmouseout; attribute EventHandler onmouseover; attribute EventHandler onmouseup; - attribute EventHandler onwheel; + attribute EventHandler onpaste; attribute EventHandler onpause; attribute EventHandler onplay; attribute EventHandler onplaying; @@ -77,11 +85,12 @@ interface mixin GlobalEventHandlers { attribute EventHandler onreset; attribute EventHandler onresize; attribute EventHandler onscroll; + attribute EventHandler onscrollend; attribute EventHandler onsecuritypolicyviolation; attribute EventHandler onseeked; attribute EventHandler onseeking; attribute EventHandler onselect; - attribute EventHandler onshow; + attribute EventHandler onslotchange; attribute EventHandler onstalled; attribute EventHandler onsubmit; attribute EventHandler onsuspend; @@ -89,6 +98,11 @@ interface mixin GlobalEventHandlers { attribute EventHandler ontoggle; attribute EventHandler onvolumechange; attribute EventHandler onwaiting; + attribute EventHandler onwebkitanimationend; + attribute EventHandler onwebkitanimationiteration; + attribute EventHandler onwebkitanimationstart; + attribute EventHandler onwebkittransitionend; + attribute EventHandler onwheel; }; // https://drafts.csswg.org/css-animations/#interface-globaleventhandlers-idl @@ -123,18 +137,12 @@ interface mixin WindowEventHandlers { attribute EventHandler onoffline; attribute EventHandler ononline; attribute EventHandler onpagehide; + attribute EventHandler onpagereveal; attribute EventHandler onpageshow; + attribute EventHandler onpageswap; attribute EventHandler onpopstate; attribute EventHandler onrejectionhandled; attribute EventHandler onstorage; attribute EventHandler onunhandledrejection; attribute EventHandler onunload; }; - -// https://html.spec.whatwg.org/multipage/#documentandelementeventhandlers -[Exposed=Window] -interface mixin DocumentAndElementEventHandlers { - attribute EventHandler oncopy; - attribute EventHandler oncut; - attribute EventHandler onpaste; -}; diff --git a/components/script_bindings/webidls/HTMLElement.webidl b/components/script_bindings/webidls/HTMLElement.webidl index 76bfada1b94..19a4b515d11 100644 --- a/components/script_bindings/webidls/HTMLElement.webidl +++ b/components/script_bindings/webidls/HTMLElement.webidl @@ -73,7 +73,6 @@ partial interface HTMLElement { }; HTMLElement includes GlobalEventHandlers; -HTMLElement includes DocumentAndElementEventHandlers; HTMLElement includes ElementContentEditable; HTMLElement includes ElementCSSInlineStyle; HTMLElement includes HTMLOrSVGElement; diff --git a/components/script_bindings/webidls/HTMLIFrameElement.webidl b/components/script_bindings/webidls/HTMLIFrameElement.webidl index 8ba58a20f33..b083f51c0f1 100644 --- a/components/script_bindings/webidls/HTMLIFrameElement.webidl +++ b/components/script_bindings/webidls/HTMLIFrameElement.webidl @@ -9,8 +9,8 @@ interface HTMLIFrameElement : HTMLElement { [CEReactions] attribute USVString src; - [CEReactions] - attribute DOMString srcdoc; + [CEReactions, SetterThrows] + attribute (TrustedHTML or DOMString) srcdoc; [CEReactions] attribute DOMString name; diff --git a/components/script_bindings/webidls/SVGElement.webidl b/components/script_bindings/webidls/SVGElement.webidl index e6bc468d5dc..08bcb4a8c99 100644 --- a/components/script_bindings/webidls/SVGElement.webidl +++ b/components/script_bindings/webidls/SVGElement.webidl @@ -18,7 +18,7 @@ interface SVGElement : Element { //void blur(); }; -//SVGElement includes GlobalEventHandlers; +SVGElement includes GlobalEventHandlers; //SVGElement includes SVGElementInstance; SVGElement includes ElementCSSInlineStyle; SVGElement includes HTMLOrSVGElement; diff --git a/components/script_bindings/webidls/SVGImageElement.webidl b/components/script_bindings/webidls/SVGImageElement.webidl new file mode 100644 index 00000000000..bced6277c5e --- /dev/null +++ b/components/script_bindings/webidls/SVGImageElement.webidl @@ -0,0 +1,16 @@ +/* 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/. */ + +// https://svgwg.org/svg2-draft/embedded.html#InterfaceSVGImageElement +[Exposed=Window, Pref="dom_svg_enabled"] +interface SVGImageElement : SVGGraphicsElement { + //[SameObject] readonly attribute SVGAnimatedLength x; + //[SameObject] readonly attribute SVGAnimatedLength y; + //[SameObject] readonly attribute SVGAnimatedLength width; + //[SameObject] readonly attribute SVGAnimatedLength height; + //[SameObject] readonly attribute SVGAnimatedPreserveAspectRatio preserveAspectRatio; + //attribute DOMString? crossOrigin; +}; + +//SVGImageElement includes SVGURIReference; diff --git a/components/script_bindings/webidls/TransformStream.webidl b/components/script_bindings/webidls/TransformStream.webidl new file mode 100644 index 00000000000..c36a49b114a --- /dev/null +++ b/components/script_bindings/webidls/TransformStream.webidl @@ -0,0 +1,18 @@ +/* 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 http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * https://streams.spec.whatwg.org/#ts-class-definition + */ + +[Exposed=*] // [Transferable] - See Bug 1562065 +interface TransformStream { + [Throws] + constructor(optional object transformer, + optional QueuingStrategy writableStrategy = {}, + optional QueuingStrategy readableStrategy = {}); + + readonly attribute ReadableStream readable; + readonly attribute WritableStream writable; +}; diff --git a/components/script_bindings/webidls/TransformStreamDefaultController.webidl b/components/script_bindings/webidls/TransformStreamDefaultController.webidl new file mode 100644 index 00000000000..5f5511ed4b6 --- /dev/null +++ b/components/script_bindings/webidls/TransformStreamDefaultController.webidl @@ -0,0 +1,15 @@ +/* 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 http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * https://streams.spec.whatwg.org/#ts-default-controller-class-definition + */ + +[Exposed=*] +interface TransformStreamDefaultController { + readonly attribute unrestricted double? desiredSize; + [Throws] undefined enqueue(optional any chunk); + [Throws] undefined error(optional any reason); + [Throws] undefined terminate(); +}; diff --git a/components/script_bindings/webidls/Transformer.webidl b/components/script_bindings/webidls/Transformer.webidl new file mode 100644 index 00000000000..652511450a4 --- /dev/null +++ b/components/script_bindings/webidls/Transformer.webidl @@ -0,0 +1,22 @@ +/* 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 http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * https://streams.spec.whatwg.org/#transformer-api + */ + +[GenerateInit] +dictionary Transformer { + TransformerStartCallback start; + TransformerTransformCallback transform; + TransformerFlushCallback flush; + TransformerCancelCallback cancel; + any readableType; + any writableType; +}; + +callback TransformerStartCallback = any (TransformStreamDefaultController controller); +callback TransformerFlushCallback = Promise<undefined> (TransformStreamDefaultController controller); +callback TransformerTransformCallback = Promise<undefined> (any chunk, TransformStreamDefaultController controller); +callback TransformerCancelCallback = Promise<undefined> (any reason); diff --git a/components/script_bindings/webidls/URLPattern.webidl b/components/script_bindings/webidls/URLPattern.webidl index 85c0c468fb5..f61b65702bc 100644 --- a/components/script_bindings/webidls/URLPattern.webidl +++ b/components/script_bindings/webidls/URLPattern.webidl @@ -4,16 +4,16 @@ // https://urlpattern.spec.whatwg.org/#urlpattern -typedef /* USVString or */ URLPatternInit URLPatternInput; +typedef (USVString or URLPatternInit) URLPatternInput; [Exposed=(Window,Worker), Pref="dom_urlpattern_enabled"] interface URLPattern { -// constructor(URLPatternInput input, USVString baseURL, optional URLPatternOptions options = {}); + [Throws] constructor(URLPatternInput input, USVString baseURL, optional URLPatternOptions options = {}); [Throws] constructor(optional URLPatternInput input = {}, optional URLPatternOptions options = {}); -// boolean test(optional URLPatternInput input = {}, optional USVString baseURL); + // [Throws] boolean test(optional URLPatternInput input = {}, optional USVString baseURL); -// URLPatternResult? exec(optional URLPatternInput input = {}, optional USVString baseURL); + // [Throws] URLPatternResult? exec(optional URLPatternInput input = {}, optional USVString baseURL); readonly attribute USVString protocol; readonly attribute USVString username; diff --git a/components/script_bindings/webidls/Window.webidl b/components/script_bindings/webidls/Window.webidl index eb7c3e1d03d..929279f7951 100644 --- a/components/script_bindings/webidls/Window.webidl +++ b/components/script_bindings/webidls/Window.webidl @@ -150,6 +150,10 @@ partial interface Window { undefined webdriverCallback(optional any result); undefined webdriverException(optional any result); undefined webdriverTimeout(); + Element? webdriverElement(DOMString id); + Element? webdriverFrame(DOMString id); + Window? webdriverWindow(DOMString id); + ShadowRoot? webdriverShadowRoot(DOMString id); }; // https://html.spec.whatwg.org/multipage/#dom-sessionstorage diff --git a/components/servo/javascript_evaluator.rs b/components/servo/javascript_evaluator.rs new file mode 100644 index 00000000000..41cb5539b05 --- /dev/null +++ b/components/servo/javascript_evaluator.rs @@ -0,0 +1,65 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::collections::HashMap; + +use base::id::WebViewId; +use constellation_traits::EmbedderToConstellationMessage; +use embedder_traits::{JSValue, JavaScriptEvaluationError, JavaScriptEvaluationId}; + +use crate::ConstellationProxy; + +struct PendingEvaluation { + callback: Box<dyn FnOnce(Result<JSValue, JavaScriptEvaluationError>)>, +} + +pub(crate) struct JavaScriptEvaluator { + current_id: JavaScriptEvaluationId, + constellation_proxy: ConstellationProxy, + pending_evaluations: HashMap<JavaScriptEvaluationId, PendingEvaluation>, +} + +impl JavaScriptEvaluator { + pub(crate) fn new(constellation_proxy: ConstellationProxy) -> Self { + Self { + current_id: JavaScriptEvaluationId(0), + constellation_proxy, + pending_evaluations: Default::default(), + } + } + + fn generate_id(&mut self) -> JavaScriptEvaluationId { + let next_id = JavaScriptEvaluationId(self.current_id.0 + 1); + std::mem::replace(&mut self.current_id, next_id) + } + + pub(crate) fn evaluate( + &mut self, + webview_id: WebViewId, + script: String, + callback: Box<dyn FnOnce(Result<JSValue, JavaScriptEvaluationError>)>, + ) { + let evaluation_id = self.generate_id(); + self.constellation_proxy + .send(EmbedderToConstellationMessage::EvaluateJavaScript( + webview_id, + evaluation_id, + script, + )); + self.pending_evaluations + .insert(evaluation_id, PendingEvaluation { callback }); + } + + pub(crate) fn finish_evaluation( + &mut self, + evaluation_id: JavaScriptEvaluationId, + result: Result<JSValue, JavaScriptEvaluationError>, + ) { + (self + .pending_evaluations + .remove(&evaluation_id) + .expect("Received request to finish unknown JavaScript evaluation.") + .callback)(result) + } +} diff --git a/components/servo/lib.rs b/components/servo/lib.rs index 366685e1123..d2c65429ba9 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -18,6 +18,7 @@ //! `WindowMethods` trait. mod clipboard_delegate; +mod javascript_evaluator; mod proxies; mod responders; mod servo_delegate; @@ -82,6 +83,7 @@ pub use gleam::gl; use gleam::gl::RENDERER; use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::router::ROUTER; +use javascript_evaluator::JavaScriptEvaluator; pub use keyboard_types::*; use layout::LayoutFactoryImpl; use log::{Log, Metadata, Record, debug, warn}; @@ -196,6 +198,9 @@ pub struct Servo { compositor: Rc<RefCell<IOCompositor>>, constellation_proxy: ConstellationProxy, embedder_receiver: Receiver<EmbedderMsg>, + /// A struct that tracks ongoing JavaScript evaluations and is responsible for + /// calling the callback when the evaluation is complete. + javascript_evaluator: Rc<RefCell<JavaScriptEvaluator>>, /// Tracks whether we are in the process of shutting down, or have shut down. /// This is shared with `WebView`s and the `ServoRenderer`. shutdown_state: Rc<Cell<ShutdownState>>, @@ -487,10 +492,14 @@ impl Servo { opts.debug.convert_mouse_to_touch, ); + let constellation_proxy = ConstellationProxy::new(constellation_chan); Self { delegate: RefCell::new(Rc::new(DefaultServoDelegate)), compositor: Rc::new(RefCell::new(compositor)), - constellation_proxy: ConstellationProxy::new(constellation_chan), + javascript_evaluator: Rc::new(RefCell::new(JavaScriptEvaluator::new( + constellation_proxy.clone(), + ))), + constellation_proxy, embedder_receiver, shutdown_state, webviews: Default::default(), @@ -738,6 +747,11 @@ impl Servo { webview.delegate().request_unload(webview, request); } }, + EmbedderMsg::FinishJavaScriptEvaluation(evaluation_id, result) => { + self.javascript_evaluator + .borrow_mut() + .finish_evaluation(evaluation_id, result); + }, EmbedderMsg::Keyboard(webview_id, keyboard_event) => { if let Some(webview) = self.get_webview_handle(webview_id) { webview @@ -1044,7 +1058,11 @@ fn create_constellation( ); let system_font_service = Arc::new( - SystemFontService::spawn(compositor_proxy.cross_process_compositor_api.clone()).to_proxy(), + SystemFontService::spawn( + compositor_proxy.cross_process_compositor_api.clone(), + mem_profiler_chan.clone(), + ) + .to_proxy(), ); let (canvas_create_sender, canvas_ipc_sender) = CanvasPaintThread::start( diff --git a/components/servo/tests/webview.rs b/components/servo/tests/webview.rs index 89fbe2025a3..41900015b94 100644 --- a/components/servo/tests/webview.rs +++ b/components/servo/tests/webview.rs @@ -11,12 +11,14 @@ mod common; -use std::cell::Cell; +use std::cell::{Cell, RefCell}; use std::rc::Rc; use anyhow::ensure; use common::{ServoTest, run_api_tests}; -use servo::{WebViewBuilder, WebViewDelegate}; +use servo::{ + JSValue, JavaScriptEvaluationError, LoadStatus, WebView, WebViewBuilder, WebViewDelegate, +}; #[derive(Default)] struct WebViewDelegateImpl { @@ -44,6 +46,81 @@ fn test_create_webview(servo_test: &ServoTest) -> Result<(), anyhow::Error> { Ok(()) } +fn evaluate_javascript( + servo_test: &ServoTest, + webview: WebView, + script: impl ToString, +) -> Result<JSValue, JavaScriptEvaluationError> { + let load_webview = webview.clone(); + let _ = servo_test.spin(move || Ok(load_webview.load_status() != LoadStatus::Complete)); + + let saved_result = Rc::new(RefCell::new(None)); + let callback_result = saved_result.clone(); + webview.evaluate_javascript(script, move |result| { + *callback_result.borrow_mut() = Some(result) + }); + + let spin_result = saved_result.clone(); + let _ = servo_test.spin(move || Ok(spin_result.borrow().is_none())); + + (*saved_result.borrow()) + .clone() + .expect("Should have waited until value available") +} + +fn test_evaluate_javascript_basic(servo_test: &ServoTest) -> Result<(), anyhow::Error> { + let delegate = Rc::new(WebViewDelegateImpl::default()); + let webview = WebViewBuilder::new(servo_test.servo()) + .delegate(delegate.clone()) + .build(); + + let result = evaluate_javascript(servo_test, webview.clone(), "undefined"); + ensure!(result == Ok(JSValue::Undefined)); + + let result = evaluate_javascript(servo_test, webview.clone(), "null"); + ensure!(result == Ok(JSValue::Null)); + + let result = evaluate_javascript(servo_test, webview.clone(), "42"); + ensure!(result == Ok(JSValue::Number(42.0))); + + let result = evaluate_javascript(servo_test, webview.clone(), "3 + 4"); + ensure!(result == Ok(JSValue::Number(7.0))); + + let result = evaluate_javascript(servo_test, webview.clone(), "'abc' + 'def'"); + ensure!(result == Ok(JSValue::String("abcdef".into()))); + + let result = evaluate_javascript(servo_test, webview.clone(), "let foo = {blah: 123}; foo"); + ensure!(matches!(result, Ok(JSValue::Object(_)))); + if let Ok(JSValue::Object(values)) = result { + ensure!(values.len() == 1); + ensure!(values.get("blah") == Some(&JSValue::Number(123.0))); + } + + let result = evaluate_javascript(servo_test, webview.clone(), "[1, 2, 3, 4]"); + let expected = JSValue::Array(vec![ + JSValue::Number(1.0), + JSValue::Number(2.0), + JSValue::Number(3.0), + JSValue::Number(4.0), + ]); + ensure!(result == Ok(expected)); + + let result = evaluate_javascript(servo_test, webview.clone(), "window"); + ensure!(matches!(result, Ok(JSValue::Window(..)))); + + let result = evaluate_javascript(servo_test, webview.clone(), "document.body"); + ensure!(matches!(result, Ok(JSValue::Element(..)))); + + let result = evaluate_javascript( + servo_test, + webview.clone(), + "document.body.innerHTML += '<iframe>'; frames[0]", + ); + ensure!(matches!(result, Ok(JSValue::Frame(..)))); + + Ok(()) +} + fn test_create_webview_and_immediately_drop_webview_before_shutdown( servo_test: &ServoTest, ) -> Result<(), anyhow::Error> { @@ -54,6 +131,7 @@ fn test_create_webview_and_immediately_drop_webview_before_shutdown( fn main() { run_api_tests!( test_create_webview, + test_evaluate_javascript_basic, // This test needs to be last, as it tests creating and dropping // a WebView right before shutdown. test_create_webview_and_immediately_drop_webview_before_shutdown diff --git a/components/servo/webview.rs b/components/servo/webview.rs index 95eb6dfd154..10786ad8b69 100644 --- a/components/servo/webview.rs +++ b/components/servo/webview.rs @@ -13,8 +13,8 @@ use compositing_traits::WebViewTrait; use constellation_traits::{EmbedderToConstellationMessage, TraversalDirection}; use dpi::PhysicalSize; use embedder_traits::{ - Cursor, InputEvent, LoadStatus, MediaSessionActionType, ScreenGeometry, Theme, TouchEventType, - ViewportDetails, + Cursor, InputEvent, JSValue, JavaScriptEvaluationError, LoadStatus, MediaSessionActionType, + ScreenGeometry, Theme, TouchEventType, ViewportDetails, }; use euclid::{Point2D, Scale, Size2D}; use servo_geometry::DeviceIndependentPixel; @@ -23,6 +23,7 @@ use webrender_api::ScrollLocation; use webrender_api::units::{DeviceIntPoint, DevicePixel, DeviceRect}; use crate::clipboard_delegate::{ClipboardDelegate, DefaultClipboardDelegate}; +use crate::javascript_evaluator::JavaScriptEvaluator; use crate::webview_delegate::{DefaultWebViewDelegate, WebViewDelegate}; use crate::{ConstellationProxy, Servo, WebRenderDebugOption}; @@ -75,6 +76,7 @@ pub(crate) struct WebViewInner { pub(crate) compositor: Rc<RefCell<IOCompositor>>, pub(crate) delegate: Rc<dyn WebViewDelegate>, pub(crate) clipboard_delegate: Rc<dyn ClipboardDelegate>, + javascript_evaluator: Rc<RefCell<JavaScriptEvaluator>>, rect: DeviceRect, hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>, @@ -117,9 +119,10 @@ impl WebView { compositor: servo.compositor.clone(), delegate: builder.delegate, clipboard_delegate: Rc::new(DefaultClipboardDelegate), + javascript_evaluator: servo.javascript_evaluator.clone(), rect: DeviceRect::from_origin_and_size(Point2D::origin(), size), hidpi_scale_factor: builder.hidpi_scale_factor, - load_status: LoadStatus::Complete, + load_status: LoadStatus::Started, url: None, status_text: None, page_title: None, @@ -549,6 +552,20 @@ impl WebView { pub fn paint(&self) -> bool { self.inner().compositor.borrow_mut().render() } + + /// Evaluate the specified string of JavaScript code. Once execution is complete or an error + /// occurs, Servo will call `callback`. + pub fn evaluate_javascript<T: ToString>( + &self, + script: T, + callback: impl FnOnce(Result<JSValue, JavaScriptEvaluationError>) + 'static, + ) { + self.inner().javascript_evaluator.borrow_mut().evaluate( + self.id(), + script.to_string(), + Box::new(callback), + ); + } } /// A structure used to expose a view of the [`WebView`] to the Servo diff --git a/components/shared/compositing/lib.rs b/components/shared/compositing/lib.rs index 31371f87529..a6701ca2b52 100644 --- a/components/shared/compositing/lib.rs +++ b/components/shared/compositing/lib.rs @@ -14,6 +14,7 @@ use embedder_traits::{ use euclid::Rect; use ipc_channel::ipc::IpcSender; use log::warn; +use malloc_size_of_derive::MallocSizeOf; use pixels::Image; use strum_macros::IntoStaticStr; use style_traits::CSSPixel; @@ -103,6 +104,8 @@ pub enum CompositorMsg { WebDriverMouseButtonEvent(WebViewId, MouseButtonAction, MouseButton, f32, f32), /// WebDriver mouse move event WebDriverMouseMoveEvent(WebViewId, f32, f32), + // Webdriver wheel scroll event + WebDriverWheelScrollEvent(WebViewId, f32, f32, f64, f64), /// Inform WebRender of the existence of this pipeline. SendInitialTransaction(WebRenderPipelineId), @@ -188,7 +191,7 @@ pub struct CompositionPipeline { } /// A mechanism to send messages from ScriptThread to the parent process' WebRender instance. -#[derive(Clone, Deserialize, Serialize)] +#[derive(Clone, Deserialize, MallocSizeOf, Serialize)] pub struct CrossProcessCompositorApi(pub IpcSender<CompositorMsg>); impl CrossProcessCompositorApi { diff --git a/components/shared/constellation/from_script_message.rs b/components/shared/constellation/from_script_message.rs index 21665c24e57..3856def660e 100644 --- a/components/shared/constellation/from_script_message.rs +++ b/components/shared/constellation/from_script_message.rs @@ -15,8 +15,8 @@ use base::id::{ use canvas_traits::canvas::{CanvasId, CanvasMsg}; use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, WorkerId}; use embedder_traits::{ - AnimationState, EmbedderMsg, FocusSequenceNumber, MediaSessionEvent, TouchEventResult, - ViewportDetails, + AnimationState, EmbedderMsg, FocusSequenceNumber, JSValue, JavaScriptEvaluationError, + JavaScriptEvaluationId, MediaSessionEvent, TouchEventResult, ViewportDetails, }; use euclid::default::Size2D as UntypedSize2D; use http::{HeaderMap, Method}; @@ -644,6 +644,11 @@ pub enum ScriptToConstellationMessage { IFrameSizes(Vec<IFrameSizeMsg>), /// Request results from the memory reporter. ReportMemory(IpcSender<MemoryReportResult>), + /// Return the result of the evaluated JavaScript with the given [`JavaScriptEvaluationId`]. + FinishJavaScriptEvaluation( + JavaScriptEvaluationId, + Result<JSValue, JavaScriptEvaluationError>, + ), } impl fmt::Debug for ScriptToConstellationMessage { diff --git a/components/shared/constellation/lib.rs b/components/shared/constellation/lib.rs index 559bc2dd2d1..d85fbe31bdf 100644 --- a/components/shared/constellation/lib.rs +++ b/components/shared/constellation/lib.rs @@ -19,8 +19,8 @@ use base::Epoch; use base::cross_process_instant::CrossProcessInstant; use base::id::{MessagePortId, PipelineId, WebViewId}; use embedder_traits::{ - CompositorHitTestResult, Cursor, InputEvent, MediaSessionActionType, Theme, ViewportDetails, - WebDriverCommandMsg, + CompositorHitTestResult, Cursor, InputEvent, JavaScriptEvaluationId, MediaSessionActionType, + Theme, ViewportDetails, WebDriverCommandMsg, }; use euclid::Vector2D; pub use from_script_message::*; @@ -92,6 +92,9 @@ pub enum EmbedderToConstellationMessage { SetScrollStates(PipelineId, Vec<ScrollState>), /// Notify the constellation that a particular paint metric event has happened for the given pipeline. PaintMetric(PipelineId, PaintMetricEvent), + /// Evaluate a JavaScript string in the context of a `WebView`. When execution is complete or an + /// error is encountered, a correpsonding message will be sent to the embedding layer. + EvaluateJavaScript(WebViewId, JavaScriptEvaluationId, String), } /// A description of a paint metric that is sent from the Servo renderer to the @@ -149,7 +152,7 @@ pub enum TraversalDirection { } /// A task on the <https://html.spec.whatwg.org/multipage/#port-message-queue> -#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] pub struct PortMessageTask { /// The origin of this task. pub origin: ImmutableOrigin, diff --git a/components/shared/constellation/structured_data/mod.rs b/components/shared/constellation/structured_data/mod.rs index 41fc05493a2..3fb9d0c5f67 100644 --- a/components/shared/constellation/structured_data/mod.rs +++ b/components/shared/constellation/structured_data/mod.rs @@ -20,7 +20,7 @@ pub use transferable::*; /// A data-holder for serialized data and transferred objects. /// <https://html.spec.whatwg.org/multipage/#structuredserializewithtransfer> -#[derive(Debug, Default, Deserialize, MallocSizeOf, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, Serialize)] pub struct StructuredSerializedData { /// Data serialized by SpiderMonkey. pub serialized: Vec<u8>, @@ -43,6 +43,7 @@ impl StructuredSerializedData { Transferrable::MessagePort => is_field_empty(&self.ports), Transferrable::ReadableStream => is_field_empty(&self.ports), Transferrable::WritableStream => is_field_empty(&self.ports), + Transferrable::TransformStream => is_field_empty(&self.ports), } } diff --git a/components/shared/constellation/structured_data/serializable.rs b/components/shared/constellation/structured_data/serializable.rs index 22370087665..194f0567c51 100644 --- a/components/shared/constellation/structured_data/serializable.rs +++ b/components/shared/constellation/structured_data/serializable.rs @@ -88,7 +88,7 @@ impl Clone for BroadcastMsg { } /// File-based blob -#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] pub struct FileBlob { #[ignore_malloc_size_of = "Uuid are hard(not really)"] id: Uuid, @@ -164,7 +164,7 @@ impl BroadcastClone for BlobImpl { } /// The data backing a DOM Blob. -#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] pub struct BlobImpl { /// UUID of the blob. blob_id: BlobId, @@ -177,7 +177,7 @@ pub struct BlobImpl { } /// Different backends of Blob -#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] pub enum BlobData { /// File-based blob, whose content lives in the net process File(FileBlob), diff --git a/components/shared/constellation/structured_data/transferable.rs b/components/shared/constellation/structured_data/transferable.rs index 7e4fe0e6d2d..528c1e79e65 100644 --- a/components/shared/constellation/structured_data/transferable.rs +++ b/components/shared/constellation/structured_data/transferable.rs @@ -24,9 +24,11 @@ pub enum Transferrable { ReadableStream, /// The `WritableStream` interface. WritableStream, + /// The `TransformStream` interface. + TransformStream, } -#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] enum MessagePortState { /// <https://html.spec.whatwg.org/multipage/#detached> Detached, @@ -40,7 +42,7 @@ enum MessagePortState { Disabled(bool), } -#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] /// The data and logic backing the DOM managed MessagePort. pub struct MessagePortImpl { /// The current state of the port. diff --git a/components/shared/embedder/input_events.rs b/components/shared/embedder/input_events.rs index 0268be6dd9c..acaa9afb3ff 100644 --- a/components/shared/embedder/input_events.rs +++ b/components/shared/embedder/input_events.rs @@ -61,15 +61,16 @@ pub enum MouseButton { Other(u16), } -impl From<u16> for MouseButton { - fn from(value: u16) -> Self { +impl<T: Into<u64>> From<T> for MouseButton { + fn from(value: T) -> Self { + let value = value.into(); match value { 0 => MouseButton::Left, 1 => MouseButton::Middle, 2 => MouseButton::Right, 3 => MouseButton::Back, 4 => MouseButton::Forward, - _ => MouseButton::Other(value), + _ => MouseButton::Other(value as u16), } } } diff --git a/components/shared/embedder/lib.rs b/components/shared/embedder/lib.rs index c87fa9019ef..e9427fcc719 100644 --- a/components/shared/embedder/lib.rs +++ b/components/shared/embedder/lib.rs @@ -13,8 +13,10 @@ pub mod resources; pub mod user_content_manager; mod webdriver; +use std::collections::HashMap; use std::ffi::c_void; use std::fmt::{Debug, Display, Error, Formatter}; +use std::hash::Hash; use std::path::PathBuf; use std::sync::Arc; @@ -372,6 +374,12 @@ pub enum EmbedderMsg { DeviceIntRect, IpcSender<Option<usize>>, ), + /// Inform the embedding layer that a JavaScript evaluation has + /// finished with the given result. + FinishJavaScriptEvaluation( + JavaScriptEvaluationId, + Result<JSValue, JavaScriptEvaluationError>, + ), } impl Debug for EmbedderMsg { @@ -857,3 +865,59 @@ impl Display for FocusSequenceNumber { Display::fmt(&self.0, f) } } + +/// An identifier for a particular JavaScript evaluation that is used to track the +/// evaluation from the embedding layer to the script layer and then back. +#[derive(Clone, Copy, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct JavaScriptEvaluationId(pub usize); + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub enum JSValue { + Undefined, + Null, + Boolean(bool), + Number(f64), + String(String), + Element(String), + Frame(String), + Window(String), + Array(Vec<JSValue>), + Object(HashMap<String, JSValue>), +} + +impl From<&WebDriverJSValue> for JSValue { + fn from(value: &WebDriverJSValue) -> Self { + match value { + WebDriverJSValue::Undefined => Self::Undefined, + WebDriverJSValue::Null => Self::Null, + WebDriverJSValue::Boolean(value) => Self::Boolean(*value), + WebDriverJSValue::Int(value) => Self::Number(*value as f64), + WebDriverJSValue::Number(value) => Self::Number(*value), + WebDriverJSValue::String(value) => Self::String(value.clone()), + WebDriverJSValue::Element(web_element) => Self::Element(web_element.0.clone()), + WebDriverJSValue::Frame(web_frame) => Self::Frame(web_frame.0.clone()), + WebDriverJSValue::Window(web_window) => Self::Window(web_window.0.clone()), + WebDriverJSValue::ArrayLike(vector) => { + Self::Array(vector.iter().map(Into::into).collect()) + }, + WebDriverJSValue::Object(map) => Self::Object( + map.iter() + .map(|(key, value)| (key.clone(), value.into())) + .collect(), + ), + } + } +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub enum JavaScriptEvaluationError { + /// An internal Servo error prevented the JavaSript evaluation from completing properly. + /// This indicates a bug in Servo. + InternalError, + /// The `WebView` on which this evaluation request was triggered is not ready. This might + /// happen if the `WebView`'s `Document` is changing due to ongoing load events, for instance. + WebViewNotReady, + /// The script executed successfully, but Servo could not serialize the JavaScript return + /// value into a [`JSValue`]. + SerializationError, +} diff --git a/components/shared/embedder/webdriver.rs b/components/shared/embedder/webdriver.rs index 9577163411e..3716a29951a 100644 --- a/components/shared/embedder/webdriver.rs +++ b/components/shared/embedder/webdriver.rs @@ -44,6 +44,8 @@ pub enum WebDriverCommandMsg { MouseButtonAction(WebViewId, MouseButtonAction, MouseButton, f32, f32), /// Act as if the mouse was moved in the browsing context with the given ID. MouseMoveAction(WebViewId, f32, f32), + /// Act as if the mouse wheel is scrolled in the browsing context given the given ID. + WheelScrollAction(WebViewId, f32, f32, f64, f64), /// Set the window size. SetWindowSize(WebViewId, DeviceIntSize, IpcSender<Size2D<f32, CSSPixel>>), /// Take a screenshot of the window. diff --git a/components/shared/net/request.rs b/components/shared/net/request.rs index 9c3693316b0..259132b55c4 100644 --- a/components/shared/net/request.rs +++ b/components/shared/net/request.rs @@ -8,7 +8,7 @@ use base::id::{PipelineId, WebViewId}; use content_security_policy::{self as csp}; use http::header::{AUTHORIZATION, HeaderName}; use http::{HeaderMap, Method}; -use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; +use ipc_channel::ipc::{self, IpcReceiver, IpcSender, IpcSharedMemory}; use malloc_size_of_derive::MallocSizeOf; use mime::Mime; use serde::{Deserialize, Serialize}; @@ -156,7 +156,7 @@ pub enum BodySource { #[derive(Debug, Deserialize, Serialize)] pub enum BodyChunkResponse { /// A chunk of bytes. - Chunk(Vec<u8>), + Chunk(IpcSharedMemory), /// The body is done. Done, /// There was an error streaming the body, diff --git a/components/shared/profile/mem.rs b/components/shared/profile/mem.rs index 1be4eb5abc4..b626facd042 100644 --- a/components/shared/profile/mem.rs +++ b/components/shared/profile/mem.rs @@ -279,7 +279,6 @@ thread_local!(static SEEN_POINTERS: LazyCell<RefCell<HashSet<*const c_void>>> = /// The function is expected to call all the desired [MallocSizeOf::size_of] /// for allocations reachable from the current thread. pub fn perform_memory_report<F: FnOnce(&mut MallocSizeOfOps)>(f: F) { - SEEN_POINTERS.with(|pointers| pointers.borrow_mut().clear()); let seen_pointer = move |ptr| SEEN_POINTERS.with(|pointers| !pointers.borrow_mut().insert(ptr)); let mut ops = MallocSizeOfOps::new( servo_allocator::usable_size, @@ -287,4 +286,9 @@ pub fn perform_memory_report<F: FnOnce(&mut MallocSizeOfOps)>(f: F) { Some(Box::new(seen_pointer)), ); f(&mut ops); + SEEN_POINTERS.with(|pointers| { + let mut pointers = pointers.borrow_mut(); + pointers.clear(); + pointers.shrink_to_fit(); + }); } diff --git a/components/shared/script/lib.rs b/components/shared/script/lib.rs index 748c42400a8..29acc51765c 100644 --- a/components/shared/script/lib.rs +++ b/components/shared/script/lib.rs @@ -27,8 +27,8 @@ use crossbeam_channel::{RecvTimeoutError, Sender}; use devtools_traits::ScriptToDevtoolsControlMsg; use embedder_traits::user_content_manager::UserContentManager; use embedder_traits::{ - CompositorHitTestResult, FocusSequenceNumber, InputEvent, MediaSessionActionType, Theme, - ViewportDetails, WebDriverScriptCommand, + CompositorHitTestResult, FocusSequenceNumber, InputEvent, JavaScriptEvaluationId, + MediaSessionActionType, Theme, ViewportDetails, WebDriverScriptCommand, }; use euclid::{Rect, Scale, Size2D, UnknownUnit}; use ipc_channel::ipc::{IpcReceiver, IpcSender}; @@ -245,6 +245,9 @@ pub enum ScriptThreadMessage { /// The compositor scrolled and is updating the scroll states of the nodes in the given /// pipeline via the Constellation. SetScrollStates(PipelineId, Vec<ScrollState>), + /// Evaluate the given JavaScript and return a result via a corresponding message + /// to the Constellation. + EvaluateJavaScript(PipelineId, JavaScriptEvaluationId, String), } impl fmt::Debug for ScriptThreadMessage { diff --git a/components/shared/script_layout/lib.rs b/components/shared/script_layout/lib.rs index 8c5d4edc4e0..1f526b64240 100644 --- a/components/shared/script_layout/lib.rs +++ b/components/shared/script_layout/lib.rs @@ -114,6 +114,7 @@ pub enum LayoutElementType { HTMLTableRowElement, HTMLTableSectionElement, HTMLTextAreaElement, + SVGImageElement, SVGSVGElement, } diff --git a/components/url/lib.rs b/components/url/lib.rs index 8aa04fd5ae4..f1ae1083c8e 100644 --- a/components/url/lib.rs +++ b/components/url/lib.rs @@ -35,7 +35,7 @@ pub enum UrlError { } #[derive(Clone, Deserialize, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize)] -pub struct ServoUrl(#[ignore_malloc_size_of = "Arc"] Arc<Url>); +pub struct ServoUrl(#[conditional_malloc_size_of] Arc<Url>); impl ServoUrl { pub fn from_url(url: Url) -> Self { diff --git a/components/webdriver_server/actions.rs b/components/webdriver_server/actions.rs index fbede5b5887..43dd0e183dc 100644 --- a/components/webdriver_server/actions.rs +++ b/components/webdriver_server/actions.rs @@ -13,28 +13,31 @@ use keyboard_types::webdriver::KeyInputState; use webdriver::actions::{ ActionSequence, ActionsType, GeneralAction, KeyAction, KeyActionItem, KeyDownAction, KeyUpAction, NullActionItem, PointerAction, PointerActionItem, PointerActionParameters, - PointerDownAction, PointerMoveAction, PointerOrigin, PointerType, PointerUpAction, + PointerDownAction, PointerMoveAction, PointerOrigin, PointerType, PointerUpAction, WheelAction, + WheelActionItem, WheelScrollAction, }; -use webdriver::error::ErrorStatus; +use webdriver::error::{ErrorStatus, WebDriverError}; -use crate::Handler; +use crate::{Handler, WebElement, wait_for_script_response}; -// Interval between pointerMove increments in ms, based on common vsync +// Interval between wheelScroll and pointerMove increments in ms, based on common vsync static POINTERMOVE_INTERVAL: u64 = 17; +static WHEELSCROLL_INTERVAL: u64 = 17; // https://w3c.github.io/webdriver/#dfn-input-source-state pub(crate) enum InputSourceState { Null, Key(KeyInputState), Pointer(PointerInputState), + Wheel, } // https://w3c.github.io/webdriver/#dfn-pointer-input-source pub(crate) struct PointerInputState { subtype: PointerType, pressed: HashSet<u64>, - x: i64, - y: i64, + x: f64, + y: f64, } impl PointerInputState { @@ -46,8 +49,8 @@ impl PointerInputState { PointerType::Touch => PointerType::Touch, }, pressed: HashSet::new(), - x: 0, - y: 0, + x: 0.0, + y: 0.0, } } } @@ -76,7 +79,15 @@ fn compute_tick_duration(tick_actions: &ActionSequence) -> u64 { } }, ActionsType::Key { actions: _ } => (), - ActionsType::Wheel { .. } => log::error!("not implemented"), + ActionsType::Wheel { actions } => { + for action in actions.iter() { + let action_duration = match action { + WheelActionItem::General(GeneralAction::Pause(action)) => action.duration, + WheelActionItem::Wheel(WheelAction::Scroll(action)) => action.duration, + }; + duration = cmp::max(duration, action_duration.unwrap_or(0)); + } + }, } duration } @@ -176,9 +187,26 @@ impl Handler { } } }, - ActionsType::Wheel { .. } => { - log::error!("not yet implemented"); - return Err(ErrorStatus::UnsupportedOperation); + ActionsType::Wheel { actions } => { + for action in actions.iter() { + match action { + WheelActionItem::General(_action) => { + self.dispatch_general_action(source_id) + }, + WheelActionItem::Wheel(action) => { + self.session_mut() + .unwrap() + .input_state_table + .entry(source_id.to_string()) + .or_insert(InputSourceState::Wheel); + match action { + WheelAction::Scroll(action) => { + self.dispatch_scroll_action(action, tick_duration)? + }, + } + }, + } + } }, } @@ -191,9 +219,8 @@ impl Handler { let raw_key = action.value.chars().next().unwrap(); let key_input_state = match session.input_state_table.get_mut(source_id).unwrap() { - InputSourceState::Null => unreachable!(), InputSourceState::Key(key_input_state) => key_input_state, - InputSourceState::Pointer(_) => unreachable!(), + _ => unreachable!(), }; session.input_cancel_list.push(ActionSequence { @@ -219,9 +246,8 @@ impl Handler { let raw_key = action.value.chars().next().unwrap(); let key_input_state = match session.input_state_table.get_mut(source_id).unwrap() { - InputSourceState::Null => unreachable!(), InputSourceState::Key(key_input_state) => key_input_state, - InputSourceState::Pointer(_) => unreachable!(), + _ => unreachable!(), }; session.input_cancel_list.push(ActionSequence { @@ -251,9 +277,8 @@ impl Handler { let session = self.session.as_mut().unwrap(); let pointer_input_state = match session.input_state_table.get_mut(source_id).unwrap() { - InputSourceState::Null => unreachable!(), - InputSourceState::Key(_) => unreachable!(), InputSourceState::Pointer(pointer_input_state) => pointer_input_state, + _ => unreachable!(), }; if pointer_input_state.pressed.contains(&action.button) { @@ -280,11 +305,10 @@ impl Handler { }, }); - let button = (action.button as u16).into(); let cmd_msg = WebDriverCommandMsg::MouseButtonAction( session.webview_id, MouseButtonAction::Down, - button, + action.button.into(), pointer_input_state.x as f32, pointer_input_state.y as f32, ); @@ -298,9 +322,8 @@ impl Handler { let session = self.session.as_mut().unwrap(); let pointer_input_state = match session.input_state_table.get_mut(source_id).unwrap() { - InputSourceState::Null => unreachable!(), - InputSourceState::Key(_) => unreachable!(), InputSourceState::Pointer(pointer_input_state) => pointer_input_state, + _ => unreachable!(), }; if !pointer_input_state.pressed.contains(&action.button) { @@ -327,11 +350,10 @@ impl Handler { }, }); - let button = (action.button as u16).into(); let cmd_msg = WebDriverCommandMsg::MouseButtonAction( session.webview_id, MouseButtonAction::Up, - button, + action.button.into(), pointer_input_state.x as f32, pointer_input_state.y as f32, ); @@ -362,59 +384,39 @@ impl Handler { .get(source_id) .unwrap() { - InputSourceState::Null => unreachable!(), - InputSourceState::Key(_) => unreachable!(), InputSourceState::Pointer(pointer_input_state) => { (pointer_input_state.x, pointer_input_state.y) }, + _ => unreachable!(), }; - // Step 5 - 6 let (x, y) = match action.origin { PointerOrigin::Viewport => (x_offset, y_offset), PointerOrigin::Pointer => (start_x + x_offset, start_y + y_offset), - PointerOrigin::Element(ref x) => { - let (sender, receiver) = ipc::channel().unwrap(); - self.browsing_context_script_command( - WebDriverScriptCommand::GetElementInViewCenterPoint(x.to_string(), sender), - ) - .unwrap(); - - let Some(point) = receiver.recv().unwrap()? else { - return Err(ErrorStatus::UnknownError); - }; - point + PointerOrigin::Element(ref web_element) => { + let point = self.get_element_origin_relative_coordinates(web_element)?; + (point.0 as f64, point.1 as f64) }, }; - let (sender, receiver) = ipc::channel().unwrap(); - let cmd_msg = - WebDriverCommandMsg::GetWindowSize(self.session.as_ref().unwrap().webview_id, sender); - self.constellation_chan - .send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg)) - .unwrap(); - - // Steps 7 - 8 - let viewport_size = receiver.recv().unwrap(); - if x < 0 || x as f32 > viewport_size.width || y < 0 || y as f32 > viewport_size.height { - return Err(ErrorStatus::MoveTargetOutOfBounds); - } + // Step 5 - 6 + self.check_viewport_bound(x, y)?; - // Step 9 + // Step 7 let duration = match action.duration { Some(duration) => duration, None => tick_duration, }; - // Step 10 + // Step 8 if duration > 0 { thread::sleep(Duration::from_millis(POINTERMOVE_INTERVAL)); } - // Step 11 + // Step 9 - 18 self.perform_pointer_move(source_id, duration, start_x, start_y, x, y, tick_start); - // Step 12 + // Step 19 Ok(()) } @@ -424,17 +426,16 @@ impl Handler { &mut self, source_id: &str, duration: u64, - start_x: i64, - start_y: i64, - target_x: i64, - target_y: i64, + start_x: f64, + start_y: f64, + target_x: f64, + target_y: f64, tick_start: Instant, ) { let session = self.session.as_mut().unwrap(); let pointer_input_state = match session.input_state_table.get_mut(source_id).unwrap() { - InputSourceState::Null => unreachable!(), - InputSourceState::Key(_) => unreachable!(), InputSourceState::Pointer(pointer_input_state) => pointer_input_state, + _ => unreachable!(), }; loop { @@ -456,8 +457,8 @@ impl Handler { (target_x, target_y) } else { ( - (duration_ratio * (target_x - start_x) as f64) as i64 + start_x, - (duration_ratio * (target_y - start_y) as f64) as i64 + start_y, + duration_ratio * (target_x - start_x) + start_x, + duration_ratio * (target_y - start_y) + start_y, ) }; @@ -470,6 +471,7 @@ impl Handler { // Step 7.2 let cmd_msg = WebDriverCommandMsg::MouseMoveAction(session.webview_id, x as f32, y as f32); + //TODO: Need Synchronization here before updating `pointer_input_state` self.constellation_chan .send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg)) .unwrap(); @@ -487,4 +489,186 @@ impl Handler { thread::sleep(Duration::from_millis(POINTERMOVE_INTERVAL)); } } + + /// <https://w3c.github.io/webdriver/#dfn-dispatch-a-scroll-action> + fn dispatch_scroll_action( + &mut self, + action: &WheelScrollAction, + tick_duration: u64, + ) -> Result<(), ErrorStatus> { + // Note: We have not implemented `extract an action sequence` which will calls + // `process a wheel action` that validate many of the variable used here. + // Hence, we do all the checking here until those functions is properly + // implemented. + // <https://w3c.github.io/webdriver/#dfn-process-a-wheel-action> + + let tick_start = Instant::now(); + + // Step 1 + let Some(x_offset) = action.x else { + return Err(ErrorStatus::InvalidArgument); + }; + + // Step 2 + let Some(y_offset) = action.y else { + return Err(ErrorStatus::InvalidArgument); + }; + + // Step 3 - 4 + // Get coordinates relative to an origin. + let (x, y) = match action.origin { + PointerOrigin::Viewport => (x_offset, y_offset), + PointerOrigin::Pointer => return Err(ErrorStatus::InvalidArgument), + PointerOrigin::Element(ref web_element) => { + self.get_element_origin_relative_coordinates(web_element)? + }, + }; + + // Step 5 - 6 + self.check_viewport_bound(x as _, y as _)?; + + // Step 7 - 8 + let Some(delta_x) = action.deltaX else { + return Err(ErrorStatus::InvalidArgument); + }; + + let Some(delta_y) = action.deltaY else { + return Err(ErrorStatus::InvalidArgument); + }; + + // Step 9 + let duration = match action.duration { + Some(duration) => duration, + None => tick_duration, + }; + + // Step 10 + if duration > 0 { + thread::sleep(Duration::from_millis(WHEELSCROLL_INTERVAL)); + } + + // Step 11 + self.perform_scroll(duration, x, y, delta_x, delta_y, 0, 0, tick_start); + + // Step 12 + Ok(()) + } + + /// <https://w3c.github.io/webdriver/#dfn-perform-a-scroll> + #[allow(clippy::too_many_arguments)] + fn perform_scroll( + &mut self, + duration: u64, + x: i64, + y: i64, + target_delta_x: i64, + target_delta_y: i64, + mut curr_delta_x: i64, + mut curr_delta_y: i64, + tick_start: Instant, + ) { + let session = self.session.as_mut().unwrap(); + + // Step 1 + let time_delta = tick_start.elapsed().as_millis(); + + // Step 2 + let duration_ratio = if duration > 0 { + time_delta as f64 / duration as f64 + } else { + 1.0 + }; + + // Step 3 + let last = 1.0 - duration_ratio < 0.001; + + // Step 4 + let (delta_x, delta_y) = if last { + (target_delta_x - curr_delta_x, target_delta_y - curr_delta_y) + } else { + ( + (duration_ratio * target_delta_x as f64) as i64 - curr_delta_x, + (duration_ratio * target_delta_y as f64) as i64 - curr_delta_y, + ) + }; + + // Step 5 + if delta_x != 0 || delta_y != 0 { + // Perform implementation-specific action dispatch steps + let cmd_msg = WebDriverCommandMsg::WheelScrollAction( + session.webview_id, + x as f32, + y as f32, + delta_x as f64, + delta_y as f64, + ); + self.constellation_chan + .send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg)) + .unwrap(); + + curr_delta_x += delta_x; + curr_delta_y += delta_y; + } + + // Step 6 + if last { + return; + } + + // Step 7 + // TODO: The two steps should be done in parallel + // 7.1. Asynchronously wait for an implementation defined amount of time to pass. + thread::sleep(Duration::from_millis(WHEELSCROLL_INTERVAL)); + // 7.2. Perform a scroll with arguments duration, x, y, target delta x, + // target delta y, current delta x, current delta y. + self.perform_scroll( + duration, + x, + y, + target_delta_x, + target_delta_y, + curr_delta_x, + curr_delta_y, + tick_start, + ); + } + + fn check_viewport_bound(&self, x: f64, y: f64) -> Result<(), ErrorStatus> { + let (sender, receiver) = ipc::channel().unwrap(); + let cmd_msg = + WebDriverCommandMsg::GetWindowSize(self.session.as_ref().unwrap().webview_id, sender); + self.constellation_chan + .send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg)) + .unwrap(); + + let viewport_size = match wait_for_script_response(receiver) { + Ok(response) => response, + Err(WebDriverError { error, .. }) => return Err(error), + }; + if x < 0.0 || x > viewport_size.width.into() || y < 0.0 || y > viewport_size.height.into() { + Err(ErrorStatus::MoveTargetOutOfBounds) + } else { + Ok(()) + } + } + + fn get_element_origin_relative_coordinates( + &self, + web_element: &WebElement, + ) -> Result<(i64, i64), ErrorStatus> { + let (sender, receiver) = ipc::channel().unwrap(); + self.browsing_context_script_command(WebDriverScriptCommand::GetElementInViewCenterPoint( + web_element.to_string(), + sender, + )) + .unwrap(); + let response = match wait_for_script_response(receiver) { + Ok(response) => response, + Err(WebDriverError { error, .. }) => return Err(error), + }; + match response? { + Some(point) => Ok(point), + None => Err(ErrorStatus::UnknownError), + } + } } diff --git a/components/webdriver_server/lib.rs b/components/webdriver_server/lib.rs index d003ebf8adb..5735594b058 100644 --- a/components/webdriver_server/lib.rs +++ b/components/webdriver_server/lib.rs @@ -23,7 +23,7 @@ use constellation_traits::{EmbedderToConstellationMessage, TraversalDirection}; use cookie::{CookieBuilder, Expiration}; use crossbeam_channel::{Receiver, Sender, after, select, unbounded}; use embedder_traits::{ - WebDriverCommandMsg, WebDriverCookieError, WebDriverFrameId, WebDriverJSError, + MouseButton, WebDriverCommandMsg, WebDriverCookieError, WebDriverFrameId, WebDriverJSError, WebDriverJSResult, WebDriverJSValue, WebDriverLoadStatus, WebDriverScriptCommand, }; use euclid::{Rect, Size2D}; @@ -1619,41 +1619,44 @@ impl Handler { InputSourceState::Pointer(PointerInputState::new(&PointerType::Mouse)), ); - // Steps 8.3 - 8.6 + // Step 8.7. Construct a pointer move action. + // Step 8.8. Set a property x to 0 on pointer move action. + // Step 8.9. Set a property y to 0 on pointer move action. + // Step 8.10. Set a property origin to element on pointer move action. let pointer_move_action = PointerMoveAction { duration: None, origin: PointerOrigin::Element(WebElement(element_id)), - x: 0, - y: 0, + x: 0.0, + y: 0.0, ..Default::default() }; - // Steps 8.7 - 8.8 + // Step 8.11. Construct pointer down action. + // Step 8.12. Set a property button to 0 on pointer down action. let pointer_down_action = PointerDownAction { - button: 1, + button: i16::from(MouseButton::Left) as u64, ..Default::default() }; - // Steps 8.9 - 8.10 + // Step 8.13. Construct pointer up action. + // Step 8.14. Set a property button to 0 on pointer up action. let pointer_up_action = PointerUpAction { - button: 1, + button: i16::from(MouseButton::Left) as u64, ..Default::default() }; - // Step 8.11 + // Step 8.16 Dispatch a list of actions with input state, + // actions, session's current browsing context, and actions options. if let Err(error) = self.dispatch_pointermove_action(&id, &pointer_move_action, 0) { return Err(WebDriverError::new(error, "")); } - // Steps 8.12 self.dispatch_pointerdown_action(&id, &pointer_down_action); - - // Steps 8.13 self.dispatch_pointerup_action(&id, &pointer_up_action); - // Step 8.14 + // Step 8.17 Remove an input source with input state and input id. self.session_mut()?.input_state_table.remove(&id); // Step 13 @@ -1915,6 +1918,15 @@ impl WebDriverHandler<ServoExtensionRoute> for Handler { } } +/// <https://w3c.github.io/webdriver/#dfn-web-element-identifier> +const ELEMENT_IDENTIFIER: &str = "element-6066-11e4-a52e-4f735466cecf"; +/// <https://w3c.github.io/webdriver/#dfn-web-frame-identifier> +const FRAME_IDENTIFIER: &str = "frame-075b-4da1-b6ba-e579c2d3230a"; +/// <https://w3c.github.io/webdriver/#dfn-web-window-identifier> +const WINDOW_IDENTIFIER: &str = "window-fcc6-11e5-b4f8-330a88ab9d7f"; +/// <https://w3c.github.io/webdriver/#dfn-shadow-root-identifier> +const SHADOW_ROOT_IDENTIFIER: &str = "shadow-6066-11e4-a52e-4f735466cecf"; + fn webdriver_value_to_js_argument(v: &Value) -> String { match v { Value::String(s) => format!("\"{}\"", s), @@ -1929,6 +1941,22 @@ fn webdriver_value_to_js_argument(v: &Value) -> String { format!("[{}]", elems.join(", ")) }, Value::Object(map) => { + let key = map.keys().next().map(String::as_str); + match (key, map.values().next()) { + (Some(ELEMENT_IDENTIFIER), Some(id)) => { + return format!("window.webdriverElement({})", id); + }, + (Some(FRAME_IDENTIFIER), Some(id)) => { + return format!("window.webdriverFrame({})", id); + }, + (Some(WINDOW_IDENTIFIER), Some(id)) => { + return format!("window.webdriverWindow({})", id); + }, + (Some(SHADOW_ROOT_IDENTIFIER), Some(id)) => { + return format!("window.webdriverShadowRoot({})", id); + }, + _ => {}, + } let elems = map .iter() .map(|(k, v)| format!("{}: {}", k, webdriver_value_to_js_argument(v))) diff --git a/components/webgl/Cargo.toml b/components/webgl/Cargo.toml index b0c1c0ceb29..542a3cb4fae 100644 --- a/components/webgl/Cargo.toml +++ b/components/webgl/Cargo.toml @@ -26,6 +26,7 @@ fnv = { workspace = true } glow = { workspace = true } half = "2" ipc-channel = { workspace = true } +itertools = { workspace = true } log = { workspace = true } pixels = { path = "../pixels" } snapshot = { workspace = true } diff --git a/components/webgl/webgl_thread.rs b/components/webgl/webgl_thread.rs index b1ac2b2d3c4..9562c4cb4e0 100644 --- a/components/webgl/webgl_thread.rs +++ b/components/webgl/webgl_thread.rs @@ -32,6 +32,7 @@ use glow::{ }; use half::f16; use ipc_channel::ipc::IpcSharedMemory; +use itertools::Itertools; use log::{debug, error, trace, warn}; use pixels::{self, PixelFormat, unmultiply_inplace}; use surfman::chains::{PreserveBuffer, SwapChains, SwapChainsAPI}; @@ -2570,24 +2571,10 @@ impl WebGLImpl { chan.send((range_min, range_max, precision)).unwrap(); } - fn get_extensions(gl: &Gl, chan: &WebGLSender<String>) { - let mut ext_count = [0]; - unsafe { - gl.get_parameter_i32_slice(gl::NUM_EXTENSIONS, &mut ext_count); - } - // Fall back to the depricated extensions API if that fails - if unsafe { gl.get_error() } != gl::NO_ERROR { - chan.send(unsafe { gl.get_parameter_string(gl::EXTENSIONS) }) - .unwrap(); - return; - } - let ext_count = ext_count[0] as usize; - let mut extensions = Vec::with_capacity(ext_count); - for idx in 0..ext_count { - extensions.push(unsafe { gl.get_parameter_indexed_string(gl::EXTENSIONS, idx as u32) }) - } - let extensions = extensions.join(" "); - chan.send(extensions).unwrap(); + /// This is an implementation of `getSupportedExtensions()` from + /// <https://registry.khronos.org/webgl/specs/latest/1.0/#5.14> + fn get_extensions(gl: &Gl, result_sender: &WebGLSender<String>) { + let _ = result_sender.send(gl.supported_extensions().iter().join(" ")); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6 diff --git a/deny.toml b/deny.toml index b2e86b9e67e..5284cb80b31 100644 --- a/deny.toml +++ b/deny.toml @@ -159,6 +159,11 @@ skip = [ # duplicated by font-kit "redox_users", "dirs-sys", + + # duplicated by winit + "objc2-app-kit", + "objc2-foundation", + "objc2", ] # github.com organizations to allow git sources for diff --git a/ports/servoshell/Cargo.toml b/ports/servoshell/Cargo.toml index e000900e4ea..a77bd885549 100644 --- a/ports/servoshell/Cargo.toml +++ b/ports/servoshell/Cargo.toml @@ -131,13 +131,13 @@ libservo = { path = "../../components/servo", features = ["no-wgl"] } windows-sys = { workspace = true, features = ["Win32_Graphics_Gdi"] } [target.'cfg(target_os = "macos")'.dependencies] -objc2-app-kit = { version = "0.2.2", default-features = false, features = [ +objc2-app-kit = { version = "0.3.1", default-features = false, features = [ "std", "NSColorSpace", "NSResponder", "NSView", "NSWindow", ] } -objc2-foundation = { version = "0.2.2", default-features = false, features = [ +objc2-foundation = { version = "0.3.1", default-features = false, features = [ "std", ] } diff --git a/ports/servoshell/egl/app_state.rs b/ports/servoshell/egl/app_state.rs index a65ed7424f4..737a2f23b7d 100644 --- a/ports/servoshell/egl/app_state.rs +++ b/ports/servoshell/egl/app_state.rs @@ -38,6 +38,14 @@ impl Coordinates { viewport: Rect::new(Point2D::new(x, y), Size2D::new(width, height)), } } + + pub fn origin(&self) -> Point2D<i32, DevicePixel> { + self.viewport.origin + } + + pub fn size(&self) -> Size2D<i32, DevicePixel> { + self.viewport.size + } } pub(super) struct ServoWindowCallbacks { @@ -115,11 +123,13 @@ impl ServoDelegate for ServoShellServoDelegate { impl WebViewDelegate for RunningAppState { fn screen_geometry(&self, _webview: WebView) -> Option<ScreenGeometry> { let coord = self.callbacks.coordinates.borrow(); - let screen_size = DeviceIntSize::new(coord.viewport.size.width, coord.viewport.size.height); + let offset = coord.origin(); + let available_size = coord.size(); + let screen_size = coord.size(); Some(ScreenGeometry { size: screen_size, - available_size: screen_size, - offset: Point2D::zero(), + available_size, + offset, }) } diff --git a/ports/servoshell/egl/ohos.rs b/ports/servoshell/egl/ohos.rs index 236f1d842da..ceccbdbe900 100644 --- a/ports/servoshell/egl/ohos.rs +++ b/ports/servoshell/egl/ohos.rs @@ -28,8 +28,9 @@ use servo::{ use xcomponent_sys::{ OH_NativeXComponent, OH_NativeXComponent_Callback, OH_NativeXComponent_GetKeyEvent, OH_NativeXComponent_GetKeyEventAction, OH_NativeXComponent_GetKeyEventCode, - OH_NativeXComponent_GetTouchEvent, OH_NativeXComponent_GetXComponentSize, - OH_NativeXComponent_KeyAction, OH_NativeXComponent_KeyCode, OH_NativeXComponent_KeyEvent, + OH_NativeXComponent_GetTouchEvent, OH_NativeXComponent_GetXComponentOffset, + OH_NativeXComponent_GetXComponentSize, OH_NativeXComponent_KeyAction, + OH_NativeXComponent_KeyCode, OH_NativeXComponent_KeyEvent, OH_NativeXComponent_RegisterCallback, OH_NativeXComponent_RegisterKeyEventCallback, OH_NativeXComponent_TouchEvent, OH_NativeXComponent_TouchEventType, }; @@ -216,6 +217,8 @@ static SERVO_CHANNEL: OnceLock<Sender<ServoAction>> = OnceLock::new(); #[unsafe(no_mangle)] extern "C" fn on_surface_created_cb(xcomponent: *mut OH_NativeXComponent, window: *mut c_void) { info!("on_surface_created_cb"); + #[cfg(feature = "tracing-hitrace")] + let _ = hitrace::ScopedTrace::start_trace(&c"on_surface_created_cb"); let xc_wrapper = XComponentWrapper(xcomponent); let window_wrapper = WindowWrapper(window); @@ -267,6 +270,33 @@ extern "C" fn on_surface_created_cb(xcomponent: *mut OH_NativeXComponent, window info!("Returning from on_surface_created_cb"); } +/// Returns the offset of the surface relative to its parent's top left corner +/// +/// # Safety +/// +/// `xcomponent` and `native_window` must be valid, non-null and aligned pointers to a +/// live xcomponent and associated native window surface. +unsafe fn get_xcomponent_offset( + xcomponent: *mut OH_NativeXComponent, + native_window: *mut c_void, +) -> Result<(i32, i32), i32> { + let mut x: f64 = 0.0; + let mut y: f64 = 0.0; + + let result = unsafe { + OH_NativeXComponent_GetXComponentOffset(xcomponent, native_window, &raw mut x, &raw mut y) + }; + if result != 0 { + error!("OH_NativeXComponent_GetXComponentOffset failed with {result}"); + return Err(result); + } + + Ok(( + (x.round() as i64).try_into().expect("X offset too large"), + (y.round() as i64).try_into().expect("Y offset too large"), + )) +} + /// Returns the size of the surface /// /// # Safety @@ -387,6 +417,7 @@ extern "C" fn on_dispatch_key_event(xc: *mut OH_NativeXComponent, _window: *mut static LOGGER: LazyLock<hilog::Logger> = LazyLock::new(|| { let mut builder = hilog::Builder::new(); + builder.set_domain(hilog::LogDomain::new(0xE0C3)); let filters = [ "fonts", "servo", @@ -678,7 +709,7 @@ impl EventLoopWaker for WakeupCallback { } fn wake(&self) { - info!("wake called!"); + log::trace!("wake called!"); self.chan.send(ServoAction::WakeUp).unwrap_or_else(|e| { error!("Failed to send wake message with: {e}"); }); @@ -783,6 +814,12 @@ impl HostTrait for HostCallbacks { #[cfg(feature = "tracing-hitrace")] let _scope = hitrace::ScopedTrace::start_trace(&c"PageLoadEndedPrompt"); self.show_alert("Page finished loading!".to_string()); + } else { + #[cfg(feature = "tracing-hitrace")] + let _ = hitrace::ScopedTrace::start_trace_str(&format!( + "load status changed {:?}", + load_status + )); } } diff --git a/ports/servoshell/egl/ohos/simpleservo.rs b/ports/servoshell/egl/ohos/simpleservo.rs index 9815c0fe4fd..c867c7a5330 100644 --- a/ports/servoshell/egl/ohos/simpleservo.rs +++ b/ports/servoshell/egl/ohos/simpleservo.rs @@ -97,7 +97,10 @@ pub fn init( let Ok(window_size) = (unsafe { super::get_xcomponent_size(xcomponent, native_window) }) else { return Err("Failed to get xcomponent size"); }; - let coordinates = Coordinates::new(0, 0, window_size.width, window_size.height); + let Ok((x, y)) = (unsafe { super::get_xcomponent_offset(xcomponent, native_window) }) else { + return Err("Failed to get xcomponent offset"); + }; + let coordinates = Coordinates::new(x, y, window_size.width, window_size.height); let display_handle = RawDisplayHandle::Ohos(OhosDisplayHandle::new()); let display_handle = unsafe { DisplayHandle::borrow_raw(display_handle) }; diff --git a/ports/servoshell/prefs.rs b/ports/servoshell/prefs.rs index 306aaa197d2..a602939cb65 100644 --- a/ports/servoshell/prefs.rs +++ b/ports/servoshell/prefs.rs @@ -428,7 +428,11 @@ pub(crate) fn parse_command_line_arguments(args: Vec<String>) -> ArgumentParsing #[cfg(target_env = "ohos")] let log_filter = { let filters = opt_match.opt_strs("log-filter").join(","); - (!filters.is_empty()).then_some(filters) + let log_filter = (!filters.is_empty()).then_some(filters).or_else(|| { + (!preferences.log_filter.is_empty()).then_some(preferences.log_filter.clone()) + }); + log::debug!("Set log_filter to: {:?}", log_filter); + log_filter }; let mut debug_options = DebugOptions::default(); @@ -574,7 +578,6 @@ pub(crate) fn parse_command_line_arguments(args: Vec<String>) -> ArgumentParsing "dom_offscreen_canvas_enabled", "dom_permissions_enabled", "dom_resize_observer_enabled", - "dom_serviceworker_enabled", "dom_svg_enabled", "dom_trusted_types_enabled", "dom_webgl2_enabled", @@ -733,7 +736,7 @@ fn print_debug_options_usage(app: &str) { ); print_option( "dump-flow-tree", - "Print the flow tree (Layout 2013) or fragment tree (Layout 2020) after each layout.", + "Print the fragment tree after each layout.", ); print_option( "dump-rule-tree", diff --git a/python/requirements.txt b/python/requirements.txt index 20d42065dde..d026ce3ee5f 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -37,3 +37,6 @@ types-requests # For mach package on macOS. Mako == 1.2.2 + +# For devtools tests. +geckordp == 1.0.3 diff --git a/python/servo/build_commands.py b/python/servo/build_commands.py index b21f89980c0..42988debf35 100644 --- a/python/servo/build_commands.py +++ b/python/servo/build_commands.py @@ -13,10 +13,11 @@ import os.path as path import pathlib import shutil import stat +import subprocess import sys from time import time -from typing import Optional +from typing import Optional, List import notifypy @@ -40,6 +41,32 @@ SUPPORTED_ASAN_TARGETS = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu"] +def get_rustc_llvm_version() -> Optional[List[int]]: + """Determine the LLVM version of `rustc` and return it as a List[major, minor, patch, ...] + + In some cases we want to ensure that the LLVM version of rustc and clang match, e.g. + when using ASAN for both C/C++ and Rust code, we want to use the same ASAN implementation. + This function assumes that rustc points to the rust compiler we are interested in, which should + be valid in both rustup managed environment and on nix. + """ + try: + result = subprocess.run(['rustc', '--version', '--verbose'], encoding='utf-8', capture_output=True) + result.check_returncode() + for line in result.stdout.splitlines(): + line_lowercase = line.lower() + if line_lowercase.startswith("llvm version:"): + llvm_version = line_lowercase.strip("llvm version:") + llvm_version = llvm_version.strip() + version = llvm_version.split('.') + print(f"Info: rustc is using LLVM version {'.'.join(version)}") + return version + else: + print(f"Error: Couldn't find LLVM version in output of `rustc --version --verbose`: `{result.stdout}`") + except Exception as e: + print(f"Error: Failed to determine rustc version: {e}") + return None + + @CommandProvider class MachCommands(CommandBase): @Command('build', description='Build Servo', category='build') @@ -99,11 +126,30 @@ class MachCommands(CommandBase): env["RUSTFLAGS"] = env.get("RUSTFLAGS", "") + " -Zsanitizer=address" opts += ["-Zbuild-std"] kwargs["target_override"] = target_triple - # TODO: Investigate sanitizers in C/C++ code: - # env.setdefault("CFLAGS", "") - # env.setdefault("CXXFLAGS", "") - # env["CFLAGS"] += " -fsanitize=address" - # env["CXXFLAGS"] += " -fsanitize=address" + + # Note: We want to use the same clang/LLVM version as rustc. + rustc_llvm_version = get_rustc_llvm_version() + if rustc_llvm_version is None: + raise RuntimeError("Unable to determine necessary clang version for ASAN support") + llvm_major: int = rustc_llvm_version[0] + target_clang = f"clang-{llvm_major}" + target_cxx = f"clang++-{llvm_major}" + if shutil.which(target_clang) is None or shutil.which(target_cxx) is None: + raise RuntimeError(f"--with-asan requires `{target_clang}` and `{target_cxx}` to be in PATH") + env.setdefault("TARGET_CC", target_clang) + env.setdefault("TARGET_CXX", target_cxx) + # TODO: We should also parse the LLVM version from the clang compiler we chose. + # It's unclear if the major version being the same is sufficient. + + # We need to use `TARGET_CFLAGS`, since we don't want to compile host dependencies with ASAN, + # since that causes issues when building build-scripts / proc macros. + env.setdefault("TARGET_CFLAGS", "") + env.setdefault("TARGET_CXXFLAGS", "") + env["TARGET_CFLAGS"] += " -fsanitize=address" + env["TARGET_CXXFLAGS"] += " -fsanitize=address" + env["TARGET_LDFLAGS"] = "-static-libasan" + # By default build mozjs from source to enable ASAN with mozjs. + env.setdefault("MOZJS_FROM_SOURCE", "1") # asan replaces system allocator with asan allocator # we need to make sure that we do not replace it with jemalloc diff --git a/python/servo/devtools_tests.py b/python/servo/devtools_tests.py new file mode 100644 index 00000000000..0d336fbcb9c --- /dev/null +++ b/python/servo/devtools_tests.py @@ -0,0 +1,207 @@ +# Copyright 2013 The Servo Project Developers. See the COPYRIGHT +# file at the top-level directory of this distribution. +# +# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +# option. This file may not be copied, modified, or distributed +# except according to those terms. + +from concurrent.futures import Future +import logging +from geckordp.actors.root import RootActor +from geckordp.actors.descriptors.tab import TabActor +from geckordp.actors.watcher import WatcherActor +from geckordp.actors.resources import Resources +from geckordp.actors.events import Events +from geckordp.rdp_client import RDPClient +import http.server +import os.path +import socketserver +import subprocess +import time +from threading import Thread +from typing import Optional +import unittest + + +# Set this to true to log requests in the internal web servers. +LOG_REQUESTS = False + + +class DevtoolsTests(unittest.IsolatedAsyncioTestCase): + # /path/to/servo/python/servo + script_path = None + + def __init__(self, methodName="runTest"): + super().__init__(methodName) + self.servoshell = None + self.base_url = None + self.web_server = None + self.web_server_thread = None + + # Classic script vs module script: + # - <https://html.spec.whatwg.org/multipage/#classic-script> + # - <https://html.spec.whatwg.org/multipage/#module-script> + # Worker scripts can be classic or module: + # - <https://html.spec.whatwg.org/multipage/#fetch-a-classic-worker-script> + # - <https://html.spec.whatwg.org/multipage/#fetch-a-module-worker-script-tree> + # Non-worker(?) script sources can be inline, external, or blob. + # Worker script sources can be external or blob. + def test_sources_list(self): + self.start_web_server(test_dir=os.path.join(DevtoolsTests.script_path, "devtools_tests/sources")) + self.run_servoshell() + self.assert_sources_list(2, set([ + tuple([f"{self.base_url}/classic.js", f"{self.base_url}/test.html", "https://servo.org/js/load-table.js"]), + tuple([f"{self.base_url}/worker.js"]), + ])) + + def test_sources_list_with_data_no_scripts(self): + self.run_servoshell(url="data:text/html,") + self.assert_sources_list(1, set([tuple()])) + + def test_sources_list_with_data_empty_inline_classic_script(self): + self.run_servoshell(url="data:text/html,<script></script>") + self.assert_sources_list(1, set([tuple()])) + + def test_sources_list_with_data_inline_classic_script(self): + self.run_servoshell(url="data:text/html,<script>;</script>") + self.assert_sources_list(1, set([tuple(["data:text/html,<script>;</script>"])])) + + def test_sources_list_with_data_external_classic_script(self): + self.start_web_server(test_dir=os.path.join(DevtoolsTests.script_path, "devtools_tests/sources")) + self.run_servoshell(url=f"data:text/html,<script src=\"{self.base_url}/classic.js\"></script>") + self.assert_sources_list(1, set([tuple([f"{self.base_url}/classic.js"])])) + + def test_sources_list_with_data_empty_inline_module_script(self): + self.run_servoshell(url="data:text/html,<script type=module></script>") + self.assert_sources_list(1, set([tuple()])) + + def test_sources_list_with_data_inline_module_script(self): + self.run_servoshell(url="data:text/html,<script type=module>;</script>") + self.assert_sources_list(1, set([tuple(["data:text/html,<script type=module>;</script>"])])) + + # Sets `base_url` and `web_server` and `web_server_thread`. + def start_web_server(self, *, test_dir=None): + if test_dir is None: + test_dir = os.path.join(DevtoolsTests.script_path, "devtools_tests") + base_url = Future() + + class Handler(http.server.SimpleHTTPRequestHandler): + def __init__(self, *args, **kwargs): + super().__init__(*args, directory=test_dir, **kwargs) + + def log_message(self, format, *args): + if LOG_REQUESTS: + return super().log_message(format, *args) + + def server_thread(): + self.web_server = socketserver.TCPServer(("0.0.0.0", 0), Handler) + base_url.set_result(f"http://127.0.0.1:{self.web_server.server_address[1]}") + self.web_server.serve_forever() + + # Start a web server for the test. + self.web_server_thread = Thread(target=server_thread) + self.web_server_thread.start() + self.base_url = base_url.result(1) + + # Sets `servoshell`. + def run_servoshell(self, *, url=None): + # Change this setting if you want to debug Servo. + os.environ["RUST_LOG"] = "error,devtools=warn" + + # Run servoshell. + if url is None: + url = f"{self.base_url}/test.html" + self.servoshell = subprocess.Popen(["target/release/servo", "--devtools=6080", url]) + + # FIXME: Don’t do this + time.sleep(1) + + def tearDown(self): + # Terminate servoshell. + if self.servoshell is not None: + self.servoshell.terminate() + self.servoshell = None + + # Stop the web server. + if self.web_server is not None: + self.web_server.shutdown() + self.web_server = None + if self.web_server_thread is not None: + self.web_server_thread.join() + self.web_server_thread = None + if self.base_url is not None: + self.base_url = None + + def assert_sources_list(self, expected_targets: int, expected_urls_by_target: set[tuple[str]]): + client = RDPClient() + client.connect("127.0.0.1", 6080) + root = RootActor(client) + tabs = root.list_tabs() + tab_dict = tabs[0] + tab = TabActor(client, tab_dict["actor"]) + watcher = tab.get_watcher() + watcher = WatcherActor(client, watcher["actor"]) + + done = Future() + targets = [] + + def on_target(data): + try: + targets.append(data["target"]) + if len(targets) == expected_targets: + done.set_result(None) + except Exception as e: + # Raising here does nothing, for some reason. + # Send the exception back so it can be raised. + done.set_result(e) + + client.add_event_listener( + watcher.actor_id, Events.Watcher.TARGET_AVAILABLE_FORM, on_target, + ) + watcher.watch_targets(WatcherActor.Targets.FRAME) + watcher.watch_targets(WatcherActor.Targets.WORKER) + + result: Optional[Exception] = done.result(1) + if result: + raise result + done = Future() + # NOTE: breaks if two targets have the same list of source urls. + # This should really be a multiset, but Python does not have multisets. + actual_urls_by_target: set[tuple[str]] = set() + + def on_source_resource(data): + for [resource_type, sources] in data["array"]: + try: + self.assertEqual(resource_type, "source") + source_urls = tuple([source["url"] for source in sources]) + self.assertFalse(source_urls in sources) # See NOTE above + actual_urls_by_target.add(source_urls) + if len(actual_urls_by_target) == expected_targets: + done.set_result(None) + except Exception as e: + # Raising here does nothing, for some reason. + # Send the exception back so it can be raised. + done.set_result(e) + + for target in targets: + client.add_event_listener( + target["actor"], + Events.Watcher.RESOURCES_AVAILABLE_ARRAY, + on_source_resource, + ) + watcher.watch_resources([Resources.SOURCE]) + + result: Optional[Exception] = done.result(1) + if result: + raise result + self.assertEqual(actual_urls_by_target, expected_urls_by_target) + client.disconnect() + + +def run_tests(script_path): + DevtoolsTests.script_path = script_path + verbosity = 1 if logging.getLogger().level >= logging.WARN else 2 + suite = unittest.TestLoader().loadTestsFromTestCase(DevtoolsTests) + return unittest.TextTestRunner(verbosity=verbosity).run(suite).wasSuccessful() diff --git a/python/servo/devtools_tests/sources/classic.js b/python/servo/devtools_tests/sources/classic.js new file mode 100644 index 00000000000..84fd1671805 --- /dev/null +++ b/python/servo/devtools_tests/sources/classic.js @@ -0,0 +1 @@ +console.log("external classic"); diff --git a/python/servo/devtools_tests/sources/module.js b/python/servo/devtools_tests/sources/module.js new file mode 100644 index 00000000000..a1d0f1f37cf --- /dev/null +++ b/python/servo/devtools_tests/sources/module.js @@ -0,0 +1,2 @@ +export default 1; +console.log("external module"); diff --git a/python/servo/devtools_tests/sources/test.html b/python/servo/devtools_tests/sources/test.html new file mode 100644 index 00000000000..b8e1aa0e334 --- /dev/null +++ b/python/servo/devtools_tests/sources/test.html @@ -0,0 +1,11 @@ +<!doctype html><meta charset=utf-8> +<script src="classic.js"></script> +<script> + console.log("inline classic"); + new Worker("worker.js"); +</script> +<script type="module"> + import module from "./module.js"; + console.log("inline module"); +</script> +<script src="https://servo.org/js/load-table.js"></script> diff --git a/python/servo/devtools_tests/sources/worker.js b/python/servo/devtools_tests/sources/worker.js new file mode 100644 index 00000000000..a7993a8b5fb --- /dev/null +++ b/python/servo/devtools_tests/sources/worker.js @@ -0,0 +1 @@ +console.log("external classic worker"); diff --git a/python/servo/testing_commands.py b/python/servo/testing_commands.py index 860217d062a..5e5a8c2e8d2 100644 --- a/python/servo/testing_commands.py +++ b/python/servo/testing_commands.py @@ -19,6 +19,7 @@ import subprocess import textwrap import json +import servo.devtools_tests from servo.post_build_commands import PostBuildCommands import wpt import wpt.manifestupdate @@ -326,6 +327,14 @@ class MachCommands(CommandBase): return 0 if passed else 1 + @Command('test-devtools', + description='Run tests for devtools.', + category='testing') + def test_devtools(self): + print("Running devtools tests...") + passed = servo.devtools_tests.run_tests(SCRIPT_PATH) + return 0 if passed else 1 + @Command('test-wpt-failure', description='Run the tests harness that verifies that the test failures are reported correctly', category='testing', diff --git a/python/servo/try_parser.py b/python/servo/try_parser.py index 9a6b31693b0..0d78599c06f 100644 --- a/python/servo/try_parser.py +++ b/python/servo/try_parser.py @@ -39,11 +39,12 @@ class JobConfig(object): unit_tests: bool = False build_libservo: bool = False bencher: bool = False + build_args: str = "" wpt_args: str = "" number_of_wpt_chunks: int = 20 # These are the fields that must match in between two JobConfigs for them to be able to be # merged. If you modify any of the fields above, make sure to update this line as well. - merge_compatibility_fields: ClassVar[List[str]] = ['workflow', 'profile', 'wpt_args'] + merge_compatibility_fields: ClassVar[List[str]] = ['workflow', 'profile', 'wpt_args', 'build_args'] def merge(self, other: JobConfig) -> bool: """Try to merge another job with this job. Returns True if merging is successful @@ -209,7 +210,8 @@ class TestParser(unittest.TestCase): 'build_libservo': False, 'workflow': 'linux', 'wpt': False, - 'wpt_args': '' + 'wpt_args': '', + 'build_args': '' }] }) @@ -225,7 +227,8 @@ class TestParser(unittest.TestCase): "unit_tests": True, 'build_libservo': False, 'bencher': True, - "wpt_args": "" + "wpt_args": "", + 'build_args': '' }, { "name": "MacOS (Unit Tests)", @@ -236,7 +239,8 @@ class TestParser(unittest.TestCase): "unit_tests": True, 'build_libservo': False, 'bencher': False, - "wpt_args": "" + "wpt_args": "", + 'build_args': '' }, { "name": "Windows (Unit Tests)", @@ -247,7 +251,8 @@ class TestParser(unittest.TestCase): "unit_tests": True, 'build_libservo': False, 'bencher': False, - "wpt_args": "" + "wpt_args": "", + 'build_args': '' }, { "name": "Android", @@ -258,7 +263,8 @@ class TestParser(unittest.TestCase): "unit_tests": False, 'build_libservo': False, 'bencher': False, - "wpt_args": "" + "wpt_args": "", + 'build_args': '' }, { "name": "OpenHarmony", @@ -269,7 +275,8 @@ class TestParser(unittest.TestCase): "unit_tests": False, 'build_libservo': False, 'bencher': False, - "wpt_args": "" + "wpt_args": "", + 'build_args': '' }, { "name": "Lint", @@ -280,7 +287,9 @@ class TestParser(unittest.TestCase): "unit_tests": False, 'build_libservo': False, 'bencher': False, - "wpt_args": ""} + "wpt_args": "", + 'build_args': '' + } ]}) def test_job_merging(self): @@ -295,7 +304,8 @@ class TestParser(unittest.TestCase): 'build_libservo': False, 'workflow': 'linux', 'wpt': True, - 'wpt_args': '' + 'wpt_args': '', + 'build_args': '' }] }) @@ -327,6 +337,11 @@ class TestParser(unittest.TestCase): self.assertFalse(a.merge(b), "Should not merge jobs that run different WPT tests.") self.assertEqual(a, JobConfig("Linux (Unit Tests)", Workflow.LINUX, unit_tests=True)) + a = JobConfig("Linux (Unit Tests)", Workflow.LINUX, unit_tests=True) + b = JobConfig("Linux (Unit Tests)", Workflow.LINUX, unit_tests=True, build_args="--help") + self.assertFalse(a.merge(b), "Should not merge jobs with different build arguments.") + self.assertEqual(a, JobConfig("Linux (Unit Tests)", Workflow.LINUX, unit_tests=True)) + def test_full(self): self.assertDictEqual(json.loads(Config("full").to_json()), json.loads(Config("").to_json())) diff --git a/servobuild.example b/servobuild.example index 40dd62e2421..8efc1e2907a 100644 --- a/servobuild.example +++ b/servobuild.example @@ -36,9 +36,6 @@ webgl-backtrace = false # that triggered it. dom-backtrace = false -# Default to the “2020” implementation of CSS layout instead of the “2013” one. -layout-2020 = true - # Pick a media stack based on the target. Other values are "gstreamer" and "dummy" media-stack = "auto" diff --git a/tests/wpt/include.ini b/tests/wpt/include.ini index ba81ab64b83..8ea1e50b8d0 100644 --- a/tests/wpt/include.ini +++ b/tests/wpt/include.ini @@ -256,6 +256,8 @@ skip: true skip: true [writable-streams] skip: false + [transform-streams] + skip: false [subresource-integrity] skip: false [touch-events] diff --git a/tests/wpt/meta/MANIFEST.json b/tests/wpt/meta/MANIFEST.json index 1469a123dd5..f428e76b966 100644 --- a/tests/wpt/meta/MANIFEST.json +++ b/tests/wpt/meta/MANIFEST.json @@ -605,6 +605,13 @@ {} ] ], + "root-element-background-image-opaque-crash.html": [ + "d380310d0968c3ad63eb6adc52acd929b3b43b14", + [ + null, + {} + ] + ], "root-element-filter-background-clip-text-crash.html": [ "e12e4bb80f40e09e189f7f239486be246ea67ade", [ @@ -799,6 +806,13 @@ {} ] ], + "chrome-bug-415627003.html": [ + "af942fbcc9701955f3778dc2e57521d10bb1ce41", + [ + null, + {} + ] + ], "pseudo-element-animation-with-marker.html": [ "ba7fc2371abfdc463f8ed36e6f1bb39fb9ef6ef5", [ @@ -4794,6 +4808,13 @@ {} ] ], + "table-col-and-dead-row-group-crash.html": [ + "e6e90a63a345fe2bb9499b729d2cf2df6ed65d9d", + [ + null, + {} + ] + ], "table-collapsed-borders-crash.html": [ "aa699317e2734fece3d662ad102fdd2772680e25", [ @@ -5866,7 +5887,32 @@ null, {} ] - ] + ], + "root-preserve3d-crash.html": [ + "1123280187506b326adc9378465bfc560af4415e", + [ + null, + {} + ] + ], + "root-replace-crash.html": [ + "165eec41b6bc56db131cc7fc19f23701bc291b8c", + [ + null, + {} + ] + ], + "scoped": { + "crashtests": { + "shadow-dom.html": [ + "a2faafc1694e15f5c513655e57e320325bd4f45a", + [ + null, + {} + ] + ] + } + } }, "css-viewport": { "zoom": { @@ -6114,6 +6160,13 @@ {} ] ], + "backdrop-filter-transform-popover-crash.html": [ + "582f0c119656020aaa5167b0547cfbde20d11b95", + [ + null, + {} + ] + ], "crashtests": { "broken-reference-crash-001.html": [ "6e18e06c317e329a58b2b5132cf17dc6086d444f", @@ -33290,6 +33343,19 @@ {} ] ], + "root-margin-001-print.html": [ + "b2ec34fe0799d7cab5ac4dacb237780b6f7503e0", + [ + null, + [ + [ + "/css/css-break/root-margin-001-print-ref.html", + "==" + ] + ], + {} + ] + ], "table": { "repeated-section": { "fixedpos-in-footer-forced-break-print.html": [ @@ -38040,6 +38106,19 @@ {} ] ], + "webkit-box-ignores-flex-wrap.tentative.html": [ + "98086f83570d4c5b4c6251f31093744442a907ca", + [ + null, + [ + [ + "/compat/green-ref.html", + "==" + ] + ], + {} + ] + ], "webkit-box-rtl-flex.html": [ "d20b3a0a9dbab237a799f62504b9b1ea7b90a564", [ @@ -124754,6 +124833,19 @@ {} ] ], + "justify-items-anonymous.tentative.html": [ + "641dea1f54b2f0cea29bc4c4c82e023a479e3d3c", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square.xht", + "==" + ] + ], + {} + ] + ], "justify-self-auto-margins-1.html": [ "b54eb775114808ff22b82a858426e764a623e432", [ @@ -159361,7 +159453,7 @@ ] ], "contain-paint-stacking-context-001a.html": [ - "71102b6c73a3d88cdd953ecf82c69602ff87baa2", + "5e588cb61742ca406b23f573dd3609a52a191a1e", [ null, [ @@ -159374,7 +159466,7 @@ ] ], "contain-paint-stacking-context-001b.html": [ - "0c4d3323bf7cb28b5a31de8aac41563013b2ecd4", + "e03323782a7fbfbb924184fa71b0ab1031381671", [ null, [ @@ -167131,6 +167223,19 @@ {} ] ], + "display-contents-inline-002.html": [ + "f40a34129f348aca37fca83f70598053cf22785b", + [ + null, + [ + [ + "/css/css-display/display-contents-pass-no-red-ref.html", + "==" + ] + ], + {} + ] + ], "display-contents-inline-flex-001.html": [ "43b502731aefbaa348671e32171f0e1eb4bca04b", [ @@ -184678,6 +184783,19 @@ {} ] ], + "font-variant-emoji-005.html": [ + "0f5c2d7a98a615323b1adc0babb479fdb738537d", + [ + null, + [ + [ + "/css/css-fonts/font-variant-emoji-005-ref.html", + "==" + ] + ], + {} + ] + ], "font-variant-emoji-1.html": [ "53cf539edd8b61a7f08f8b95ae83f4f7a5179308", [ @@ -186484,6 +186602,19 @@ ], {} ] + ], + "flex-gap-decorations-022.html": [ + "5f3b512ef76214b3645c329c92bd06ebf23efd9d", + [ + null, + [ + [ + "/css/css-gaps/agnostic/gap-decorations-002-ref.html", + "==" + ] + ], + {} + ] ] }, "grid": { @@ -186837,6 +186968,84 @@ ], {} ] + ], + "grid-gap-decorations-029.html": [ + "6da75548d94b5e17e1ad7ba1011662c42b818d83", + [ + null, + [ + [ + "/css/css-gaps/grid/grid-gap-decorations-029-ref.html", + "==" + ] + ], + {} + ] + ], + "grid-gap-decorations-030.html": [ + "ef4507ef92638eeff660e67e2024bbce323c1aa8", + [ + null, + [ + [ + "/css/css-gaps/grid/grid-gap-decorations-030-ref.html", + "==" + ] + ], + {} + ] + ], + "grid-gap-decorations-031.html": [ + "530b661bfe32c5af7687954f3f4b2cbdfa61380a", + [ + null, + [ + [ + "/css/css-gaps/grid/grid-gap-decorations-031-ref.html", + "==" + ] + ], + {} + ] + ], + "grid-gap-decorations-032.html": [ + "ac2d38fdda0ec6a1f4a3b7b091d03c896e3e5e47", + [ + null, + [ + [ + "/css/css-gaps/grid/grid-gap-decorations-032-ref.html", + "==" + ] + ], + {} + ] + ], + "grid-gap-decorations-033.html": [ + "47ef35cc3688b2fcd2329558b1d0a27880983b38", + [ + null, + [ + [ + "/css/css-gaps/grid/grid-gap-decorations-033-ref.html", + "==" + ] + ], + {} + ] + ], + "grid-gap-decorations-38.html": [ + "8b87bcd4860ac50a1c2f3de98cec9599bebb77a3", + [ + null, + [ + [ + "/css/css-gaps/agnostic/gap-decorations-002-ref.html", + "==" + ] + ], + {} + ] ] }, "multicol": { @@ -187047,6 +187256,19 @@ ], {} ] + ], + "multicol-gap-decorations-017.html": [ + "c7d1fe42584f77e77acd3676f249ffa6510ada3b", + [ + null, + [ + [ + "/css/css-gaps/multicol/multicol-gap-decorations-017-ref.html", + "==" + ] + ], + {} + ] ] } }, @@ -201929,6 +202151,45 @@ {} ] ], + "linear-gradient-body-sibling-index.html": [ + "299b86d464ad54f383ce8f384ec6677b3d3e305b", + [ + null, + [ + [ + "/css/css-images/linear-gradient-body-sibling-index-ref.html", + "==" + ] + ], + {} + ] + ], + "linear-gradient-calc-em-units.html": [ + "e764e62890dad81adfda9d60d50d82a1e07c5de5", + [ + null, + [ + [ + "/css/css-images/linear-gradient-calc-em-units-ref.html", + "==" + ] + ], + {} + ] + ], + "linear-gradient-sibling-index.html": [ + "b92897e0697441df6876b88e53daa148a5edb009", + [ + null, + [ + [ + "/css/css-images/linear-gradient-sibling-index-ref.html", + "==" + ] + ], + {} + ] + ], "multiple-position-color-stop-conic-2.html": [ "f3171bf3fb9823095312ab080ee3d0481a8545f6", [ @@ -256915,6 +257176,19 @@ {} ] ], + "letter-spacing-cursive-001.html": [ + "5700c600981ab815e2ecf33577a6408d8e54dee1", + [ + null, + [ + [ + "/css/css-text/letter-spacing/reference/letter-spacing-cursive-001-ref.html", + "==" + ] + ], + {} + ] + ], "letter-spacing-end-of-line-001.html": [ "645ef10509803cb86b31c7250928c007acb21431", [ @@ -277158,6 +277432,35 @@ {} ] ], + "css-scale-of-clip-path.html": [ + "dda72c0d139979213741174da8c9572f1237bcc3", + [ + null, + [ + [ + "/css/css-transforms/css-scale-of-clip-path-ref.html", + "==" + ] + ], + { + "fuzzy": [ + [ + null, + [ + [ + 0, + 40 + ], + [ + 0, + 3000 + ] + ] + ] + ] + } + ] + ], "css-skew-001.html": [ "f209834121bb00ff3542029d44ba1ce878b0dd77", [ @@ -281127,6 +281430,35 @@ {} ] ], + "scale-transform-filtered-text.html": [ + "a479b12528a0bb6903d58d46a4b2a0d073b9ee97", + [ + null, + [ + [ + "/css/css-transforms/scale-transform-filtered-text-ref.html", + "==" + ] + ], + { + "fuzzy": [ + [ + null, + [ + [ + 0, + 25 + ], + [ + 0, + 500 + ] + ] + ] + ] + } + ] + ], "scale-transform-overlap.html": [ "0cb8db40b66accf337babe82d281d0a77c0ffa0d", [ @@ -328573,6 +328905,19 @@ {} ] ], + "backdrop-filter-scale-transform.html": [ + "813fb95d5ad3b5e4844e480980b4c712d6b8f28d", + [ + null, + [ + [ + "/css/filter-effects/backdrop-filter-scale-transform-ref.html", + "==" + ] + ], + {} + ] + ], "backdrop-filter-svg-background-image-blur.html": [ "9cd18b0542c86911b3a0b774fe892a61102c8b85", [ @@ -356396,7 +356741,7 @@ "the-button-element": { "interest-target": { "interesttarget-keyboard-invalidation.tentative.html": [ - "8c0ebd7a2cdcbab16e13618dbd5b8e36c58359f8", + "b6d0ff10932f1717fb038efb28c42c2bbf57cbf8", [ null, [ @@ -356410,19 +356755,6 @@ } ] ], - "interesttarget-outline-appearance.tentative.html": [ - "28cb2052a8d430b5dfadd31171b17d32a567d073", - [ - null, - [ - [ - "/html/semantics/the-button-element/interest-target/interesttarget-outline-appearance-ref.html", - "!=" - ] - ], - {} - ] - ], "interesttarget-plain-inline-element.tentative.html": [ "68ce00193f70c021cc392e0e2460662d7eb38ad9", [ @@ -372429,6 +372761,14 @@ [] ] } + }, + "translator": { + "resources": { + "util.js": [ + "ad06086a123bccbe2c852dffd1a36ffde0613ada", + [] + ] + } } }, "ambient-light": { @@ -374690,6 +375030,10 @@ "840e187cbad1cca4ea09e144c88df2a14032136a", [] ], + "WEB_FEATURES.yml": [ + "3c4f69200f67b3cd856f56913025c14de17a0e73", + [] + ], "resources": { "setAppBadge_iframe.html": [ "8738099658fbb2de893658e70242e855ecccd4b1", @@ -375026,6 +375370,10 @@ "65ca96dbb9d774e97cc3a60f6b6dde952104893b", [] ], + "WEB_FEATURES.yml": [ + "d55b5faaf11be9aa92df01f814d5ce165b23025f", + [] + ], "support": { "clear-cache-helper.sub.js": [ "5db0caf834cbf6a598f02761beda267dc8f1bcf8", @@ -417931,6 +418279,10 @@ "8c19f1cf4c6c0173b45face28d624368d365ec70", [] ], + "root-margin-001-print-ref.html": [ + "320da2e0f551b82e2e699500179fe06db612151b", + [] + ], "rounded-clipped-border-ref.html": [ "be08126fd2c3bab356a3a4fd5ff1cc1579184662", [] @@ -419214,7 +419566,7 @@ [] ], "contain-paint-stacking-context-001-ref.html": [ - "c7553716ab6c257bb0d9407582eed186bd763369", + "e2516f3d3788fd7f28356d266ee73e6b7ec0a4e3", [] ], "contain-size-block-001-ref.html": [ @@ -424631,6 +424983,10 @@ "633a94c4e51f32328bc68eeb74837aada6f4aff0", [] ], + "font-variant-emoji-005-ref.html": [ + "d4275b5c951c05ea270f1a2a474ff4ccaf9afc4a", + [] + ], "font-variant-emoji-1-notref.html": [ "bbf3654ccfcdd415fe5a28468e3329ba94ce63da", [] @@ -431505,6 +431861,10 @@ "gap-decorations-001-ref.html": [ "a7314fc7dca1545dacd710c261c7594117a3894a", [] + ], + "gap-decorations-002-ref.html": [ + "42c5ef128b47fc9cdd14c9ce4f8abdffb209d94e", + [] ] }, "flex": { @@ -431669,6 +432029,26 @@ "grid-gap-decorations-027-ref.html": [ "7e0d9cbcd6ff64b17bc8fe57616a37ba29a9fd8e", [] + ], + "grid-gap-decorations-029-ref.html": [ + "35450e07ae5579707c94b4a0a58f29eb83d7f7fa", + [] + ], + "grid-gap-decorations-030-ref.html": [ + "d4953ae2ddda701324d350ccf25a113b8e67fc37", + [] + ], + "grid-gap-decorations-031-ref.html": [ + "0615305bc9f8de791f1c2061ad6d90039932b157", + [] + ], + "grid-gap-decorations-032-ref.html": [ + "f90e3dd68132078e2557f33b7e32605d278cd0eb", + [] + ], + "grid-gap-decorations-033-ref.html": [ + "e26143a6b93e9fdd53d4055234d89e40d98dc2af", + [] ] }, "multicol": { @@ -431735,6 +432115,10 @@ "multicol-gap-decorations-016-ref.html": [ "8b2458fccce5a190c2c804b7e552dc0d85a8849a", [] + ], + "multicol-gap-decorations-017-ref.html": [ + "23fd089ac8e575e4035c5614c10ae55d7c3b06a4", + [] ] } }, @@ -434305,10 +434689,22 @@ "a80236dcf75c22ecd8e43935d004f1fddf0550cb", [] ], + "linear-gradient-body-sibling-index-ref.html": [ + "7325685511a542964859c34a359b107823db18df", + [] + ], + "linear-gradient-calc-em-units-ref.html": [ + "be13be8e260ef8d89be878d811edf83411dd2cee", + [] + ], "linear-gradient-ref.html": [ "033f1767799d718f072b48b6a99a4eaf7070f90e", [] ], + "linear-gradient-sibling-index-ref.html": [ + "eea8518d95b9393a5c1e1d34afc20744b8834c34", + [] + ], "multiple-position-color-stop-conic-2-ref.html": [ "2ee92a11ee43a60031302ecb24b5a1e2865590ec", [] @@ -439016,7 +439412,7 @@ [] ], "scroll-buttons-appearance-ref.html": [ - "462186407d55da46e14f5ebbc8f90218cd0b80b7", + "c382f738a6fc01402668747529ce7ab7b46f5f4f", [] ], "scroll-buttons-disabled-ref.html": [ @@ -445745,6 +446141,10 @@ "977d8211457a6e7b4976b596be91068cdf518869", [] ], + "letter-spacing-cursive-001-ref.html": [ + "720c9491b4d8cfdba5c2279dacdc297d495c2b67", + [] + ], "letter-spacing-end-of-line-001-ref.html": [ "eb474ab8d234d0836c3466e29bfe9b313e10d302", [] @@ -449499,6 +449899,10 @@ "54940566cd107ba443d3183dccecb505a811d14e", [] ], + "css-scale-of-clip-path-ref.html": [ + "d6fa480b0408ffc9c3687515e25a30ddc68f199f", + [] + ], "css-skew-001-ref.html": [ "70c256ebec653128a2d91555e817b54880666a35", [] @@ -449949,6 +450353,10 @@ ] } }, + "scale-transform-filtered-text-ref.html": [ + "e8b564ce3ddd6560299d91f347b4407fe6a5db6b", + [] + ], "scale-transform-overlap-ref.html": [ "4750764ae93092e7b7e9f7d151eb1b7904c32ca8", [] @@ -457138,6 +457546,10 @@ "f8947e0472f489ca1a98a9e231b17e7d8ce6585e", [] ], + "scrollIntoView-frame.html": [ + "ee2be2fd3ab04ad9a1bde48ee188789d92d9ca8a", + [] + ], "simultaneousScrollIntoViews.js": [ "32de35fb544c5a83b0c8159de21f9a6ebfed19ba", [] @@ -457465,6 +457877,10 @@ "c92741df4300e5416cbe26339d52b819ec35347c", [] ], + "backdrop-filter-scale-transform-ref.html": [ + "d7faa0e97a175fc5dabf6f2d89ce71ba0fd498e1", + [] + ], "backdrop-filter-transform-ref.html": [ "1af8776cf0dc663ec7bac1f83ba361ccb8b2b5d4", [] @@ -458240,7 +458656,7 @@ [] ], "WEB_FEATURES.yml": [ - "24027360aab4a27b72975f0d77062ffe8fb1a060", + "7f15b7fa475230b42981a3f92f60f434e9ccf1af", [] ], "prefers-color-scheme-svg-as-image-ref.html": [ @@ -460304,7 +460720,7 @@ [] ], "testdriver.md": [ - "67757af63f2ed6af4026577dd35000c183509d44", + "e0fae529875ee89d5e8a2457cdab35e1866f663b", [] ], "testharness-api.md": [ @@ -460874,6 +461290,12 @@ ] } }, + "observable": { + "WEB_FEATURES.yml": [ + "3e872791165e41402949da13fedd7a8780aaf660", + [] + ] + }, "parts": { "resources": { "domparts-utils.js": [ @@ -461338,7 +461760,7 @@ [] ], "element-timing-helpers.js": [ - "2c1bd195ea76f8126d739e705fed7af3a562fc45", + "5ff01bfa1209a26d84d69bb5a80d7fd21d671fab", [] ], "iframe-stores-entry.html": [ @@ -462656,7 +463078,7 @@ [] ], "event-timing-test-utils.js": [ - "59c78ebf39c8a8e0f31aafa1eccd0809bea54376", + "afad67bb6fc2d90e02bf7b40633be891bb2fe3d0", [] ], "slow-image.py": [ @@ -464352,6 +464774,10 @@ ] }, "request": { + "WEB_FEATURES.yml": [ + "69b2ea582a2281403c4899e122343262e43976db", + [] + ], "destination": { "resources": { "dummy": [ @@ -465226,6 +465652,10 @@ [] ], "resources": { + "cached_pragma_rand.py": [ + "18c7d25159fa3041b8aa278c8ac022003eee0e36", + [] + ], "http-cache.py": [ "3ab610dd1421cea5289ad14a8ef472ade27f07f1", [] @@ -465244,6 +465674,30 @@ ] } }, + "local-network-access": { + "META.yml": [ + "4c5c6983ed0467f3ebca3182a6b5d095270f85d2", + [] + ], + "README.md": [ + "95066cdcd0ba6f2baed3cd02d67610420367b6df", + [] + ], + "resources": { + "fetch-private.html": [ + "b96a207ec33a13e5dd4c53083ac3d73123b23cbb", + [] + ], + "support.sub.js": [ + "774e34d0a6fe59bab19aca14cb71b6e520acb798", + [] + ], + "target.py": [ + "eabcdd47517c8938d12cd08a0d66f0db2e518456", + [] + ] + } + }, "metadata": { "META.yml": [ "85f0a7d2ee12616aa7a5698b346954829d8a8bb0", @@ -465865,7 +466319,7 @@ [] ], "WEB_FEATURES.yml": [ - "45e40acba1dcb6b4abe11d1478c682264a4c1e90", + "7faf67952b5b82107a2797706e41dfdc780ef041", [] ], "resources": { @@ -471334,6 +471788,10 @@ [] ], "wide-gamut-canvas": { + "WEB_FEATURES.yml": [ + "b4d11212a6c677339784cdab33e0e31b0b948379", + [] + ], "canvas-display-p3.js": [ "c6ee97b7880a4ac2333b82b60407102045d1f509", [] @@ -471696,6 +472154,10 @@ "2d.path.stroke.overlap.png": [ "e2a35d48d4c4363294aec671a38cbd4b39c9a53c", [] + ], + "WEB_FEATURES.yml": [ + "1bbef5e88d6630d820e2206f283ac38cffceb67d", + [] ] }, "pixel-manipulation": { @@ -471879,6 +472341,12 @@ [] ] } + }, + "wide-gamut-canvas": { + "WEB_FEATURES.yml": [ + "b4d11212a6c677339784cdab33e0e31b0b948379", + [] + ] } }, "offscreen": { @@ -472358,8 +472826,20 @@ "fc7265a48c393566ad8c9fe7b73281e4f5137b21", [] ] + }, + "wide-gamut-canvas": { + "WEB_FEATURES.yml": [ + "b4d11212a6c677339784cdab33e0e31b0b948379", + [] + ] } }, + "path-objects": { + "WEB_FEATURES.yml": [ + "1bbef5e88d6630d820e2206f283ac38cffceb67d", + [] + ] + }, "reset": { "2d.reset.after-rasterization-expected.html": [ "3162e16e63d9fa5cc495401ca53d6c0242d3ee65", @@ -472471,6 +472951,12 @@ "00ecdccad3743db0d0a1ec99850f80ea7964d512", [] ] + }, + "wide-gamut-canvas": { + "WEB_FEATURES.yml": [ + "b4d11212a6c677339784cdab33e0e31b0b948379", + [] + ] } }, "resources": { @@ -474038,7 +474524,7 @@ [] ], "reflection.js": [ - "b2c3b30aae36b390a60c05b39901826ba71e0b1a", + "eeecd450fca8139e924affb298e7feb1a1fb46fb", [] ], "render-blocking": { @@ -477418,6 +477904,14 @@ } } }, + "embedded-content": { + "the-img-element": { + "WEB_FEATURES.yml": [ + "5716baa5def9bd2af6813751aa7a93ffba64c4cd", + [] + ] + } + }, "iana": { "application-x-www-form-urlencoded": { "original-id.json": [ @@ -478970,6 +479464,10 @@ }, "scripting": { "the-script-element": { + "WEB_FEATURES.yml": [ + "864bd3398ee21fb62665a2d5c259a52acdd04232", + [] + ], "resources": { "script.js": [ "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", @@ -482984,11 +483482,7 @@ }, "interest-target": { "interesttarget-keyboard-invalidation-ref.html": [ - "0ba07b16f497203f231d03cc7908453c6ccc43c4", - [] - ], - "interesttarget-outline-appearance-ref.html": [ - "33b0cf668587f182caf87dfb98665a9301fca19a", + "9d520943d5f542a2aeb45ac6cb1417eb20c25477", [] ], "resources": { @@ -483000,6 +483494,10 @@ } }, "the-link-element": { + "WEB_FEATURES.yml": [ + "6a444ee171ad29c442d573fb2131b544820b6cad", + [] + ], "resources": { "stylesheet.css": [ "9d9d772fb468756d1a90f72325f89cd372f812ef", @@ -486735,7 +487233,7 @@ [] ], "largest-contentful-paint.idl": [ - "872ba552b0d3b0398be533beb578fdedd260baea", + "d1630cc7daae802bf27107ce0ab07185d0746d7f", [] ], "layout-instability.idl": [ @@ -487059,7 +487557,7 @@ [] ], "service-workers.idl": [ - "d9ff2f651f8235968dd29bd6d7f86e9e8fdcb4cf", + "34af3372401eed53328b8bc5e7ea42b87ddd9b20", [] ], "shape-detection-api.idl": [ @@ -487211,7 +487709,7 @@ [] ], "webauthn.idl": [ - "a33c85e7bad86753211fa7aa9270abac18b1e54e", + "7fbe55e67652b3fd35079d1a06423bb441657bd5", [] ], "webcodecs-aac-codec-registration.idl": [ @@ -487702,7 +488200,7 @@ [] ], "largest-contentful-paint-helpers.js": [ - "a01080c00881d7b66a80886e34367624913e1d6f", + "99916f5c5d31110d83c91d4ce346890efb494b12", [] ], "lcp-sw-from-cache.js": [ @@ -491118,7 +491616,7 @@ [] ], "utils.js": [ - "ed58c957c97132276bb813603e1d25133cf0d860", + "41c76d6826658c0f73301f70ef0ae08cf84572e4", [] ] } @@ -494280,6 +494778,10 @@ "da9bbc387f41ab069c27edc055962f72036b96f2", [] ], + "WEB_FEATURES.yml": [ + "f59321971a7668545949ee3212e54420e424c7ae", + [] + ], "prepare-device.js": [ "a12dbaf28717cf3776d9e34f5a12e4adaa7bae18", [] @@ -494388,7 +494890,7 @@ [] ], "report-helper.js": [ - "5b5438903de11c346ac3901a9de81a96e4446386", + "216da22eae8c0ccde223c70a110a7a892676c14f", [] ], "report.py": [ @@ -495204,7 +495706,7 @@ [] ], "api-tests-1.html": [ - "9de875b0f127776e0d60df68075402d32c195749", + "6ec396c221b98f282b46a366e99a76bd9daef492", [] ], "api-tests-2.html": [ @@ -495589,7 +496091,7 @@ [] ], "testdriver.js": [ - "6e8410b7ea46064438c8d276274b6fbb689f81d2", + "992b9e3ab2ce0b63b94be2a5aa671b321f65e334", [] ], "testdriver.js.headers": [ @@ -495609,7 +496111,7 @@ [] ], "testharness.js": [ - "d1d9d61eab890f2090e06a286aa76aedc98db7c7", + "6ccede34483b227cc41fcb15235e56aa60d4022e", [] ], "testharness.js.headers": [ @@ -499883,7 +500385,7 @@ [] ], "basic-service-worker.js": [ - "17fccd448d2412287515f6176b0731189eb8eede", + "59d7d8bae9ee4d46d8a0bb12bcc3377d041280f4", [] ], "conditional-status.py": [ @@ -499895,11 +500397,11 @@ [] ], "counting-executor.py": [ - "cbcbc8eccb9bd8cf536c56cf576a4b5ea73ca44a", + "3511fe4905e2d8962175f0e4bc318a13c41c35f5", [] ], "executor.sub.html": [ - "d27acfe100a6a3567606bd64df0b8cd1c5bdbfc1", + "975a3e5092ed149b687f34ba6b9c6d5af11432ea", [] ], "executor.sub.html.headers": [ @@ -500875,14 +501377,6 @@ [] ], "animations": { - "reftests": { - "reference": { - "green-100x100.svg": [ - "120941444a4898197d6b6001f9908a6cd48b62ba", - [] - ] - } - }, "support": { "animated-path-helpers.js": [ "8fd4492265c2eb8aa00fb173bd8d625f119adaac", @@ -500927,6 +501421,10 @@ } }, "embedded": { + "WEB_FEATURES.yml": [ + "8482e555584bf205a6afa11836b35d046976e0df", + [] + ], "image-embedding-svg-with-near-integral-width-ref.html": [ "655d982b16ce0582f6b83e26844b2a6e2437b17b", [] @@ -501765,6 +502263,10 @@ } }, "scripted": { + "WEB_FEATURES.yml": [ + "864bd3398ee21fb62665a2d5c259a52acdd04232", + [] + ], "script-style-attribute-csp-ref.html": [ "a7316213f13a57fa1c3ab5c058bc9d21f14d5055", [] @@ -513355,7 +513857,7 @@ [] ], "emulation.py": [ - "e587a082c037d4fdcd72441616a3800d341c981a", + "fd5cf733ab761639493df402268429f5ab09f528", [] ], "input.py": [ @@ -513714,7 +514216,7 @@ [] ], "android_webview.py": [ - "0e02d3c02cd5456d62401423e7bb7581d70dc32f", + "c796be18a734cfbb33fd73b0d99a7c30512c8dd6", [] ], "base.py": [ @@ -513726,7 +514228,7 @@ [] ], "chrome_android.py": [ - "820323e615ad57d89df7ccdee68a4587b8acc6fc", + "4ed7707c3bed88e2f5061a331e49683db8a4f583", [] ], "chrome_ios.py": [ @@ -513750,7 +514252,7 @@ [] ], "firefox.py": [ - "494a7514efda2f2607c7394bca5ff9ddb19adf51", + "c63bfa2ceebb281e01e49a79aaf6cdeb2bbd92e5", [] ], "firefox_android.py": [ @@ -513826,7 +514328,7 @@ [] ], "asyncactions.py": [ - "9925a4b511def24fda82522acb5c5f0f9f3e80a1", + "8397d7838a3d6bd45e28dc613322d00ff8f2aebb", [] ], "base.py": [ @@ -513858,7 +514360,7 @@ [] ], "executorwebdriver.py": [ - "87403c2944d57e31c2612871262af9b77ce73235", + "7ca46a05a7b0f2bc3da65512fdee57080946b7d0", [] ], "executorwktr.py": [ @@ -513874,7 +514376,7 @@ [] ], "protocol.py": [ - "833dff45636c7e22697e47cfe692ebecfcf2d76f", + "16eb3cbb4a528ac9e4fc4897aa36f2a58e6d68ec", [] ], "pytestrunner": { @@ -513929,18 +514431,10 @@ "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", [] ], - "chromium.py": [ - "95f53011bfc32de47a93e0bb7d2ac99f84be4e40", - [] - ], "tests": { "__init__.py": [ "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", [] - ], - "test_chromium.py": [ - "bf815d5dc761b869f89ec71fdec66c395b453fbd", - [] ] }, "wptreport.py": [ @@ -513989,7 +514483,7 @@ [] ], "testdriver-extra.js": [ - "ab8b04ba3cdfc3d7c351ce99c6289de6e99b8f3e", + "3c2dd8b42dd5b572841b5b5529b42a17f724cef9", [] ], "testdriver-vendor.js": [ @@ -514017,7 +514511,7 @@ [] ], "testrunner.py": [ - "64668fc470b730ecc664c06c0c5412a657f3d2da", + "2c2da790e901f8e56f5f5f77673a281530c53294", [] ], "tests": { @@ -514131,7 +514625,7 @@ [] ], "wptcommandline.py": [ - "647cc7f21b1e2f35f9111650cb6eba2dce5376a4", + "7bfeef317170f2032152df46d9f976a1a82cbc90", [] ], "wptlogging.py": [ @@ -516425,6 +516919,10 @@ "afa5e4eaf032e0ad26e730615d633ce498a73c12", [] ], + "simd_select.wast.js": [ + "e997d81f75abc7ef0ba8e6e78e5435e3bd63e13c", + [] + ], "simd_splat.wast.js": [ "3ecdc10e2791eda422bb2fa657386ea476951592", [] @@ -518417,7 +518915,7 @@ [] ], "__init__.py": [ - "955335ea87e7fc427104a9a4eab5ba7c9e338558", + "587dc91a9387d3047fa56fa85ad80612637b24fd", [] ], "browser": { @@ -518641,11 +519139,11 @@ ], "set_geolocation_override": { "__init__.py": [ - "33dcca90f047b58e9026a104b99f537678191666", + "5e1fd1aa8637529a20b48356fe20e4f970b8428c", [] ], "conftest.py": [ - "a90895c74ed90c04b5f181af22c3bddec9ac132d", + "5fb9451f0a23821bbd7d25ac59efeb928f84b5ac", [] ] } @@ -519964,7 +520462,7 @@ }, "resources": { "utils.js": [ - "50d7911a9181a5139d9e16f7c866112dc54d6d01", + "9d5cfc70c10187743807096421975a099f22afc9", [] ], "utils_validation.js": [ @@ -520184,7 +520682,7 @@ [] ], "RTCPeerConnection-insertable-streams.js": [ - "0bf820acde48058711163708b05d02e89501546b", + "f3873e1de4b12aa668f711dfbbf41cb9b2d0ac66", [] ], "RTCPeerConnection-sender-worker-single-frame.js": [ @@ -529018,7 +529516,7 @@ ] ], "idbcursor-request-source.any.js": [ - "2fe8c66f2e590b170a1456648e1dca74077464d8", + "8e1b34ee798a69c3d61386c5ae1f9adaeba6f21a", [ "IndexedDB/idbcursor-request-source.any.html", { @@ -549735,7 +550233,7 @@ ] ], "idlharness.https.any.js": [ - "ae65eb49f2120e1a6d7222a7c0ecc9836c4ff5a8", + "5ddf7eab6dba2116d0d3bbc6826b176463107eb0", [ "WebCryptoAPI/idlharness.https.any.html", { @@ -551569,10 +552067,10 @@ ] }, "translator": { - "translator-bad-input.tentative.https.window.js": [ - "53a184bfd196f6006e7df9c908518ba47deacda6", + "translator-bad-input.https.window.js": [ + "db8905a61f660f8dc0aa7666c044794d22683799", [ - "ai/translator/translator-bad-input.tentative.https.window.html", + "ai/translator/translator-bad-input.https.window.html", { "script_metadata": [ [ @@ -551592,15 +552090,15 @@ } ] ], - "translator-translate.tentative.https.window.js": [ - "a8aad5e03e1f46db43a47966d66f7aecc526b255", + "translator.optional.https.window.js": [ + "96eca09d28bd61ccc4d31b746216f1f252d0bf3b", [ - "ai/translator/translator-translate.tentative.https.window.html", + "ai/translator/translator.optional.https.window.html", { "script_metadata": [ [ "title", - "Translate from English to Japanese" + "Translator Translate" ], [ "global", @@ -551621,6 +552119,10 @@ [ "script", "/resources/testdriver.js" + ], + [ + "script", + "resources/util.js" ] ], "timeout": "long" @@ -569742,7 +570244,7 @@ ] ], "icon-blocked.sub.html": [ - "cc882347a1ac7b595275c2263ef266826e6f07bd", + "4c39e5dec735c10635a603356367610d3aad3fa2", [ null, {} @@ -569797,6 +570299,13 @@ {} ] ], + "img-src-targeting.html": [ + "3b4fe7c690b0b980a9626de0deb02c8950f5d4a0", + [ + null, + {} + ] + ], "img-src-wildcard-allowed.html": [ "050a4d14100bb1ef719d6700bfbd37a97424af59", [ @@ -571859,6 +572368,13 @@ {} ] ], + "style-src-inline-style-with-csstext.html": [ + "5e812b4aee9d0d081673a0f333f8b29187619c3d", + [ + null, + {} + ] + ], "style-src-multiple-policies-multiple-hashing-algorithms.html": [ "027c61d8c632f2387408b8fb6869dee69bb8913d", [ @@ -573010,7 +573526,7 @@ ] ], "cookieListItem_attributes.https.any.js": [ - "6716d91788db746765593a2a95d34e1ecbb4e3a5", + "542bd6c53870b5baaa9ac0f75c2235079b7bfe88", [ "cookie-store/cookieListItem_attributes.https.any.html", { @@ -573764,7 +574280,7 @@ ] ], "httponly_cookies.https.window.js": [ - "8a10e358ef6de72d5476ae8dc8a571482881d7ef", + "605e94e67440aaedbcaa39f185270fe77b316ad3", [ "cookie-store/httponly_cookies.https.window.html", { @@ -577587,7 +578103,7 @@ ] ], "anchor-position-grid-001.html": [ - "92fb4d275b8988641ed0736969e918703e4d649d", + "7bd389b39d8d6642f02181e475bbc5eaa7322f2d", [ null, {} @@ -577629,7 +578145,7 @@ ] ], "anchor-position-multicol-002.html": [ - "7b2691a2b904ffb7fdc3d720b07ca454e1df7fe1", + "1e1f0a5c95b1b5f1d86efc5f22ffc87afbbab453", [ null, {} @@ -577643,7 +578159,7 @@ ] ], "anchor-position-multicol-004.html": [ - "399494120ea0ea5da5534a0a14a9b99e598222d5", + "8f7a3dad12915ddbea9bd88077694434d0a17b48", [ null, {} @@ -577685,7 +578201,7 @@ ] ], "anchor-position-multicol-nested-001.html": [ - "35ab2cfc15cea1fa1788f9b4cb09f9af39d079ec", + "c5ce41299af29008356b7a049a3d479038379fae", [ null, {} @@ -581618,7 +582134,7 @@ ], "table": { "border-spacing.html": [ - "fc5e87e35dde4b4cb2ed5c457f5cd22ec73d96b0", + "9fd94760681f0186f86f9a55da300250499f2a50", [ null, {} @@ -581662,7 +582178,7 @@ ] ], "table-parts-offsets-vertical-rl.tentative.html": [ - "9d4a472d4382825c13af35114acc48a3b5face2a", + "1eb751032ca4b7598ee3a413408c61c5a592b3e4", [ null, {} @@ -581839,6 +582355,13 @@ {} ] ], + "inline-style-background.html": [ + "11451f8cefd3e725b017b263dded4bf9a806d785", + [ + null, + {} + ] + ], "layer-basic.html": [ "e214bffc25f5fc3b56480ec9c8704a80bf109c17", [ @@ -586149,6 +586672,20 @@ {} ] ], + "flex-container-max-content-002.tentative.html": [ + "77a074b153a3b3fcb47c5665bab614c1720a73d2", + [ + null, + {} + ] + ], + "flex-container-min-content-002.tentative.html": [ + "92d37e3b9d8db7b57b813b49c26a3e9cd03b5179", + [ + null, + {} + ] + ], "flex-direction-column-overlap-001.html": [ "4d483a44fc630e46ddbe75ca79d8246eed67a94d", [ @@ -588881,6 +589418,13 @@ {} ] ], + "gap-decorations-col-rule-width.html": [ + "db7b97e74a58f30a52b22b68a92475f48f865ed7", + [ + null, + {} + ] + ], "gap-decorations-color-computed.html": [ "fbc3c3f4c976b344d48a9bd927547214f072e3ab", [ @@ -595009,8 +595553,8 @@ {} ] ], - "dashed-function-cycles.tentative.html": [ - "11e653e9b7d02d944e4bd74bda6e08e838c4ea42", + "dashed-function-cycles.html": [ + "15305be2b287ca3e4fbfad928f8d101ed8463104", [ null, {} @@ -595244,6 +595788,34 @@ {} ] ], + "getclientrects-005.html": [ + "ee60a607dccb3fdba2ed7dc99cb344071f5bb34a", + [ + null, + {} + ] + ], + "getclientrects-006.html": [ + "9f07a7de7444de7a2f91f97ff0a4fe44b7e177af", + [ + null, + {} + ] + ], + "getclientrects-007.html": [ + "0791c5b931fe3fade4c7b4483ef2469435ffbd77", + [ + null, + {} + ] + ], + "getclientrects-008.html": [ + "21505a7e48a2d2f9947342329de19dfde974d8e5", + [ + null, + {} + ] + ], "going-out-of-flow-after-spanner.html": [ "2fe0e42a7522647b7eca4db1e6f710a9975faaa4", [ @@ -596318,6 +596890,24 @@ } ] ], + "scroll-button-activation-without-scroller.html": [ + "f5951efc419d62a28f661c4f7d93cd9654b42616", + [ + null, + { + "testdriver": true + } + ] + ], + "scroll-button-disabled-no-focus.html": [ + "a88977068742f03bdd4929ff1e3f96e581d9ba2d", + [ + null, + { + "testdriver": true + } + ] + ], "scroll-button-display-none.html": [ "8b3068be6dad6d1ccbe1134b9833ecc1c8b0f594", [ @@ -611449,7 +612039,7 @@ ] ], "transform.html": [ - "7a852545a74a494348d90d727fc7266140033d2f", + "d12714a499c75ecbae8c4586f452d335594240fb", [ null, {} @@ -613518,7 +614108,7 @@ ] ], "sibling-function-descriptors.tentative.html": [ - "d31d4acf499fb62f777817f24186e394626d507e", + "76d2ff8ee4db660e41ede9b188465a6c31843e3b", [ null, {} @@ -613538,6 +614128,20 @@ {} ] ], + "sibling-index-keyframe-length-value-dynamic.html": [ + "cbd34602fb3c74f3cea8a1e2192d1b5687e38a05", + [ + null, + {} + ] + ], + "sibling-index-keyframe-value-dynamic.html": [ + "286e0d3d3e2eecdd091df74c8c47f738cb700dd1", + [ + null, + {} + ] + ], "tree-scoped-sibling-function.html": [ "979466bb7cf3d5e5c4e9a2681f02f969858953e6", [ @@ -614620,6 +615224,13 @@ ] ] }, + "start-skip-start.html": [ + "e04979472f1725b0225cdb98bbbd5222ed98c16c", + [ + null, + {} + ] + ], "start-view-transtion-skips-active.html": [ "971c7b38d4c816489caab9c611adc800b3638971", [ @@ -614696,7 +615307,7 @@ ] ], "view-transition-types-mutable-no-document-element-crashtest.html": [ - "d5c0abd652cd3c673a71979a458df8183ab057ba", + "3ac946b273ba9fa9f9da27b10e492420daa3430a", [ null, {} @@ -617053,7 +617664,7 @@ ] ], "getBoundingClientRect-newline.html": [ - "40e29181d6fed001b641b0b0050cf21d6f3c589c", + "ce82b7237d003d79fe4333e5a67aa74b004bb11c", [ null, {} @@ -617528,6 +618139,13 @@ {} ] ], + "scrollIntoView-container.html": [ + "3b241ea22517ecc5db6f286422233ed9f25cab5f", + [ + null, + {} + ] + ], "scrollIntoView-fixed-outside-of-viewport.html": [ "b3d61a430a3d2b566ff73b0844a79bf5ec4e92db", [ @@ -617832,7 +618450,7 @@ ] ], "table-client-props.html": [ - "4af06d6bf71f0df75d3710ec0906445e943d340d", + "2895bebebbf1868ef60f947d230ecca96dd6e85a", [ null, {} @@ -618022,6 +618640,13 @@ {} ] ], + "css-filters-opacity-hit-testing.html": [ + "3b288fca4df13fcad7cfe0104c0f333aec73e45c", + [ + null, + {} + ] + ], "filter-sign-function.html": [ "97e5a26073b097728cf4571712bb9bd472ed69fd", [ @@ -618217,7 +618842,7 @@ }, "geometry": { "DOMMatrix-001.html": [ - "f578da076cbe6d7d7eb69b2cf438aa6f11a5c060", + "3436e17ced00de235f23117ed3a2bf8eba828a03", [ null, {} @@ -624130,7 +624755,7 @@ } ] ], - "overscroll-deltas.html": [ + "overscroll-deltas.tentative.html": [ "e13e9f1cce5949da74227a1f069f64baad1f517c", [ null, @@ -624139,7 +624764,7 @@ } ] ], - "overscroll-event-fired-to-document.html": [ + "overscroll-event-fired-to-document.tentative.html": [ "c054ffca9c471f78ce5d1cbaa210fa7aad3aee3b", [ null, @@ -624148,7 +624773,7 @@ } ] ], - "overscroll-event-fired-to-element-with-overscroll-behavior.html": [ + "overscroll-event-fired-to-element-with-overscroll-behavior.tentative.html": [ "750080e6568e863a9249bc62e52971d9459f8418", [ null, @@ -624157,7 +624782,7 @@ } ] ], - "overscroll-event-fired-to-scrolled-element.html": [ + "overscroll-event-fired-to-scrolled-element.tentative.html": [ "be4176df59d6a1d59d90beee839bc5ca6a72f1c5", [ null, @@ -624166,7 +624791,7 @@ } ] ], - "overscroll-event-fired-to-window.html": [ + "overscroll-event-fired-to-window.tentative.html": [ "ef5ae3daef8158c9424cf07ad091c6a945456d0a", [ null, @@ -627813,7 +628438,7 @@ ] ], "XMLSerializer-serializeToString.html": [ - "6c294e464a5dc787abd4d10281ab2fe0555a0a3c", + "352a62c7d5db0710da6819bbc094ebfae46f4099", [ null, {} @@ -646895,6 +647520,15 @@ } ] ], + "orphan-keydown.html": [ + "0ab105d4d7284c19513a92241a9e16976fadefd0", + [ + null, + { + "testdriver": true + } + ] + ], "pointerdown.html": [ "2eb6ae898ccd006da1633a7f812dbcfc0fcbe9f1", [ @@ -648829,7 +649463,7 @@ ] ], "fedcm-disconnect.sub.https.html": [ - "2ea2d4a2599751cad941552964c700ced2f1b7cc", + "04fcd272dcf261a19cf71abb049e41c8b41abbbf", [ null, { @@ -662480,6 +663114,13 @@ } ] ], + "pragma-no-cache-with-cache-control.html": [ + "19a80fe5edc6fcfdaad36219bc608312f5d15a3b", + [ + null, + {} + ] + ], "split-cache.html": [ "c822abba3acee8db1079ad08dd7fbcbf38d9e3b9", [ @@ -662759,6 +663400,17 @@ ] ] }, + "local-network-access": { + "fetch.tentative.https.html": [ + "9c591f309b75af6731c6d6be9c0907d3b8c22618", + [ + null, + { + "testdriver": true + } + ] + ] + }, "metadata": { "audio-worklet.https.html": [ "3b768ef0b5ddd2d13361629afd011e6987cb38d0", @@ -692441,6 +693093,13 @@ }, "windows": { "auxiliary-browsing-contexts": { + "named-lookup-noopener.html": [ + "91cf6ae6ee24611856b1a5eeb87f60b99cb82d64", + [ + null, + {} + ] + ], "named-lookup-scoped-to-browsing-context-group.html": [ "0450d479196a19535419c286fa2447580b344b25", [ @@ -721858,6 +722517,15 @@ } ] ], + "pointerdown-add-display-none.html": [ + "653944e155b71055761ab900f9f485ba812b46d2", + [ + null, + { + "testdriver": true + } + ] + ], "pointerdown-add-overflow-hidden.html": [ "ac953adfd874b19ab42379039dc26ac0efb67b7b", [ @@ -733702,6 +734370,15 @@ } ] ], + "select-input-keyboard-behavior.tentative.html": [ + "bf3fbab9d18a4747f903d082a8c11d6ebad737aa", + [ + null, + { + "testdriver": true + } + ] + ], "select-inside-top-layer.tentative.html": [ "6d70f3aaf8d3dc23b2f5fb784c399991913dc938", [ @@ -740732,6 +741409,15 @@ } ] ], + "interesttarget-partial-interest.tentative.html": [ + "bc7184d42d2528fe8e027e7af51463a796177ca9", + [ + null, + { + "testdriver": true + } + ] + ], "interesttarget-popover-focus.tentative.html": [ "9dfc39a37a60d438ecf9287d0ddffc0212e7a21c", [ @@ -748357,7 +749043,7 @@ }, "emulation": { "set_geolocation_override.https.html": [ - "5cbcf546423c772a98fd3725531754c366ab7b91", + "e339f39dcd89aa311e91c744669bf2cd9aadc103", [ null, { @@ -748868,7 +749554,7 @@ ] ], "input-events-spin-button-click-on-number-input-delete-document.html": [ - "fb3655398dbd1bdde84f032df392b6352b7fcafe", + "9e5d9b9ef99a4290b4195ff5901eab324935c3fe", [ null, { @@ -750749,7 +751435,7 @@ ] ], "idlharness.html": [ - "5f5d286b356cf5dcffd76f2ed6f689804e8426ba", + "0dd006e711c6d7b02fcbcd4d31a5c11381fe8d69", [ null, {} @@ -750952,7 +751638,7 @@ ] ], "observe-text.html": [ - "0724a0bb92bf5248938c33fe1377d2e3a23309a5", + "d029d500aa1217b92c3898e90a50de83a98bc9e0", [ null, {} @@ -763872,7 +764558,7 @@ ] ], "paint-timing-mixin.html": [ - "c43b7668b5c9b376774672746b6f1e31e5feb3cb", + "048c985c7c75cc3fe717b3b836120f170e847d1e", [ null, {} @@ -785582,7 +786268,7 @@ ] ], "sanitizer-basic-filtering.tentative.html": [ - "0b6924b0980d8295d02a5ed3d552ba4e58beb212", + "38c764ae181418014488cf86126398158e653157", [ null, {} @@ -785609,6 +786295,13 @@ {} ] ], + "sanitizer-parseHTML.tentative.html": [ + "c4a31e2eb0365f67479968f4ce08117d04384889", + [ + null, + {} + ] + ], "sanitizer-removeUnsafe.tentative.html": [ "7ad8253ad5fdec29b79c590e50e9c0d211d17031", [ @@ -797059,7 +797752,7 @@ ] ], "innertext.tentative.html": [ - "d40b6047924834ef09137fa975cbd2d142999bcb", + "7716488f25a08ea62635fceec679d97650d1f383", [ null, { @@ -797068,7 +797761,7 @@ ] ], "interaction-with-paint-before-back.tentative.html": [ - "7b884f2bdb38945a346c3b5a8b178c04e88989ad", + "5961a6ebcdfdd2b10bf16173d3f0cece2733fa1c", [ null, { @@ -797248,6 +797941,19 @@ } ] ], + "smoke": { + "tentative": { + "basic.html": [ + "9bfedf09b1934e00a85cd030388ed470f575f7b0", + [ + null, + { + "testdriver": true + } + ] + ] + } + }, "soft-navigation-detection-main-descendent.tentative.html": [ "96ff55260c3b9553ac5735cc5fe209ee7cfd5f37", [ @@ -797355,7 +798061,7 @@ ] ], "text-lcp-followed-by-anim-image-softnav-lcp.tentative.html": [ - "0615b513e61319d61bcbed3540aa1a5d47289cb6", + "b34a6e81a58333c61ccedb4e25551feba4893736", [ null, { @@ -797382,7 +798088,7 @@ ] ], "visited-link.tentative.html": [ - "0bb149f00e91fc2f0033a6f763543967ef542ab2", + "0bb31aaac1540b3d2b9f6a3157bb43d96e430a58", [ null, { @@ -798302,6 +799008,75 @@ "timeout": "long" } ] + ], + "redirect.sub.https.html": [ + "f56f6bf09b9f61956e9297f8098677b846270d49", + [ + "speculation-rules/prefetch/tentative/service-worker/redirect.sub.https.html?origin=same-site&sc=in-in&sw=fetch-handler", + { + "timeout": "long" + } + ], + [ + "speculation-rules/prefetch/tentative/service-worker/redirect.sub.https.html?origin=same-site&sc=in-in&sw=fetch-handler-synthetic", + { + "timeout": "long" + } + ], + [ + "speculation-rules/prefetch/tentative/service-worker/redirect.sub.https.html?origin=same-site&sc=in-in&sw=fetch-handler-to-fallback", + { + "timeout": "long" + } + ], + [ + "speculation-rules/prefetch/tentative/service-worker/redirect.sub.https.html?origin=same-site&sc=in-in&sw=no-fetch-handler", + { + "timeout": "long" + } + ], + [ + "speculation-rules/prefetch/tentative/service-worker/redirect.sub.https.html?origin=same-site&sc=in-out&sw=fetch-handler", + { + "timeout": "long" + } + ], + [ + "speculation-rules/prefetch/tentative/service-worker/redirect.sub.https.html?origin=same-site&sc=in-out&sw=fetch-handler-synthetic", + { + "timeout": "long" + } + ], + [ + "speculation-rules/prefetch/tentative/service-worker/redirect.sub.https.html?origin=same-site&sc=in-out&sw=fetch-handler-to-fallback", + { + "timeout": "long" + } + ], + [ + "speculation-rules/prefetch/tentative/service-worker/redirect.sub.https.html?origin=same-site&sc=in-out&sw=no-fetch-handler", + { + "timeout": "long" + } + ], + [ + "speculation-rules/prefetch/tentative/service-worker/redirect.sub.https.html?origin=same-site&sc=out-in&sw=fetch-handler", + { + "timeout": "long" + } + ], + [ + "speculation-rules/prefetch/tentative/service-worker/redirect.sub.https.html?origin=same-site&sc=out-in&sw=fetch-handler-to-fallback", + { + "timeout": "long" + } + ], + [ + "speculation-rules/prefetch/tentative/service-worker/redirect.sub.https.html?origin=same-site&sc=out-in&sw=no-fetch-handler", + { + "timeout": "long" + } + ] ] } }, @@ -800067,6 +800842,26 @@ } ] ], + "prefetch-eagerness-pointer-down.https.html": [ + "cbf69cefba61f23b706c9b397308625467671237", + [ + null, + { + "testdriver": true, + "timeout": "long" + } + ] + ], + "prefetch-eagerness-pointer-hover.https.html": [ + "f5e749b9c6f63f80c40ed9ff7cac42570f8b8d13", + [ + null, + { + "testdriver": true, + "timeout": "long" + } + ] + ], "prerender-target-hint.https.html": [ "cab8f4586c514f562976ee41acb2cd6f74568eac", [ @@ -800144,10 +800939,12 @@ ] ], "SpeechRecognition-installOnDevice.https.html": [ - "1d1dd35edc26d75419a49120d8a76c4ea7c84233", + "2f5b359c571d6d9cc97815ae6ba46e4249ec569e", [ null, - {} + { + "testdriver": true + } ] ], "SpeechRecognition-onerror.https.html": [ @@ -807076,7 +807873,7 @@ ], "crashtests": { "garbage-collection.any.js": [ - "cf10f7f2e8317ed16dd5fe3a967c6f26efd0a335", + "6e9d80c41425b13800717f9e27184f64f1514752", [ "streams/readable-streams/crashtests/garbage-collection.any.html", { @@ -812273,6 +813070,71 @@ } ] ], + "crashtests": { + "garbage-collection.any.js": [ + "a3796881c9fb544a0b446db39af233b3866ae2b9", + [ + "streams/writable-streams/crashtests/garbage-collection.any.html", + { + "script_metadata": [ + [ + "global", + "window,worker" + ], + [ + "script", + "/common/gc.js" + ] + ] + } + ], + [ + "streams/writable-streams/crashtests/garbage-collection.any.serviceworker.html", + { + "script_metadata": [ + [ + "global", + "window,worker" + ], + [ + "script", + "/common/gc.js" + ] + ] + } + ], + [ + "streams/writable-streams/crashtests/garbage-collection.any.sharedworker.html", + { + "script_metadata": [ + [ + "global", + "window,worker" + ], + [ + "script", + "/common/gc.js" + ] + ] + } + ], + [ + "streams/writable-streams/crashtests/garbage-collection.any.worker.html", + { + "script_metadata": [ + [ + "global", + "window,worker" + ], + [ + "script", + "/common/gc.js" + ] + ] + } + ] + ] + }, "error.any.js": [ "d08c8a54863bb9693e76116946bd118592d6928c", [ @@ -813742,6 +814604,34 @@ {} ] ], + "tentative": { + "integrity-policy": { + "parsing.https.html": [ + "205854419a7d58871e39a5cfb3a86ccd38cd5492", + [ + "subresource-integrity/tentative/integrity-policy/parsing.https.html?type=enforce", + { + "timeout": "long" + } + ], + [ + "subresource-integrity/tentative/integrity-policy/parsing.https.html?type=report", + { + "timeout": "long" + } + ] + ], + "script.https.html": [ + "783374db920a24ae32a3492d8cfe2edc5d8eb5d3", + [ + null, + { + "timeout": "long" + } + ] + ] + } + }, "unencoded-digest": { "tentative": { "fetch.any.js": [ @@ -814687,13 +815577,6 @@ {} ] ], - "discard-check-removal-order.html": [ - "2935a69addaabdeee2eb381de847cd7f77688ce3", - [ - null, - {} - ] - ], "end-attribute-change-end-time.html": [ "9f05d7d405d364a1eadfa104c1c4bd90a2bf3b8c", [ @@ -815915,7 +816798,7 @@ ] ], "idlharness.window.js": [ - "b363128c25a9068df93b26966b82b50cdd9b87f7", + "57beca77fd45b83f40fcdf378103d6f1c1c0940d", [ "svg/idlharness.window.html", { @@ -817079,7 +817962,7 @@ ] ], "presentation-attributes-special-cases.html": [ - "7bfa001030a0e0969d845e353a9d721d9ce940a0", + "9a5da751ce1ace3c277bfce3bf9b99352ac7786f", [ null, {} @@ -825112,6 +825995,13 @@ {} ] ], + "simd_select.wast.js.html": [ + "5d50138374b1ce1edaa9d8b28d03f33b6cb6bab3", + [ + null, + {} + ] + ], "simd_splat.wast.js.html": [ "45204220343e96015990bb8c6374c2a98aa62382", [ @@ -851940,7 +852830,7 @@ ] ], "conv2d.https.any.js": [ - "9fe246c9395c6cacf7e2f5245dac420e295707be", + "45fecb2b40cc399ace227264e881d2009d467cd2", [ "webnn/conformance_tests/conv2d.https.any.html?cpu", { @@ -852051,7 +852941,7 @@ ] ], "conv_transpose2d.https.any.js": [ - "67ce5dbaf65cc99ba7f2fdd02aaf2d9b3e9022ce", + "a17df911e4482de7e49874d84f5818694515e722", [ "webnn/conformance_tests/conv_transpose2d.https.any.html?cpu", { @@ -856140,7 +857030,7 @@ ] ], "log.https.any.js": [ - "011beef53aca200f5916dc51152fe745866989ec", + "8ed807b3401330e961f2ae9721f6378147580ddd", [ "webnn/conformance_tests/log.https.any.html?cpu", { @@ -858129,7 +859019,7 @@ ] ], "pooling.https.any.js": [ - "f385aab1f154387bbe35b171927d29684c8d5120", + "8f81ff565d232dec427381dfdae638cffceb0d91", [ "webnn/conformance_tests/pooling.https.any.html?cpu", { @@ -858462,7 +859352,7 @@ ] ], "qdq_subgraph.https.any.js": [ - "3b59c3bb49d6441a77e83309f48a97dd982aea74", + "aa816cce7fb2cd3d0a3f81d0453c8c199bf052fe", [ "webnn/conformance_tests/qdq_subgraph.https.any.html?cpu", { @@ -861804,7 +862694,7 @@ ] ], "sign.https.any.js": [ - "4c3a330f850ca591bcc5a1b9c8fc784469ee821e", + "004c03bdf1339d74a84c95d5b03b31b56876d0fe", [ "webnn/conformance_tests/sign.https.any.html?cpu", { @@ -881534,7 +882424,7 @@ ], "tentative": { "RTCEncodedAudioFrame-clone.https.html": [ - "9f07713d4439c676fd7a5cc2f7d32af92a350e19", + "c93f8b3e54150b94334c33f30031d0b9f3afcd19", [ null, { @@ -881543,7 +882433,7 @@ ] ], "RTCEncodedAudioFrame-metadata.https.html": [ - "435e1c067834fe2050fd5c510f95516c5e6aeb61", + "df4577c5614a82afd7b02d988d0f15caa3b9e848", [ null, { @@ -927337,7 +928227,7 @@ ] ], "user_contexts.py": [ - "64c261637342868e90ee67a2332503e00fa96b0d", + "fa1e748d6b2a91addeb6c0569ebd271c00865fb8", [ null, {} @@ -927417,28 +928307,35 @@ "emulation": { "set_geolocation_override": { "contexts.py": [ - "8a0e43475da52609a362bf5f3e0853ceb2a9725f", + "068bb804e73bd06e2d901363280792d479cfb38f", [ null, {} ] ], "coordinates.py": [ - "b23354e9e42ef1bdd9d6684d11b782ce15f4eb8c", + "ea4fe643c3ecfc6e3609172706b51df17456e2e7", + [ + null, + {} + ] + ], + "error.py": [ + "27c23dc7811458feb7cd43c8a286c83809d9d2d0", [ null, {} ] ], "invalid.py": [ - "e804848e0be6c483340dcd8b3b4def10dddedce9", + "2334d37517df00cde8f845e7ec6412d1570e405b", [ null, {} ] ], "user_contexts.py": [ - "008dee5698de43a3371c1f7d697bb69014cf1b65", + "b3038bb59587b661e75945c5b8eeb4e943c72d9e", [ null, {} diff --git a/tests/wpt/meta/WebCryptoAPI/idlharness.https.any.js.ini b/tests/wpt/meta/WebCryptoAPI/idlharness.https.any.js.ini deleted file mode 100644 index 88bb87548ec..00000000000 --- a/tests/wpt/meta/WebCryptoAPI/idlharness.https.any.js.ini +++ /dev/null @@ -1,8 +0,0 @@ -[idlharness.https.any.html] - [idl_test setup] - expected: FAIL - - -[idlharness.https.any.worker.html] - [idl_test setup] - expected: FAIL diff --git a/tests/wpt/meta/__dir__.ini b/tests/wpt/meta/__dir__.ini index 5d513146b3f..057fb932df2 100644 --- a/tests/wpt/meta/__dir__.ini +++ b/tests/wpt/meta/__dir__.ini @@ -1,4 +1,5 @@ prefs: [ + "dom_serviceworker_enabled:true", "dom_testutils_enabled:true", "dom_urlpattern_enabled:true", ] diff --git a/tests/wpt/meta/clipboard-apis/idlharness.https.window.js.ini b/tests/wpt/meta/clipboard-apis/idlharness.https.window.js.ini index 98bb3d8b303..e648b62a314 100644 --- a/tests/wpt/meta/clipboard-apis/idlharness.https.window.js.ini +++ b/tests/wpt/meta/clipboard-apis/idlharness.https.window.js.ini @@ -8,9 +8,6 @@ [Clipboard interface: operation read(optional ClipboardUnsanitizedFormats)] expected: FAIL - [Clipboard interface: operation readText()] - expected: FAIL - [Clipboard interface: operation write(ClipboardItems)] expected: FAIL @@ -20,9 +17,6 @@ [Clipboard interface: calling read(optional ClipboardUnsanitizedFormats) on navigator.clipboard with too few arguments must throw TypeError] expected: FAIL - [Clipboard interface: navigator.clipboard must inherit property "readText()" with the proper type] - expected: FAIL - [Clipboard interface: navigator.clipboard must inherit property "write(ClipboardItems)" with the proper type] expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/default-src/default-src-sri_hash.sub.html.ini b/tests/wpt/meta/content-security-policy/default-src/default-src-sri_hash.sub.html.ini deleted file mode 100644 index ee237b70bc4..00000000000 --- a/tests/wpt/meta/content-security-policy/default-src/default-src-sri_hash.sub.html.ini +++ /dev/null @@ -1,12 +0,0 @@ -[default-src-sri_hash.sub.html] - [multiple matching integrity] - expected: FAIL - - [matching integrity] - expected: FAIL - - [matching integrity (case-insensitive algorithm)] - expected: FAIL - - [matching plus unsupported integrity] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.http.html.ini b/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.http.html.ini index e29f4dd5d4e..5891a18681e 100644 --- a/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.http.html.ini +++ b/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.http.html.ini @@ -1,13 +1,4 @@ [script-tag.http.html] - [Content Security Policy: Expects blocked for script-tag to cross-http origin and keep-origin redirection from http context.: securitypolicyviolation] - expected: FAIL - - [Content Security Policy: Expects blocked for script-tag to cross-http origin and no-redirect redirection from http context.: securitypolicyviolation] - expected: FAIL - - [Content Security Policy: Expects blocked for script-tag to cross-http origin and swap-origin redirection from http context.: securitypolicyviolation] - expected: FAIL - [Content Security Policy: Expects blocked for script-tag to same-http origin and swap-origin redirection from http context.] expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.https.html.ini b/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.https.html.ini index bbe30519d0b..699a0dd6238 100644 --- a/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.https.html.ini +++ b/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.https.html.ini @@ -1,13 +1,4 @@ [script-tag.https.html] - [Content Security Policy: Expects blocked for script-tag to cross-https origin and keep-origin redirection from https context.: securitypolicyviolation] - expected: FAIL - - [Content Security Policy: Expects blocked for script-tag to cross-https origin and no-redirect redirection from https context.: securitypolicyviolation] - expected: FAIL - - [Content Security Policy: Expects blocked for script-tag to cross-https origin and swap-origin redirection from https context.: securitypolicyviolation] - expected: FAIL - [Content Security Policy: Expects blocked for script-tag to same-https origin and swap-origin redirection from https context.] expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.http.html.ini b/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.http.html.ini index e29f4dd5d4e..5891a18681e 100644 --- a/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.http.html.ini +++ b/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.http.html.ini @@ -1,13 +1,4 @@ [script-tag.http.html] - [Content Security Policy: Expects blocked for script-tag to cross-http origin and keep-origin redirection from http context.: securitypolicyviolation] - expected: FAIL - - [Content Security Policy: Expects blocked for script-tag to cross-http origin and no-redirect redirection from http context.: securitypolicyviolation] - expected: FAIL - - [Content Security Policy: Expects blocked for script-tag to cross-http origin and swap-origin redirection from http context.: securitypolicyviolation] - expected: FAIL - [Content Security Policy: Expects blocked for script-tag to same-http origin and swap-origin redirection from http context.] expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.https.html.ini b/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.https.html.ini index bbe30519d0b..699a0dd6238 100644 --- a/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.https.html.ini +++ b/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.https.html.ini @@ -1,13 +1,4 @@ [script-tag.https.html] - [Content Security Policy: Expects blocked for script-tag to cross-https origin and keep-origin redirection from https context.: securitypolicyviolation] - expected: FAIL - - [Content Security Policy: Expects blocked for script-tag to cross-https origin and no-redirect redirection from https context.: securitypolicyviolation] - expected: FAIL - - [Content Security Policy: Expects blocked for script-tag to cross-https origin and swap-origin redirection from https context.: securitypolicyviolation] - expected: FAIL - [Content Security Policy: Expects blocked for script-tag to same-https origin and swap-origin redirection from https context.] expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/navigation/to-javascript-url-script-src.html.ini b/tests/wpt/meta/content-security-policy/navigation/to-javascript-url-script-src.html.ini deleted file mode 100644 index 213ed2d4692..00000000000 --- a/tests/wpt/meta/content-security-policy/navigation/to-javascript-url-script-src.html.ini +++ /dev/null @@ -1,13 +0,0 @@ -[to-javascript-url-script-src.html] - expected: TIMEOUT - [<iframe src='javascript:'> blocked without 'unsafe-inline'.] - expected: TIMEOUT - - [<iframe> navigated to 'javascript:' blocked without 'unsafe-inline'.] - expected: NOTRUN - - [<iframe src='...'> with 'unsafe-inline' navigated to 'javascript:' blocked in this document] - expected: NOTRUN - - [<iframe src='...'> without 'unsafe-inline' navigated to 'javascript:' blocked in this document.] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/nonce-hiding/nonces.html.ini b/tests/wpt/meta/content-security-policy/nonce-hiding/nonces.html.ini deleted file mode 100644 index dbf3c1db85e..00000000000 --- a/tests/wpt/meta/content-security-policy/nonce-hiding/nonces.html.ini +++ /dev/null @@ -1,24 +0,0 @@ -[nonces.html] - [Basic nonce tests for meh in HTML namespace] - expected: FAIL - - [Basic nonce tests for div in HTML namespace] - expected: FAIL - - [Basic nonce tests for script in HTML namespace] - expected: FAIL - - [Basic nonce tests for meh in SVG namespace] - expected: FAIL - - [Basic nonce tests for svg in SVG namespace] - expected: FAIL - - [Basic nonce tests for script in SVG namespace] - expected: FAIL - - [Basic nonce tests for style in HTML namespace] - expected: FAIL - - [Basic nonce tests for link in HTML namespace] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/nonce-hiding/script-nonces-hidden-meta.sub.html.ini b/tests/wpt/meta/content-security-policy/nonce-hiding/script-nonces-hidden-meta.sub.html.ini index 7268cf08bda..94d7bf64166 100644 --- a/tests/wpt/meta/content-security-policy/nonce-hiding/script-nonces-hidden-meta.sub.html.ini +++ b/tests/wpt/meta/content-security-policy/nonce-hiding/script-nonces-hidden-meta.sub.html.ini @@ -1,6 +1,3 @@ [script-nonces-hidden-meta.sub.html] - [Writing 'nonce' IDL attribute.] - expected: FAIL - [createElement.nonce.] expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/nonce-hiding/script-nonces-hidden.html.ini b/tests/wpt/meta/content-security-policy/nonce-hiding/script-nonces-hidden.html.ini index 8b687e4ecfb..e8896cde9c4 100644 --- a/tests/wpt/meta/content-security-policy/nonce-hiding/script-nonces-hidden.html.ini +++ b/tests/wpt/meta/content-security-policy/nonce-hiding/script-nonces-hidden.html.ini @@ -1,30 +1,3 @@ [script-nonces-hidden.html] - [Reading 'nonce' content attribute and IDL attribute.] - expected: FAIL - - [Cloned node retains nonce.] - expected: FAIL - - [Cloned node retains nonce when inserted.] - expected: FAIL - - [Writing 'nonce' IDL attribute.] - expected: FAIL - - [Document-written script's nonce value.] - expected: FAIL - [createElement.nonce.] expected: FAIL - - [setAttribute('nonce') overwrites '.nonce' upon insertion.] - expected: FAIL - - [createElement.setAttribute.] - expected: FAIL - - [Custom elements expose the correct events.] - expected: FAIL - - [Nonces don't leak via CSS side-channels.] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/nonce-hiding/svgscript-nonces-hidden-meta.sub.html.ini b/tests/wpt/meta/content-security-policy/nonce-hiding/svgscript-nonces-hidden-meta.sub.html.ini index 14a7cfae7aa..54fff6e8d97 100644 --- a/tests/wpt/meta/content-security-policy/nonce-hiding/svgscript-nonces-hidden-meta.sub.html.ini +++ b/tests/wpt/meta/content-security-policy/nonce-hiding/svgscript-nonces-hidden-meta.sub.html.ini @@ -2,9 +2,3 @@ expected: TIMEOUT [Document-written script executes.] expected: NOTRUN - - [createElement.nonce.] - expected: FAIL - - [Writing 'nonce' IDL attribute.] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/nonce-hiding/svgscript-nonces-hidden.html.ini b/tests/wpt/meta/content-security-policy/nonce-hiding/svgscript-nonces-hidden.html.ini index 77270d6f4a2..277bef043c8 100644 --- a/tests/wpt/meta/content-security-policy/nonce-hiding/svgscript-nonces-hidden.html.ini +++ b/tests/wpt/meta/content-security-policy/nonce-hiding/svgscript-nonces-hidden.html.ini @@ -1,22 +1,4 @@ [svgscript-nonces-hidden.html] expected: TIMEOUT - [Reading 'nonce' content attribute and IDL attribute.] - expected: FAIL - - [Cloned node retains nonce.] - expected: FAIL - - [Cloned node retains nonce when inserted.] - expected: FAIL - [Document-written script executes.] expected: NOTRUN - - [createElement.nonce.] - expected: FAIL - - [createElement.setAttribute.] - expected: FAIL - - [Writing 'nonce' IDL attribute.] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/reporting/report-clips-sample.https.html.ini b/tests/wpt/meta/content-security-policy/reporting/report-clips-sample.https.html.ini index 783a58399e0..282ea4398ee 100644 --- a/tests/wpt/meta/content-security-policy/reporting/report-clips-sample.https.html.ini +++ b/tests/wpt/meta/content-security-policy/reporting/report-clips-sample.https.html.ini @@ -16,6 +16,3 @@ [AsyncGenerator Function constructor is also clipped.] expected: FAIL - - [Trusted Types violation sample is clipped to 40 characters excluded the sink name.] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/reporting/report-original-url.sub.html.ini b/tests/wpt/meta/content-security-policy/reporting/report-original-url.sub.html.ini index 9d64a7e6bc4..66a2ee93f3b 100644 --- a/tests/wpt/meta/content-security-policy/reporting/report-original-url.sub.html.ini +++ b/tests/wpt/meta/content-security-policy/reporting/report-original-url.sub.html.ini @@ -1,11 +1,5 @@ [report-original-url.sub.html] expected: TIMEOUT - [Direct block, same-origin = full URL in report] - expected: TIMEOUT - - [Direct block, cross-origin = full URL in report] - expected: TIMEOUT - [Block after redirect, same-origin = original URL in report] expected: TIMEOUT diff --git a/tests/wpt/meta/content-security-policy/script-src/script-src-report-only-policy-works-with-hash-policy.html.ini b/tests/wpt/meta/content-security-policy/script-src/script-src-report-only-policy-works-with-hash-policy.html.ini deleted file mode 100644 index 31bfeae4a12..00000000000 --- a/tests/wpt/meta/content-security-policy/script-src/script-src-report-only-policy-works-with-hash-policy.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[script-src-report-only-policy-works-with-hash-policy.html] - expected: TIMEOUT - [Test that the securitypolicyviolation event is fired] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/script-src/script-src-sri_hash.sub.html.ini b/tests/wpt/meta/content-security-policy/script-src/script-src-sri_hash.sub.html.ini deleted file mode 100644 index 3324bf91bd7..00000000000 --- a/tests/wpt/meta/content-security-policy/script-src/script-src-sri_hash.sub.html.ini +++ /dev/null @@ -1,12 +0,0 @@ -[script-src-sri_hash.sub.html] - [multiple matching integrity] - expected: FAIL - - [matching integrity] - expected: FAIL - - [matching integrity (case-insensitive algorithm)] - expected: FAIL - - [matching plus unsupported integrity] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/script-src/scripthash-case-insensitive.sub.html.ini b/tests/wpt/meta/content-security-policy/script-src/scripthash-case-insensitive.sub.html.ini deleted file mode 100644 index 19b7395760a..00000000000 --- a/tests/wpt/meta/content-security-policy/script-src/scripthash-case-insensitive.sub.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[scripthash-case-insensitive.sub.html] - [Expecting alerts: ["PASS (1/6)","PASS (2/6)","PASS (3/6)","PASS (4/6)","PASS (5/6)","PASS (6/6)"\]] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/script-src/scriptnonce-basic-blocked.sub.html.ini b/tests/wpt/meta/content-security-policy/script-src/scriptnonce-basic-blocked.sub.html.ini deleted file mode 100644 index dc8de328080..00000000000 --- a/tests/wpt/meta/content-security-policy/script-src/scriptnonce-basic-blocked.sub.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[scriptnonce-basic-blocked.sub.html] - [Expecting alerts: ["PASS (closely-quoted nonce)","PASS (nonce w/whitespace)", "violated-directive=script-src-elem", "violated-directive=script-src-elem", "violated-directive=script-src-elem"\]] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/blockeduri-eval.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/blockeduri-eval.html.ini deleted file mode 100644 index bebd42f2743..00000000000 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/blockeduri-eval.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[blockeduri-eval.html] - expected: TIMEOUT - [Eval violations have a blockedURI of 'eval'] - expected: TIMEOUT diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/blockeduri-inline.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/blockeduri-inline.html.ini index 9c191e43078..3d0febce2b5 100644 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/blockeduri-inline.html.ini +++ b/tests/wpt/meta/content-security-policy/securitypolicyviolation/blockeduri-inline.html.ini @@ -1,4 +1,3 @@ [blockeduri-inline.html] - expected: TIMEOUT [Inline violations have a blockedURI of 'inline'] - expected: TIMEOUT + expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/blockeduri-ws-wss-scheme.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/blockeduri-ws-wss-scheme.html.ini index 6ebb357445f..14fa5353a94 100644 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/blockeduri-ws-wss-scheme.html.ini +++ b/tests/wpt/meta/content-security-policy/securitypolicyviolation/blockeduri-ws-wss-scheme.html.ini @@ -1,13 +1,3 @@ [blockeduri-ws-wss-scheme.html] - expected: TIMEOUT - [ws] - expected: FAIL - - [wss] - expected: FAIL - - [cross-origin] - expected: FAIL - [redirect] - expected: TIMEOUT + expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/linenumber.tentative.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/linenumber.tentative.html.ini index fed78a0aa49..e8114229ab9 100644 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/linenumber.tentative.html.ini +++ b/tests/wpt/meta/content-security-policy/securitypolicyviolation/linenumber.tentative.html.ini @@ -1,4 +1,3 @@ [linenumber.tentative.html] - expected: TIMEOUT [linenumber] - expected: NOTRUN + expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/script-sample-no-opt-in.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/script-sample-no-opt-in.html.ini index b12f81377d1..409022079e0 100644 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/script-sample-no-opt-in.html.ini +++ b/tests/wpt/meta/content-security-policy/securitypolicyviolation/script-sample-no-opt-in.html.ini @@ -1,13 +1,7 @@ [script-sample-no-opt-in.html] expected: TIMEOUT - [Inline script should not have a sample.] - expected: TIMEOUT - - [Inline event handlers should not have a sample.] - expected: TIMEOUT - [JavaScript URLs in iframes should not have a sample.] expected: TIMEOUT - [eval()-alikes should not have a sample.] + [Inline event handlers should not have a sample.] expected: TIMEOUT diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/script-sample.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/script-sample.html.ini index f4c315396f6..8723775f27e 100644 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/script-sample.html.ini +++ b/tests/wpt/meta/content-security-policy/securitypolicyviolation/script-sample.html.ini @@ -1,19 +1,7 @@ [script-sample.html] expected: TIMEOUT - [Inline script should have a sample.] - expected: TIMEOUT - - [Inline event handlers should have a sample.] - expected: TIMEOUT - [JavaScript URLs in iframes should have a sample.] expected: TIMEOUT - [eval() should have a sample.] - expected: TIMEOUT - - [setInterval() should have a sample.] - expected: TIMEOUT - - [setTimeout() should have a sample.] + [Inline event handlers should have a sample.] expected: TIMEOUT diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/source-file-blob-scheme.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/source-file-blob-scheme.html.ini index 03d164a4050..514f91961e3 100644 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/source-file-blob-scheme.html.ini +++ b/tests/wpt/meta/content-security-policy/securitypolicyviolation/source-file-blob-scheme.html.ini @@ -1,4 +1,3 @@ [source-file-blob-scheme.html] - expected: TIMEOUT [Violations from data:-URL scripts have a sourceFile of 'blob'] - expected: TIMEOUT + expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/source-file-data-scheme.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/source-file-data-scheme.html.ini index 387a7e2ff98..ed4dba7d3c3 100644 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/source-file-data-scheme.html.ini +++ b/tests/wpt/meta/content-security-policy/securitypolicyviolation/source-file-data-scheme.html.ini @@ -1,4 +1,3 @@ [source-file-data-scheme.html] - expected: TIMEOUT [Violations from data:-URL scripts have a sourceFile of 'data'] - expected: TIMEOUT + expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/style-sample-no-opt-in.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/style-sample-no-opt-in.html.ini index eb10ad61b2c..ef89e7cb1a9 100644 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/style-sample-no-opt-in.html.ini +++ b/tests/wpt/meta/content-security-policy/securitypolicyviolation/style-sample-no-opt-in.html.ini @@ -1,7 +1,4 @@ [style-sample-no-opt-in.html] expected: TIMEOUT - [Inline style blocks should not have a sample.] - expected: TIMEOUT - [Inline style attributes should not have a sample.] expected: TIMEOUT diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/style-sample.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/style-sample.html.ini index 460e21bd6cd..63e278182d8 100644 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/style-sample.html.ini +++ b/tests/wpt/meta/content-security-policy/securitypolicyviolation/style-sample.html.ini @@ -1,7 +1,4 @@ [style-sample.html] expected: TIMEOUT - [Inline style blocks should have a sample.] - expected: TIMEOUT - [Inline style attributes should have a sample.] expected: TIMEOUT diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/targeting.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/targeting.html.ini index 88da5e48238..952c5185dd8 100644 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/targeting.html.ini +++ b/tests/wpt/meta/content-security-policy/securitypolicyviolation/targeting.html.ini @@ -4,13 +4,10 @@ expected: NOTRUN [Inline violations target the right element.] - expected: TIMEOUT + expected: FAIL [Correct targeting inside shadow tree (inline handler).] expected: TIMEOUT - [Correct targeting inside shadow tree (style).] - expected: TIMEOUT - [Elements created in this document, but pushed into a same-origin frame trigger on that frame's document, not on this frame's document.] expected: TIMEOUT diff --git a/tests/wpt/meta/content-security-policy/style-src-attr-elem/style-src-attr-blocked-src-allowed.html.ini b/tests/wpt/meta/content-security-policy/style-src-attr-elem/style-src-attr-blocked-src-allowed.html.ini deleted file mode 100644 index a5cf5faf238..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src-attr-elem/style-src-attr-blocked-src-allowed.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[style-src-attr-blocked-src-allowed.html] - expected: TIMEOUT - [Should fire a security policy violation event] - expected: NOTRUN - - [The attribute style should not be applied] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/style-src-attr-elem/style-src-elem-allowed-attr-blocked.html.ini b/tests/wpt/meta/content-security-policy/style-src-attr-elem/style-src-elem-allowed-attr-blocked.html.ini deleted file mode 100644 index 979fc151f38..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src-attr-elem/style-src-elem-allowed-attr-blocked.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[style-src-elem-allowed-attr-blocked.html] - expected: TIMEOUT - [Should fire a security policy violation for the attribute] - expected: NOTRUN - - [The attribute style should not be applied and the inline style should be applied] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/style-src-attr-elem/style-src-elem-blocked-attr-allowed.html.ini b/tests/wpt/meta/content-security-policy/style-src-attr-elem/style-src-elem-blocked-attr-allowed.html.ini deleted file mode 100644 index c67fec5245c..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src-attr-elem/style-src-elem-blocked-attr-allowed.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[style-src-elem-blocked-attr-allowed.html] - expected: TIMEOUT - [Should fire a security policy violation for the inline block] - expected: NOTRUN - - [The inline style should not be applied and the attribute style should be applied] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/style-src-attr-elem/style-src-elem-blocked-src-allowed.html.ini b/tests/wpt/meta/content-security-policy/style-src-attr-elem/style-src-elem-blocked-src-allowed.html.ini deleted file mode 100644 index 06439a8cc0a..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src-attr-elem/style-src-elem-blocked-src-allowed.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[style-src-elem-blocked-src-allowed.html] - expected: TIMEOUT - [Should fire a security policy violation event] - expected: NOTRUN - - [The inline style should not be applied] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/style-src/injected-inline-style-blocked.sub.html.ini b/tests/wpt/meta/content-security-policy/style-src/injected-inline-style-blocked.sub.html.ini deleted file mode 100644 index 06132f67bfc..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/injected-inline-style-blocked.sub.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[injected-inline-style-blocked.sub.html] - [Expecting logs: ["violated-directive=style-src-elem","violated-directive=style-src-elem","PASS"\]] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/style-src/inline-style-allowed-while-cloning-objects.sub.html.ini b/tests/wpt/meta/content-security-policy/style-src/inline-style-allowed-while-cloning-objects.sub.html.ini index 0c8111987c0..c99d7bd7844 100644 --- a/tests/wpt/meta/content-security-policy/style-src/inline-style-allowed-while-cloning-objects.sub.html.ini +++ b/tests/wpt/meta/content-security-policy/style-src/inline-style-allowed-while-cloning-objects.sub.html.ini @@ -1,13 +1,36 @@ [inline-style-allowed-while-cloning-objects.sub.html] - expected: TIMEOUT - [Test that violation report event was fired] - expected: NOTRUN + [non-HTML namespace] + expected: FAIL - [inline-style-allowed-while-cloning-objects 12] + [inline-style-allowed-while-cloning-objects 1] expected: FAIL - [inline-style-allowed-while-cloning-objects 14] + [inline-style-allowed-while-cloning-objects 3] expected: FAIL - [non-HTML namespace] + [inline-style-allowed-while-cloning-objects 5] + expected: FAIL + + [inline-style-allowed-while-cloning-objects 7] + expected: FAIL + + [inline-style-allowed-while-cloning-objects 8] + expected: FAIL + + [inline-style-allowed-while-cloning-objects 9] + expected: FAIL + + [inline-style-allowed-while-cloning-objects 10] + expected: FAIL + + [inline-style-allowed-while-cloning-objects 11] + expected: FAIL + + [inline-style-allowed-while-cloning-objects 17] + expected: FAIL + + [inline-style-allowed-while-cloning-objects 18] + expected: FAIL + + [inline-style-allowed-while-cloning-objects 19] expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/style-src/inline-style-attribute-blocked.sub.html.ini b/tests/wpt/meta/content-security-policy/style-src/inline-style-attribute-blocked.sub.html.ini deleted file mode 100644 index 92f00acdffe..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/inline-style-attribute-blocked.sub.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[inline-style-attribute-blocked.sub.html] - [Expecting logs: ["violated-directive=style-src-attr","PASS"\]] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/style-src/inline-style-blocked.sub.html.ini b/tests/wpt/meta/content-security-policy/style-src/inline-style-blocked.sub.html.ini deleted file mode 100644 index 62bbd2f0e13..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/inline-style-blocked.sub.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[inline-style-blocked.sub.html] - expected: TIMEOUT - [Triggers securitypolicyviolation.] - expected: TIMEOUT - - [Inline style element is blocked by CSP.] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/style-src/style-src-hash-blocked.html.ini b/tests/wpt/meta/content-security-policy/style-src/style-src-hash-blocked.html.ini deleted file mode 100644 index a79f011aec3..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/style-src-hash-blocked.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[style-src-hash-blocked.html] - expected: TIMEOUT - [Should not load style that does not match hash] - expected: FAIL - - [Should fire a securitypolicyviolation event] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/style-src/style-src-imported-style-blocked.html.ini b/tests/wpt/meta/content-security-policy/style-src/style-src-imported-style-blocked.html.ini deleted file mode 100644 index fba57c0f24f..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/style-src-imported-style-blocked.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[style-src-imported-style-blocked.html] - expected: TIMEOUT - [Should fire a securitypolicyviolation event] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/style-src/style-src-injected-inline-style-blocked.html.ini b/tests/wpt/meta/content-security-policy/style-src/style-src-injected-inline-style-blocked.html.ini deleted file mode 100644 index bb5c48df1b0..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/style-src-injected-inline-style-blocked.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[style-src-injected-inline-style-blocked.html] - expected: TIMEOUT - [Injected style attributes should not be applied] - expected: FAIL - - [Should fire a securitypolicyviolation event] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/style-src/style-src-injected-stylesheet-blocked.sub.html.ini b/tests/wpt/meta/content-security-policy/style-src/style-src-injected-stylesheet-blocked.sub.html.ini deleted file mode 100644 index 0b431bab548..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/style-src-injected-stylesheet-blocked.sub.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[style-src-injected-stylesheet-blocked.sub.html] - expected: TIMEOUT - [Should fire a securitypolicyviolation event] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-attribute-blocked.html.ini b/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-attribute-blocked.html.ini deleted file mode 100644 index d910f28e56a..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-attribute-blocked.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[style-src-inline-style-attribute-blocked.html] - expected: TIMEOUT - [Inline style attribute should not be applied without 'unsafe-inline'] - expected: FAIL - - [Should fire a securitypolicyviolation event] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-blocked.html.ini b/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-blocked.html.ini deleted file mode 100644 index 33ee0df35af..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-blocked.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[style-src-inline-style-blocked.html] - expected: TIMEOUT - [Inline style element should not load without 'unsafe-inline'] - expected: FAIL - - [Should fire a securitypolicyviolation event] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-nonce-blocked-error-event.html.ini b/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-nonce-blocked-error-event.html.ini index 63a1c9b6240..eb2f1c46fbb 100644 --- a/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-nonce-blocked-error-event.html.ini +++ b/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-nonce-blocked-error-event.html.ini @@ -1,7 +1,4 @@ [style-src-inline-style-nonce-blocked-error-event.html] expected: TIMEOUT - [Should fire a securitypolicyviolation event] - expected: NOTRUN - [Test that paragraph remains unmodified and error events received.] expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-nonce-blocked.html.ini b/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-nonce-blocked.html.ini deleted file mode 100644 index df0fb590691..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-nonce-blocked.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[style-src-inline-style-nonce-blocked.html] - expected: TIMEOUT - [Should not load inline style element with invalid nonce] - expected: FAIL - - [Should fire a securitypolicyviolation event] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/style-src/style-src-none-blocked.html.ini b/tests/wpt/meta/content-security-policy/style-src/style-src-none-blocked.html.ini deleted file mode 100644 index c3014b37c31..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/style-src-none-blocked.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[style-src-none-blocked.html] - expected: TIMEOUT - [Should fire a securitypolicyviolation event] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/style-src/style-src-stylesheet-nonce-blocked.html.ini b/tests/wpt/meta/content-security-policy/style-src/style-src-stylesheet-nonce-blocked.html.ini deleted file mode 100644 index b8645f13da7..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/style-src-stylesheet-nonce-blocked.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[style-src-stylesheet-nonce-blocked.html] - expected: TIMEOUT - [Should fire a securitypolicyviolation event] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/style-src/stylehash-basic-blocked.sub.html.ini b/tests/wpt/meta/content-security-policy/style-src/stylehash-basic-blocked.sub.html.ini deleted file mode 100644 index 10375077b42..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/stylehash-basic-blocked.sub.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[stylehash-basic-blocked.sub.html] - [Expecting alerts: ["PASS: The 'p' element's text is green, which means the style was correctly applied.", "violated-directive=style-src-elem"\]] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/style-src/stylenonce-allowed.sub.html.ini b/tests/wpt/meta/content-security-policy/style-src/stylenonce-allowed.sub.html.ini deleted file mode 100644 index ae2f83c41d8..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/stylenonce-allowed.sub.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[stylenonce-allowed.sub.html] - expected: TIMEOUT - [Should fire securitypolicyviolation] - expected: NOTRUN - - [stylenonce-allowed] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/style-src/stylenonce-blocked.sub.html.ini b/tests/wpt/meta/content-security-policy/style-src/stylenonce-blocked.sub.html.ini deleted file mode 100644 index 97a86a69eea..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/stylenonce-blocked.sub.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[stylenonce-blocked.sub.html] - expected: TIMEOUT - [Should fire securitypolicyviolation] - expected: NOTRUN - - [stylenonce-blocked] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/unsafe-hashes/style_attribute_denied_missing_unsafe_hashes.html.ini b/tests/wpt/meta/content-security-policy/unsafe-hashes/style_attribute_denied_missing_unsafe_hashes.html.ini deleted file mode 100644 index 26dc98e8f62..00000000000 --- a/tests/wpt/meta/content-security-policy/unsafe-hashes/style_attribute_denied_missing_unsafe_hashes.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[style_attribute_denied_missing_unsafe_hashes.html] - expected: TIMEOUT - [Test that the inline style attribute is blocked] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/unsafe-hashes/style_attribute_denied_wrong_hash.html.ini b/tests/wpt/meta/content-security-policy/unsafe-hashes/style_attribute_denied_wrong_hash.html.ini deleted file mode 100644 index 3031a4f6f77..00000000000 --- a/tests/wpt/meta/content-security-policy/unsafe-hashes/style_attribute_denied_wrong_hash.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[style_attribute_denied_wrong_hash.html] - expected: TIMEOUT - [Test that the inline style attribute is blocked] - expected: NOTRUN diff --git a/tests/wpt/meta/css/css-flexbox/flex-container-max-content-002.tentative.html.ini b/tests/wpt/meta/css/css-flexbox/flex-container-max-content-002.tentative.html.ini new file mode 100644 index 00000000000..8b9bd7fdf97 --- /dev/null +++ b/tests/wpt/meta/css/css-flexbox/flex-container-max-content-002.tentative.html.ini @@ -0,0 +1,12 @@ +[flex-container-max-content-002.tentative.html] + [.flex 2] + expected: FAIL + + [.flex 3] + expected: FAIL + + [.flex 5] + expected: FAIL + + [.flex 6] + expected: FAIL diff --git a/tests/wpt/meta/css/css-flexbox/flex-container-min-content-002.tentative.html.ini b/tests/wpt/meta/css/css-flexbox/flex-container-min-content-002.tentative.html.ini new file mode 100644 index 00000000000..408a853b460 --- /dev/null +++ b/tests/wpt/meta/css/css-flexbox/flex-container-min-content-002.tentative.html.ini @@ -0,0 +1,21 @@ +[flex-container-min-content-002.tentative.html] + [.flex 2] + expected: FAIL + + [.flex 3] + expected: FAIL + + [.flex 5] + expected: FAIL + + [.flex 6] + expected: FAIL + + [.flex 13] + expected: FAIL + + [.flex 14] + expected: FAIL + + [.flex 15] + expected: FAIL diff --git a/tests/wpt/meta/css/css-gaps/flex/flex-gap-decorations-022.html.ini b/tests/wpt/meta/css/css-gaps/flex/flex-gap-decorations-022.html.ini new file mode 100644 index 00000000000..1d8edacc4e1 --- /dev/null +++ b/tests/wpt/meta/css/css-gaps/flex/flex-gap-decorations-022.html.ini @@ -0,0 +1,2 @@ +[flex-gap-decorations-022.html] + expected: FAIL diff --git a/tests/wpt/meta/css/css-gaps/grid/grid-gap-decorations-029.html.ini b/tests/wpt/meta/css/css-gaps/grid/grid-gap-decorations-029.html.ini new file mode 100644 index 00000000000..6198f1bf534 --- /dev/null +++ b/tests/wpt/meta/css/css-gaps/grid/grid-gap-decorations-029.html.ini @@ -0,0 +1,2 @@ +[grid-gap-decorations-029.html] + expected: FAIL diff --git a/tests/wpt/meta/css/css-gaps/grid/grid-gap-decorations-030.html.ini b/tests/wpt/meta/css/css-gaps/grid/grid-gap-decorations-030.html.ini new file mode 100644 index 00000000000..76e055a6a0a --- /dev/null +++ b/tests/wpt/meta/css/css-gaps/grid/grid-gap-decorations-030.html.ini @@ -0,0 +1,2 @@ +[grid-gap-decorations-030.html] + expected: FAIL diff --git a/tests/wpt/meta/css/css-gaps/grid/grid-gap-decorations-031.html.ini b/tests/wpt/meta/css/css-gaps/grid/grid-gap-decorations-031.html.ini new file mode 100644 index 00000000000..d9765648ea7 --- /dev/null +++ b/tests/wpt/meta/css/css-gaps/grid/grid-gap-decorations-031.html.ini @@ -0,0 +1,2 @@ +[grid-gap-decorations-031.html] + expected: FAIL diff --git a/tests/wpt/meta/css/css-gaps/grid/grid-gap-decorations-032.html.ini b/tests/wpt/meta/css/css-gaps/grid/grid-gap-decorations-032.html.ini new file mode 100644 index 00000000000..1e5a6bc2a68 --- /dev/null +++ b/tests/wpt/meta/css/css-gaps/grid/grid-gap-decorations-032.html.ini @@ -0,0 +1,2 @@ +[grid-gap-decorations-032.html] + expected: FAIL diff --git a/tests/wpt/meta/css/css-gaps/grid/grid-gap-decorations-033.html.ini b/tests/wpt/meta/css/css-gaps/grid/grid-gap-decorations-033.html.ini new file mode 100644 index 00000000000..aaeff2efe72 --- /dev/null +++ b/tests/wpt/meta/css/css-gaps/grid/grid-gap-decorations-033.html.ini @@ -0,0 +1,2 @@ +[grid-gap-decorations-033.html] + expected: FAIL diff --git a/tests/wpt/meta/css/css-gaps/grid/grid-gap-decorations-38.html.ini b/tests/wpt/meta/css/css-gaps/grid/grid-gap-decorations-38.html.ini new file mode 100644 index 00000000000..eb1f89bfb6d --- /dev/null +++ b/tests/wpt/meta/css/css-gaps/grid/grid-gap-decorations-38.html.ini @@ -0,0 +1,2 @@ +[grid-gap-decorations-38.html] + expected: FAIL diff --git a/tests/wpt/meta/css/css-gaps/multicol/multicol-gap-decorations-017.html.ini b/tests/wpt/meta/css/css-gaps/multicol/multicol-gap-decorations-017.html.ini new file mode 100644 index 00000000000..b4d67fce3e3 --- /dev/null +++ b/tests/wpt/meta/css/css-gaps/multicol/multicol-gap-decorations-017.html.ini @@ -0,0 +1,2 @@ +[multicol-gap-decorations-017.html] + expected: FAIL diff --git a/tests/wpt/meta/css/css-gaps/parsing/gap-decorations-col-rule-width.html.ini b/tests/wpt/meta/css/css-gaps/parsing/gap-decorations-col-rule-width.html.ini new file mode 100644 index 00000000000..02d385579b9 --- /dev/null +++ b/tests/wpt/meta/css/css-gaps/parsing/gap-decorations-col-rule-width.html.ini @@ -0,0 +1,9 @@ +[gap-decorations-col-rule-width.html] + [`column-rule-width` should be `0px` when `column-rule-style` is `none` with single value] + expected: FAIL + + [`column-rule-width` should be as specified regardless of `column-rule-style` with multiple values] + expected: FAIL + + [`column-rule-width` should be as specified regardless of `column-rule-style` with multiple (repeat) values] + expected: FAIL diff --git a/tests/wpt/meta/css/css-images/linear-gradient-body-sibling-index.html.ini b/tests/wpt/meta/css/css-images/linear-gradient-body-sibling-index.html.ini new file mode 100644 index 00000000000..638b6c21891 --- /dev/null +++ b/tests/wpt/meta/css/css-images/linear-gradient-body-sibling-index.html.ini @@ -0,0 +1,2 @@ +[linear-gradient-body-sibling-index.html] + expected: FAIL diff --git a/tests/wpt/meta/css/css-images/linear-gradient-sibling-index.html.ini b/tests/wpt/meta/css/css-images/linear-gradient-sibling-index.html.ini new file mode 100644 index 00000000000..d81ff383b17 --- /dev/null +++ b/tests/wpt/meta/css/css-images/linear-gradient-sibling-index.html.ini @@ -0,0 +1,2 @@ +[linear-gradient-sibling-index.html] + expected: FAIL diff --git a/tests/wpt/meta/css/css-mixins/dashed-function-cycles.tentative.html.ini b/tests/wpt/meta/css/css-mixins/dashed-function-cycles.html.ini index e16c79da6ad..7faf9c2b2e4 100644 --- a/tests/wpt/meta/css/css-mixins/dashed-function-cycles.tentative.html.ini +++ b/tests/wpt/meta/css/css-mixins/dashed-function-cycles.html.ini @@ -1,4 +1,4 @@ -[dashed-function-cycles.tentative.html] +[dashed-function-cycles.html] [Local with self-cycle] expected: FAIL @@ -11,7 +11,10 @@ [Local shadowing cyclic property --x] expected: FAIL - [Local shadowing cyclic outer local --x ] + [Local shadowing cyclic outer local --x] + expected: FAIL + + [Argument shadowing cyclic outer local --x] expected: FAIL [Arguments shadowing cyclic properties] @@ -53,6 +56,9 @@ [Cycle through local, other function] expected: FAIL + [Cycle through local, other function, fallback in function] + expected: FAIL + [Cycle through various variables and other functions] expected: FAIL @@ -62,15 +68,6 @@ [Cyclic defaults] expected: FAIL - [Local shadowing cyclic outer local --x] - expected: FAIL - - [Argument shadowing cyclic outer local --x] - expected: FAIL - - [Cycle through local, other function, fallback in function] - expected: FAIL - [Cyclic outer --b shadows custom property] expected: FAIL diff --git a/tests/wpt/meta/css/css-transforms/transform-translate-background-001.html.ini b/tests/wpt/meta/css/css-transforms/transform-translate-background-001.html.ini deleted file mode 100644 index abb0cf51515..00000000000 --- a/tests/wpt/meta/css/css-transforms/transform-translate-background-001.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[transform-translate-background-001.html] - expected: FAIL diff --git a/tests/wpt/meta/css/css-transforms/transform-translate-background-002.html.ini b/tests/wpt/meta/css/css-transforms/transform-translate-background-002.html.ini deleted file mode 100644 index bd8e02d68f5..00000000000 --- a/tests/wpt/meta/css/css-transforms/transform-translate-background-002.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[transform-translate-background-002.html] - expected: FAIL diff --git a/tests/wpt/meta/css/css-values/tree-counting/sibling-function-descriptors.tentative.html.ini b/tests/wpt/meta/css/css-values/tree-counting/sibling-function-descriptors.tentative.html.ini index 287f1c19314..3c65d9bcf19 100644 --- a/tests/wpt/meta/css/css-values/tree-counting/sibling-function-descriptors.tentative.html.ini +++ b/tests/wpt/meta/css/css-values/tree-counting/sibling-function-descriptors.tentative.html.ini @@ -10,3 +10,15 @@ [sibling-count() should not be allowed in @font-face descriptors] expected: FAIL + + [sibling-index() should not be allowed in @font-palette-values descriptors] + expected: FAIL + + [sibling-count() should not be allowed in @font-palette-values descriptors] + expected: FAIL + + [sibling-index() should not be allowed in @counter-style descriptors] + expected: FAIL + + [sibling-count() should not be allowed in @counter-style descriptors] + expected: FAIL diff --git a/tests/wpt/meta/css/css-values/tree-counting/sibling-index-keyframe-length-value-dynamic.html.ini b/tests/wpt/meta/css/css-values/tree-counting/sibling-index-keyframe-length-value-dynamic.html.ini new file mode 100644 index 00000000000..f61a6205280 --- /dev/null +++ b/tests/wpt/meta/css/css-values/tree-counting/sibling-index-keyframe-length-value-dynamic.html.ini @@ -0,0 +1,6 @@ +[sibling-index-keyframe-length-value-dynamic.html] + [Initially, the sibling-index() is 2 for #target] + expected: FAIL + + [Removing a preceding sibling of #target reduces the sibling-index()] + expected: FAIL diff --git a/tests/wpt/meta/css/css-values/tree-counting/sibling-index-keyframe-value-dynamic.html.ini b/tests/wpt/meta/css/css-values/tree-counting/sibling-index-keyframe-value-dynamic.html.ini new file mode 100644 index 00000000000..df16c83f839 --- /dev/null +++ b/tests/wpt/meta/css/css-values/tree-counting/sibling-index-keyframe-value-dynamic.html.ini @@ -0,0 +1,6 @@ +[sibling-index-keyframe-value-dynamic.html] + [Initially, the sibling-index() is 3 for #target] + expected: FAIL + + [Removing a preceding sibling of #target reduces the sibling-index()] + expected: FAIL diff --git a/tests/wpt/meta/css/cssom-view/scrollIntoView-container.html.ini b/tests/wpt/meta/css/cssom-view/scrollIntoView-container.html.ini new file mode 100644 index 00000000000..0e9b4a98ac4 --- /dev/null +++ b/tests/wpt/meta/css/cssom-view/scrollIntoView-container.html.ini @@ -0,0 +1,15 @@ +[scrollIntoView-container.html] + [scrollIntoView() defaults to scrolling ancestors] + expected: FAIL + + [scrollIntoView({container: 'all'}) scrolls ancestors] + expected: FAIL + + [scrollIntoView({container: 'nearest'}) only scrolls nearest scroll container] + expected: FAIL + + [scrollIntoView({container: 'nearest'}) doesn't stop at itself] + expected: FAIL + + [scrollIntoView({container: 'nearest'}) doesn't propagate to outer frames] + expected: FAIL diff --git a/tests/wpt/meta/css/filter-effects/backdrop-filter-scale-transform.html.ini b/tests/wpt/meta/css/filter-effects/backdrop-filter-scale-transform.html.ini new file mode 100644 index 00000000000..128ff19f7aa --- /dev/null +++ b/tests/wpt/meta/css/filter-effects/backdrop-filter-scale-transform.html.ini @@ -0,0 +1,2 @@ +[backdrop-filter-scale-transform.html] + expected: FAIL diff --git a/tests/wpt/meta/css/geometry/DOMMatrix-001.html.ini b/tests/wpt/meta/css/geometry/DOMMatrix-001.html.ini new file mode 100644 index 00000000000..cb607530097 --- /dev/null +++ b/tests/wpt/meta/css/geometry/DOMMatrix-001.html.ini @@ -0,0 +1,6 @@ +[DOMMatrix-001.html] + [new DOMMatrix("scale(sign(1em))")] + expected: FAIL + + [new DOMMatrixReadOnly("scale(sign(1em))")] + expected: FAIL diff --git a/tests/wpt/meta/custom-elements/parser/serializing-html-fragments-customized-builtins.html.ini b/tests/wpt/meta/custom-elements/parser/serializing-html-fragments-customized-builtins.html.ini deleted file mode 100644 index 15f02f181e4..00000000000 --- a/tests/wpt/meta/custom-elements/parser/serializing-html-fragments-customized-builtins.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[serializing-html-fragments-customized-builtins.html] - ["is" value should be serialized if the custom element has no "is" content attribute] - expected: FAIL - - ["is" value should be serialized even for an undefined element] - expected: FAIL diff --git a/tests/wpt/meta/dom/events/webkit-animation-end-event.html.ini b/tests/wpt/meta/dom/events/webkit-animation-end-event.html.ini index 3369a3a2a49..5750c2948be 100644 --- a/tests/wpt/meta/dom/events/webkit-animation-end-event.html.ini +++ b/tests/wpt/meta/dom/events/webkit-animation-end-event.html.ini @@ -1,28 +1,22 @@ [webkit-animation-end-event.html] expected: TIMEOUT - [onanimationend and onwebkitanimationend are not aliases] - expected: FAIL - [dispatchEvent of a webkitAnimationEnd event does trigger a prefixed event handler or listener] expected: FAIL - [dispatchEvent of an animationend event does not trigger a prefixed event handler or listener] - expected: FAIL - [onwebkitanimationend event handler should trigger for an animation] - expected: FAIL + expected: TIMEOUT [onwebkitanimationend event handler should not trigger if an unprefixed event handler also exists] - expected: FAIL + expected: NOTRUN [onwebkitanimationend event handler should not trigger if an unprefixed listener also exists] - expected: FAIL + expected: NOTRUN [event types for prefixed and unprefixed animationend event handlers should be named appropriately] - expected: FAIL + expected: NOTRUN [webkitAnimationEnd event listener should trigger for an animation] - expected: TIMEOUT + expected: NOTRUN [webkitAnimationEnd event listener should not trigger if an unprefixed listener also exists] expected: NOTRUN diff --git a/tests/wpt/meta/dom/events/webkit-animation-iteration-event.html.ini b/tests/wpt/meta/dom/events/webkit-animation-iteration-event.html.ini index 880ef51e59d..233a98ed69b 100644 --- a/tests/wpt/meta/dom/events/webkit-animation-iteration-event.html.ini +++ b/tests/wpt/meta/dom/events/webkit-animation-iteration-event.html.ini @@ -1,28 +1,22 @@ [webkit-animation-iteration-event.html] expected: TIMEOUT - [onanimationiteration and onwebkitanimationiteration are not aliases] - expected: FAIL - [dispatchEvent of a webkitAnimationIteration event does trigger a prefixed event handler or listener] expected: FAIL - [dispatchEvent of an animationiteration event does not trigger a prefixed event handler or listener] - expected: FAIL - [onwebkitanimationiteration event handler should trigger for an animation] - expected: FAIL + expected: TIMEOUT [onwebkitanimationiteration event handler should not trigger if an unprefixed event handler also exists] - expected: FAIL + expected: NOTRUN [onwebkitanimationiteration event handler should not trigger if an unprefixed listener also exists] - expected: FAIL + expected: NOTRUN [event types for prefixed and unprefixed animationiteration event handlers should be named appropriately] - expected: FAIL + expected: NOTRUN [webkitAnimationIteration event listener should trigger for an animation] - expected: TIMEOUT + expected: NOTRUN [webkitAnimationIteration event listener should not trigger if an unprefixed listener also exists] expected: NOTRUN diff --git a/tests/wpt/meta/dom/events/webkit-animation-start-event.html.ini b/tests/wpt/meta/dom/events/webkit-animation-start-event.html.ini index ca7e2b96918..6e3e5febb82 100644 --- a/tests/wpt/meta/dom/events/webkit-animation-start-event.html.ini +++ b/tests/wpt/meta/dom/events/webkit-animation-start-event.html.ini @@ -1,4 +1,5 @@ [webkit-animation-start-event.html] + expected: TIMEOUT [onanimationstart and onwebkitanimationstart are not aliases] expected: FAIL @@ -8,32 +9,29 @@ [dispatchEvent of a webkitAnimationStart event does not trigger an unprefixed event handler or listener] expected: FAIL - [dispatchEvent of an animationstart event does not trigger a prefixed event handler or listener] - expected: FAIL - [onwebkitanimationstart event handler should trigger for an animation] - expected: FAIL + expected: TIMEOUT [onwebkitanimationstart event handler should not trigger if an unprefixed event handler also exists] - expected: FAIL + expected: NOTRUN [onwebkitanimationstart event handler should not trigger if an unprefixed listener also exists] - expected: FAIL + expected: NOTRUN [event types for prefixed and unprefixed animationstart event handlers should be named appropriately] - expected: FAIL + expected: NOTRUN [webkitAnimationStart event listener should trigger for an animation] - expected: FAIL + expected: NOTRUN [webkitAnimationStart event listener should not trigger if an unprefixed listener also exists] - expected: FAIL + expected: NOTRUN [webkitAnimationStart event listener should not trigger if an unprefixed event handler also exists] - expected: FAIL + expected: NOTRUN [event types for prefixed and unprefixed animationstart event listeners should be named appropriately] - expected: FAIL + expected: NOTRUN [webkitAnimationStart event listener is case sensitive] - expected: FAIL + expected: NOTRUN diff --git a/tests/wpt/meta/dom/events/webkit-transition-end-event.html.ini b/tests/wpt/meta/dom/events/webkit-transition-end-event.html.ini index b7fe8688a23..d1d6814c761 100644 --- a/tests/wpt/meta/dom/events/webkit-transition-end-event.html.ini +++ b/tests/wpt/meta/dom/events/webkit-transition-end-event.html.ini @@ -1,27 +1,15 @@ [webkit-transition-end-event.html] - [dispatchEvent of an transitionend event does not trigger a prefixed event handler or listener] - expected: FAIL - [onwebkittransitionend event handler should trigger for an animation] expected: FAIL - [onwebkittransitionend event handler should not trigger if an unprefixed listener also exists] - expected: FAIL - [event types for prefixed and unprefixed transitionend event handlers should be named appropriately] expected: FAIL [event types for prefixed and unprefixed transitionend event listeners should be named appropriately] expected: FAIL - [ontransitionend and onwebkittransitionend are not aliases] - expected: FAIL - [webkitTransitionEnd event listener should trigger for an animation] expected: FAIL [dispatchEvent of a webkitTransitionEnd event does trigger a prefixed event handler or listener] expected: FAIL - - [onwebkittransitionend event handler should not trigger if an unprefixed event handler also exists] - expected: FAIL diff --git a/tests/wpt/meta/fetch/http-cache/pragma-no-cache-with-cache-control.html.ini b/tests/wpt/meta/fetch/http-cache/pragma-no-cache-with-cache-control.html.ini new file mode 100644 index 00000000000..5e3ad18d789 --- /dev/null +++ b/tests/wpt/meta/fetch/http-cache/pragma-no-cache-with-cache-control.html.ini @@ -0,0 +1,3 @@ +[pragma-no-cache-with-cache-control.html] + [Response with Cache-Control: max-age=2592000, public and Pragma: no-cache should be cached] + expected: FAIL diff --git a/tests/wpt/meta/fetch/metadata/generated/css-font-face.https.sub.tentative.html.ini b/tests/wpt/meta/fetch/metadata/generated/css-font-face.https.sub.tentative.html.ini index a02fcf871ab..12a2314e747 100644 --- a/tests/wpt/meta/fetch/metadata/generated/css-font-face.https.sub.tentative.html.ini +++ b/tests/wpt/meta/fetch/metadata/generated/css-font-face.https.sub.tentative.html.ini @@ -52,3 +52,6 @@ [sec-fetch-storage-access - Cross-site] expected: FAIL + + [sec-fetch-user] + expected: FAIL diff --git a/tests/wpt/meta/fetch/metadata/generated/svg-image.https.sub.html.ini b/tests/wpt/meta/fetch/metadata/generated/svg-image.https.sub.html.ini index 5d85add7f82..1e547ae5f21 100644 --- a/tests/wpt/meta/fetch/metadata/generated/svg-image.https.sub.html.ini +++ b/tests/wpt/meta/fetch/metadata/generated/svg-image.https.sub.html.ini @@ -1,70 +1,69 @@ [svg-image.https.sub.html] - expected: TIMEOUT [sec-fetch-site - Same origin no attributes] - expected: TIMEOUT + expected: FAIL [sec-fetch-site - Cross-site no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-site - Same site no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-site - Same-Origin -> Cross-Site -> Same-Origin redirect no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-site - Same-Origin -> Same-Site -> Same-Origin redirect no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-site - Cross-Site -> Same Origin no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-site - Cross-Site -> Same-Site no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-site - Cross-Site -> Cross-Site no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-site - Same-Origin -> Same Origin no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-site - Same-Origin -> Same-Site no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-site - Same-Origin -> Cross-Site no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-site - Same-Site -> Same Origin no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-site - Same-Site -> Same-Site no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-site - Same-Site -> Cross-Site no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-site - HTTPS downgrade-upgrade no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-mode no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-mode attributes: crossorigin] - expected: NOTRUN + expected: FAIL [sec-fetch-mode attributes: crossorigin=anonymous] - expected: NOTRUN + expected: FAIL [sec-fetch-mode attributes: crossorigin=use-credentials] - expected: NOTRUN + expected: FAIL [sec-fetch-dest no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-user no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-storage-access - Cross-site no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-storage-access - Same site no attributes] - expected: NOTRUN + expected: FAIL diff --git a/tests/wpt/meta/fetch/metadata/generated/svg-image.sub.html.ini b/tests/wpt/meta/fetch/metadata/generated/svg-image.sub.html.ini index dc3a74db79c..cec7a0548b1 100644 --- a/tests/wpt/meta/fetch/metadata/generated/svg-image.sub.html.ini +++ b/tests/wpt/meta/fetch/metadata/generated/svg-image.sub.html.ini @@ -1,55 +1,54 @@ [svg-image.sub.html] - expected: TIMEOUT [sec-fetch-site - Not sent to non-trustworthy same-origin destination no attributes] - expected: TIMEOUT + expected: FAIL [sec-fetch-site - Not sent to non-trustworthy same-site destination no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-site - Not sent to non-trustworthy cross-site destination no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-mode - Not sent to non-trustworthy same-origin destination no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-mode - Not sent to non-trustworthy same-site destination no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-mode - Not sent to non-trustworthy cross-site destination no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-dest - Not sent to non-trustworthy same-origin destination no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-dest - Not sent to non-trustworthy same-site destination no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-dest - Not sent to non-trustworthy cross-site destination no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-user - Not sent to non-trustworthy same-origin destination no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-user - Not sent to non-trustworthy same-site destination no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-user - Not sent to non-trustworthy cross-site destination no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-site - HTTPS downgrade (header not sent) no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-site - HTTPS upgrade no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-site - HTTPS downgrade-upgrade no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-storage-access - Not sent to non-trustworthy same-origin destination no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-storage-access - Not sent to non-trustworthy same-site destination no attributes] - expected: NOTRUN + expected: FAIL [sec-fetch-storage-access - Not sent to non-trustworthy cross-site destination no attributes] - expected: NOTRUN + expected: FAIL diff --git a/tests/wpt/meta/fetch/metadata/report.https.sub.html.ini b/tests/wpt/meta/fetch/metadata/report.https.sub.html.ini index 058151d81ce..0348be9f384 100644 --- a/tests/wpt/meta/fetch/metadata/report.https.sub.html.ini +++ b/tests/wpt/meta/fetch/metadata/report.https.sub.html.ini @@ -1,2 +1,10 @@ [report.https.sub.html] - expected: TIMEOUT + expected: ERROR + [same-origin report] + expected: TIMEOUT + + [same-site report] + expected: TIMEOUT + + [cross-site report] + expected: TIMEOUT diff --git a/tests/wpt/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-nosrc.html.ini b/tests/wpt/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-nosrc.html.ini index 2241163d563..6313c3e33dd 100644 --- a/tests/wpt/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-nosrc.html.ini +++ b/tests/wpt/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-nosrc.html.ini @@ -2,8 +2,5 @@ [window.open] expected: FAIL - [form submission] - expected: FAIL - [link click] expected: FAIL diff --git a/tests/wpt/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-aboutblank-navigate-immediately.html.ini b/tests/wpt/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-aboutblank-navigate-immediately.html.ini index 5f9a07f92f0..277436ffad2 100644 --- a/tests/wpt/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-aboutblank-navigate-immediately.html.ini +++ b/tests/wpt/meta/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-aboutblank-navigate-immediately.html.ini @@ -1,6 +1,3 @@ [iframe-src-aboutblank-navigate-immediately.html] [Navigating to a different document with window.open] expected: FAIL - - [Navigating to a different document with form submission] - expected: FAIL diff --git a/tests/wpt/meta/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-click.html.ini b/tests/wpt/meta/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-click.html.ini new file mode 100644 index 00000000000..60a4fa51f8a --- /dev/null +++ b/tests/wpt/meta/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-click.html.ini @@ -0,0 +1,3 @@ +[a-click.html] + [aElement.click() before the load event must NOT replace] + expected: FAIL diff --git a/tests/wpt/meta/html/browsers/browsing-the-web/scroll-to-fragid/scroll-to-top.html.ini b/tests/wpt/meta/html/browsers/browsing-the-web/scroll-to-fragid/scroll-to-top.html.ini deleted file mode 100644 index 6e8c7d20d83..00000000000 --- a/tests/wpt/meta/html/browsers/browsing-the-web/scroll-to-fragid/scroll-to-top.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[scroll-to-top.html] - [Fragment Navigation: When fragid is TOP scroll to the top of the document] - expected: FAIL diff --git a/tests/wpt/meta/html/browsers/history/the-history-interface/traverse_the_history_3.html.ini b/tests/wpt/meta/html/browsers/history/the-history-interface/traverse_the_history_5.html.ini index a03a8322165..7a5fcb79165 100644 --- a/tests/wpt/meta/html/browsers/history/the-history-interface/traverse_the_history_3.html.ini +++ b/tests/wpt/meta/html/browsers/history/the-history-interface/traverse_the_history_5.html.ini @@ -1,3 +1,3 @@ -[traverse_the_history_3.html] +[traverse_the_history_5.html] [Multiple history traversals, last would be aborted] expected: FAIL diff --git a/tests/wpt/meta/html/browsers/windows/auxiliary-browsing-contexts/named-lookup-noopener.html.ini b/tests/wpt/meta/html/browsers/windows/auxiliary-browsing-contexts/named-lookup-noopener.html.ini new file mode 100644 index 00000000000..6907354e049 --- /dev/null +++ b/tests/wpt/meta/html/browsers/windows/auxiliary-browsing-contexts/named-lookup-noopener.html.ini @@ -0,0 +1,7 @@ +[named-lookup-noopener.html] + expected: TIMEOUT + [Two noopener window.open() calls create separate windows] + expected: TIMEOUT + + [Two rel=noopener <a href> clicks create separate windows] + expected: NOTRUN diff --git a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-drawImage.html.ini b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-drawImage.html.ini index c94490ae5ee..9cf5b2af86a 100644 --- a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-drawImage.html.ini +++ b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-drawImage.html.ini @@ -1,13 +1,13 @@ [createImageBitmap-drawImage.html] expected: ERROR [createImageBitmap from an OffscreenCanvas resized, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from a vector HTMLImageElement resized, and drawImage on the created ImageBitmap] expected: FAIL [createImageBitmap from an OffscreenCanvas, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from an HTMLCanvasElement, and drawImage on the created ImageBitmap] expected: FAIL @@ -22,34 +22,34 @@ expected: FAIL [createImageBitmap from an ImageData scaled down, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from an OffscreenCanvas scaled down, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from a bitmap SVGImageElement, and drawImage on the created ImageBitmap] - expected: TIMEOUT + expected: FAIL [createImageBitmap from a bitmap SVGImageElement resized, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from an HTMLCanvasElement scaled down, and drawImage on the created ImageBitmap] expected: FAIL [createImageBitmap from a vector SVGImageElement, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from an ImageData scaled up, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from an HTMLVideoElement with negative sw/sh, and drawImage on the created ImageBitmap] expected: FAIL [createImageBitmap from a bitmap SVGImageElement scaled up, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from a vector SVGImageElement resized, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from a bitmap HTMLImageElement scaled up, and drawImage on the created ImageBitmap] expected: FAIL @@ -58,10 +58,10 @@ expected: FAIL [createImageBitmap from a vector SVGImageElement scaled down, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from a vector SVGImageElement with negative sw/sh, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from a bitmap HTMLImageElement scaled down, and drawImage on the created ImageBitmap] expected: FAIL @@ -73,16 +73,16 @@ expected: FAIL [createImageBitmap from a Blob scaled down, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from an ImageData resized, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from a vector HTMLImageElement scaled down, and drawImage on the created ImageBitmap] expected: FAIL [createImageBitmap from an ImageData, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from an HTMLCanvasElement with negative sw/sh, and drawImage on the created ImageBitmap] expected: FAIL @@ -91,16 +91,16 @@ expected: FAIL [createImageBitmap from a vector SVGImageElement scaled up, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from an ImageBitmap, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from a Blob scaled up, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from a bitmap SVGImageElement scaled down, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from an HTMLVideoElement scaled up, and drawImage on the created ImageBitmap] expected: FAIL @@ -109,7 +109,7 @@ expected: FAIL [createImageBitmap from a Blob, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from an HTMLVideoElement resized, and drawImage on the created ImageBitmap] expected: FAIL @@ -118,31 +118,31 @@ expected: FAIL [createImageBitmap from an ImageBitmap scaled down, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from a Blob with negative sw/sh, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from a bitmap SVGImageElement with negative sw/sh, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from an ImageData with negative sw/sh, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from an ImageBitmap scaled up, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from an ImageBitmap resized, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from an OffscreenCanvas scaled up, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from an HTMLCanvasElement scaled up, and drawImage on the created ImageBitmap] expected: FAIL [createImageBitmap from a Blob resized, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from an HTMLVideoElement from a data URL, and drawImage on the created ImageBitmap] expected: FAIL @@ -154,10 +154,10 @@ expected: FAIL [createImageBitmap from an ImageBitmap with negative sw/sh, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from an OffscreenCanvas with negative sw/sh, and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from a vector HTMLImageElement with negative sw/sh, and drawImage on the created ImageBitmap] expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-flipY.html.ini b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-flipY.html.ini index f08e8a2918a..0cb93c5abc9 100644 --- a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-flipY.html.ini +++ b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-flipY.html.ini @@ -4,7 +4,7 @@ expected: NOTRUN [createImageBitmap from a vector SVGImageElement imageOrientation: "flipY", and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from an HTMLCanvasElement imageOrientation: "none", and drawImage on the created ImageBitmap] expected: FAIL @@ -16,7 +16,7 @@ expected: FAIL [createImageBitmap from an OffscreenCanvas imageOrientation: "flipY", and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from a vector HTMLImageElement imageOrientation: "flipY", and drawImage on the created ImageBitmap] expected: FAIL @@ -25,7 +25,7 @@ expected: FAIL [createImageBitmap from a Blob imageOrientation: "flipY", and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from an HTMLCanvasElement imageOrientation: "flipY", and drawImage on the created ImageBitmap] expected: FAIL @@ -34,7 +34,7 @@ expected: FAIL [createImageBitmap from an ImageData imageOrientation: "flipY", and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from a bitmap HTMLImageElement imageOrientation: "flipY", and drawImage on the created ImageBitmap] expected: FAIL @@ -43,7 +43,7 @@ expected: NOTRUN [createImageBitmap from an ImageBitmap imageOrientation: "flipY", and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from a vector HTMLImageElement imageOrientation: "none", and drawImage on the created ImageBitmap] expected: FAIL @@ -61,7 +61,7 @@ expected: FAIL [createImageBitmap from a bitmap SVGImageElement imageOrientation: "flipY", and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from a bitmap SVGImageElement imageOrientation: "none", and drawImage on the created ImageBitmap] expected: TIMEOUT @@ -82,19 +82,19 @@ expected: FAIL [createImageBitmap from a bitmap SVGImageElement imageOrientation: "from-image", and drawImage on the created ImageBitmap] - expected: TIMEOUT + expected: FAIL [createImageBitmap from a vector SVGImageElement imageOrientation: "from-image", and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from an OffscreenCanvas imageOrientation: "from-image", and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from an ImageData imageOrientation: "from-image", and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from an ImageBitmap imageOrientation: "from-image", and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL [createImageBitmap from a Blob imageOrientation: "from-image", and drawImage on the created ImageBitmap] - expected: NOTRUN + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-invalid-args.html.ini b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-invalid-args.html.ini index 8daf97ba765..aba246039a0 100644 --- a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-invalid-args.html.ini +++ b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-invalid-args.html.ini @@ -1,5 +1,5 @@ [createImageBitmap-invalid-args.html] - expected: TIMEOUT + expected: CRASH [createImageBitmap with a vector HTMLImageElement source and sw set to 0] expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-origin.sub.html.ini b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-origin.sub.html.ini index 210d26f7740..d7226bfed74 100644 --- a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-origin.sub.html.ini +++ b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-origin.sub.html.ini @@ -1,34 +1,34 @@ [createImageBitmap-origin.sub.html] expected: TIMEOUT [redirected to cross-origin HTMLVideoElement: origin unclear 2dContext.drawImage] - expected: NOTRUN + expected: FAIL [redirected to cross-origin HTMLVideoElement: origin unclear bitmaprenderer.transferFromImageBitmap] - expected: NOTRUN + expected: FAIL [unclean HTMLCanvasElement: origin unclear bitmaprenderer.transferFromImageBitmap] - expected: NOTRUN + expected: FAIL [unclean HTMLCanvasElement: origin unclear getImageData] - expected: NOTRUN + expected: FAIL [cross-origin HTMLVideoElement: origin unclear getImageData] - expected: NOTRUN + expected: FAIL [cross-origin SVGImageElement: origin unclear bitmaprenderer.transferFromImageBitmap] - expected: NOTRUN + expected: FAIL [cross-origin HTMLVideoElement: origin unclear bitmaprenderer.transferFromImageBitmap] - expected: NOTRUN + expected: FAIL [redirected to same-origin HTMLVideoElement: origin unclear getImageData] - expected: NOTRUN + expected: FAIL [cross-origin HTMLImageElement: origin unclear 2dContext.drawImage] expected: FAIL [cross-origin SVGImageElement: origin unclear 2dContext.drawImage] - expected: NOTRUN + expected: FAIL [cross-origin HTMLImageElement: origin unclear getImageData] expected: FAIL @@ -37,28 +37,28 @@ expected: FAIL [redirected to same-origin HTMLVideoElement: origin unclear 2dContext.drawImage] - expected: NOTRUN + expected: FAIL [unclean ImageBitmap: origin unclear bitmaprenderer.transferFromImageBitmap] - expected: NOTRUN + expected: FAIL [redirected to same-origin HTMLVideoElement: origin unclear bitmaprenderer.transferFromImageBitmap] - expected: NOTRUN + expected: FAIL [redirected to cross-origin HTMLVideoElement: origin unclear getImageData] - expected: NOTRUN + expected: FAIL [unclean ImageBitmap: origin unclear getImageData] - expected: NOTRUN + expected: FAIL [unclean HTMLCanvasElement: origin unclear 2dContext.drawImage] - expected: NOTRUN + expected: FAIL [cross-origin HTMLVideoElement: origin unclear 2dContext.drawImage] - expected: NOTRUN + expected: FAIL [unclean ImageBitmap: origin unclear 2dContext.drawImage] - expected: NOTRUN + expected: FAIL [cross-origin SVGImageElement: origin unclear getImageData] - expected: TIMEOUT + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-serializable.html.ini b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-serializable.html.ini index 7616121487c..4234bcb2133 100644 --- a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-serializable.html.ini +++ b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-serializable.html.ini @@ -1,7 +1,6 @@ [createImageBitmap-serializable.html] - expected: TIMEOUT [Serialize ImageBitmap created from a vector SVGImageElement] - expected: NOTRUN + expected: FAIL [Serialize ImageBitmap created from an HTMLVideoElement] expected: FAIL @@ -13,25 +12,25 @@ expected: FAIL [Serialize ImageBitmap created from an OffscreenCanvas] - expected: NOTRUN + expected: FAIL [Serialize ImageBitmap created from a vector HTMLImageElement] expected: FAIL [Serialize ImageBitmap created from a Blob] - expected: NOTRUN + expected: FAIL [Serialize ImageBitmap created from a bitmap HTMLImageElement] expected: FAIL [Serializing a non-origin-clean ImageBitmap throws.] - expected: NOTRUN + expected: FAIL [Serialize ImageBitmap created from an ImageData] - expected: NOTRUN + expected: FAIL [Serialize ImageBitmap created from an ImageBitmap] - expected: NOTRUN + expected: FAIL [Serialize ImageBitmap created from a bitmap SVGImageElement] - expected: TIMEOUT + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-transfer.html.ini b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-transfer.html.ini index 7e036a1c4e4..cff84fd3a26 100644 --- a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-transfer.html.ini +++ b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-transfer.html.ini @@ -1,22 +1,21 @@ [createImageBitmap-transfer.html] - expected: TIMEOUT [Transfer ImageBitmap created from a vector HTMLImageElement] expected: FAIL [Transfer ImageBitmap created from an ImageData] - expected: NOTRUN + expected: FAIL [Transfer ImageBitmap created from a vector SVGImageElement] - expected: NOTRUN + expected: FAIL [Transfer ImageBitmap created from a Blob] - expected: NOTRUN + expected: FAIL [Transfer ImageBitmap created from an HTMLCanvasElement] expected: FAIL [Transfer ImageBitmap created from an OffscreenCanvas] - expected: NOTRUN + expected: FAIL [Transfer ImageBitmap created from a bitmap HTMLImageElement] expected: FAIL @@ -25,13 +24,13 @@ expected: FAIL [Transfer ImageBitmap created from a bitmap SVGImageElement] - expected: TIMEOUT + expected: FAIL [Transfer ImageBitmap created from an ImageBitmap] - expected: NOTRUN + expected: FAIL [Transfer ImageBitmap created from an HTMLVideoElement] expected: FAIL [Transferring a non-origin-clean ImageBitmap throws.] - expected: NOTRUN + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.disconnected.html.ini b/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.disconnected.html.ini new file mode 100644 index 00000000000..5cdcce07c65 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.disconnected.html.ini @@ -0,0 +1,2 @@ +[canvas.2d.disconnected.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/pixel-manipulation/2d.imageData.object.ctor.basics.html.ini b/tests/wpt/meta/html/canvas/element/pixel-manipulation/2d.imageData.object.ctor.basics.html.ini deleted file mode 100644 index 1a3c292f9f2..00000000000 --- a/tests/wpt/meta/html/canvas/element/pixel-manipulation/2d.imageData.object.ctor.basics.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.imageData.object.ctor.basics.html] - [Testing different type of ImageData constructor] - expected: FAIL diff --git a/tests/wpt/meta/html/dom/idlharness.https.html.ini b/tests/wpt/meta/html/dom/idlharness.https.html.ini index 25375c6ea47..981cd249aa6 100644 --- a/tests/wpt/meta/html/dom/idlharness.https.html.ini +++ b/tests/wpt/meta/html/dom/idlharness.https.html.ini @@ -1657,48 +1657,27 @@ [Window interface: window must inherit property "external" with the proper type] expected: FAIL - [Window interface: attribute onwebkitanimationstart] - expected: FAIL - [Window interface: window must inherit property "statusbar" with the proper type] expected: FAIL - [Document interface: new Document() must inherit property "onwebkittransitionend" with the proper type] - expected: FAIL - [Document interface: calling queryCommandEnabled(DOMString) on documentWithHandlers with too few arguments must throw TypeError] expected: FAIL - [Document interface: new Document() must inherit property "onwebkitanimationstart" with the proper type] - expected: FAIL - [Document interface: calling queryCommandIndeterm(DOMString) on documentWithHandlers with too few arguments must throw TypeError] expected: FAIL - [Document interface: iframe.contentDocument must inherit property "onwebkitanimationend" with the proper type] - expected: FAIL - [Document interface: iframe.contentDocument must inherit property "dir" with the proper type] expected: FAIL - [Window interface: window must inherit property "onwebkitanimationiteration" with the proper type] - expected: FAIL - [Window interface: window must inherit property "applicationCache" with the proper type] expected: FAIL - [Document interface: attribute onauxclick] - expected: FAIL - [Window interface: window must inherit property "menubar" with the proper type] expected: FAIL [Window interface: internal [[SetPrototypeOf\]\] method of interface prototype object - setting to a new value via Reflect.setPrototypeOf should return false] expected: FAIL - [Document interface: new Document() must inherit property "onwebkitanimationiteration" with the proper type] - expected: FAIL - [Document interface: attribute designMode] expected: FAIL @@ -1714,9 +1693,6 @@ [Document interface: calling execCommand(DOMString, optional boolean, optional DOMString) on new Document() with too few arguments must throw TypeError] expected: FAIL - [Document interface: attribute onwebkitanimationiteration] - expected: FAIL - [Document interface: operation queryCommandEnabled(DOMString)] expected: FAIL @@ -1741,42 +1717,24 @@ [Document interface: iframe.contentDocument must inherit property "linkColor" with the proper type] expected: FAIL - [Document interface: documentWithHandlers must inherit property "onslotchange" with the proper type] - expected: FAIL - [Document interface: documentWithHandlers must inherit property "alinkColor" with the proper type] expected: FAIL [Document interface: documentWithHandlers must inherit property "dir" with the proper type] expected: FAIL - [Window interface: window must inherit property "onslotchange" with the proper type] - expected: FAIL - [SVGAElement includes HTMLHyperlinkElementUtils: member names are unique] expected: FAIL [Window interface: attribute applicationCache] expected: FAIL - [Document interface: iframe.contentDocument must inherit property "onwebkitanimationstart" with the proper type] - expected: FAIL - - [Document interface: attribute onslotchange] - expected: FAIL - [Document interface: calling queryCommandIndeterm(DOMString) on iframe.contentDocument with too few arguments must throw TypeError] expected: FAIL [Document interface: iframe.contentDocument must inherit property "queryCommandValue(DOMString)" with the proper type] expected: FAIL - [Document interface: iframe.contentDocument must inherit property "onwebkitanimationiteration" with the proper type] - expected: FAIL - - [Document interface: attribute onwebkittransitionend] - expected: FAIL - [Document interface: iframe.contentDocument must inherit property "all" with the proper type] expected: FAIL @@ -1789,9 +1747,6 @@ [Document interface: attribute linkColor] expected: FAIL - [Window interface: attribute onwebkitanimationiteration] - expected: FAIL - [Window interface: window must inherit property "scrollbars" with the proper type] expected: FAIL @@ -1807,18 +1762,12 @@ [Document interface: iframe.contentDocument must inherit property "designMode" with the proper type] expected: FAIL - [Document interface: documentWithHandlers must inherit property "onwebkitanimationiteration" with the proper type] - expected: FAIL - [Window interface: attribute toolbar] expected: FAIL [Window interface: attribute statusbar] expected: FAIL - [Window interface: window must inherit property "onwebkittransitionend" with the proper type] - expected: FAIL - [Document interface: new Document() must inherit property "linkColor" with the proper type] expected: FAIL @@ -1828,15 +1777,9 @@ [Document interface: new Document() must inherit property "queryCommandState(DOMString)" with the proper type] expected: FAIL - [Window interface: attribute onwebkittransitionend] - expected: FAIL - [Document interface: calling execCommand(DOMString, optional boolean, optional DOMString) on iframe.contentDocument with too few arguments must throw TypeError] expected: FAIL - [Document interface: new Document() must inherit property "onslotchange" with the proper type] - expected: FAIL - [Document interface: documentWithHandlers must inherit property "designMode" with the proper type] expected: FAIL @@ -1855,9 +1798,6 @@ [Window interface: window must inherit property "personalbar" with the proper type] expected: FAIL - [Document interface: attribute onwebkitanimationend] - expected: FAIL - [Window interface: internal [[SetPrototypeOf\]\] method of interface prototype object - setting to a new value via __proto__ should throw a TypeError] expected: FAIL @@ -1882,24 +1822,15 @@ [Document interface: attribute vlinkColor] expected: FAIL - [Window interface: window must inherit property "onauxclick" with the proper type] - expected: FAIL - [Document interface: iframe.contentDocument must inherit property "queryCommandEnabled(DOMString)" with the proper type] expected: FAIL - [Document interface: iframe.contentDocument must inherit property "onslotchange" with the proper type] - expected: FAIL - [Document interface: new Document() must inherit property "alinkColor" with the proper type] expected: FAIL [Document interface: new Document() must inherit property "queryCommandEnabled(DOMString)" with the proper type] expected: FAIL - [Window interface: attribute onslotchange] - expected: FAIL - [Document interface: new Document() must inherit property "all" with the proper type] expected: FAIL @@ -1909,24 +1840,6 @@ [Window interface: window must inherit property "print()" with the proper type] expected: FAIL - [Document interface: documentWithHandlers must inherit property "onauxclick" with the proper type] - expected: FAIL - - [Document interface: documentWithHandlers must inherit property "onwebkitanimationend" with the proper type] - expected: FAIL - - [Document interface: new Document() must inherit property "onauxclick" with the proper type] - expected: FAIL - - [Document interface: documentWithHandlers must inherit property "onwebkitanimationstart" with the proper type] - expected: FAIL - - [Document interface: attribute onwebkitanimationstart] - expected: FAIL - - [Document interface: iframe.contentDocument must inherit property "onauxclick" with the proper type] - expected: FAIL - [Document interface: calling queryCommandValue(DOMString) on documentWithHandlers with too few arguments must throw TypeError] expected: FAIL @@ -1939,9 +1852,6 @@ [Document interface: calling queryCommandState(DOMString) on new Document() with too few arguments must throw TypeError] expected: FAIL - [Window interface: attribute onauxclick] - expected: FAIL - [Document interface: iframe.contentDocument must inherit property "queryCommandState(DOMString)" with the proper type] expected: FAIL @@ -1954,48 +1864,30 @@ [Document interface: new Document() must inherit property "queryCommandValue(DOMString)" with the proper type] expected: FAIL - [Window interface: window must inherit property "onwebkitanimationend" with the proper type] - expected: FAIL - [Window interface: window must inherit property "toolbar" with the proper type] expected: FAIL [Document interface: documentWithHandlers must inherit property "linkColor" with the proper type] expected: FAIL - [Document interface: new Document() must inherit property "onwebkitanimationend" with the proper type] - expected: FAIL - [Document interface: operation queryCommandIndeterm(DOMString)] expected: FAIL - [Document interface: iframe.contentDocument must inherit property "onwebkittransitionend" with the proper type] - expected: FAIL - [Document interface: calling queryCommandIndeterm(DOMString) on new Document() with too few arguments must throw TypeError] expected: FAIL - [Window interface: attribute onwebkitanimationend] - expected: FAIL - [Document interface: calling execCommand(DOMString, optional boolean, optional DOMString) on documentWithHandlers with too few arguments must throw TypeError] expected: FAIL [Document interface: documentWithHandlers must inherit property "queryCommandState(DOMString)" with the proper type] expected: FAIL - [Document interface: documentWithHandlers must inherit property "onwebkittransitionend" with the proper type] - expected: FAIL - [Document interface: new Document() must inherit property "vlinkColor" with the proper type] expected: FAIL [Window interface: window must inherit property "locationbar" with the proper type] expected: FAIL - [Window interface: window must inherit property "onwebkitanimationstart" with the proper type] - expected: FAIL - [Document interface: documentWithHandlers must inherit property "queryCommandIndeterm(DOMString)" with the proper type] expected: FAIL @@ -2023,24 +1915,12 @@ [Window interface: attribute clientInformation] expected: FAIL - [Window interface: attribute oncontextlost] - expected: FAIL - - [Window interface: attribute oncontextrestored] - expected: FAIL - [Window interface: operation reportError(any)] expected: FAIL [Window interface: window must inherit property "clientInformation" with the proper type] expected: FAIL - [Window interface: window must inherit property "oncontextlost" with the proper type] - expected: FAIL - - [Window interface: window must inherit property "oncontextrestored" with the proper type] - expected: FAIL - [Window interface: window must inherit property "reportError(any)" with the proper type] expected: FAIL @@ -2050,129 +1930,15 @@ [Document interface: attribute onvisibilitychange] expected: FAIL - [Document interface: attribute oncontextlost] - expected: FAIL - - [Document interface: attribute oncontextrestored] - expected: FAIL - [Document interface: iframe.contentDocument must inherit property "onvisibilitychange" with the proper type] expected: FAIL - [Document interface: iframe.contentDocument must inherit property "oncontextlost" with the proper type] - expected: FAIL - - [Document interface: iframe.contentDocument must inherit property "oncontextrestored" with the proper type] - expected: FAIL - [Document interface: new Document() must inherit property "onvisibilitychange" with the proper type] expected: FAIL - [Document interface: new Document() must inherit property "oncontextlost" with the proper type] - expected: FAIL - - [Document interface: new Document() must inherit property "oncontextrestored" with the proper type] - expected: FAIL - [Document interface: documentWithHandlers must inherit property "onvisibilitychange" with the proper type] expected: FAIL - [Document interface: documentWithHandlers must inherit property "oncontextlost" with the proper type] - expected: FAIL - - [Document interface: documentWithHandlers must inherit property "oncontextrestored" with the proper type] - expected: FAIL - - [Window interface: attribute onbeforeinput] - expected: FAIL - - [Window interface: attribute onbeforematch] - expected: FAIL - - [Window interface: attribute onscrollend] - expected: FAIL - - [Window interface: window must inherit property "onbeforeinput" with the proper type] - expected: FAIL - - [Window interface: window must inherit property "onbeforematch" with the proper type] - expected: FAIL - - [Window interface: window must inherit property "onscrollend" with the proper type] - expected: FAIL - - [Document interface: attribute onbeforeinput] - expected: FAIL - - [Document interface: attribute onbeforematch] - expected: FAIL - - [Document interface: attribute onscrollend] - expected: FAIL - - [Document interface: iframe.contentDocument must inherit property "onbeforeinput" with the proper type] - expected: FAIL - - [Document interface: iframe.contentDocument must inherit property "onbeforematch" with the proper type] - expected: FAIL - - [Document interface: iframe.contentDocument must inherit property "onscrollend" with the proper type] - expected: FAIL - - [Document interface: new Document() must inherit property "onbeforeinput" with the proper type] - expected: FAIL - - [Document interface: new Document() must inherit property "onbeforematch" with the proper type] - expected: FAIL - - [Document interface: new Document() must inherit property "onscrollend" with the proper type] - expected: FAIL - - [Document interface: documentWithHandlers must inherit property "onbeforeinput" with the proper type] - expected: FAIL - - [Document interface: documentWithHandlers must inherit property "onbeforematch" with the proper type] - expected: FAIL - - [Document interface: documentWithHandlers must inherit property "onscrollend" with the proper type] - expected: FAIL - - [Window interface: attribute oncopy] - expected: FAIL - - [Window interface: attribute oncut] - expected: FAIL - - [Window interface: attribute onpaste] - expected: FAIL - - [Window interface: window must inherit property "oncopy" with the proper type] - expected: FAIL - - [Window interface: window must inherit property "oncut" with the proper type] - expected: FAIL - - [Window interface: window must inherit property "onpaste" with the proper type] - expected: FAIL - - [Window interface: attribute onbeforetoggle] - expected: FAIL - - [Window interface: window must inherit property "onbeforetoggle" with the proper type] - expected: FAIL - - [Document interface: attribute onbeforetoggle] - expected: FAIL - - [Document interface: iframe.contentDocument must inherit property "onbeforetoggle" with the proper type] - expected: FAIL - - [Document interface: new Document() must inherit property "onbeforetoggle" with the proper type] - expected: FAIL - - [Document interface: documentWithHandlers must inherit property "onbeforetoggle" with the proper type] - expected: FAIL - [Window interface: attribute navigation] expected: FAIL @@ -2191,18 +1957,6 @@ [Document interface: calling parseHTMLUnsafe(DOMString) on documentWithHandlers with too few arguments must throw TypeError] expected: FAIL - [Window interface: attribute onpagereveal] - expected: FAIL - - [Window interface: window must inherit property "onpagereveal" with the proper type] - expected: FAIL - - [Window interface: attribute onpageswap] - expected: FAIL - - [Window interface: window must inherit property "onpageswap" with the proper type] - expected: FAIL - [Document interface: operation parseHTMLUnsafe(HTMLString)] expected: FAIL @@ -2227,24 +1981,6 @@ [Document interface: calling parseHTMLUnsafe((TrustedHTML or DOMString)) on documentWithHandlers with too few arguments must throw TypeError] expected: FAIL - [Window interface: attribute oncommand] - expected: FAIL - - [Window interface: window must inherit property "oncommand" with the proper type] - expected: FAIL - - [Document interface: attribute oncommand] - expected: FAIL - - [Document interface: iframe.contentDocument must inherit property "oncommand" with the proper type] - expected: FAIL - - [Document interface: new Document() must inherit property "oncommand" with the proper type] - expected: FAIL - - [Document interface: documentWithHandlers must inherit property "oncommand" with the proper type] - expected: FAIL - [idlharness.https.html?include=HTML.*] [HTMLTableSectionElement interface: document.createElement("tfoot") must inherit property "align" with the proper type] @@ -2610,12 +2346,6 @@ [HTMLImageElement interface: document.createElement("img") must inherit property "decoding" with the proper type] expected: FAIL - [HTMLElement interface: attribute onwebkitanimationiteration] - expected: FAIL - - [HTMLElement interface: attribute onslotchange] - expected: FAIL - [HTMLVideoElement interface: attribute width] expected: FAIL @@ -2727,9 +2457,6 @@ [HTMLAnchorElement interface: attribute type] expected: FAIL - [HTMLElement interface: attribute onwebkitanimationend] - expected: FAIL - [HTMLInputElement interface: attribute height] expected: FAIL @@ -2949,9 +2676,6 @@ [HTMLElement interface: attribute tabIndex] expected: FAIL - [HTMLElement interface: attribute onwebkitanimationstart] - expected: FAIL - [HTMLImageElement interface: new Image() must inherit property "loading" with the proper type] expected: FAIL @@ -3639,9 +3363,6 @@ [HTMLAllCollection interface: document.all must inherit property "namedItem(DOMString)" with the proper type] expected: FAIL - [HTMLElement interface: attribute onwebkittransitionend] - expected: FAIL - [HTMLLinkElement interface: document.createElement("link") must inherit property "imageSrcset" with the proper type] expected: FAIL @@ -3945,12 +3666,6 @@ [HTMLSlotElement interface: calling assign((Element or Text)...) on document.createElement("slot") with too few arguments must throw TypeError] expected: FAIL - [HTMLElement interface: attribute oncontextlost] - expected: FAIL - - [HTMLElement interface: attribute oncontextrestored] - expected: FAIL - [HTMLElement interface: document.createElement("noscript") must inherit property "oncontextlost" with the proper type] expected: FAIL @@ -4044,15 +3759,6 @@ [HTMLElement interface: attribute inert] expected: FAIL - [HTMLElement interface: attribute onbeforeinput] - expected: FAIL - - [HTMLElement interface: attribute onbeforematch] - expected: FAIL - - [HTMLElement interface: attribute onscrollend] - expected: FAIL - [HTMLElement interface: document.createElement("noscript") must inherit property "inert" with the proper type] expected: FAIL @@ -4317,36 +4023,12 @@ [HTMLSelectElement interface: document.createElement("select") must inherit property "showPicker()" with the proper type] expected: FAIL - [HTMLBodyElement interface: attribute onpagereveal] - expected: FAIL - - [HTMLBodyElement interface: document.createElement("body") must inherit property "onpagereveal" with the proper type] - expected: FAIL - - [HTMLFrameSetElement interface: attribute onpagereveal] - expected: FAIL - - [HTMLFrameSetElement interface: document.createElement("frameset") must inherit property "onpagereveal" with the proper type] - expected: FAIL - [HTMLTemplateElement interface: attribute shadowRootClonable] expected: FAIL [HTMLTemplateElement interface: document.createElement("template") must inherit property "shadowRootClonable" with the proper type] expected: FAIL - [HTMLBodyElement interface: attribute onpageswap] - expected: FAIL - - [HTMLBodyElement interface: document.createElement("body") must inherit property "onpageswap" with the proper type] - expected: FAIL - - [HTMLFrameSetElement interface: attribute onpageswap] - expected: FAIL - - [HTMLFrameSetElement interface: document.createElement("frameset") must inherit property "onpageswap" with the proper type] - expected: FAIL - [HTMLElement interface: attribute writingSuggestions] expected: FAIL @@ -5441,231 +5123,6 @@ [External interface: window.external must inherit property "IsSearchProviderInstalled()" with the proper type] expected: FAIL - [SVGElement interface: attribute onabort] - expected: FAIL - - [SVGElement interface: attribute onauxclick] - expected: FAIL - - [SVGElement interface: attribute onbeforeinput] - expected: FAIL - - [SVGElement interface: attribute onbeforematch] - expected: FAIL - - [SVGElement interface: attribute onbeforetoggle] - expected: FAIL - - [SVGElement interface: attribute onblur] - expected: FAIL - - [SVGElement interface: attribute oncancel] - expected: FAIL - - [SVGElement interface: attribute oncanplay] - expected: FAIL - - [SVGElement interface: attribute oncanplaythrough] - expected: FAIL - - [SVGElement interface: attribute onchange] - expected: FAIL - - [SVGElement interface: attribute onclick] - expected: FAIL - - [SVGElement interface: attribute onclose] - expected: FAIL - - [SVGElement interface: attribute oncontextlost] - expected: FAIL - - [SVGElement interface: attribute oncontextmenu] - expected: FAIL - - [SVGElement interface: attribute oncontextrestored] - expected: FAIL - - [SVGElement interface: attribute oncopy] - expected: FAIL - - [SVGElement interface: attribute oncuechange] - expected: FAIL - - [SVGElement interface: attribute oncut] - expected: FAIL - - [SVGElement interface: attribute ondblclick] - expected: FAIL - - [SVGElement interface: attribute ondrag] - expected: FAIL - - [SVGElement interface: attribute ondragend] - expected: FAIL - - [SVGElement interface: attribute ondragenter] - expected: FAIL - - [SVGElement interface: attribute ondragleave] - expected: FAIL - - [SVGElement interface: attribute ondragover] - expected: FAIL - - [SVGElement interface: attribute ondragstart] - expected: FAIL - - [SVGElement interface: attribute ondrop] - expected: FAIL - - [SVGElement interface: attribute ondurationchange] - expected: FAIL - - [SVGElement interface: attribute onemptied] - expected: FAIL - - [SVGElement interface: attribute onended] - expected: FAIL - - [SVGElement interface: attribute onerror] - expected: FAIL - - [SVGElement interface: attribute onfocus] - expected: FAIL - - [SVGElement interface: attribute onformdata] - expected: FAIL - - [SVGElement interface: attribute oninput] - expected: FAIL - - [SVGElement interface: attribute oninvalid] - expected: FAIL - - [SVGElement interface: attribute onkeydown] - expected: FAIL - - [SVGElement interface: attribute onkeypress] - expected: FAIL - - [SVGElement interface: attribute onkeyup] - expected: FAIL - - [SVGElement interface: attribute onload] - expected: FAIL - - [SVGElement interface: attribute onloadeddata] - expected: FAIL - - [SVGElement interface: attribute onloadedmetadata] - expected: FAIL - - [SVGElement interface: attribute onloadstart] - expected: FAIL - - [SVGElement interface: attribute onmousedown] - expected: FAIL - - [SVGElement interface: attribute onmouseenter] - expected: FAIL - - [SVGElement interface: attribute onmouseleave] - expected: FAIL - - [SVGElement interface: attribute onmousemove] - expected: FAIL - - [SVGElement interface: attribute onmouseout] - expected: FAIL - - [SVGElement interface: attribute onmouseover] - expected: FAIL - - [SVGElement interface: attribute onmouseup] - expected: FAIL - - [SVGElement interface: attribute onpaste] - expected: FAIL - - [SVGElement interface: attribute onpause] - expected: FAIL - - [SVGElement interface: attribute onplay] - expected: FAIL - - [SVGElement interface: attribute onplaying] - expected: FAIL - - [SVGElement interface: attribute onprogress] - expected: FAIL - - [SVGElement interface: attribute onratechange] - expected: FAIL - - [SVGElement interface: attribute onreset] - expected: FAIL - - [SVGElement interface: attribute onresize] - expected: FAIL - - [SVGElement interface: attribute onscroll] - expected: FAIL - - [SVGElement interface: attribute onscrollend] - expected: FAIL - - [SVGElement interface: attribute onsecuritypolicyviolation] - expected: FAIL - - [SVGElement interface: attribute onseeked] - expected: FAIL - - [SVGElement interface: attribute onseeking] - expected: FAIL - - [SVGElement interface: attribute onselect] - expected: FAIL - - [SVGElement interface: attribute onslotchange] - expected: FAIL - - [SVGElement interface: attribute onstalled] - expected: FAIL - - [SVGElement interface: attribute onsubmit] - expected: FAIL - - [SVGElement interface: attribute onsuspend] - expected: FAIL - - [SVGElement interface: attribute ontimeupdate] - expected: FAIL - - [SVGElement interface: attribute ontoggle] - expected: FAIL - - [SVGElement interface: attribute onvolumechange] - expected: FAIL - - [SVGElement interface: attribute onwaiting] - expected: FAIL - - [SVGElement interface: attribute onwebkitanimationend] - expected: FAIL - - [SVGElement interface: attribute onwebkitanimationiteration] - expected: FAIL - - [SVGElement interface: attribute onwebkitanimationstart] - expected: FAIL - - [SVGElement interface: attribute onwebkittransitionend] - expected: FAIL - - [SVGElement interface: attribute onwheel] - expected: FAIL - [SVGElement interface: attribute dataset] expected: FAIL @@ -5762,9 +5219,6 @@ [CommandEvent interface: attribute command] expected: FAIL - [SVGElement interface: attribute oncommand] - expected: FAIL - [CanvasRenderingContext2D interface: attribute lang] expected: FAIL @@ -5866,42 +5320,6 @@ [HTMLElement interface: attribute popover] expected: FAIL - [HTMLElement interface: attribute onauxclick] - expected: FAIL - - [HTMLElement interface: attribute onbeforeinput] - expected: FAIL - - [HTMLElement interface: attribute onbeforematch] - expected: FAIL - - [HTMLElement interface: attribute onbeforetoggle] - expected: FAIL - - [HTMLElement interface: attribute oncontextlost] - expected: FAIL - - [HTMLElement interface: attribute oncontextrestored] - expected: FAIL - - [HTMLElement interface: attribute onscrollend] - expected: FAIL - - [HTMLElement interface: attribute onslotchange] - expected: FAIL - - [HTMLElement interface: attribute onwebkitanimationend] - expected: FAIL - - [HTMLElement interface: attribute onwebkitanimationiteration] - expected: FAIL - - [HTMLElement interface: attribute onwebkitanimationstart] - expected: FAIL - - [HTMLElement interface: attribute onwebkittransitionend] - expected: FAIL - [HTMLElement interface: attribute enterKeyHint] expected: FAIL @@ -5950,42 +5368,6 @@ [HTMLElement interface: document.createElement("noscript") must inherit property "popover" with the proper type] expected: FAIL - [HTMLElement interface: document.createElement("noscript") must inherit property "onauxclick" with the proper type] - expected: FAIL - - [HTMLElement interface: document.createElement("noscript") must inherit property "onbeforeinput" with the proper type] - expected: FAIL - - [HTMLElement interface: document.createElement("noscript") must inherit property "onbeforematch" with the proper type] - expected: FAIL - - [HTMLElement interface: document.createElement("noscript") must inherit property "onbeforetoggle" with the proper type] - expected: FAIL - - [HTMLElement interface: document.createElement("noscript") must inherit property "oncontextlost" with the proper type] - expected: FAIL - - [HTMLElement interface: document.createElement("noscript") must inherit property "oncontextrestored" with the proper type] - expected: FAIL - - [HTMLElement interface: document.createElement("noscript") must inherit property "onscrollend" with the proper type] - expected: FAIL - - [HTMLElement interface: document.createElement("noscript") must inherit property "onslotchange" with the proper type] - expected: FAIL - - [HTMLElement interface: document.createElement("noscript") must inherit property "onwebkitanimationend" with the proper type] - expected: FAIL - - [HTMLElement interface: document.createElement("noscript") must inherit property "onwebkitanimationiteration" with the proper type] - expected: FAIL - - [HTMLElement interface: document.createElement("noscript") must inherit property "onwebkitanimationstart" with the proper type] - expected: FAIL - - [HTMLElement interface: document.createElement("noscript") must inherit property "onwebkittransitionend" with the proper type] - expected: FAIL - [HTMLElement interface: document.createElement("noscript") must inherit property "enterKeyHint" with the proper type] expected: FAIL @@ -6064,12 +5446,6 @@ [HTMLBodyElement interface: attribute aLink] expected: FAIL - [HTMLBodyElement interface: attribute onpagereveal] - expected: FAIL - - [HTMLBodyElement interface: attribute onpageswap] - expected: FAIL - [HTMLBodyElement interface: document.createElement("body") must inherit property "link" with the proper type] expected: FAIL @@ -6079,12 +5455,6 @@ [HTMLBodyElement interface: document.createElement("body") must inherit property "aLink" with the proper type] expected: FAIL - [HTMLBodyElement interface: document.createElement("body") must inherit property "onpagereveal" with the proper type] - expected: FAIL - - [HTMLBodyElement interface: document.createElement("body") must inherit property "onpageswap" with the proper type] - expected: FAIL - [HTMLHeadingElement interface: attribute align] expected: FAIL @@ -7681,24 +7051,12 @@ [HTMLFrameSetElement interface: attribute rows] expected: FAIL - [HTMLFrameSetElement interface: attribute onpagereveal] - expected: FAIL - - [HTMLFrameSetElement interface: attribute onpageswap] - expected: FAIL - [HTMLFrameSetElement interface: document.createElement("frameset") must inherit property "cols" with the proper type] expected: FAIL [HTMLFrameSetElement interface: document.createElement("frameset") must inherit property "rows" with the proper type] expected: FAIL - [HTMLFrameSetElement interface: document.createElement("frameset") must inherit property "onpagereveal" with the proper type] - expected: FAIL - - [HTMLFrameSetElement interface: document.createElement("frameset") must inherit property "onpageswap" with the proper type] - expected: FAIL - [HTMLFrameElement interface: attribute name] expected: FAIL @@ -7813,12 +7171,6 @@ [HTMLDialogElement interface: operation requestClose(optional DOMString)] expected: FAIL - [HTMLElement interface: attribute oncommand] - expected: FAIL - - [HTMLElement interface: document.createElement("noscript") must inherit property "oncommand" with the proper type] - expected: FAIL - [HTMLButtonElement interface: attribute command] expected: FAIL diff --git a/tests/wpt/meta/html/rendering/replaced-elements/the-select-element/select-1-block-size.html.ini b/tests/wpt/meta/html/rendering/replaced-elements/the-select-element/select-1-block-size.html.ini deleted file mode 100644 index 706aff5771a..00000000000 --- a/tests/wpt/meta/html/rendering/replaced-elements/the-select-element/select-1-block-size.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[select-1-block-size.html] - expected: FAIL diff --git a/tests/wpt/meta/html/rendering/replaced-elements/the-select-element/select-multiple-re-add-option-via-document-fragment.html.ini b/tests/wpt/meta/html/rendering/replaced-elements/the-select-element/select-multiple-re-add-option-via-document-fragment.html.ini deleted file mode 100644 index bacaac5e98d..00000000000 --- a/tests/wpt/meta/html/rendering/replaced-elements/the-select-element/select-multiple-re-add-option-via-document-fragment.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[select-multiple-re-add-option-via-document-fragment.html] - expected: FAIL diff --git a/tests/wpt/meta/html/semantics/embedded-content/the-canvas-element/security.pattern.fillStyle.sub.html.ini b/tests/wpt/meta/html/semantics/embedded-content/the-canvas-element/security.pattern.fillStyle.sub.html.ini index cacfdf56967..81329bc1635 100644 --- a/tests/wpt/meta/html/semantics/embedded-content/the-canvas-element/security.pattern.fillStyle.sub.html.ini +++ b/tests/wpt/meta/html/semantics/embedded-content/the-canvas-element/security.pattern.fillStyle.sub.html.ini @@ -1,16 +1,16 @@ [security.pattern.fillStyle.sub.html] expected: TIMEOUT [cross-origin SVGImageElement: Setting fillStyle to an origin-unclean pattern makes the canvas origin-unclean] - expected: TIMEOUT + expected: FAIL [cross-origin HTMLVideoElement: Setting fillStyle to an origin-unclean pattern makes the canvas origin-unclean] - expected: NOTRUN + expected: FAIL [redirected to cross-origin HTMLVideoElement: Setting fillStyle to an origin-unclean pattern makes the canvas origin-unclean] - expected: NOTRUN + expected: FAIL [redirected to same-origin HTMLVideoElement: Setting fillStyle to an origin-unclean pattern makes the canvas origin-unclean] - expected: NOTRUN + expected: TIMEOUT [unclean HTMLCanvasElement: Setting fillStyle to an origin-unclean pattern makes the canvas origin-unclean] expected: NOTRUN diff --git a/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-3.html.ini b/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-3.html.ini index 6a420504feb..7da2bc5ac80 100644 --- a/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-3.html.ini +++ b/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-3.html.ini @@ -1,3 +1,4 @@ [iframe_sandbox_popups_escaping-3.html] + expected: TIMEOUT [Check that popups from a sandboxed iframe escape the sandbox if\n allow-popups-to-escape-sandbox is used] - expected: FAIL + expected: TIMEOUT diff --git a/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-1.html.ini b/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-1.html.ini index bbc1f35d8d9..e8872b3585b 100644 --- a/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-1.html.ini +++ b/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-1.html.ini @@ -1,3 +1,4 @@ [iframe_sandbox_popups_nonescaping-1.html] + expected: TIMEOUT [Check that popups from a sandboxed iframe do not escape the sandbox] - expected: FAIL + expected: NOTRUN diff --git a/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-3.html.ini b/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-3.html.ini index d5fd800f09d..ff6467094b8 100644 --- a/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-3.html.ini +++ b/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-3.html.ini @@ -1,4 +1,3 @@ [iframe_sandbox_popups_nonescaping-3.html] - expected: TIMEOUT [Check that popups from a sandboxed iframe do not escape the sandbox] - expected: NOTRUN + expected: FAIL diff --git a/tests/wpt/meta/html/semantics/forms/form-submission-0/reparent-form-during-planned-navigation-task.html.ini b/tests/wpt/meta/html/semantics/forms/form-submission-0/reparent-form-during-planned-navigation-task.html.ini deleted file mode 100644 index 7682a4830bf..00000000000 --- a/tests/wpt/meta/html/semantics/forms/form-submission-0/reparent-form-during-planned-navigation-task.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[reparent-form-during-planned-navigation-task.html] - expected: TIMEOUT - [reparent-form-during-planned-navigation-task] - expected: TIMEOUT diff --git a/tests/wpt/meta/html/semantics/forms/form-submission-0/text-plain.window.js.ini b/tests/wpt/meta/html/semantics/forms/form-submission-0/text-plain.window.js.ini index 1abe0eb8b88..320f8cf856c 100644 --- a/tests/wpt/meta/html/semantics/forms/form-submission-0/text-plain.window.js.ini +++ b/tests/wpt/meta/html/semantics/forms/form-submission-0/text-plain.window.js.ini @@ -181,3 +181,6 @@ [text/plain: non-ASCII in filename (formdata event)] expected: FAIL + + [text/plain: 0x00 in name (normal form)] + expected: FAIL diff --git a/tests/wpt/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/blob-url.any.js.ini b/tests/wpt/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/blob-url.any.js.ini index 095b97aa5a0..8dba4e9c469 100644 --- a/tests/wpt/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/blob-url.any.js.ini +++ b/tests/wpt/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/blob-url.any.js.ini @@ -5,9 +5,6 @@ [Different blob URLs pointing to the same blob resolve to different modules] expected: FAIL - [Revoking a blob URL immediately after calling import will not fail] - expected: FAIL - [blob-url.any.worker-module.html] expected: TIMEOUT diff --git a/tests/wpt/meta/html/semantics/the-button-element/command-and-commandfor/button-event-dispatch-content-attribute.html.ini b/tests/wpt/meta/html/semantics/the-button-element/command-and-commandfor/button-event-dispatch-content-attribute.html.ini index bf4c7b0b9c9..3a75e5ee0ee 100644 --- a/tests/wpt/meta/html/semantics/the-button-element/command-and-commandfor/button-event-dispatch-content-attribute.html.ini +++ b/tests/wpt/meta/html/semantics/the-button-element/command-and-commandfor/button-event-dispatch-content-attribute.html.ini @@ -1,6 +1,3 @@ [button-event-dispatch-content-attribute.html] [oncommand content attribute works] expected: FAIL - - [oncommand content with a value of false prevents default] - expected: FAIL diff --git a/tests/wpt/meta/html/semantics/the-button-element/interest-target/interesttarget-outline-appearance.tentative.html.ini b/tests/wpt/meta/html/semantics/the-button-element/interest-target/interesttarget-outline-appearance.tentative.html.ini deleted file mode 100644 index 45513bfcfe5..00000000000 --- a/tests/wpt/meta/html/semantics/the-button-element/interest-target/interesttarget-outline-appearance.tentative.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[interesttarget-outline-appearance.tentative.html] - expected: FAIL diff --git a/tests/wpt/meta/html/webappapis/scripting/events/event-handler-all-global-events.html.ini b/tests/wpt/meta/html/webappapis/scripting/events/event-handler-all-global-events.html.ini index cd77f908ffb..5c286c73da4 100644 --- a/tests/wpt/meta/html/webappapis/scripting/events/event-handler-all-global-events.html.ini +++ b/tests/wpt/meta/html/webappapis/scripting/events/event-handler-all-global-events.html.ini @@ -1,520 +1,16 @@ [event-handler-all-global-events.html] - [onabort: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onabort: the default value must be null] - expected: FAIL - - [onauxclick: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onauxclick: the default value must be null] - expected: FAIL - - [onauxclick: the content attribute must be compiled into a function as the corresponding property] - expected: FAIL - - [onblur: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onblur: the default value must be null] - expected: FAIL - - [oncancel: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [oncancel: the default value must be null] - expected: FAIL - - [oncanplay: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [oncanplay: the default value must be null] - expected: FAIL - - [oncanplaythrough: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [oncanplaythrough: the default value must be null] - expected: FAIL - - [onchange: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onchange: the default value must be null] - expected: FAIL - - [onclick: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onclick: the default value must be null] - expected: FAIL - - [onclose: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onclose: the default value must be null] - expected: FAIL - - [oncontextlost: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [oncontextlost: the default value must be null] - expected: FAIL - - [oncontextlost: the content attribute must be compiled into a function as the corresponding property] - expected: FAIL - - [oncontextmenu: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [oncontextmenu: the default value must be null] - expected: FAIL - - [oncontextrestored: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [oncontextrestored: the default value must be null] - expected: FAIL - - [oncontextrestored: the content attribute must be compiled into a function as the corresponding property] - expected: FAIL - - [oncuechange: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [oncuechange: the default value must be null] - expected: FAIL - - [ondblclick: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [ondblclick: the default value must be null] - expected: FAIL - - [ondrag: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [ondrag: the default value must be null] - expected: FAIL - - [ondragend: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [ondragend: the default value must be null] - expected: FAIL - - [ondragenter: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [ondragenter: the default value must be null] - expected: FAIL - - [ondragleave: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [ondragleave: the default value must be null] - expected: FAIL - - [ondragover: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [ondragover: the default value must be null] - expected: FAIL - - [ondragstart: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [ondragstart: the default value must be null] - expected: FAIL - - [ondrop: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [ondrop: the default value must be null] - expected: FAIL - - [ondurationchange: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [ondurationchange: the default value must be null] - expected: FAIL - - [onemptied: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onemptied: the default value must be null] - expected: FAIL - - [onended: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onended: the default value must be null] - expected: FAIL - - [onfocus: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onfocus: the default value must be null] - expected: FAIL - - [onformdata: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onformdata: the default value must be null] - expected: FAIL - - [oninput: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [oninput: the default value must be null] - expected: FAIL - - [oninvalid: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [oninvalid: the default value must be null] - expected: FAIL - - [onkeydown: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onkeydown: the default value must be null] - expected: FAIL - - [onkeypress: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onkeypress: the default value must be null] - expected: FAIL - - [onkeyup: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onkeyup: the default value must be null] - expected: FAIL - - [onload: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onload: the default value must be null] - expected: FAIL - - [onloadeddata: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onloadeddata: the default value must be null] - expected: FAIL - - [onloadedmetadata: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onloadedmetadata: the default value must be null] - expected: FAIL - - [onloadstart: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onloadstart: the default value must be null] - expected: FAIL - - [onmousedown: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onmousedown: the default value must be null] - expected: FAIL - - [onmouseenter: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onmouseenter: the default value must be null] - expected: FAIL - - [onmouseleave: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onmouseleave: the default value must be null] - expected: FAIL - - [onmousemove: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onmousemove: the default value must be null] - expected: FAIL - - [onmouseout: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onmouseout: the default value must be null] - expected: FAIL - - [onmouseover: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onmouseover: the default value must be null] - expected: FAIL - - [onmouseup: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onmouseup: the default value must be null] - expected: FAIL - - [onpause: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onpause: the default value must be null] - expected: FAIL - - [onplay: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onplay: the default value must be null] - expected: FAIL - - [onplaying: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onplaying: the default value must be null] - expected: FAIL - - [onprogress: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onprogress: the default value must be null] - expected: FAIL - - [onratechange: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onratechange: the default value must be null] - expected: FAIL - - [onreset: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onreset: the default value must be null] - expected: FAIL - - [onresize: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onresize: the default value must be null] - expected: FAIL - - [onscroll: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onscroll: the default value must be null] - expected: FAIL - - [onsecuritypolicyviolation: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onsecuritypolicyviolation: the default value must be null] - expected: FAIL - - [onseeked: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onseeked: the default value must be null] - expected: FAIL - - [onseeking: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onseeking: the default value must be null] - expected: FAIL - - [onselect: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onselect: the default value must be null] - expected: FAIL - - [onslotchange: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onslotchange: the default value must be null] - expected: FAIL - - [onslotchange: the content attribute must be compiled into a function as the corresponding property] - expected: FAIL - - [onstalled: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onstalled: the default value must be null] - expected: FAIL - - [onsubmit: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onsubmit: the default value must be null] - expected: FAIL - - [onsuspend: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onsuspend: the default value must be null] - expected: FAIL - - [ontimeupdate: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [ontimeupdate: the default value must be null] - expected: FAIL - - [ontoggle: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [ontoggle: the default value must be null] - expected: FAIL - - [onvolumechange: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onvolumechange: the default value must be null] - expected: FAIL - - [onwaiting: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onwaiting: the default value must be null] - expected: FAIL - - [onwebkitanimationend: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onwebkitanimationend: the default value must be null] - expected: FAIL - - [onwebkitanimationend: the content attribute must be compiled into a function as the corresponding property] - expected: FAIL - [onwebkitanimationend: the content attribute must execute when an event is dispatched] expected: FAIL - [onwebkitanimationiteration: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onwebkitanimationiteration: the default value must be null] - expected: FAIL - - [onwebkitanimationiteration: the content attribute must be compiled into a function as the corresponding property] - expected: FAIL - [onwebkitanimationiteration: the content attribute must execute when an event is dispatched] expected: FAIL - [onwebkitanimationstart: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onwebkitanimationstart: the default value must be null] - expected: FAIL - - [onwebkitanimationstart: the content attribute must be compiled into a function as the corresponding property] - expected: FAIL - [onwebkitanimationstart: the content attribute must execute when an event is dispatched] expected: FAIL - [onwebkittransitionend: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onwebkittransitionend: the default value must be null] - expected: FAIL - - [onwebkittransitionend: the content attribute must be compiled into a function as the corresponding property] - expected: FAIL - [onwebkittransitionend: the content attribute must execute when an event is dispatched] expected: FAIL - [onwheel: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onwheel: the default value must be null] - expected: FAIL - - [onbeforeinput: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onbeforeinput: the default value must be null] - expected: FAIL - - [onbeforeinput: the content attribute must be compiled into a function as the corresponding property] - expected: FAIL - - [onbeforematch: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onbeforematch: the default value must be null] - expected: FAIL - - [onbeforematch: the content attribute must be compiled into a function as the corresponding property] - expected: FAIL - - [onscrollend: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onscrollend: the default value must be null] - expected: FAIL - - [onscrollend: the content attribute must be compiled into a function as the corresponding property] - expected: FAIL - - [oncopy: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [oncopy: the default value must be null] - expected: FAIL - - [oncut: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [oncut: the default value must be null] - expected: FAIL - - [onpaste: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onpaste: the default value must be null] - expected: FAIL - - [onbeforetoggle: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [onbeforetoggle: the default value must be null] - expected: FAIL - - [onbeforetoggle: the content attribute must be compiled into a function as the corresponding property] - expected: FAIL - - [onauxclick: dispatching an Event at a <meta> element must trigger element.onauxclick] - expected: FAIL - - [onbeforeinput: dispatching an Event at a <meta> element must trigger element.onbeforeinput] - expected: FAIL - - [onbeforematch: dispatching an Event at a <meta> element must trigger element.onbeforematch] - expected: FAIL - - [onbeforetoggle: dispatching an Event at a <meta> element must trigger element.onbeforetoggle] - expected: FAIL - - [oncontextlost: dispatching an Event at a <meta> element must trigger element.oncontextlost] - expected: FAIL - - [oncontextrestored: dispatching an Event at a <meta> element must trigger element.oncontextrestored] - expected: FAIL - - [onscrollend: dispatching an Event at a <meta> element must trigger element.onscrollend] - expected: FAIL - - [onslotchange: dispatching an Event at a <meta> element must trigger element.onslotchange] - expected: FAIL - [onwebkitanimationend: dispatching an Event at a <meta> element must trigger element.onwebkitanimationend] expected: FAIL @@ -526,15 +22,3 @@ [onwebkittransitionend: dispatching an Event at a <meta> element must trigger element.onwebkittransitionend] expected: FAIL - - [oncommand: must be on the appropriate locations for GlobalEventHandlers] - expected: FAIL - - [oncommand: the default value must be null] - expected: FAIL - - [oncommand: the content attribute must be compiled into a function as the corresponding property] - expected: FAIL - - [oncommand: dispatching an Event at a <meta> element must trigger element.oncommand] - expected: FAIL diff --git a/tests/wpt/meta/html/webappapis/scripting/events/event-handler-attributes-body-window.html.ini b/tests/wpt/meta/html/webappapis/scripting/events/event-handler-attributes-body-window.html.ini index deec7c5c132..3b3a37ff7c2 100644 --- a/tests/wpt/meta/html/webappapis/scripting/events/event-handler-attributes-body-window.html.ini +++ b/tests/wpt/meta/html/webappapis/scripting/events/event-handler-attributes-body-window.html.ini @@ -1,181 +1,19 @@ [event-handler-attributes-body-window.html] - [not shadowed auxclick (document.body)] - expected: FAIL - - [not shadowed contextlost (document.body)] - expected: FAIL - - [not shadowed contextrestored (document.body)] - expected: FAIL - - [not shadowed slotchange (document.body)] - expected: FAIL - - [not shadowed webkitanimationend (document.body)] - expected: FAIL - - [not shadowed webkitanimationiteration (document.body)] - expected: FAIL - - [not shadowed webkitanimationstart (document.body)] - expected: FAIL - - [not shadowed webkittransitionend (document.body)] - expected: FAIL - - [not shadowed copy (document.body)] - expected: FAIL - - [not shadowed cut (document.body)] - expected: FAIL - - [not shadowed paste (document.body)] - expected: FAIL - - [not shadowed auxclick (document.createElement("body"))] - expected: FAIL - - [not shadowed contextlost (document.createElement("body"))] - expected: FAIL - - [not shadowed contextrestored (document.createElement("body"))] - expected: FAIL - - [not shadowed slotchange (document.createElement("body"))] - expected: FAIL - - [not shadowed webkitanimationend (document.createElement("body"))] - expected: FAIL - - [not shadowed webkitanimationiteration (document.createElement("body"))] - expected: FAIL - - [not shadowed webkitanimationstart (document.createElement("body"))] - expected: FAIL - - [not shadowed webkittransitionend (document.createElement("body"))] - expected: FAIL - - [not shadowed copy (document.createElement("body"))] - expected: FAIL - - [not shadowed cut (document.createElement("body"))] - expected: FAIL - - [not shadowed paste (document.createElement("body"))] - expected: FAIL - - [not shadowed auxclick (window)] - expected: FAIL - - [not shadowed contextlost (window)] - expected: FAIL - - [not shadowed contextrestored (window)] - expected: FAIL - - [not shadowed slotchange (window)] - expected: FAIL - - [not shadowed webkitanimationend (window)] - expected: FAIL - - [not shadowed webkitanimationiteration (window)] - expected: FAIL - - [not shadowed webkitanimationstart (window)] - expected: FAIL - - [not shadowed webkittransitionend (window)] - expected: FAIL - - [not shadowed beforeinput (document.body)] - expected: FAIL - - [not shadowed beforematch (document.body)] - expected: FAIL - - [not shadowed scrollend (document.body)] - expected: FAIL - - [not shadowed beforeinput (document.createElement("body"))] - expected: FAIL - - [not shadowed beforematch (document.createElement("body"))] - expected: FAIL - - [not shadowed scrollend (document.createElement("body"))] - expected: FAIL - - [not shadowed beforeinput (window)] - expected: FAIL - - [not shadowed beforematch (window)] - expected: FAIL - - [not shadowed scrollend (window)] - expected: FAIL - - [not shadowed beforetoggle (document.body)] - expected: FAIL - - [not shadowed beforetoggle (document.createElement("body"))] - expected: FAIL - - [not shadowed beforetoggle (window)] - expected: FAIL - - [shadowed pagereveal (document.body)] - expected: FAIL - - [shadowed pagereveal removal (document.body)] - expected: FAIL - - [shadowed pagereveal (document.createElement("body"))] - expected: FAIL - [shadowed pagereveal removal (document.createElement("body"))] expected: FAIL - [shadowed pagereveal (window)] - expected: FAIL - [shadowed pagereveal removal (window)] expected: FAIL - [shadowed pageswap (document.body)] - expected: FAIL - - [shadowed pageswap removal (document.body)] - expected: FAIL - - [shadowed pageswap (document.createElement("body"))] - expected: FAIL - [shadowed pageswap removal (document.createElement("body"))] expected: FAIL - [shadowed pageswap (window)] - expected: FAIL - [shadowed pageswap removal (window)] expected: FAIL [shadowed blur on body fires when event dispatched on window] expected: FAIL - [shadowed messageerror on body fires when event dispatched on window] - expected: FAIL - - [shadowed pagereveal on body fires when event dispatched on window] - expected: FAIL - - [shadowed pageswap on body fires when event dispatched on window] - expected: FAIL - - [shadowed rejectionhandled on body fires when event dispatched on window] - expected: FAIL - [shadowed focus removal (document.createElement("body"))] expected: FAIL @@ -206,6 +44,9 @@ [shadowed message removal (document.createElement("body"))] expected: FAIL + [shadowed messageerror removal (document.createElement("body"))] + expected: FAIL + [shadowed offline removal (document.createElement("body"))] expected: FAIL @@ -233,6 +74,9 @@ [shadowed load removal (window)] expected: FAIL + [shadowed rejectionhandled removal (document.createElement("body"))] + expected: FAIL + [shadowed resize removal (window)] expected: FAIL @@ -257,6 +101,9 @@ [shadowed message removal (window)] expected: FAIL + [shadowed messageerror removal (window)] + expected: FAIL + [shadowed offline removal (window)] expected: FAIL @@ -272,17 +119,11 @@ [shadowed popstate removal (window)] expected: FAIL - [shadowed storage removal (window)] + [shadowed rejectionhandled removal (window)] expected: FAIL - [shadowed unload removal (window)] - expected: FAIL - - [not shadowed command (document.body)] - expected: FAIL - - [not shadowed command (document.createElement("body"))] + [shadowed storage removal (window)] expected: FAIL - [not shadowed command (window)] + [shadowed unload removal (window)] expected: FAIL diff --git a/tests/wpt/meta/html/webappapis/scripting/events/event-handler-attributes-frameset-window.html.ini b/tests/wpt/meta/html/webappapis/scripting/events/event-handler-attributes-frameset-window.html.ini index 4484356912c..b18a8cf6d5b 100644 --- a/tests/wpt/meta/html/webappapis/scripting/events/event-handler-attributes-frameset-window.html.ini +++ b/tests/wpt/meta/html/webappapis/scripting/events/event-handler-attributes-frameset-window.html.ini @@ -1,166 +1,4 @@ [event-handler-attributes-frameset-window.html] - [not shadowed auxclick (document.body)] - expected: FAIL - - [not shadowed contextlost (document.body)] - expected: FAIL - - [not shadowed contextrestored (document.body)] - expected: FAIL - - [not shadowed slotchange (document.body)] - expected: FAIL - - [not shadowed webkitanimationend (document.body)] - expected: FAIL - - [not shadowed webkitanimationiteration (document.body)] - expected: FAIL - - [not shadowed webkitanimationstart (document.body)] - expected: FAIL - - [not shadowed webkittransitionend (document.body)] - expected: FAIL - - [not shadowed copy (document.body)] - expected: FAIL - - [not shadowed cut (document.body)] - expected: FAIL - - [not shadowed paste (document.body)] - expected: FAIL - - [not shadowed auxclick (document.createElement("frameset"))] - expected: FAIL - - [not shadowed contextlost (document.createElement("frameset"))] - expected: FAIL - - [not shadowed contextrestored (document.createElement("frameset"))] - expected: FAIL - - [not shadowed slotchange (document.createElement("frameset"))] - expected: FAIL - - [not shadowed webkitanimationend (document.createElement("frameset"))] - expected: FAIL - - [not shadowed webkitanimationiteration (document.createElement("frameset"))] - expected: FAIL - - [not shadowed webkitanimationstart (document.createElement("frameset"))] - expected: FAIL - - [not shadowed webkittransitionend (document.createElement("frameset"))] - expected: FAIL - - [not shadowed copy (document.createElement("frameset"))] - expected: FAIL - - [not shadowed cut (document.createElement("frameset"))] - expected: FAIL - - [not shadowed paste (document.createElement("frameset"))] - expected: FAIL - - [not shadowed auxclick (window)] - expected: FAIL - - [not shadowed contextlost (window)] - expected: FAIL - - [not shadowed contextrestored (window)] - expected: FAIL - - [not shadowed slotchange (window)] - expected: FAIL - - [not shadowed webkitanimationend (window)] - expected: FAIL - - [not shadowed webkitanimationiteration (window)] - expected: FAIL - - [not shadowed webkitanimationstart (window)] - expected: FAIL - - [not shadowed webkittransitionend (window)] - expected: FAIL - - [not shadowed beforeinput (document.body)] - expected: FAIL - - [not shadowed beforematch (document.body)] - expected: FAIL - - [not shadowed scrollend (document.body)] - expected: FAIL - - [not shadowed beforeinput (document.createElement("frameset"))] - expected: FAIL - - [not shadowed beforematch (document.createElement("frameset"))] - expected: FAIL - - [not shadowed scrollend (document.createElement("frameset"))] - expected: FAIL - - [not shadowed beforeinput (window)] - expected: FAIL - - [not shadowed beforematch (window)] - expected: FAIL - - [not shadowed scrollend (window)] - expected: FAIL - - [not shadowed beforetoggle (document.body)] - expected: FAIL - - [not shadowed beforetoggle (document.createElement("frameset"))] - expected: FAIL - - [not shadowed beforetoggle (window)] - expected: FAIL - - [shadowed pagereveal (document.body)] - expected: FAIL - - [shadowed pagereveal removal (document.body)] - expected: FAIL - - [shadowed pagereveal (document.createElement("frameset"))] - expected: FAIL - - [shadowed pagereveal removal (document.createElement("frameset"))] - expected: FAIL - - [shadowed pagereveal (window)] - expected: FAIL - - [shadowed pagereveal removal (window)] - expected: FAIL - - [shadowed pageswap (document.body)] - expected: FAIL - - [shadowed pageswap removal (document.body)] - expected: FAIL - - [shadowed pageswap (document.createElement("frameset"))] - expected: FAIL - - [shadowed pageswap removal (document.createElement("frameset"))] - expected: FAIL - - [shadowed pageswap (window)] - expected: FAIL - - [shadowed pageswap removal (window)] - expected: FAIL - [shadowed blur on body fires when event dispatched on window] expected: FAIL @@ -226,12 +64,3 @@ [shadowed unload on body fires when event dispatched on window] expected: FAIL - - [not shadowed command (document.body)] - expected: FAIL - - [not shadowed command (document.createElement("frameset"))] - expected: FAIL - - [not shadowed command (window)] - expected: FAIL diff --git a/tests/wpt/meta/html/webappapis/scripting/events/event-handler-attributes-windowless-body.html.ini b/tests/wpt/meta/html/webappapis/scripting/events/event-handler-attributes-windowless-body.html.ini deleted file mode 100644 index db4d6e3321d..00000000000 --- a/tests/wpt/meta/html/webappapis/scripting/events/event-handler-attributes-windowless-body.html.ini +++ /dev/null @@ -1,102 +0,0 @@ -[event-handler-attributes-windowless-body.html] - [auxclick is unaffected on a windowless body] - expected: FAIL - - [contextlost is unaffected on a windowless body] - expected: FAIL - - [contextrestored is unaffected on a windowless body] - expected: FAIL - - [slotchange is unaffected on a windowless body] - expected: FAIL - - [webkitanimationend is unaffected on a windowless body] - expected: FAIL - - [webkitanimationiteration is unaffected on a windowless body] - expected: FAIL - - [webkitanimationstart is unaffected on a windowless body] - expected: FAIL - - [webkittransitionend is unaffected on a windowless body] - expected: FAIL - - [auxclick is unaffected on a windowless frameset] - expected: FAIL - - [contextlost is unaffected on a windowless frameset] - expected: FAIL - - [contextrestored is unaffected on a windowless frameset] - expected: FAIL - - [slotchange is unaffected on a windowless frameset] - expected: FAIL - - [webkitanimationend is unaffected on a windowless frameset] - expected: FAIL - - [webkitanimationiteration is unaffected on a windowless frameset] - expected: FAIL - - [webkitanimationstart is unaffected on a windowless frameset] - expected: FAIL - - [webkittransitionend is unaffected on a windowless frameset] - expected: FAIL - - [beforeinput is unaffected on a windowless body] - expected: FAIL - - [beforematch is unaffected on a windowless body] - expected: FAIL - - [scrollend is unaffected on a windowless body] - expected: FAIL - - [beforeinput is unaffected on a windowless frameset] - expected: FAIL - - [beforematch is unaffected on a windowless frameset] - expected: FAIL - - [scrollend is unaffected on a windowless frameset] - expected: FAIL - - [beforetoggle is unaffected on a windowless body] - expected: FAIL - - [beforetoggle is unaffected on a windowless frameset] - expected: FAIL - - [Return null when getting the pagereveal event handler of a windowless body] - expected: FAIL - - [Ignore setting of pagereveal window event handlers on windowless body] - expected: FAIL - - [Return null when getting the pagereveal event handler of a windowless frameset] - expected: FAIL - - [Ignore setting of pagereveal window event handlers on windowless frameset] - expected: FAIL - - [Return null when getting the pageswap event handler of a windowless body] - expected: FAIL - - [Ignore setting of pageswap window event handlers on windowless body] - expected: FAIL - - [Return null when getting the pageswap event handler of a windowless frameset] - expected: FAIL - - [Ignore setting of pageswap window event handlers on windowless frameset] - expected: FAIL - - [command is unaffected on a windowless body] - expected: FAIL - - [command is unaffected on a windowless frameset] - expected: FAIL diff --git a/tests/wpt/meta/streams/transferable/transfer-with-messageport.window.js.ini b/tests/wpt/meta/streams/transferable/transfer-with-messageport.window.js.ini deleted file mode 100644 index fc885c5a594..00000000000 --- a/tests/wpt/meta/streams/transferable/transfer-with-messageport.window.js.ini +++ /dev/null @@ -1,12 +0,0 @@ -[transfer-with-messageport.window.html] - [Transferring a MessagePort with a TransformStream should set `.ports`] - expected: FAIL - - [Transferring a MessagePort with a TransformStream should set `.ports`, advanced] - expected: FAIL - - [Transferring a MessagePort with multiple streams should set `.ports`] - expected: FAIL - - [TransformStream must not be serializable] - expected: FAIL diff --git a/tests/wpt/meta/streams/transferable/transform-stream-members.any.js.ini b/tests/wpt/meta/streams/transferable/transform-stream-members.any.js.ini index c5d5f43d1a4..4414054a1cf 100644 --- a/tests/wpt/meta/streams/transferable/transform-stream-members.any.js.ini +++ b/tests/wpt/meta/streams/transferable/transform-stream-members.any.js.ini @@ -10,14 +10,10 @@ [transform-stream-members.any.shadowrealm-in-window.html] expected: ERROR -[transform-stream-members.any.html] - expected: ERROR [transform-stream-members.https.any.shadowrealm-in-serviceworker.html] expected: ERROR -[transform-stream-members.any.worker.html] - expected: ERROR [transform-stream-members.any.shadowrealm-in-dedicatedworker.html] expected: ERROR diff --git a/tests/wpt/meta/streams/transferable/transform-stream.html.ini b/tests/wpt/meta/streams/transferable/transform-stream.html.ini index dc6fe8a6c75..a5097f80874 100644 --- a/tests/wpt/meta/streams/transferable/transform-stream.html.ini +++ b/tests/wpt/meta/streams/transferable/transform-stream.html.ini @@ -1,15 +1,3 @@ [transform-stream.html] - [window.postMessage should be able to transfer a TransformStream] - expected: FAIL - - [a TransformStream with a locked writable should not be transferable] - expected: FAIL - - [a TransformStream with a locked readable should not be transferable] - expected: FAIL - - [a TransformStream with both sides locked should not be transferable] - expected: FAIL - [piping through transferred transforms should work] expected: FAIL diff --git a/tests/wpt/meta/streams/transferable/writable-stream.html.ini b/tests/wpt/meta/streams/transferable/writable-stream.html.ini deleted file mode 100644 index 47326208f88..00000000000 --- a/tests/wpt/meta/streams/transferable/writable-stream.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[writable-stream.html] - [window.postMessage should be able to transfer a {readable, writable} pair] - expected: FAIL diff --git a/tests/wpt/meta/streams/transform-streams/backpressure.any.js.ini b/tests/wpt/meta/streams/transform-streams/backpressure.any.js.ini new file mode 100644 index 00000000000..099d3a6f2e0 --- /dev/null +++ b/tests/wpt/meta/streams/transform-streams/backpressure.any.js.ini @@ -0,0 +1,23 @@ +[backpressure.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[backpressure.any.shadowrealm-in-window.html] + expected: ERROR + +[backpressure.any.serviceworker.html] + expected: ERROR + +[backpressure.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[backpressure.any.sharedworker.html] + expected: ERROR + +[backpressure.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[backpressure.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[backpressure.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR
\ No newline at end of file diff --git a/tests/wpt/meta/streams/transform-streams/cancel.any.js.ini b/tests/wpt/meta/streams/transform-streams/cancel.any.js.ini new file mode 100644 index 00000000000..7e5a1b9af50 --- /dev/null +++ b/tests/wpt/meta/streams/transform-streams/cancel.any.js.ini @@ -0,0 +1,32 @@ +[cancel.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[cancel.any.serviceworker.html] + expected: ERROR + +[cancel.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[cancel.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[cancel.any.sharedworker.html] + expected: ERROR + +[cancel.any.shadowrealm-in-window.html] + expected: ERROR + +[cancel.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[cancel.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[cancel.any.worker.html] + [readable.cancel() and a parallel writable.close() should reject if a transformer.cancel() calls controller.error()] + expected: FAIL + + +[cancel.any.html] + [readable.cancel() and a parallel writable.close() should reject if a transformer.cancel() calls controller.error()] + expected: FAIL diff --git a/tests/wpt/meta/streams/transform-streams/errors.any.js.ini b/tests/wpt/meta/streams/transform-streams/errors.any.js.ini new file mode 100644 index 00000000000..02eaa76ca8e --- /dev/null +++ b/tests/wpt/meta/streams/transform-streams/errors.any.js.ini @@ -0,0 +1,32 @@ +[errors.any.sharedworker.html] + expected: ERROR + +[errors.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[errors.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[errors.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[errors.any.serviceworker.html] + expected: ERROR + +[errors.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[errors.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[errors.any.shadowrealm-in-window.html] + expected: ERROR + +[errors.any.html] + [abort should set the close reason for the writable when it happens before cancel during start, and cancel should reject] + expected: FAIL + + +[errors.any.worker.html] + [abort should set the close reason for the writable when it happens before cancel during start, and cancel should reject] + expected: FAIL diff --git a/tests/wpt/meta/streams/transform-streams/flush.any.js.ini b/tests/wpt/meta/streams/transform-streams/flush.any.js.ini new file mode 100644 index 00000000000..a2ee0101993 --- /dev/null +++ b/tests/wpt/meta/streams/transform-streams/flush.any.js.ini @@ -0,0 +1,23 @@ +[flush.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[flush.any.shadowrealm-in-window.html] + expected: ERROR + +[flush.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[flush.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[flush.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[flush.any.sharedworker.html] + expected: ERROR + +[flush.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[flush.any.serviceworker.html] + expected: ERROR
\ No newline at end of file diff --git a/tests/wpt/meta/streams/transform-streams/general.any.js.ini b/tests/wpt/meta/streams/transform-streams/general.any.js.ini new file mode 100644 index 00000000000..11dfc7d4ece --- /dev/null +++ b/tests/wpt/meta/streams/transform-streams/general.any.js.ini @@ -0,0 +1,23 @@ +[general.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[general.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[general.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[general.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[general.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[general.any.serviceworker.html] + expected: ERROR + +[general.any.shadowrealm-in-window.html] + expected: ERROR + +[general.any.sharedworker.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/transform-streams/lipfuzz.any.js.ini b/tests/wpt/meta/streams/transform-streams/lipfuzz.any.js.ini new file mode 100644 index 00000000000..5af2be4a1ae --- /dev/null +++ b/tests/wpt/meta/streams/transform-streams/lipfuzz.any.js.ini @@ -0,0 +1,23 @@ +[lipfuzz.any.shadowrealm-in-window.html] + expected: ERROR + +[lipfuzz.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[lipfuzz.any.serviceworker.html] + expected: ERROR + +[lipfuzz.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[lipfuzz.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[lipfuzz.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[lipfuzz.any.sharedworker.html] + expected: ERROR + +[lipfuzz.any.shadowrealm-in-sharedworker.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/transform-streams/patched-global.any.js.ini b/tests/wpt/meta/streams/transform-streams/patched-global.any.js.ini new file mode 100644 index 00000000000..06a324cb060 --- /dev/null +++ b/tests/wpt/meta/streams/transform-streams/patched-global.any.js.ini @@ -0,0 +1,23 @@ +[patched-global.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[patched-global.any.shadowrealm-in-window.html] + expected: ERROR + +[patched-global.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[patched-global.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[patched-global.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[patched-global.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[patched-global.any.sharedworker.html] + expected: ERROR + +[patched-global.any.serviceworker.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/transform-streams/properties.any.js.ini b/tests/wpt/meta/streams/transform-streams/properties.any.js.ini new file mode 100644 index 00000000000..f5573ee57e4 --- /dev/null +++ b/tests/wpt/meta/streams/transform-streams/properties.any.js.ini @@ -0,0 +1,23 @@ +[properties.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[properties.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[properties.any.shadowrealm-in-window.html] + expected: ERROR + +[properties.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[properties.any.sharedworker.html] + expected: ERROR + +[properties.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[properties.any.serviceworker.html] + expected: ERROR + +[properties.any.shadowrealm-in-sharedworker.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/transform-streams/reentrant-strategies.any.js.ini b/tests/wpt/meta/streams/transform-streams/reentrant-strategies.any.js.ini new file mode 100644 index 00000000000..1c6b9fd51da --- /dev/null +++ b/tests/wpt/meta/streams/transform-streams/reentrant-strategies.any.js.ini @@ -0,0 +1,23 @@ +[reentrant-strategies.any.shadowrealm-in-window.html] + expected: ERROR + +[reentrant-strategies.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[reentrant-strategies.any.sharedworker.html] + expected: ERROR + +[reentrant-strategies.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[reentrant-strategies.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[reentrant-strategies.any.serviceworker.html] + expected: ERROR + +[reentrant-strategies.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[reentrant-strategies.any.shadowrealm-in-shadowrealm.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/transform-streams/strategies.any.js.ini b/tests/wpt/meta/streams/transform-streams/strategies.any.js.ini new file mode 100644 index 00000000000..52c18b67fd4 --- /dev/null +++ b/tests/wpt/meta/streams/transform-streams/strategies.any.js.ini @@ -0,0 +1,24 @@ +[strategies.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[strategies.any.serviceworker.html] + expected: ERROR + +[strategies.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[strategies.any.shadowrealm-in-window.html] + expected: ERROR + +[strategies.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[strategies.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[strategies.any.sharedworker.html] + expected: ERROR + +[strategies.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + diff --git a/tests/wpt/meta/streams/transform-streams/terminate.any.js.ini b/tests/wpt/meta/streams/transform-streams/terminate.any.js.ini new file mode 100644 index 00000000000..c81b1e40d1e --- /dev/null +++ b/tests/wpt/meta/streams/transform-streams/terminate.any.js.ini @@ -0,0 +1,23 @@ +[terminate.any.serviceworker.html] + expected: ERROR + +[terminate.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[terminate.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[terminate.any.shadowrealm-in-window.html] + expected: ERROR + +[terminate.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[terminate.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[terminate.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[terminate.any.sharedworker.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/writable-streams/crashtests/garbage-collection.any.js.ini b/tests/wpt/meta/streams/writable-streams/crashtests/garbage-collection.any.js.ini new file mode 100644 index 00000000000..6204bdc21da --- /dev/null +++ b/tests/wpt/meta/streams/writable-streams/crashtests/garbage-collection.any.js.ini @@ -0,0 +1,9 @@ +[garbage-collection.any.html] + +[garbage-collection.any.sharedworker.html] + expected: ERROR + +[garbage-collection.any.serviceworker.html] + expected: ERROR + +[garbage-collection.any.worker.html] diff --git a/tests/wpt/meta/subresource-integrity/tentative/integrity-policy/parsing.https.html.ini b/tests/wpt/meta/subresource-integrity/tentative/integrity-policy/parsing.https.html.ini new file mode 100644 index 00000000000..8634900f9c2 --- /dev/null +++ b/tests/wpt/meta/subresource-integrity/tentative/integrity-policy/parsing.https.html.ini @@ -0,0 +1,9 @@ +[parsing.https.html?type=enforce] + [Ensure that test is working with a valid destination] + expected: FAIL + + [Ensure that test is working with a valid destination and source] + expected: FAIL + + +[parsing.https.html?type=report] diff --git a/tests/wpt/meta/subresource-integrity/tentative/integrity-policy/script.https.html.ini b/tests/wpt/meta/subresource-integrity/tentative/integrity-policy/script.https.html.ini new file mode 100644 index 00000000000..3563393c660 --- /dev/null +++ b/tests/wpt/meta/subresource-integrity/tentative/integrity-policy/script.https.html.ini @@ -0,0 +1,15 @@ +[script.https.html] + [Ensure that a script without integrity did not run] + expected: FAIL + + [Ensure that a script with unknown integrity algorithm did not run] + expected: FAIL + + [Ensure that a script without integrity algorithm runs and gets reported in report-only mode] + expected: FAIL + + [Ensure that a no-cors script gets blocked] + expected: FAIL + + [Ensure that ReportingObserver gets called without endpoints] + expected: FAIL diff --git a/tests/wpt/meta/trusted-types/HTMLElement-generic.html.ini b/tests/wpt/meta/trusted-types/HTMLElement-generic.html.ini deleted file mode 100644 index 8ed1a875db3..00000000000 --- a/tests/wpt/meta/trusted-types/HTMLElement-generic.html.ini +++ /dev/null @@ -1,24 +0,0 @@ -[HTMLElement-generic.html] - [TT enabled: div.innerHTML\n = String on a\n connected element\n ] - expected: FAIL - - [TT enabled: div.innerHTML\n = String on a\n non-connected element\n ] - expected: FAIL - - [TT enabled: iframe.srcdoc\n = String on a\n connected element\n ] - expected: FAIL - - [TT enabled: iframe.srcdoc\n = String on a\n non-connected element\n ] - expected: FAIL - - [TT enabled: div.innerHTML\n = String on a\n connected element\n after removing the "require-trusted-types-for 'script' directive] - expected: FAIL - - [TT enabled: div.innerHTML\n = String on a\n non-connected element\n after removing the "require-trusted-types-for 'script' directive] - expected: FAIL - - [TT enabled: iframe.srcdoc\n = String on a\n connected element\n after removing the "require-trusted-types-for 'script' directive] - expected: FAIL - - [TT enabled: iframe.srcdoc\n = String on a\n non-connected element\n after removing the "require-trusted-types-for 'script' directive] - expected: FAIL diff --git a/tests/wpt/meta/trusted-types/block-string-assignment-to-Element-insertAdjacentHTML.html.ini b/tests/wpt/meta/trusted-types/block-string-assignment-to-Element-insertAdjacentHTML.html.ini deleted file mode 100644 index 1306015ff46..00000000000 --- a/tests/wpt/meta/trusted-types/block-string-assignment-to-Element-insertAdjacentHTML.html.ini +++ /dev/null @@ -1,12 +0,0 @@ -[block-string-assignment-to-Element-insertAdjacentHTML.html] - [`insertAdjacentHTML(string)` throws.] - expected: FAIL - - [`insertAdjacentHTML(string)` still throws TypeError when position invalid.] - expected: FAIL - - [`insertAdjacentHTML(null)` throws.] - expected: FAIL - - [`insertAdjacentHTML(string)` assigned via default policy (successful HTML transformation).] - expected: FAIL diff --git a/tests/wpt/meta/trusted-types/block-string-assignment-to-Element-outerHTML.html.ini b/tests/wpt/meta/trusted-types/block-string-assignment-to-Element-outerHTML.html.ini deleted file mode 100644 index e5abefcc766..00000000000 --- a/tests/wpt/meta/trusted-types/block-string-assignment-to-Element-outerHTML.html.ini +++ /dev/null @@ -1,15 +0,0 @@ -[block-string-assignment-to-Element-outerHTML.html] - [`outerHTML = string` throws.] - expected: FAIL - - [`outerHTML = string` throws TypeError even when parent is a document.] - expected: FAIL - - [`outerHTML = null` throws.] - expected: FAIL - - [`outerHTML = string` assigned via default policy (successful HTML transformation).] - expected: FAIL - - [`outerHTML = null` assigned via default policy does not throw] - expected: FAIL diff --git a/tests/wpt/meta/trusted-types/block-string-assignment-to-Element-setAttribute.html.ini b/tests/wpt/meta/trusted-types/block-string-assignment-to-Element-setAttribute.html.ini index 7ad472ef325..0df917b86be 100644 --- a/tests/wpt/meta/trusted-types/block-string-assignment-to-Element-setAttribute.html.ini +++ b/tests/wpt/meta/trusted-types/block-string-assignment-to-Element-setAttribute.html.ini @@ -20,9 +20,6 @@ [div.onclick's mutationobservers receive the default policy's value.] expected: FAIL - [iframe.srcdoc accepts string and null after default policy was created.] - expected: FAIL - [div.onclick accepts string and null after default policy was created.] expected: FAIL diff --git a/tests/wpt/meta/trusted-types/block-string-assignment-to-Element-setHTMLUnsafe.html.ini b/tests/wpt/meta/trusted-types/block-string-assignment-to-Element-setHTMLUnsafe.html.ini deleted file mode 100644 index e6b59ba6714..00000000000 --- a/tests/wpt/meta/trusted-types/block-string-assignment-to-Element-setHTMLUnsafe.html.ini +++ /dev/null @@ -1,12 +0,0 @@ -[block-string-assignment-to-Element-setHTMLUnsafe.html] - [`element.setHTMLUnsafe(string)` throws.] - expected: FAIL - - [`element.setHTMLUnsafe(null)` throws.] - expected: FAIL - - [`element.setHTMLUnsafe(string)` assigned via default policy (successful HTML transformation).] - expected: FAIL - - [`element.setHTMLUnsafe(string)` assigned via default policy does not throw] - expected: FAIL diff --git a/tests/wpt/meta/trusted-types/block-string-assignment-to-HTMLElement-generic.html.ini b/tests/wpt/meta/trusted-types/block-string-assignment-to-HTMLElement-generic.html.ini deleted file mode 100644 index ed3a70b31ab..00000000000 --- a/tests/wpt/meta/trusted-types/block-string-assignment-to-HTMLElement-generic.html.ini +++ /dev/null @@ -1,12 +0,0 @@ -[block-string-assignment-to-HTMLElement-generic.html] - [div.innerHTML accepts only TrustedHTML] - expected: FAIL - - [iframe.srcdoc accepts only TrustedHTML] - expected: FAIL - - [div.innerHTML accepts string and null after default policy was created] - expected: FAIL - - [iframe.srcdoc accepts string and null after default policy was created] - expected: FAIL diff --git a/tests/wpt/meta/trusted-types/block-string-assignment-to-HTMLIFrameElement-srcdoc.html.ini b/tests/wpt/meta/trusted-types/block-string-assignment-to-HTMLIFrameElement-srcdoc.html.ini deleted file mode 100644 index e7747a96001..00000000000 --- a/tests/wpt/meta/trusted-types/block-string-assignment-to-HTMLIFrameElement-srcdoc.html.ini +++ /dev/null @@ -1,9 +0,0 @@ -[block-string-assignment-to-HTMLIFrameElement-srcdoc.html] - [`iframe.srcdoc = string` throws.] - expected: FAIL - - [`iframe.srcdoc = null` throws.] - expected: FAIL - - [`iframe.srcdoc = string` assigned via default policy (successful HTML transformation).] - expected: FAIL diff --git a/tests/wpt/meta/trusted-types/block-string-assignment-to-text-and-url-sinks.html.ini b/tests/wpt/meta/trusted-types/block-string-assignment-to-text-and-url-sinks.html.ini deleted file mode 100644 index 1e9f6e44f44..00000000000 --- a/tests/wpt/meta/trusted-types/block-string-assignment-to-text-and-url-sinks.html.ini +++ /dev/null @@ -1,9 +0,0 @@ -[block-string-assignment-to-text-and-url-sinks.html] - [Setting HTMLDivElement.innerHTML to a plain string] - expected: FAIL - - [Setting HTMLScriptElement.innerHTML to a plain string] - expected: FAIL - - [Setting SVGScriptElement.innerHTML to a plain string] - expected: FAIL diff --git a/tests/wpt/meta/trusted-types/default-policy.html.ini b/tests/wpt/meta/trusted-types/default-policy.html.ini deleted file mode 100644 index 15588646951..00000000000 --- a/tests/wpt/meta/trusted-types/default-policy.html.ini +++ /dev/null @@ -1,22 +0,0 @@ -[default-policy.html] - expected: TIMEOUT - [Count SecurityPolicyViolation events.] - expected: TIMEOUT - - [div.innerHTML no default policy] - expected: FAIL - - [div.innerHTML default] - expected: FAIL - - [div.innerHTML null] - expected: FAIL - - [div.innerHTML throw] - expected: FAIL - - [div.innerHTML undefined] - expected: FAIL - - [div.innerHTML typeerror] - expected: FAIL diff --git a/tests/wpt/meta/trusted-types/empty-default-policy.html.ini b/tests/wpt/meta/trusted-types/empty-default-policy.html.ini deleted file mode 100644 index 4f06e4c971f..00000000000 --- a/tests/wpt/meta/trusted-types/empty-default-policy.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[empty-default-policy.html] - expected: TIMEOUT - [Count SecurityPolicyViolation events.] - expected: TIMEOUT - - [div.innerHTML default] - expected: FAIL diff --git a/tests/wpt/meta/trusted-types/require-trusted-types-for-TypeError-belongs-to-the-global-object-realm.html.ini b/tests/wpt/meta/trusted-types/require-trusted-types-for-TypeError-belongs-to-the-global-object-realm.html.ini index df0dfe485ed..63099aaaaff 100644 --- a/tests/wpt/meta/trusted-types/require-trusted-types-for-TypeError-belongs-to-the-global-object-realm.html.ini +++ b/tests/wpt/meta/trusted-types/require-trusted-types-for-TypeError-belongs-to-the-global-object-realm.html.ini @@ -1,6 +1,3 @@ [require-trusted-types-for-TypeError-belongs-to-the-global-object-realm.html] - [Setting innerHTML on a node inserted by the parser.] - expected: FAIL - [Setting innerHTML on a node adopted from a subframe.] expected: FAIL diff --git a/tests/wpt/meta/trusted-types/require-trusted-types-for-report-only.html.ini b/tests/wpt/meta/trusted-types/require-trusted-types-for-report-only.html.ini deleted file mode 100644 index 39ec281d5f2..00000000000 --- a/tests/wpt/meta/trusted-types/require-trusted-types-for-report-only.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[require-trusted-types-for-report-only.html] - [Require trusted types for 'script' block create HTML.] - expected: FAIL diff --git a/tests/wpt/meta/trusted-types/require-trusted-types-for.html.ini b/tests/wpt/meta/trusted-types/require-trusted-types-for.html.ini deleted file mode 100644 index 38d5f9eb35a..00000000000 --- a/tests/wpt/meta/trusted-types/require-trusted-types-for.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[require-trusted-types-for.html] - [Require trusted types for 'script' block create HTML.] - expected: FAIL diff --git a/tests/wpt/meta/trusted-types/should-sink-type-mismatch-violation-be-blocked-by-csp-001.html.ini b/tests/wpt/meta/trusted-types/should-sink-type-mismatch-violation-be-blocked-by-csp-001.html.ini index 57cd759f8bf..d3218647e91 100644 --- a/tests/wpt/meta/trusted-types/should-sink-type-mismatch-violation-be-blocked-by-csp-001.html.ini +++ b/tests/wpt/meta/trusted-types/should-sink-type-mismatch-violation-be-blocked-by-csp-001.html.ini @@ -1,20 +1,5 @@ [should-sink-type-mismatch-violation-be-blocked-by-csp-001.html] expected: TIMEOUT - [Multiple enforce require-trusted-types-for directives.] - expected: FAIL - - [Multiple report-only require-trusted-types-for directives.] - expected: FAIL - - [One violated report-only require-trusted-types-for directive followed by multiple enforce directives] - expected: FAIL - - [One violated enforce require-trusted-types-for directive followed by multiple report-only directives] - expected: FAIL - - [Mixing enforce and report-only require-trusted-types-for directives.] - expected: FAIL - [directive "require-trusted-types-for 'script'%09'script'%0A'script'%0C'script'%0D'script'%20'script'" (required-ascii-whitespace)] expected: TIMEOUT @@ -33,9 +18,6 @@ [directive "require-trusted-types-for unquoted-invalid 'script' also-unquoted-invalid (unknown sink group)] expected: NOTRUN - [directive "require-trusted-types-for 'invalid'%09'script'" (required-ascii-whitespace)] - expected: FAIL - [directive "require-trusted-types-for 'invalid'%0A%20'script'" (required-ascii-whitespace)] expected: TIMEOUT diff --git a/tests/wpt/meta/trusted-types/trusted-types-createHTMLDocument.html.ini b/tests/wpt/meta/trusted-types/trusted-types-createHTMLDocument.html.ini deleted file mode 100644 index cca7dc42f2b..00000000000 --- a/tests/wpt/meta/trusted-types/trusted-types-createHTMLDocument.html.ini +++ /dev/null @@ -1,24 +0,0 @@ -[trusted-types-createHTMLDocument.html] - [Trusted Type instances created in the main doc can be used. (document)] - expected: FAIL - - [Trusted Type instances created in the main doc can be used. (createHTMLDocument)] - expected: FAIL - - [Trusted Type instances created in the main doc can be used. (DOMParser)] - expected: FAIL - - [Trusted Type instances created in the main doc can be used. (XHR)] - expected: FAIL - - [Default policy applies. (document)] - expected: FAIL - - [Default policy applies. (createHTMLDocument)] - expected: FAIL - - [Default policy applies. (DOMParser)] - expected: FAIL - - [Default policy applies. (XHR)] - expected: FAIL diff --git a/tests/wpt/meta/trusted-types/trusted-types-event-handlers.html.ini b/tests/wpt/meta/trusted-types/trusted-types-event-handlers.html.ini index 225c4d84121..205dd5886a8 100644 --- a/tests/wpt/meta/trusted-types/trusted-types-event-handlers.html.ini +++ b/tests/wpt/meta/trusted-types/trusted-types-event-handlers.html.ini @@ -23,9 +23,21 @@ [Event handler div.onpaste should be blocked.] expected: FAIL + [Event handler div.onauxclick should be blocked.] + expected: FAIL + [Event handler div.onabort should be blocked.] expected: FAIL + [Event handler div.onbeforeinput should be blocked.] + expected: FAIL + + [Event handler div.onbeforematch should be blocked.] + expected: FAIL + + [Event handler div.onbeforetoggle should be blocked.] + expected: FAIL + [Event handler div.onblur should be blocked.] expected: FAIL @@ -47,9 +59,18 @@ [Event handler div.onclose should be blocked.] expected: FAIL + [Event handler div.oncommand should be blocked.] + expected: FAIL + [Event handler div.oncontextmenu should be blocked.] expected: FAIL + [Event handler div.oncontextlost should be blocked.] + expected: FAIL + + [Event handler div.oncontextrestored should be blocked.] + expected: FAIL + [Event handler div.oncuechange should be blocked.] expected: FAIL @@ -173,6 +194,9 @@ [Event handler div.onscroll should be blocked.] expected: FAIL + [Event handler div.onscrollend should be blocked.] + expected: FAIL + [Event handler div.onsecuritypolicyviolation should be blocked.] expected: FAIL @@ -188,6 +212,9 @@ [Event handler div.onshow should be blocked.] expected: FAIL + [Event handler div.onslotchange should be blocked.] + expected: FAIL + [Event handler div.onstalled should be blocked.] expected: FAIL @@ -229,3 +256,15 @@ [Event handler div.onselectionchange should be blocked.] expected: FAIL + + [Event handler div.onwebkitanimationend should be blocked.] + expected: FAIL + + [Event handler div.onwebkitanimationiteration should be blocked.] + expected: FAIL + + [Event handler div.onwebkitanimationstart should be blocked.] + expected: FAIL + + [Event handler div.onwebkittransitionend should be blocked.] + expected: FAIL diff --git a/tests/wpt/meta/trusted-types/trusted-types-report-only.html.ini b/tests/wpt/meta/trusted-types/trusted-types-report-only.html.ini deleted file mode 100644 index 253b126c18f..00000000000 --- a/tests/wpt/meta/trusted-types/trusted-types-report-only.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[trusted-types-report-only.html] - [Trusted Type violation report-only: assign string to html] - expected: FAIL - - [Trusted Type violation report-only: assign string to script content] - expected: FAIL diff --git a/tests/wpt/meta/trusted-types/trusted-types-reporting-for-Element-innerHTML.html.ini b/tests/wpt/meta/trusted-types/trusted-types-reporting-for-Element-innerHTML.html.ini deleted file mode 100644 index ae31cf41b62..00000000000 --- a/tests/wpt/meta/trusted-types/trusted-types-reporting-for-Element-innerHTML.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[trusted-types-reporting-for-Element-innerHTML.html] - [Violation report for plain string.] - expected: FAIL diff --git a/tests/wpt/meta/trusted-types/trusted-types-reporting-for-Element-insertAdjacentHTML.html.ini b/tests/wpt/meta/trusted-types/trusted-types-reporting-for-Element-insertAdjacentHTML.html.ini deleted file mode 100644 index ea9822131e6..00000000000 --- a/tests/wpt/meta/trusted-types/trusted-types-reporting-for-Element-insertAdjacentHTML.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[trusted-types-reporting-for-Element-insertAdjacentHTML.html] - [Violation report for plain string.] - expected: FAIL diff --git a/tests/wpt/meta/trusted-types/trusted-types-reporting-for-Element-outerHTML.html.ini b/tests/wpt/meta/trusted-types/trusted-types-reporting-for-Element-outerHTML.html.ini deleted file mode 100644 index ccf4d385ddc..00000000000 --- a/tests/wpt/meta/trusted-types/trusted-types-reporting-for-Element-outerHTML.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[trusted-types-reporting-for-Element-outerHTML.html] - [Violation report for plain string.] - expected: FAIL diff --git a/tests/wpt/meta/trusted-types/trusted-types-reporting-for-Element-setHTMLUnsafe.html.ini b/tests/wpt/meta/trusted-types/trusted-types-reporting-for-Element-setHTMLUnsafe.html.ini deleted file mode 100644 index a288f0fc52b..00000000000 --- a/tests/wpt/meta/trusted-types/trusted-types-reporting-for-Element-setHTMLUnsafe.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[trusted-types-reporting-for-Element-setHTMLUnsafe.html] - [Violation report for plain string.] - expected: FAIL diff --git a/tests/wpt/meta/trusted-types/trusted-types-reporting-for-HTMLIFrameElement-srcdoc.html.ini b/tests/wpt/meta/trusted-types/trusted-types-reporting-for-HTMLIFrameElement-srcdoc.html.ini deleted file mode 100644 index 9f2a73c0900..00000000000 --- a/tests/wpt/meta/trusted-types/trusted-types-reporting-for-HTMLIFrameElement-srcdoc.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[trusted-types-reporting-for-HTMLIFrameElement-srcdoc.html] - [Violation report for plain string.] - expected: FAIL diff --git a/tests/wpt/meta/trusted-types/trusted-types-reporting-for-HTMLScriptElement-innerHTML.html.ini b/tests/wpt/meta/trusted-types/trusted-types-reporting-for-HTMLScriptElement-innerHTML.html.ini deleted file mode 100644 index 74b6b2803cd..00000000000 --- a/tests/wpt/meta/trusted-types/trusted-types-reporting-for-HTMLScriptElement-innerHTML.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[trusted-types-reporting-for-HTMLScriptElement-innerHTML.html] - [Violation report for plain string.] - expected: FAIL diff --git a/tests/wpt/meta/trusted-types/trusted-types-reporting-for-SVGScriptElement-innerHTML.html.ini b/tests/wpt/meta/trusted-types/trusted-types-reporting-for-SVGScriptElement-innerHTML.html.ini deleted file mode 100644 index 55b972f6001..00000000000 --- a/tests/wpt/meta/trusted-types/trusted-types-reporting-for-SVGScriptElement-innerHTML.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[trusted-types-reporting-for-SVGScriptElement-innerHTML.html] - [Violation report for plain string.] - expected: FAIL diff --git a/tests/wpt/meta/trusted-types/trusted-types-sandbox-allow-scripts.html.ini b/tests/wpt/meta/trusted-types/trusted-types-sandbox-allow-scripts.html.ini deleted file mode 100644 index 7053da4f6d0..00000000000 --- a/tests/wpt/meta/trusted-types/trusted-types-sandbox-allow-scripts.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[trusted-types-sandbox-allow-scripts.html] - [Default Trusted Types policy in a sandboxed page with allow-scripts.] - expected: FAIL diff --git a/tests/wpt/meta/trusted-types/trusted-types-source-file-path.html.ini b/tests/wpt/meta/trusted-types/trusted-types-source-file-path.html.ini deleted file mode 100644 index a05cfef0db5..00000000000 --- a/tests/wpt/meta/trusted-types/trusted-types-source-file-path.html.ini +++ /dev/null @@ -1,9 +0,0 @@ -[trusted-types-source-file-path.html] - [same-document script] - expected: FAIL - - [same-origin script] - expected: FAIL - - [cross-origin script] - expected: FAIL diff --git a/tests/wpt/meta/urlpattern/urlpattern.any.js.ini b/tests/wpt/meta/urlpattern/urlpattern.any.js.ini index 74d44f4fdb7..7248fced522 100644 --- a/tests/wpt/meta/urlpattern/urlpattern.any.js.ini +++ b/tests/wpt/meta/urlpattern/urlpattern.any.js.ini @@ -581,9 +581,6 @@ [Pattern: [{"pathname":"/foo/bar"}\] Inputs: ["./foo/bar","https://example.com"\]] expected: FAIL - [Pattern: [{"pathname":"/foo/bar"}\] Inputs: [{"pathname":"/foo/bar"},"https://example.com"\]] - expected: FAIL - [Pattern: ["https://example.com:8080/foo?bar#baz"\] Inputs: [{"pathname":"/foo","search":"bar","hash":"baz","baseURL":"https://example.com:8080"}\]] expected: FAIL @@ -932,6 +929,12 @@ [Pattern: ["https://{sub.}?example{.com/}foo"\] Inputs: ["https://example.com/foo"\]] expected: FAIL + [Pattern: [{"hostname":"bad\\\\:hostname"}\] Inputs: undefined] + expected: FAIL + + [Pattern: [{"pathname":"/foo","baseURL":""}\] Inputs: undefined] + expected: FAIL + [urlpattern.any.sharedworker.html] expected: ERROR @@ -1519,9 +1522,6 @@ [Pattern: [{"pathname":"/foo/bar"}\] Inputs: ["./foo/bar","https://example.com"\]] expected: FAIL - [Pattern: [{"pathname":"/foo/bar"}\] Inputs: [{"pathname":"/foo/bar"},"https://example.com"\]] - expected: FAIL - [Pattern: ["https://example.com:8080/foo?bar#baz"\] Inputs: [{"pathname":"/foo","search":"bar","hash":"baz","baseURL":"https://example.com:8080"}\]] expected: FAIL @@ -1870,6 +1870,12 @@ [Pattern: ["https://{sub.}?example{.com/}foo"\] Inputs: ["https://example.com/foo"\]] expected: FAIL + [Pattern: [{"hostname":"bad\\\\:hostname"}\] Inputs: undefined] + expected: FAIL + + [Pattern: [{"pathname":"/foo","baseURL":""}\] Inputs: undefined] + expected: FAIL + [urlpattern.any.serviceworker.html] expected: ERROR diff --git a/tests/wpt/meta/urlpattern/urlpattern.https.any.js.ini b/tests/wpt/meta/urlpattern/urlpattern.https.any.js.ini index 1b230cf4b42..f1b0add7805 100644 --- a/tests/wpt/meta/urlpattern/urlpattern.https.any.js.ini +++ b/tests/wpt/meta/urlpattern/urlpattern.https.any.js.ini @@ -584,9 +584,6 @@ [Pattern: [{"pathname":"/foo/bar"}\] Inputs: ["./foo/bar","https://example.com"\]] expected: FAIL - [Pattern: [{"pathname":"/foo/bar"}\] Inputs: [{"pathname":"/foo/bar"},"https://example.com"\]] - expected: FAIL - [Pattern: ["https://example.com:8080/foo?bar#baz"\] Inputs: [{"pathname":"/foo","search":"bar","hash":"baz","baseURL":"https://example.com:8080"}\]] expected: FAIL @@ -935,6 +932,12 @@ [Pattern: ["https://{sub.}?example{.com/}foo"\] Inputs: ["https://example.com/foo"\]] expected: FAIL + [Pattern: [{"hostname":"bad\\\\:hostname"}\] Inputs: undefined] + expected: FAIL + + [Pattern: [{"pathname":"/foo","baseURL":""}\] Inputs: undefined] + expected: FAIL + [urlpattern.https.any.worker.html] [Pattern: [{"pathname":"/foo/bar"}\] Inputs: [{"pathname":"/foo/bar"}\]] @@ -1519,9 +1522,6 @@ [Pattern: [{"pathname":"/foo/bar"}\] Inputs: ["./foo/bar","https://example.com"\]] expected: FAIL - [Pattern: [{"pathname":"/foo/bar"}\] Inputs: [{"pathname":"/foo/bar"},"https://example.com"\]] - expected: FAIL - [Pattern: ["https://example.com:8080/foo?bar#baz"\] Inputs: [{"pathname":"/foo","search":"bar","hash":"baz","baseURL":"https://example.com:8080"}\]] expected: FAIL @@ -1870,6 +1870,12 @@ [Pattern: ["https://{sub.}?example{.com/}foo"\] Inputs: ["https://example.com/foo"\]] expected: FAIL + [Pattern: [{"hostname":"bad\\\\:hostname"}\] Inputs: undefined] + expected: FAIL + + [Pattern: [{"pathname":"/foo","baseURL":""}\] Inputs: undefined] + expected: FAIL + [urlpattern.https.any.serviceworker.html] expected: ERROR diff --git a/tests/wpt/meta/wasm/webapi/esm-integration/script-src-blocks-wasm.tentative.sub.html.ini b/tests/wpt/meta/wasm/webapi/esm-integration/script-src-blocks-wasm.tentative.sub.html.ini deleted file mode 100644 index c804530024c..00000000000 --- a/tests/wpt/meta/wasm/webapi/esm-integration/script-src-blocks-wasm.tentative.sub.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[script-src-blocks-wasm.tentative.sub.html] - [Importing a WebAssembly module should be guarded by script-src CSP.] - expected: FAIL diff --git a/tests/wpt/meta/webaudio/the-audio-api/the-audiobuffersourcenode-interface/sub-sample-buffer-stitching.html.ini b/tests/wpt/meta/webaudio/the-audio-api/the-audiobuffersourcenode-interface/sub-sample-buffer-stitching.html.ini index 590f4ee6a07..c9d88fc368b 100644 --- a/tests/wpt/meta/webaudio/the-audio-api/the-audiobuffersourcenode-interface/sub-sample-buffer-stitching.html.ini +++ b/tests/wpt/meta/webaudio/the-audio-api/the-audiobuffersourcenode-interface/sub-sample-buffer-stitching.html.ini @@ -784,3 +784,15 @@ [X SNR (42.96525288004425 dB) is not greater than or equal to 65.737. Got 42.96525288004425.] expected: FAIL + + [X Stitched sine-wave buffers at sample rate 43800 does not equal [0,0.06264832615852356,0.12505052983760834,0.18696144223213196,0.24813786149024963,0.308339387178421,0.36732959747314453,0.4248766601085663,0.480754554271698,0.5347436666488647,0.5866320133209229,0.6362156271934509,0.6832997798919678,0.7276994585990906,0.7692402601242065,0.8077589869499207...\] with an element-wise tolerance of {"absoluteThreshold":0.0038986,"relativeThreshold":0}.\n\tIndex\tActual\t\t\tExpected\t\tAbsError\t\tRelError\t\tTest threshold\n\t[14650\]\t-2.7243524527875707e-5\t8.6956524848937988e-1\t8.6959249201390776e-1\t1.0000313300520867e+0\t3.8985999999999999e-3\n\t[14651\]\t3.0547976493835449e-1\t8.9879405498504639e-1\t5.9331429004669189e-1\t6.6012262403823208e-1\t3.8985999999999999e-3\n\tMax AbsError of 8.6959249201390776e-1 at index of 14650.\n\tMax RelError of 1.0000313300520867e+0 at index of 14650.\n] + expected: FAIL + + [X SNR (42.96506816850161 dB) is not greater than or equal to 65.737. Got 42.96506816850161.] + expected: FAIL + + [X Stitched sine-wave buffers at sample rate 43800 does not equal [0,0.06264832615852356,0.12505052983760834,0.18696144223213196,0.24813786149024963,0.308339387178421,0.36732959747314453,0.4248766601085663,0.480754554271698,0.5347436666488647,0.5866320133209229,0.6362156271934509,0.6832997798919678,0.7276994585990906,0.7692402601242065,0.8077589869499207...\] with an element-wise tolerance of {"absoluteThreshold":0.0038986,"relativeThreshold":0}.\n\tIndex\tActual\t\t\tExpected\t\tAbsError\t\tRelError\t\tTest threshold\n\t[14650\]\t-1.7532469034194946e+0\t8.6956524848937988e-1\t2.6228121519088745e+0\t3.0162338668262767e+0\t3.8985999999999999e-3\n\t[14651\]\t3.0547976493835449e-1\t8.9879405498504639e-1\t5.9331429004669189e-1\t6.6012262403823208e-1\t3.8985999999999999e-3\n\tMax AbsError of 2.6228121519088745e+0 at index of 14650.\n\tMax RelError of 3.0162338668262767e+0 at index of 14650.\n] + expected: FAIL + + [X SNR (34.8385032008375 dB) is not greater than or equal to 65.737. Got 34.8385032008375.] + expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/element_click/click.py.ini b/tests/wpt/meta/webdriver/tests/classic/element_click/click.py.ini index ad0f9714ad1..9cdf1e0d0da 100644 --- a/tests/wpt/meta/webdriver/tests/classic/element_click/click.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/element_click/click.py.ini @@ -13,9 +13,3 @@ [test_no_such_element_from_other_frame[closed\]] expected: FAIL - - [test_stale_element_reference[top_context\]] - expected: FAIL - - [test_stale_element_reference[child_context\]] - expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/element_click/scroll_into_view.py.ini b/tests/wpt/meta/webdriver/tests/classic/element_click/scroll_into_view.py.ini index 87c9c813881..2627072cf91 100644 --- a/tests/wpt/meta/webdriver/tests/classic/element_click/scroll_into_view.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/element_click/scroll_into_view.py.ini @@ -1,30 +1,30 @@ [scroll_into_view.py] [test_scroll_into_view] - expected: ERROR + expected: FAIL [test_partially_visible_does_not_scroll[9\]] - expected: ERROR + expected: FAIL [test_partially_visible_does_not_scroll[8\]] - expected: ERROR + expected: FAIL [test_partially_visible_does_not_scroll[7\]] - expected: ERROR + expected: FAIL [test_partially_visible_does_not_scroll[6\]] - expected: ERROR + expected: FAIL [test_partially_visible_does_not_scroll[5\]] - expected: ERROR + expected: FAIL [test_partially_visible_does_not_scroll[4\]] - expected: ERROR + expected: FAIL [test_partially_visible_does_not_scroll[3\]] - expected: ERROR + expected: FAIL [test_partially_visible_does_not_scroll[2\]] - expected: ERROR + expected: FAIL [test_partially_visible_does_not_scroll[1\]] - expected: ERROR + expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/element_send_keys/form_controls.py.ini b/tests/wpt/meta/webdriver/tests/classic/element_send_keys/form_controls.py.ini index 5d4a3bd4de5..1dea194f9b2 100644 --- a/tests/wpt/meta/webdriver/tests/classic/element_send_keys/form_controls.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/element_send_keys/form_controls.py.ini @@ -4,3 +4,6 @@ [test_textarea_append] expected: FAIL + + [test_date] + expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/element_send_keys/scroll_into_view.py.ini b/tests/wpt/meta/webdriver/tests/classic/element_send_keys/scroll_into_view.py.ini index 3e260cade03..d141c6022db 100644 --- a/tests/wpt/meta/webdriver/tests/classic/element_send_keys/scroll_into_view.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/element_send_keys/scroll_into_view.py.ini @@ -1,7 +1,4 @@ [scroll_into_view.py] - [test_element_outside_of_not_scrollable_viewport] - expected: FAIL - [test_element_outside_of_scrollable_viewport] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/element_send_keys/send_keys.py.ini b/tests/wpt/meta/webdriver/tests/classic/element_send_keys/send_keys.py.ini index b2b09490191..10a5a86e3d2 100644 --- a/tests/wpt/meta/webdriver/tests/classic/element_send_keys/send_keys.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/element_send_keys/send_keys.py.ini @@ -13,9 +13,3 @@ [test_no_such_element_from_other_frame[closed\]] expected: FAIL - - [test_stale_element_reference[top_context\]] - expected: FAIL - - [test_stale_element_reference[child_context\]] - expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/execute_async_script/collections.py.ini b/tests/wpt/meta/webdriver/tests/classic/execute_async_script/collections.py.ini index 5d0711fe4ad..710ae93d053 100644 --- a/tests/wpt/meta/webdriver/tests/classic/execute_async_script/collections.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/execute_async_script/collections.py.ini @@ -1,10 +1,4 @@ [collections.py] - [test_array_in_array] - expected: FAIL - - [test_dom_token_list] - expected: FAIL - [test_file_list] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/execute_script/collections.py.ini b/tests/wpt/meta/webdriver/tests/classic/execute_script/collections.py.ini index 68e5ec4b830..710ae93d053 100644 --- a/tests/wpt/meta/webdriver/tests/classic/execute_script/collections.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/execute_script/collections.py.ini @@ -1,12 +1,6 @@ [collections.py] - [test_dom_token_list] - expected: FAIL - [test_file_list] expected: FAIL [test_html_all_collection] expected: FAIL - - [test_array_in_array] - expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/get_computed_role/get.py.ini b/tests/wpt/meta/webdriver/tests/classic/get_computed_role/get.py.ini index abd4a7750b7..f00172fdc5a 100644 --- a/tests/wpt/meta/webdriver/tests/classic/get_computed_role/get.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/get_computed_role/get.py.ini @@ -14,11 +14,5 @@ [test_no_such_element_from_other_frame[closed\]] expected: FAIL - [test_stale_element_reference[top_context\]] - expected: FAIL - - [test_stale_element_reference[child_context\]] - expected: FAIL - [test_computed_roles[<article>foo</article>-article-article\]] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/get_element_css_value/get.py.ini b/tests/wpt/meta/webdriver/tests/classic/get_element_css_value/get.py.ini index bcd25112776..d55c5312a47 100644 --- a/tests/wpt/meta/webdriver/tests/classic/get_element_css_value/get.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/get_element_css_value/get.py.ini @@ -13,9 +13,3 @@ [test_no_such_element_from_other_frame[closed\]] expected: FAIL - - [test_stale_element_reference[top_context\]] - expected: FAIL - - [test_stale_element_reference[child_context\]] - expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/get_element_rect/get.py.ini b/tests/wpt/meta/webdriver/tests/classic/get_element_rect/get.py.ini index 065e9fbc4ce..67875a58cd9 100644 --- a/tests/wpt/meta/webdriver/tests/classic/get_element_rect/get.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/get_element_rect/get.py.ini @@ -14,11 +14,5 @@ [test_no_such_element_from_other_frame[closed\]] expected: FAIL - [test_stale_element_reference[top_context\]] - expected: FAIL - - [test_stale_element_reference[child_context\]] - expected: FAIL - [test_basic] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/get_element_tag_name/get.py.ini b/tests/wpt/meta/webdriver/tests/classic/get_element_tag_name/get.py.ini index b810c389100..0ac8ff98d59 100644 --- a/tests/wpt/meta/webdriver/tests/classic/get_element_tag_name/get.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/get_element_tag_name/get.py.ini @@ -14,11 +14,5 @@ [test_no_such_element_from_other_frame[closed\]] expected: FAIL - [test_stale_element_reference[top_context\]] - expected: FAIL - - [test_stale_element_reference[child_context\]] - expected: FAIL - [test_get_element_tag_name] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/get_element_text/get.py.ini b/tests/wpt/meta/webdriver/tests/classic/get_element_text/get.py.ini index 5f04f967054..ad870f8f49b 100644 --- a/tests/wpt/meta/webdriver/tests/classic/get_element_text/get.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/get_element_text/get.py.ini @@ -14,12 +14,6 @@ [test_no_such_element_from_other_frame[closed\]] expected: FAIL - [test_stale_element_reference[top_context\]] - expected: FAIL - - [test_stale_element_reference[child_context\]] - expected: FAIL - [test_transform_capitalize[space\]] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/is_element_selected/selected.py.ini b/tests/wpt/meta/webdriver/tests/classic/is_element_selected/selected.py.ini index f75724979c5..eb4c0299197 100644 --- a/tests/wpt/meta/webdriver/tests/classic/is_element_selected/selected.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/is_element_selected/selected.py.ini @@ -13,9 +13,3 @@ [test_no_such_element_from_other_frame[closed\]] expected: FAIL - - [test_stale_element_reference[top_context\]] - expected: FAIL - - [test_stale_element_reference[child_context\]] - expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/new_window/new_tab.py.ini b/tests/wpt/meta/webdriver/tests/classic/new_window/new_tab.py.ini index 77be6174789..db51a3496ae 100644 --- a/tests/wpt/meta/webdriver/tests/classic/new_window/new_tab.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/new_window/new_tab.py.ini @@ -1,15 +1,9 @@ [new_tab.py] [test_keeps_current_window_handle] - expected: ERROR + expected: FAIL [test_opens_about_blank_in_new_tab] - expected: ERROR + expected: FAIL [test_initial_selection_for_contenteditable] - expected: ERROR - - [test_sets_no_window_name] - expected: ERROR - - [test_sets_no_opener] - expected: ERROR + expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/perform_actions/perform.py.ini b/tests/wpt/meta/webdriver/tests/classic/perform_actions/perform.py.ini deleted file mode 100644 index b4a8841b9ae..00000000000 --- a/tests/wpt/meta/webdriver/tests/classic/perform_actions/perform.py.ini +++ /dev/null @@ -1,9 +0,0 @@ -[perform.py] - [test_input_source_action_sequence_actions_pause_duration_valid[wheel\]] - expected: FAIL - - [test_input_source_action_sequence_actions_pause_duration_missing[wheel\]] - expected: FAIL - - [test_input_source_action_sequence_pointer_parameters_not_processed[wheel\]] - expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_mouse.py.ini b/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_mouse.py.ini index 4a163fe2fac..4222966b349 100644 --- a/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_mouse.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_mouse.py.ini @@ -1,18 +1,11 @@ [pointer_mouse.py] - expected: TIMEOUT [test_no_top_browsing_context] expected: FAIL [test_no_browsing_context] - expected: ERROR - - [test_pointer_down_closes_browsing_context] - expected: FAIL - - [test_stale_element_reference[top_context\]] expected: FAIL - [test_stale_element_reference[child_context\]] + [test_pointer_down_closes_browsing_context] expected: FAIL [test_click_at_coordinates] @@ -39,9 +32,6 @@ [test_click_element_in_shadow_tree[inner-closed\]] expected: FAIL - [test_click_navigation] - expected: FAIL - [test_move_to_position_in_viewport[x\]] expected: FAIL @@ -60,5 +50,5 @@ [test_move_to_origin_position_within_frame[element\]] expected: FAIL - [test_invalid_element_origin] + [test_params_actions_origin_outside_viewport[element\]] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_mouse_drag.py.ini b/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_mouse_drag.py.ini index 8657edd79e8..3fa735ac7ea 100644 --- a/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_mouse_drag.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_mouse_drag.py.ini @@ -1,63 +1,63 @@ [pointer_mouse_drag.py] [test_drag_and_drop[20-0-0\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[20-0-300\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[20-0-800\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[0-15-0\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[0-15-300\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[0-15-800\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[10-15-0\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[10-15-300\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[10-15-800\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[-20-0-0\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[-20-0-300\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[-20-0-800\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[10--15-0\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[10--15-300\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[10--15-800\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[-10--15-0\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[-10--15-300\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[-10--15-800\]] - expected: ERROR + expected: FAIL [test_drag_and_drop_with_draggable_element[0\]] - expected: ERROR + expected: FAIL [test_drag_and_drop_with_draggable_element[300\]] - expected: ERROR + expected: FAIL [test_drag_and_drop_with_draggable_element[800\]] - expected: ERROR + expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_origin.py.ini b/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_origin.py.ini index 112a4f9ddf4..8102334d66b 100644 --- a/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_origin.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_origin.py.ini @@ -1,27 +1,18 @@ [pointer_origin.py] [test_viewport_inside] - expected: ERROR + expected: FAIL [test_pointer_inside] - expected: ERROR + expected: FAIL [test_element_center_point] - expected: ERROR + expected: FAIL [test_element_center_point_with_offset] - expected: ERROR + expected: FAIL [test_element_in_view_center_point_partly_visible] - expected: ERROR + expected: FAIL [test_element_larger_than_viewport] - expected: ERROR - - [test_element_outside_of_view_port] - expected: ERROR - - [test_viewport_outside] - expected: ERROR - - [test_pointer_outside] - expected: ERROR + expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_pen.py.ini b/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_pen.py.ini index a3d52579f7d..5c08076b7b2 100644 --- a/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_pen.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_pen.py.ini @@ -8,12 +8,6 @@ [test_pointer_down_closes_browsing_context] expected: FAIL - [test_stale_element_reference[top_context\]] - expected: FAIL - - [test_stale_element_reference[child_context\]] - expected: FAIL - [test_pen_pointer_in_shadow_tree[outer-open\]] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_touch.py.ini b/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_touch.py.ini index 65101d138aa..2dd2ee19891 100644 --- a/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_touch.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_touch.py.ini @@ -8,12 +8,6 @@ [test_pointer_down_closes_browsing_context] expected: FAIL - [test_stale_element_reference[top_context\]] - expected: FAIL - - [test_stale_element_reference[child_context\]] - expected: FAIL - [test_touch_pointer_in_shadow_tree[outer-open\]] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/perform_actions/sequence.py.ini b/tests/wpt/meta/webdriver/tests/classic/perform_actions/sequence.py.ini deleted file mode 100644 index 54ee376545d..00000000000 --- a/tests/wpt/meta/webdriver/tests/classic/perform_actions/sequence.py.ini +++ /dev/null @@ -1,3 +0,0 @@ -[sequence.py] - [test_perform_no_actions_send_no_events] - expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/perform_actions/wheel.py.ini b/tests/wpt/meta/webdriver/tests/classic/perform_actions/wheel.py.ini index 3f6abc70f3e..c8a0364b783 100644 --- a/tests/wpt/meta/webdriver/tests/classic/perform_actions/wheel.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/perform_actions/wheel.py.ini @@ -1,19 +1,10 @@ [wheel.py] - [test_null_response_value] - expected: FAIL - [test_no_top_browsing_context] expected: FAIL [test_no_browsing_context] expected: FAIL - [test_params_actions_origin_outside_viewport[element\]] - expected: FAIL - - [test_params_actions_origin_outside_viewport[viewport\]] - expected: FAIL - [test_scroll_not_scrollable] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/switch_to_frame/switch_webelement.py.ini b/tests/wpt/meta/webdriver/tests/classic/switch_to_frame/switch_webelement.py.ini index 9932ab9a5d5..1f469cd85c3 100644 --- a/tests/wpt/meta/webdriver/tests/classic/switch_to_frame/switch_webelement.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/switch_to_frame/switch_webelement.py.ini @@ -1,10 +1,4 @@ [switch_webelement.py] - [test_frame_id_webelement_stale_element_reference[top_context\]] - expected: FAIL - - [test_frame_id_webelement_stale_element_reference[child_context\]] - expected: FAIL - [test_frame_id_webelement_frame[0-foo\]] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/take_element_screenshot/screenshot.py.ini b/tests/wpt/meta/webdriver/tests/classic/take_element_screenshot/screenshot.py.ini index 319e4bf848f..981c68641a8 100644 --- a/tests/wpt/meta/webdriver/tests/classic/take_element_screenshot/screenshot.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/take_element_screenshot/screenshot.py.ini @@ -19,12 +19,3 @@ [test_no_such_element_from_other_frame[closed\]] expected: FAIL - - [test_stale_element_reference[top_context\]] - expected: FAIL - - [test_stale_element_reference[child_context\]] - expected: FAIL - - [test_format_and_dimensions] - expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/take_screenshot/iframe.py.ini b/tests/wpt/meta/webdriver/tests/classic/take_screenshot/iframe.py.ini deleted file mode 100644 index 62f7ab5e4da..00000000000 --- a/tests/wpt/meta/webdriver/tests/classic/take_screenshot/iframe.py.ini +++ /dev/null @@ -1,3 +0,0 @@ -[iframe.py] - [test_always_captures_top_browsing_context] - expected: FAIL diff --git a/tests/wpt/meta/webmessaging/messageerror.html.ini b/tests/wpt/meta/webmessaging/messageerror.html.ini deleted file mode 100644 index 9b875551f90..00000000000 --- a/tests/wpt/meta/webmessaging/messageerror.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[messageerror.html] - [The onmessageerror content attribute must be compiled into the onmessageerror property] - expected: FAIL - - [The onmessageerror content attribute must execute when an event is dispatched on the window] - expected: FAIL diff --git a/tests/wpt/meta/webxr/render_state_update.https.html.ini b/tests/wpt/meta/webxr/render_state_update.https.html.ini new file mode 100644 index 00000000000..0e57356683e --- /dev/null +++ b/tests/wpt/meta/webxr/render_state_update.https.html.ini @@ -0,0 +1,2 @@ +[render_state_update.https.html] + expected: ERROR diff --git a/tests/wpt/meta/workers/WorkerGlobalScope-close.html.ini b/tests/wpt/meta/workers/WorkerGlobalScope-close.html.ini new file mode 100644 index 00000000000..24daae4c2e7 --- /dev/null +++ b/tests/wpt/meta/workers/WorkerGlobalScope-close.html.ini @@ -0,0 +1,3 @@ +[WorkerGlobalScope-close.html] + [Test sending a message after closing.] + expected: FAIL diff --git a/tests/wpt/meta/xhr/formdata/append.any.js.ini b/tests/wpt/meta/xhr/formdata/append.any.js.ini new file mode 100644 index 00000000000..e727a77e38d --- /dev/null +++ b/tests/wpt/meta/xhr/formdata/append.any.js.ini @@ -0,0 +1,8 @@ +[append.any.worker.html] + [testFormDataAppendEmptyBlob] + expected: FAIL + + +[append.any.html] + [testFormDataAppendEmptyBlob] + expected: FAIL diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 7035ae424dc..2797b63f2ae 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -16,6 +16,13 @@ null, {} ] + ], + "transfer-control-to-offscreencanvas-then-change-dimensions-crash.html": [ + "bc0c7afd6ab2e1c313bb61ea4a5c68b402105f5d", + [ + null, + {} + ] ] }, "datatransferitem-crash.html": [ @@ -13575,14 +13582,14 @@ ] ], "interfaces.https.html": [ - "76d746b0663ed73865816e678c2536eceff31f2d", + "eee8c799727b91e00b512795756b693a5f121f86", [ null, {} ] ], "interfaces.worker.js": [ - "8d109502622fac7266a4564de09684a3ab94118c", + "e86f34f261442aeaa7074c525fb4b1206219769d", [ "mozilla/interfaces.worker.html", {} diff --git a/tests/wpt/mozilla/meta/__dir__.ini b/tests/wpt/mozilla/meta/__dir__.ini index 925f07e1c50..e7a12669952 100644 --- a/tests/wpt/mozilla/meta/__dir__.ini +++ b/tests/wpt/mozilla/meta/__dir__.ini @@ -1,4 +1,5 @@ prefs: [ + "dom_serviceworker_enabled:true", "dom_urlpattern_enabled:true", "media_testing_enabled:true", ] diff --git a/tests/wpt/mozilla/tests/mozilla/canvas/transfer-control-to-offscreencanvas-then-change-dimensions-crash.html b/tests/wpt/mozilla/tests/mozilla/canvas/transfer-control-to-offscreencanvas-then-change-dimensions-crash.html new file mode 100644 index 00000000000..bc0c7afd6ab --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/canvas/transfer-control-to-offscreencanvas-then-change-dimensions-crash.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<script> + let canvas = document.createElement("canvas"); + let offscreenCanvas = canvas.transferControlToOffscreen(); + offscreenCanvas.height = 0; +</script> + 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/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/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.window.js b/tests/wpt/tests/ai/translator/translator-bad-input.https.window.js index 53a184bfd19..db8905a61f6 100644 --- a/tests/wpt/tests/ai/translator/translator-bad-input.tentative.https.window.js +++ b/tests/wpt/tests/ai/translator/translator-bad-input.https.window.js @@ -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.window.js b/tests/wpt/tests/ai/translator/translator.optional.https.window.js index a8aad5e03e1..96eca09d28b 100644 --- a/tests/wpt/tests/ai/translator/translator-translate.tentative.https.window.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'}; @@ -141,7 +136,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/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/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 @@ +<!DOCTYPE html> +<link rel="help" href="https://compat.spec.whatwg.org/#valdef-flex--webkit-box"> +<link rel="match" href="green-ref.html"> +<div style="display: -webkit-box; flex-wrap: wrap; width: 50px;"> + <div style="width: 50px; height: 100px; background: green;"></div> + <div style="width: 50px; height: 100px; background: green;"></div> +</div> 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 @@ +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="Content-Security-Policy" content="img-src 'none';"> + <script src='/resources/testharness.js'></script> + <script src='/resources/testharnessreport.js'></script> +</head> +<body> +<p>Check that img-src sets correct target</p> + <script> + var t = async_test("Test that image does not load"); + 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')); + })); + </script> + <img src='/content-security-policy/support/fail.png' + onload='t.step(function() { assert_unreached("Image should not have loaded"); t.done(); });' + onerror='t.done();'> +</body> + +</html> 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 @@ +<!doctype html> +<html> +<head> + <meta http-equiv="Content-Security-Policy" content="style-src 'self';"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + <script> + var t = async_test("Manipulating cssText should be allowed with 'self'"); + document.addEventListener("securitypolicyviolation", t.unreached_func("Should not trigger a security policy violation")); + </script> +</head> +<body> + <div id='log'></div> + + <div id="content">Lorem ipsum</div> + + <script> + t.step(function() { + var contentEl = document.getElementById("content"); + contentEl.style.cssText = 'margin-left: 2px;'; + var marginLeftVal = getComputedStyle(contentEl).getPropertyValue('margin-left'); + assert_equals(marginLeftVal, "2px"); + t.done(); + }); + </script> + +</body> +</html> 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..542bd6c5387 100644 --- a/tests/wpt/tests/cookie-store/cookieListItem_attributes.https.any.js +++ b/tests/wpt/tests/cookie-store/cookieListItem_attributes.https.any.js @@ -207,3 +207,20 @@ promise_test(async testCase => { const cookie = await cookieStore.get('cookie-name'); assert_equals(cookie.secure, true); }, 'CookieListItem - secure defaults to true'); + +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/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/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 @@ +<!doctype html> +<style> + :root { + background-color: rgba(255, 255, 255, 0.98); + background-image: url(/images/computer.jpg); /* opaque */ + background-position: top left; + background-repeat: repeat; + } +</style> diff --git a/tests/wpt/tests/css/css-align/blocks/justify-items-anonymous.tentative.html b/tests/wpt/tests/css/css-align/blocks/justify-items-anonymous.tentative.html new file mode 100644 index 00000000000..641dea1f54b --- /dev/null +++ b/tests/wpt/tests/css/css-align/blocks/justify-items-anonymous.tentative.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/11461"> +<link rel="match" href="../../reference/ref-filled-green-100px-square.xht"> +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div style="width: 100px; height: 100px; background: red;"> + <div style="width: 200px; justify-items: right;"> + <span style="display: inline-block; width: 100px; height: 100px; background: green;"></span> + <div></div> <!-- Forces an anonymous block around <span> in most browsers. --> + </div> +</div> diff --git a/tests/wpt/tests/css/css-anchor-position/anchor-position-grid-001.html b/tests/wpt/tests/css/css-anchor-position/anchor-position-grid-001.html index 92fb4d275b8..7bd389b39d8 100644 --- a/tests/wpt/tests/css/css-anchor-position/anchor-position-grid-001.html +++ b/tests/wpt/tests/css/css-anchor-position/anchor-position-grid-001.html @@ -75,8 +75,8 @@ <div class="target target1-l" data-offset-x=100 data-expected-height=100></div> <div class="target target1-r" data-offset-x=404 data-expected-height=100></div> - <div class="target target1-t" data-offset-y=0 data-expected-width=310></div> - <div class="target target1-b" data-offset-y=95 data-expected-width=310></div> + <div class="target target1-t" data-offset-y=-10 data-expected-width=310></div> + <div class="target target1-b" data-offset-y=85 data-expected-width=310></div> </div> </div> </div> diff --git a/tests/wpt/tests/css/css-anchor-position/anchor-position-multicol-002.html b/tests/wpt/tests/css/css-anchor-position/anchor-position-multicol-002.html index 7b2691a2b90..1e1f0a5c95b 100644 --- a/tests/wpt/tests/css/css-anchor-position/anchor-position-multicol-002.html +++ b/tests/wpt/tests/css/css-anchor-position/anchor-position-multicol-002.html @@ -71,15 +71,15 @@ <!-- The containing block of querying elements is block-fragmented. --> <div class="target target1" - data-offset-x=18 data-offset-y=65 + data-offset-x=128 data-offset-y=-35 data-expected-width=160 data-expected-height=100></div> <div class="target target1-rb" - data-offset-x=168 data-offset-y=155></div> + data-offset-x=278 data-offset-y=55></div> <div class="target fixed target1" - data-offset-x=26 data-offset-y=70 + data-offset-x=136 data-offset-y=-30 data-expected-width=160 data-expected-height=100></div> <div class="target fixed target1-rb" - data-offset-x=176 data-offset-y=160></div> + data-offset-x=286 data-offset-y=60></div> </div> <!-- The containing block of querying elements is a multi-column. --> diff --git a/tests/wpt/tests/css/css-anchor-position/anchor-position-multicol-004.html b/tests/wpt/tests/css/css-anchor-position/anchor-position-multicol-004.html index 399494120ea..8f7a3dad129 100644 --- a/tests/wpt/tests/css/css-anchor-position/anchor-position-multicol-004.html +++ b/tests/wpt/tests/css/css-anchor-position/anchor-position-multicol-004.html @@ -69,16 +69,16 @@ <div class="anchor1" style="width: 20px"></div> <div class="target target1" - data-offset-x=18 data-offset-y=65 + data-offset-x=128 data-offset-y=-35 data-expected-width=130 data-expected-height=100></div> <div class="target target1-rb" - data-offset-x=138 data-offset-y=155></div> + data-offset-x=248 data-offset-y=55></div> </div> <div class="target target1" - data-offset-x=34 data-offset-y=225 + data-offset-x=364 data-offset-y=-75 data-expected-width=130 data-expected-height=100></div> <div class="target target1-rb" - data-offset-x=154 data-offset-y=315></div> + data-offset-x=484 data-offset-y=15></div> </div> </body> diff --git a/tests/wpt/tests/css/css-anchor-position/anchor-position-multicol-nested-001.html b/tests/wpt/tests/css/css-anchor-position/anchor-position-multicol-nested-001.html index 35ab2cfc15c..c5ce41299af 100644 --- a/tests/wpt/tests/css/css-anchor-position/anchor-position-multicol-nested-001.html +++ b/tests/wpt/tests/css/css-anchor-position/anchor-position-multicol-nested-001.html @@ -68,8 +68,8 @@ data-expected-width=180 data-expected-height=100></div> </div> </div> - <div class="target" - data-offset-x=13 data-offset-y=97 + <div class="target" style="background:hotpink;" + data-offset-x=173 data-offset-y=-3 data-expected-width=180 data-expected-height=100></div> </div> </div> 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 @@ +<!DOCTYPE html> +<link rel="help" href="https://crbug.com/415627003"> +<style> + @keyframes --anim { + from { + text-size-adjust: calc(50% * sibling-index()); + } + to { + text-size-adjust: calc(50%); + } + } + #target { + animation: --anim 2s; + } +</style> +<p>Pass if no crash</p> +<div id="target"></div> 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 @@ +<!DOCTYPE html> +<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org"> +<div style="height:1px;"></div> +<div style="break-before:page; margin-top:8px;"></div> +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 @@ +<!DOCTYPE html> +<title>Root block-start margin taller than the page</title> +<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org"> +<link rel="help" href="https://drafts.csswg.org/css-break/#break-margins"> +<link rel="match" href="root-margin-001-print-ref.html"> +<style> + html { margin-top: 10in; } +</style> +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; } </style> <p>No red should be seen below.</p> -<div id="multicol" style="columns:2; column-fill:auto; height:70px; width:200px; line-height:20px;"> +<div id="multicol" style="columns:2; column-fill:auto; gap:0; height:70px; width:200px; line-height:20px;"> <div style="position:relative; display:table; border-spacing:10px;"> <div class="cell" style="display:table-cell; width:30px; background:red;"> <div id="child1">1<br></div> @@ -20,15 +20,20 @@ <script src="/resources/testharnessreport.js"></script> <script> test(() => { + assert_equals(document.getElementById("child1").offsetLeft, 10); assert_equals(document.getElementById("child1").offsetTop, 10); assert_equals(document.getElementById("child1").offsetHeight, 20); + assert_equals(document.getElementById("child2").offsetLeft, 10); assert_equals(document.getElementById("child2").offsetTop, 30); assert_equals(document.getElementById("child2").offsetHeight, 20); + assert_equals(document.getElementById("child3").offsetLeft, 10); assert_equals(document.getElementById("child3").offsetTop, 50); assert_equals(document.getElementById("child3").offsetHeight, 20); - assert_equals(document.getElementById("child4").offsetTop, 70); + assert_equals(document.getElementById("child4").offsetLeft, 110); + assert_equals(document.getElementById("child4").offsetTop, 0); assert_equals(document.getElementById("child4").offsetHeight, 20); - assert_equals(document.getElementById("child5").offsetTop, 90); + assert_equals(document.getElementById("child5").offsetLeft, 110); + assert_equals(document.getElementById("child5").offsetTop, 20); assert_equals(document.getElementById("child5").offsetHeight, 20); }, "Table with border spacing"); </script> 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..1eb751032ca 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,104 +37,104 @@ <script src="/resources/testharnessreport.js"></script> <script> test(() => { - assert_equals(table.offsetTop, 408, "offsetTop"); - assert_equals(table.offsetLeft, 41, "offsetLeft"); + assert_equals(table.offsetTop, 8, "offsetTop"); + assert_equals(table.offsetLeft, 8, "offsetLeft"); assert_equals(table.offsetWidth, 177, "offsetWidth"); assert_equals(table.offsetHeight, 184, "offsetHeight"); }, "table"); test(() => { - assert_equals(colgroup.offsetTop, 418, "offsetTop"); - assert_equals(colgroup.offsetLeft, 51, "offsetLeft"); + assert_equals(colgroup.offsetTop, 18, "offsetTop"); + assert_equals(colgroup.offsetLeft, 8, "offsetLeft"); assert_equals(colgroup.offsetWidth, 157, "offsetWidth"); assert_equals(colgroup.offsetHeight, 107, "offsetHeight"); }, "colgroup"); test(() => { - assert_equals(col.offsetTop, 418, "offsetTop"); - assert_equals(col.offsetLeft, 51, "offsetLeft"); + assert_equals(col.offsetTop, 18, "offsetTop"); + assert_equals(col.offsetLeft, 8, "offsetLeft"); assert_equals(col.offsetWidth, 157, "offsetWidth"); assert_equals(col.offsetHeight, 50, "offsetHeight"); }, "col"); test(() => { - assert_equals(col2.offsetTop, 475, "offsetTop"); - assert_equals(col2.offsetLeft, 51, "offsetLeft"); + assert_equals(col2.offsetTop, 75, "offsetTop"); + assert_equals(col2.offsetLeft, 8, "offsetLeft"); assert_equals(col2.offsetWidth, 157, "offsetWidth"); assert_equals(col2.offsetHeight, 50, "offsetHeight"); }, "col2"); test(() => { - assert_equals(colgroup2.offsetTop, 532, "offsetTop"); - assert_equals(colgroup2.offsetLeft, 51, "offsetLeft"); + assert_equals(colgroup2.offsetTop, 132, "offsetTop"); + assert_equals(colgroup2.offsetLeft, 8, "offsetLeft"); assert_equals(colgroup2.offsetWidth, 157, "offsetWidth"); assert_equals(colgroup2.offsetHeight, 50, "offsetHeight"); }, "colgroup2"); test(() => { - assert_equals(col3.offsetTop, 532, "offsetTop"); - assert_equals(col3.offsetLeft, 51, "offsetLeft"); + assert_equals(col3.offsetTop, 132, "offsetTop"); + assert_equals(col3.offsetLeft, 8, "offsetLeft"); assert_equals(col3.offsetWidth, 157, "offsetWidth"); assert_equals(col3.offsetHeight, 50, "offsetHeight"); }, "col3"); test(() => { - assert_equals(rowgroup.offsetTop, 418, "offsetTop"); - assert_equals(rowgroup.offsetLeft, 51, "offsetLeft"); + assert_equals(rowgroup.offsetTop, 18, "offsetTop"); + assert_equals(rowgroup.offsetLeft, 8, "offsetLeft"); assert_equals(rowgroup.offsetWidth, 157, "offsetWidth"); assert_equals(rowgroup.offsetHeight, 164, "offsetHeight"); }, "rowgroup"); test(() => { - assert_equals(row.offsetTop, 218, "offsetTop"); - assert_equals(row.offsetLeft, 38, "offsetLeft"); + assert_equals(row.offsetTop, 18, "offsetTop"); + assert_equals(row.offsetLeft, 8, "offsetLeft"); assert_equals(row.offsetWidth, 100, "offsetWidth"); assert_equals(row.offsetHeight, 164, "offsetHeight"); }, "row"); test(() => { - assert_equals(cell.offsetTop, 218, "offsetTop"); - assert_equals(cell.offsetLeft, 38, "offsetLeft"); + assert_equals(cell.offsetTop, 18, "offsetTop"); + assert_equals(cell.offsetLeft, 8, "offsetLeft"); assert_equals(cell.offsetWidth, 100, "offsetWidth"); assert_equals(cell.offsetHeight, 50, "offsetHeight"); }, "cell"); test(() => { - assert_equals(content.offsetTop, 218, "offsetTop"); - assert_equals(content.offsetLeft, 38, "offsetLeft"); + assert_equals(content.offsetTop, 18, "offsetTop"); + assert_equals(content.offsetLeft, 8, "offsetLeft"); assert_equals(content.offsetWidth, 100, "offsetWidth"); assert_equals(content.offsetHeight, 50, "offsetHeight"); }, "content"); test(() => { - assert_equals(cell2.offsetTop, 275, "offsetTop"); - assert_equals(cell2.offsetLeft, 38, "offsetLeft"); + assert_equals(cell2.offsetTop, 75, "offsetTop"); + assert_equals(cell2.offsetLeft, 8, "offsetLeft"); assert_equals(cell2.offsetWidth, 100, "offsetWidth"); assert_equals(cell2.offsetHeight, 50, "offsetHeight"); }, "cell2"); test(() => { - assert_equals(content2.offsetTop, 275, "offsetTop"); - assert_equals(content2.offsetLeft, 38, "offsetLeft"); + assert_equals(content2.offsetTop, 75, "offsetTop"); + assert_equals(content2.offsetLeft, 8, "offsetLeft"); assert_equals(content2.offsetWidth, 100, "offsetWidth"); assert_equals(content2.offsetHeight, 50, "offsetHeight"); }, "content2"); test(() => { - assert_equals(cell3.offsetTop, 332, "offsetTop"); - assert_equals(cell3.offsetLeft, 38, "offsetLeft"); + assert_equals(cell3.offsetTop, 132, "offsetTop"); + assert_equals(cell3.offsetLeft, 8, "offsetLeft"); assert_equals(cell3.offsetWidth, 100, "offsetWidth"); assert_equals(cell3.offsetHeight, 50, "offsetHeight"); }, "cell3"); test(() => { - assert_equals(content3.offsetTop, 332, "offsetTop"); - assert_equals(content3.offsetLeft, 38, "offsetLeft"); + assert_equals(content3.offsetTop, 132, "offsetTop"); + assert_equals(content3.offsetLeft, 8, "offsetLeft"); assert_equals(content3.offsetWidth, 100, "offsetWidth"); assert_equals(content3.offsetHeight, 50, "offsetHeight"); }, "content3"); test(() => { - assert_equals(row2.offsetTop, 418, "offsetTop"); - assert_equals(row2.offsetLeft, 51, "offsetLeft"); + assert_equals(row2.offsetTop, 218, "offsetTop"); + assert_equals(row2.offsetLeft, 8, "offsetLeft"); assert_equals(row2.offsetWidth, 50, "offsetWidth"); assert_equals(row2.offsetHeight, 164, "offsetHeight"); }, "row2"); test(() => { - assert_equals(cell4.offsetTop, 418, "offsetTop"); - assert_equals(cell4.offsetLeft, 51, "offsetLeft"); + assert_equals(cell4.offsetTop, 218, "offsetTop"); + assert_equals(cell4.offsetLeft, 8, "offsetLeft"); assert_equals(cell4.offsetWidth, 50, "offsetWidth"); assert_equals(cell4.offsetHeight, 50, "offsetHeight"); }, "cell4"); test(() => { - assert_equals(content4.offsetTop, 418, "offsetTop"); - assert_equals(content4.offsetLeft, 51, "offsetLeft"); + assert_equals(content4.offsetTop, 218, "offsetTop"); + assert_equals(content4.offsetLeft, 8, "offsetLeft"); assert_equals(content4.offsetWidth, 50, "offsetWidth"); assert_equals(content4.offsetHeight, 50, "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 @@ +<!DOCTYPE html> +<head> + <link rel="help" href="https://crbug.com/40934009"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> +</head> +<body> + <div id="d" style="background:revert">Test passes if it does not crash.</div> + <script> + test(() => { + d.offsetTop; + d.style.background = "url(dummy.png)"; + d.offsetLeft; + }); + </script> +</body> 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 @@ <!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>CSS Reftest Reference</title> - <link rel="author" title="Yusuf Sermet" href="mailto:ysermet@mozilla.com"> - <style> - div { - position: relative; - width: 100px; - } - #div1, - #div3 { - background-color: #cfc; - } - #div1 { - z-index: 5; - } - #div2 { - z-index: 1; - background-color: #fdd; - height: 100px; - top: -20px; - } - #div2_1 { - background-color: #ffc; - z-index: 6; - top: -10px; - } - #div2_2 { - z-index: 3; - position: absolute; - top: -15px; - width: 40px; - height: 100px; - background-color: #ddf; - } - #div3 { - z-index: 2; - top: -50px; - } - </style> -</head> -<body> - <div id="div1"> - <br/><br/> - </div> - - <div id="div2"> - <div id="div2_1"> - <br/><br/> - </div> - - <div id="div2_2"> - </div> - </div> - - <div id="div3"> - <br/><br/> - </div> -</body> -</html> +<meta charset="utf-8"> +<title>Paint Containment Stacking Context Reference</title> +<link rel="author" title="Psychpsyo" href="mailto:psychpsyo@gmail.com"> +<style> + div { + width: 100px; + height: 100px; + background-color: green; + } +</style> +<div></div> +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 @@ <!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>CSS Test: 'contain: paint' with stacking contents. Z-index is defined only for siblings and children.</title> - <link rel="author" title="Yusuf Sermet" href="mailto:ysermet@mozilla.com"> - - <link rel="help" href="https://drafts.csswg.org/css2/visuren.html#x43"> - <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-paint"> - <link rel="match" href="contain-paint-stacking-context-001-ref.html"> - <style> - div { - position: relative; - width: 100px; - } - #div1, - #div3 { - background-color: #cfc; - } - #div1 { - z-index: 5; - } - #div2 { - contain: paint; - background-color: #fdd; - height: 100px; - top: -20px; - } - #div2_1 { - background-color: #ffc; - z-index: 6; - top: -10px; - } - #div2_2 { - z-index: 3; - position: absolute; - top: -15px; - width: 40px; - height: 100px; - background-color: #ddf; - } - #div3 { - z-index: 2; - top: -50px; - } - </style> -</head> -<body> - <div id="div1"> - <br/><br/> - </div> - - <div id="div2"> - <div id="div2_1"> - <br/><br/> - </div> - - <div id="div2_2"> - </div> - </div> - - <div id="div3"> - <br/><br/> - </div> -</body> -</html> +<title>'contain: paint' establishes stacking context.</title> +<link rel="author" title="Psychpsyo" href="mailto:psychpsyo@gmail.com"> +<link rel="help" href="https://drafts.csswg.org/css2/visuren.html#x43"> +<link rel="help" href="https://drafts.csswg.org/css-contain/#containment-paint"> +<link rel="match" href="contain-paint-stacking-context-001-ref.html"> +<style> + div { + width: 100px; + height: 100px; + } + #front { + background-color: green; + /* makes a stacking context and puts this on top */ + position: absolute; + z-index: 10; + } + #back { + contain: paint; + } + #notOnTop { + background-color: red; + /* z-index is higher than on #front, but this should still be covered up because it is inside #back, which has 'contain: paint' */ + position: absolute; + z-index: 1000; + } +</style> +<div id="front"></div> +<div id="back"> + <div id="notOnTop"></div> +</div> +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 @@ <!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>CSS Test: 'will-change: contain' with stacking contents. Z-index is defined only for siblings and children.</title> - <link rel="author" title="Yusuf Sermet" href="mailto:ysermet@mozilla.com"> - - <link rel="help" href="https://drafts.csswg.org/css2/visuren.html#x43"> - <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-paint"> - <link rel="match" href="contain-paint-stacking-context-001-ref.html"> - <style> - div { - position: relative; - width: 100px; - } - #div1, - #div3 { - background-color: #cfc; - } - #div1 { - z-index: 5; - } - #div2 { - will-change: contain; - background-color: #fdd; - height: 100px; - top: -20px; - } - #div2_1 { - background-color: #ffc; - z-index: 6; - top: -10px; - } - #div2_2 { - z-index: 3; - position: absolute; - top: -15px; - width: 40px; - height: 100px; - background-color: #ddf; - } - #div3 { - z-index: 2; - top: -50px; - } - </style> -</head> -<body> - <div id="div1"> - <br/><br/> - </div> - - <div id="div2"> - <div id="div2_1"> - <br/><br/> - </div> - - <div id="div2_2"> - </div> - </div> - - <div id="div3"> - <br/><br/> - </div> -</body> -</html> +<title>'will-change: contain' establishes stacking context.</title> +<link rel="author" title="Psychpsyo" href="mailto:psychpsyo@gmail.com"> +<link rel="help" href="https://drafts.csswg.org/css2/visuren.html#x43"> +<link rel="help" href="https://drafts.csswg.org/css-contain/#containment-paint"> +<link rel="match" href="contain-paint-stacking-context-001-ref.html"> +<style> + div { + width: 100px; + height: 100px; + } + #front { + background-color: green; + /* makes a stacking context and puts this on top */ + position: absolute; + z-index: 10; + } + #back { + will-change: contain; + } + #notOnTop { + background-color: red; + /* z-index is higher than on #front, but this should still be covered up because it is inside #back, which has 'will-change: contain' */ + position: absolute; + z-index: 1000; + } +</style> +<div id="front"></div> +<div id="back"> + <div id="notOnTop"></div> +</div> +Test succeeds if there is no red. 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 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Display: display:contents in inline layout should affect style of descendants</title> +<link rel="author" title="Martin Robinson" href="mailto:mrobinson@igalia.com"> +<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com"> +<link rel="help" href="https://drafts.csswg.org/css-display-3/#valdef-display-contents"> +<link rel="match" href="display-contents-pass-no-red-ref.html"> +<style> + #contents { + display: contents; + color: black; + } + .red { color: red; } +</style> +<p>You should see the word PASS and no red below.</p> +<span> + P<span class="red"><div id="contents">AS</div></span>S +</span> 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 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Flex Container Max-Content Sizes</title> +<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com"> +<link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#intrinsic-main-sizes" + title="9.9.1. Flex Container Intrinsic Main Sizes"> +<link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#intrinsic-cross-sizes" + title="9.9.2. Flex Container Intrinsic Cross Sizes"> +<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/12123"> + +<link rel="stylesheet" type="text/css" href="/fonts/ahem.css"> +<style> +.flex { + display: inline-flex; + vertical-align: top; + border: 5px solid magenta; + width: max-content; + height: max-content; +} +.flex.min { + width: 0; + height: 0; + min-width: max-content; + min-height: max-content; +} +.flex.max { + width: 200px; + height: 200px; + max-width: max-content; + max-height: max-content; +} +.flex > div { + font: 25px/1 Ahem; + border: 5px solid cyan; +} +</style> + +<!-- Single-line row flex container --> +<div class="flex" style="flex-flow: row nowrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> +<div class="flex min" style="flex-flow: row nowrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> +<div class="flex max" style="flex-flow: row nowrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> + +<div class="flex" style="flex-flow: row nowrap" + data-expected-width="180" data-expected-height="45"> + <div data-expected-width="85" data-expected-height="35">X X</div> + <div data-expected-width="85" data-expected-height="35">X X</div> +</div> +<div class="flex min" style="flex-flow: row nowrap" + data-expected-width="180" data-expected-height="45"> + <div data-expected-width="85" data-expected-height="35">X X</div> + <div data-expected-width="85" data-expected-height="35">X X</div> +</div> +<div class="flex max" style="flex-flow: row nowrap" + data-expected-width="180" data-expected-height="45"> + <div data-expected-width="85" data-expected-height="35">X X</div> + <div data-expected-width="85" data-expected-height="35">X X</div> +</div> + +<hr> + +<!-- Single-line column flex container --> +<div class="flex" style="flex-flow: column nowrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> +<div class="flex min" style="flex-flow: column nowrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> +<div class="flex max" style="flex-flow: column nowrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> + +<div class="flex" style="flex-flow: column nowrap" + data-expected-width="95" data-expected-height="80"> + <div data-expected-width="85" data-expected-height="35">X X</div> + <div data-expected-width="85" data-expected-height="35">X X</div> +</div> +<div class="flex min" style="flex-flow: column nowrap" + data-expected-width="95" data-expected-height="80"> + <div data-expected-width="85" data-expected-height="35">X X</div> + <div data-expected-width="85" data-expected-height="35">X X</div> +</div> +<div class="flex max" style="flex-flow: column nowrap" + data-expected-width="95" data-expected-height="80"> + <div data-expected-width="85" data-expected-height="35">X X</div> + <div data-expected-width="85" data-expected-height="35">X X</div> +</div> + +<hr> + +<!-- Multi-line row flex container --> +<div class="flex" style="flex-flow: row wrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> +<div class="flex min" style="flex-flow: row wrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> +<div class="flex max" style="flex-flow: row wrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> + +<div class="flex" style="flex-flow: row wrap" + data-expected-width="180" data-expected-height="45"> + <div data-expected-width="85" data-expected-height="35">X X</div> + <div data-expected-width="85" data-expected-height="35">X X</div> +</div> +<div class="flex min" style="flex-flow: row wrap" + data-expected-width="180" data-expected-height="45"> + <div data-expected-width="85" data-expected-height="35">X X</div> + <div data-expected-width="85" data-expected-height="35">X X</div> +</div> +<div class="flex max" style="flex-flow: row wrap" + data-expected-width="180" data-expected-height="45"> + <div data-expected-width="85" data-expected-height="35">X X</div> + <div data-expected-width="85" data-expected-height="35">X X</div> +</div> + +<hr> + +<!-- Multi-line column flex container --> +<div class="flex" style="flex-flow: column wrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> +<div class="flex min" style="flex-flow: column wrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> +<div class="flex max" style="flex-flow: column wrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> + +<div class="flex" style="flex-flow: column wrap" + data-expected-width="95" data-expected-height="80"> + <div data-expected-width="85" data-expected-height="35">X X</div> + <div data-expected-width="85" data-expected-height="35">X X</div> +</div> +<div class="flex min" style="flex-flow: column wrap" + data-expected-width="95" data-expected-height="80"> + <div data-expected-width="85" data-expected-height="35">X X</div> + <div data-expected-width="85" data-expected-height="35">X X</div> +</div> +<div class="flex max" style="flex-flow: column wrap" + data-expected-width="95" data-expected-height="80"> + <div data-expected-width="85" data-expected-height="35">X X</div> + <div data-expected-width="85" data-expected-height="35">X X</div> +</div> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/check-layout-th.js"></script> +<script> +checkLayout(".flex"); +</script> 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 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Flex Container Min-Content Sizes</title> +<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com"> +<link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#intrinsic-main-sizes" + title="9.9.1. Flex Container Intrinsic Main Sizes"> +<link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#intrinsic-cross-sizes" + title="9.9.2. Flex Container Intrinsic Cross Sizes"> +<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/12123"> + +<link rel="stylesheet" type="text/css" href="/fonts/ahem.css"> +<style> +.flex { + display: inline-flex; + vertical-align: top; + border: 5px solid magenta; + width: min-content; + height: min-content; +} +.flex.min { + width: 0; + height: 0; + min-width: min-content; + min-height: min-content; +} +.flex.max { + width: 200px; + height: 200px; + max-width: min-content; + max-height: min-content; +} +.flex > div { + font: 25px/1 Ahem; + border: 5px solid cyan; +} +</style> + +<!-- Single-line row flex container --> +<div class="flex" style="flex-flow: row nowrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> +<div class="flex min" style="flex-flow: row nowrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> +<div class="flex max" style="flex-flow: row nowrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> + +<div class="flex" style="flex-flow: row nowrap" + data-expected-width="80" data-expected-height="70"> + <div data-expected-width="35" data-expected-height="60">X X</div> + <div data-expected-width="35" data-expected-height="60">X X</div> +</div> +<div class="flex min" style="flex-flow: row nowrap" + data-expected-width="80" data-expected-height="70"> + <div data-expected-width="35" data-expected-height="60">X X</div> + <div data-expected-width="35" data-expected-height="60">X X</div> +</div> +<div class="flex max" style="flex-flow: row nowrap" + data-expected-width="80" data-expected-height="70"> + <div data-expected-width="35" data-expected-height="60">X X</div> + <div data-expected-width="35" data-expected-height="60">X X</div> +</div> + +<hr> + +<!-- Single-line column flex container --> +<div class="flex" style="flex-flow: column nowrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> +<div class="flex min" style="flex-flow: column nowrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> +<div class="flex max" style="flex-flow: column nowrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> + +<div class="flex" style="flex-flow: column nowrap" + data-expected-width="45" data-expected-height="130"> + <div data-expected-width="35" data-expected-height="60">X X</div> + <div data-expected-width="35" data-expected-height="60">X X</div> +</div> +<div class="flex min" style="flex-flow: column nowrap" + data-expected-width="45" data-expected-height="130"> + <div data-expected-width="35" data-expected-height="60">X X</div> + <div data-expected-width="35" data-expected-height="60">X X</div> +</div> +<div class="flex max" style="flex-flow: column nowrap" + data-expected-width="45" data-expected-height="130"> + <div data-expected-width="35" data-expected-height="60">X X</div> + <div data-expected-width="35" data-expected-height="60">X X</div> +</div> + +<hr> + +<!-- Multi-line row flex container --> +<div class="flex" style="flex-flow: row wrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> +<div class="flex min" style="flex-flow: row wrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> +<div class="flex max" style="flex-flow: row wrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> + +<div class="flex" style="flex-flow: row wrap" + data-expected-width="45" data-expected-height="130"> + <div data-expected-width="35" data-expected-height="60">X X</div> + <div data-expected-width="35" data-expected-height="60">X X</div> +</div> +<div class="flex min" style="flex-flow: row wrap" + data-expected-width="45" data-expected-height="130"> + <div data-expected-width="35" data-expected-height="60">X X</div> + <div data-expected-width="35" data-expected-height="60">X X</div> +</div> +<div class="flex max" style="flex-flow: row wrap" + data-expected-width="45" data-expected-height="130"> + <div data-expected-width="35" data-expected-height="60">X X</div> + <div data-expected-width="35" data-expected-height="60">X X</div> +</div> + +<hr> + +<!-- Multi-line column flex container --> +<div class="flex" style="flex-flow: column wrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> +<div class="flex min" style="flex-flow: column wrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> +<div class="flex max" style="flex-flow: column wrap" + data-expected-width="45" data-expected-height="45"> + <div style="width: 25px; height: 25px">X</div> +</div> + +<div class="flex" style="flex-flow: column wrap" + data-expected-width="45" data-expected-height="130"> + <div data-expected-width="35" data-expected-height="60">X X</div> + <div data-expected-width="35" data-expected-height="60">X X</div> +</div> +<div class="flex min" style="flex-flow: column wrap" + data-expected-width="45" data-expected-height="130"> + <div data-expected-width="35" data-expected-height="60">X X</div> + <div data-expected-width="35" data-expected-height="60">X X</div> +</div> +<div class="flex max" style="flex-flow: column wrap" + data-expected-width="45" data-expected-height="130"> + <div data-expected-width="35" data-expected-height="60">X X</div> + <div data-expected-width="35" data-expected-height="60">X X</div> +</div> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/check-layout-th.js"></script> +<script> +checkLayout(".flex"); +</script> 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 @@ +<!DOCTYPE html> +<meta charset="UTF-8" /> +<title>CSS Fonts reference</title> +<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" /> + +<style> +body { background: white; } +p { font: 16px/3 serif; } +span { color: white; font: 25px Ahem; } +span span { color: initial; font-variant-emoji: emoji; } +</style> + +<h4>Only lines 3 and 4 should show a keycap numeral:</h4> + +<p>1. text: <span>X</span></p> + +<p>2. unicode: <span>X</span></p> + +<p>3. emoji: <span>X<span>3️⃣</span>X</span></p> + +<p>4. with VS16: <span>X<span>4️⃣</span>X</span></p> + +<p>5. emoji, with VS15: <span>X</span></p> 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 @@ +<!DOCTYPE html> +<meta charset="UTF-8" /> +<title>CSS Fonts: font-variant-emoji web font test</title> +<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-variant-emoji-prop" /> +<link rel="help" href="https://www.unicode.org/reports/tr51/tr51-25.html#Emoji_Properties_and_Data_Files" /> +<link rel="match" href="font-variant-emoji-005-ref.html"/> +<meta name="assert" content="Digit-keycap sequences render as emoji if required, in preference to using the named font"/> +<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" /> + +<style> +body { background: white; } +p { font: 16px/3 serif; } +/* A color-emoji glyph in the span will be visible despite the white color; + * if color-emoji presentation is not used, the span will be invisible. + */ +span { color: white; font: 25px Ahem; } +</style> + +<h4>Only lines 3 and 4 should show a keycap numeral:</h4> + +<p>1. text: <span style="font-variant-emoji: text">X1⃣X</span></p> + +<p>2. unicode: <span style="font-variant-emoji: unicode">X2⃣X</span></p> + +<p>3. emoji: <span style="font-variant-emoji: emoji">X3⃣X</span></p> + +<p>4. with VS16: <span style="font-variant-emoji: normal">X4️⃣X</span></p> + +<p>5. emoji, with VS15: <span style="font-variant-emoji: emoji">X5︎⃣X</span></p> diff --git a/tests/wpt/tests/css/css-gaps/agnostic/gap-decorations-002-ref.html b/tests/wpt/tests/css/css-gaps/agnostic/gap-decorations-002-ref.html new file mode 100644 index 00000000000..42c5ef128b4 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/agnostic/gap-decorations-002-ref.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1/"> +<link rel="author" title="Javier Contreras" href="mailto:javiercon@microsoft.com"> +<style> + body { + margin: 0px; + } + + .container { + display: flex; + width: 120px; + height: 110px; + column-gap: 20px; + row-gap: 10px; + flex-wrap: wrap; + } + + .item { + background: skyblue; + height: 50px; + width: 50px; + margin: 0; + } + + .row-gap { + position: absolute; + top: 50px; + background: gold; + width: 120px; + height: 10px; + } + + .column-gap { + position: absolute; + top: 0px; + left: 50px; + background: blue; + height: 110px; + width: 20px; + } + +</style> +<div class="container"> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> +</div> + +<div class="row-gap"></div> +<div class="column-gap"></div> 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 @@ +<!DOCTYPE html> +<title> + CSS Gap Decorations: flex column gaps are painted with different sized gaps and row-rule-outset 0. +</title> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1/"> +<link rel="match" href="../agnostic/gap-decorations-002-ref.html"> +<link rel="author" title="Javier Contreras" href="mailto:javiercon@microsoft.com"> +<style> + body { + margin: 0px; + } + + .flex-container { + height: 110px; + width: 120px; + + display: flex; + + column-gap: 20px; + row-gap: 10px; + + column-rule-color: blue; + column-rule-style: solid; + column-rule-width: 20px; + + row-rule-color: gold; + row-rule-style: solid; + row-rule-width: 10px; + + row-rule-break: intersection; + row-rule-outset: 0; + + flex-wrap: wrap; + } + + .flex-item { + background: skyblue; + width: 50px; + } +</style> +<div class="flex-container"> + <div class="flex-item"></div> + <div class="flex-item"></div> + <div class="flex-item"></div> + <div class="flex-item"></div> +</div> diff --git a/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-029-ref.html b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-029-ref.html new file mode 100644 index 00000000000..35450e07ae5 --- /dev/null +++ b/tests/wpt/tests/css/css-gaps/grid/grid-gap-decorations-029-ref.html @@ -0,0 +1,70 @@ +<!DOCTYPE html> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1"> +<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com"> +<style> + body { + margin: 0px; + } + + .grid-container { + display: grid; + grid-gap: 10px; + grid-template-columns: 100px 100px 100px; + grid-template-rows: 100px 100px 100px; + width: 120px; + height: 120px; + } + + .item { + background: gray; + opacity: 0.5; + } + + .row-gap { + position: absolute; + width: 320px; + height: 0px; + border-bottom: solid 5px red; + } + + .row-gap1 { + top: 102.5px; + } + + .row-gap2 { + top: 212.5px; + } + + .col-gap { + position: absolute; + top: 0px; + width: 0px; + height: 320px; + border-left: solid 5px blue; + } + + .col-gap1 { + left: 102.5px; + } + + .col-gap2 { + left: 212.5px; + } +</style> +<div class="grid-container"> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> +</div> + +<div class="col-gap col-gap1"> </div> +<div class="col-gap col-gap2"> </div> + +<div class="row-gap row-gap1"> </div> +<div class="row-gap row-gap2"> </div> 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 @@ +<!DOCTYPE html> +<title> + CSS Gap Decorations: Gaps are painted when items overflow container. +</title> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1/"> +<link rel="match" href="grid-gap-decorations-029-ref.html"> +<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com"> +<style> + body { + margin: 0px; + } + + .grid-container { + display: grid; + grid-gap: 10px; + grid-template-columns: 100px 100px 100px; + grid-template-rows: 100px 100px 100px; + width: 120px; + height: 120px; + + column-rule-color: blue; + column-rule-style: solid; + column-rule-width: 5px; + + row-rule-color: red; + row-rule-style: solid; + row-rule-width: 5px; + } + + .item { + background: gray; + opacity: 0.5; + } +</style> + +<body> + <div class="grid-container"> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + </div> +</body> 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 @@ +<!DOCTYPE html> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1"> +<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com"> +<style> + body { + margin: 0px; + } + + .grid-container { + display: grid; + grid-gap: 10px; + grid-template-columns: 100px 100px 100px; + width: 120px; + height: 120px; + } + + .item { + background: gray; + opacity: 0.5; + } + + .col-gap { + position: absolute; + top: 0px; + width: 0px; + height: 120px; + border-left: solid 5px blue; + } + + .col-gap1 { + left: 102.5px; + } + + .col-gap2 { + left: 212.5px; + } +</style> +<div class="grid-container"> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> +</div> + +<div class="col-gap col-gap1"> </div> +<div class="col-gap col-gap2"> </div> 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 @@ +<!DOCTYPE html> +<title> + CSS Gap Decorations: Gaps are painted when items overflow container - no row gaps. +</title> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1/"> +<link rel="match" href="grid-gap-decorations-030-ref.html"> +<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com"> +<style> + body { + margin: 0px; + } + + .grid-container { + display: grid; + grid-gap: 10px; + grid-template-columns: 100px 100px 100px; + width: 120px; + height: 120px; + + column-rule-color: blue; + column-rule-style: solid; + column-rule-width: 5px; + } + + .item { + background: gray; + opacity: 0.5; + } +</style> + +<body> + <div class="grid-container"> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + </div> +</body> 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 @@ +<!DOCTYPE html> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1"> +<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com"> +<style> + body { + margin: 0px; + } + + .grid-container { + display: grid; + grid-gap: 10px; + grid-template-rows: 100px 100px 100px; + grid-auto-flow: column; + width: 120px; + height: 120px; + } + + .item { + background: gray; + opacity: 0.5; + } + + .row-gap { + position: absolute; + width: 120px; + height: 0px; + border-bottom: solid 5px red; + } + + .row-gap1 { + top: 102.5px; + } + + .row-gap2 { + top: 212.5px; + } +</style> +<div class="grid-container"> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> +</div> + +<div class="row-gap row-gap1"> </div> +<div class="row-gap row-gap2"> </div> 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 @@ +<!DOCTYPE html> +<title> + CSS Gap Decorations: Gaps are painted when items overflow container - no column gaps. +</title> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1/"> +<link rel="match" href="grid-gap-decorations-031-ref.html"> +<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com"> +<style> + body { + margin: 0px; + } + + .grid-container { + display: grid; + grid-gap: 10px; + grid-template-rows: 100px 100px 100px; + grid-auto-flow: column; + width: 120px; + height: 120px; + + row-rule-color: red; + row-rule-style: solid; + row-rule-width: 5px; + } + + .item { + background: gray; + opacity: 0.5; + } +</style> + +<body> + <div class="grid-container"> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + </div> +</body> 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 @@ +<!DOCTYPE html> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1"> +<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com"> +<style> + body { + margin: 0px; + overflow: hidden; + } + + .grid-container { + display: grid; + grid-gap: 10px; + grid-template-columns: 100px 100px 100px; + grid-template-rows: repeat(6, 100px); + width: 120px; + height: 120px; + } + + .row-gap { + position: absolute; + width: 320px; + height: 0px; + border-bottom: solid 5px red; + } + + .row-gap1 { + top: 102.5px; + } + + .row-gap2 { + top: 212.5px; + } + + .row-gap3 { + top: 322.5px; + } + + .row-gap4 { + top: 432.5px; + } + + .row-gap5 { + top: 542.5px; + } + + .col-gap { + position: absolute; + top: 0px; + width: 0px; + height: 650px; + border-left: solid 10px blue; + } + + .col-gap1 { + left: 100px; + } + + .col-gap2 { + left: 210px; + } +</style> +<div class="grid-container"></div> + +<div class="col-gap col-gap1"> </div> +<div class="col-gap col-gap2"> </div> + +<div class="row-gap row-gap1"> </div> +<div class="row-gap row-gap2"> </div> +<div class="row-gap row-gap3"> </div> +<div class="row-gap row-gap4"> </div> +<div class="row-gap row-gap5"> </div> 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 @@ +<!DOCTYPE html> +<title> + CSS Gap Decorations: Gaps are painted when rows are dynamically added and overflow container. +</title> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1/"> +<link rel="match" href="grid-gap-decorations-032-ref.html"> +<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com"> +<style> + body { + margin: 0px; + overflow: hidden; + } + + .grid-container { + display: grid; + grid-gap: 10px; + grid-template-columns: 100px 100px 100px; + grid-template-rows: 100px; + width: 120px; + height: 120px; + + column-rule-color: blue; + column-rule-style: solid; + column-rule-width: 10px; + } + +</style> + +<body> + <div class="grid-container"></div> +<script> + const grid = document.querySelector('.grid-container'); + grid.style.gridTemplateRows = 'repeat(6, 100px)'; + + grid.style.rowRuleColor = 'red'; + grid.style.rowRuleStyle = 'solid'; + grid.style.rowRuleWidth = '5px'; +</script> +</body> 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 @@ +<!DOCTYPE html> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1"> +<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com"> +<style> + body { + margin: 0px; + overflow: hidden; + } + + .grid-container { + display: grid; + grid-gap: 10px; + grid-template-columns: repeat(3, 50px); + grid-template-rows: repeat(6, 50px); + width: 50px; + height: 50px; + } + + .row-gap { + position: absolute; + width: 170px; + height: 0px; + border-bottom: solid 5px red; + } + + .row-gap1 { + top: 52.5px; + } + + .row-gap2 { + top: 112.5px; + } + + .row-gap3 { + top: 172.5px; + } + + .row-gap4 { + top: 232.5px; + } + + .row-gap5 { + top: 195px; + border-bottom: solid 200px red; /*expand the last row gap's height*/ + } + + .col-gap { + position: absolute; + top: 0px; + width: 0px; + height: 350px; + border-left: solid 10px blue; + } + + .col-gap1 { + left: 50px; + } + + .col-gap2 { + left: 110px; + } +</style> +<div class="grid-container"></div> + +<div class="col-gap col-gap1"> </div> +<div class="col-gap col-gap2"> </div> + +<div class="row-gap row-gap1"> </div> +<div class="row-gap row-gap2"> </div> +<div class="row-gap row-gap3"> </div> +<div class="row-gap row-gap4"> </div> +<div class="row-gap row-gap5"> </div> 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 @@ +<!DOCTYPE html> +<title> + CSS Gap Decorations: Decorations are painted when rule thickness is greater than gap size. +</title> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1/"> +<link rel="match" href="grid-gap-decorations-033-ref.html"> +<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com"> +<style> + body { + margin: 0px; + overflow: hidden; + } + + .grid-container { + display: grid; + gap: 10px; + grid-template-columns: repeat(3, 50px); + grid-template-rows: repeat(6, 50px); + width: 50px; + height: 50px; + + column-rule-color: blue; + column-rule-style: solid; + column-rule-width: 10px; + } + +</style> + +<body> + <div class="grid-container"></div> +<script> + const grid = document.querySelector('.grid-container'); + + grid.style.rowRuleColor = 'red'; + grid.style.rowRuleStyle = 'solid'; + grid.style.rowRuleWidth = 'repeat(4, 5px) 200px'; +</script> +</body> 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 @@ +<!DOCTYPE html> +<title> + CSS Gap Decorations: grid column gaps are painted with different sized gaps and column-rule-outset 0. +</title> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1/"> +<link rel="match" href="../agnostic/gap-decorations-002-ref.html"> +<link rel="author" title="Javier Contreras" href="mailto:javiercon@microsoft.com"> +<style> + body { + margin: 0px; + } + + .grid-container { + height: 110px; + width: 120px; + + display: grid; + grid-template-columns: repeat(2, 1fr); + + column-gap: 20px; + row-gap: 10px; + + column-rule-color: blue; + column-rule-style: solid; + column-rule-width: 20px; + + row-rule-color: gold; + row-rule-style: solid; + row-rule-width: 10px; + + row-rule-break: intersection; + row-rule-outset: 0; + } + + .grid-item { + background: skyblue; + } +</style> +<div class="grid-container"> + <div class="grid-item"></div> + <div class="grid-item"></div> + <div class="grid-item"></div> + <div class="grid-item"></div> +</div> 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 @@ +<!DOCTYPE html> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1/"> +<link rel="author" title="Javier Contreras" href="mailto:javiercon@microsoft.com"> +<style> + body { + margin: 0px; + } + + .container { + border: 2px solid rgb(96 139 168); + width: 210px; + height: 130px; + column-gap: 20px; + display: flex; + } + + .items { + background: rgb(96 139 168 / 0.2); + height: 130px; + margin: 0px; + width: 56.666px; + } + + .row-gap { + position: absolute; + height: 10px; + width: 210px; + background: gold; + left: 2px; + top: 62px; + } + + .column-gap { + position: absolute; + height: 130px; + width: 20px; + background: blue; + top: 2px; + } +</style> + +<div class="container"> + <div class="items"></div> + <div class="items"></div> + <div class="items"></div> +</div> +<div class="row-gap"></div> +<div class="column-gap" style="left:58.666px;"></div> +<div class="column-gap" style="left:135.332px;"></div> 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 @@ +<!DOCTYPE html> +<title> + CSS Gap Decorations: Multicolumn gap decorations painted with column-wrap and with different sized gaps and row-rule-outset 0. +</title> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1/"> +<link rel="match" href="multicol-gap-decorations-017-ref.html"> +<link rel="author" title="Javier Contreras" href="mailto:javiercon@microsoft.com"> +<style> + body { + margin: 0px; + } + + .container { + border: 2px solid rgb(96 139 168); + width: 210px; + height: 130px; + column-count: 3; + column-width: 56.666px; + column-height: 60px; + column-gap: 20px; + row-gap: 10px; + column-rule-width: 20px; + column-rule-style: solid; + column-rule-color: blue; + row-rule-width: 10px; + row-rule-style: solid; + row-rule-color: gold; + column-wrap: wrap; + + row-rule-break: intersection; + row-rule-outset: 0; + } + + p { + background: rgb(96 139 168 / 0.2); + height: 60px; + margin: 0px; + } +</style> + +<div class="container"> + <p></p> + <p></p> + <p></p> + <p></p> + <p></p> + <p></p> +</div> 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 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Gap Decorations: Ensure getComputedStyle for column-rule-width is as specified with multiple values</title> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1/#column-row-rule-width"> +<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com"> +<script src="/resources/testharness.js" type="text/javascript"></script> +<script src="/resources/testharnessreport.js" type="text/javascript"></script> +</head> +<body> +<div id="target1"></div> +<div id="target2"></div> +<div id="target3"></div> +<style> + #target1 { + column-rule-width: thin; + } + + #target2 { + column-rule-width: 5px 10px 15px; + } + + #target3 { + column-rule-width: repeat(auto, 5px); + } +</style> +<script> + test(function() { + const containerStyle = window.getComputedStyle(document.querySelector('#target1')); + const columnRuleWidth = containerStyle.getPropertyValue('column-rule-width'); + assert_equals(columnRuleWidth, '0px'); + + }, "`column-rule-width` should be `0px` when `column-rule-style` is `none` with single value"); + + test(function() { + const containerStyle = window.getComputedStyle(document.querySelector('#target2')); + const columnRuleWidth = containerStyle.getPropertyValue('column-rule-width'); + assert_equals(columnRuleWidth, '5px 10px 15px'); + + }, "`column-rule-width` should be as specified regardless of `column-rule-style` with multiple values"); + + test(function() { + const containerStyle = window.getComputedStyle(document.querySelector('#target3')); + const columnRuleWidth = containerStyle.getPropertyValue('column-rule-width'); + assert_equals(columnRuleWidth, 'repeat(auto, 5px)'); + + }, "`column-rule-width` should be as specified regardless of `column-rule-style` with multiple (repeat) values"); +</script> +</body> +</html> diff --git a/tests/wpt/tests/css/css-images/linear-gradient-body-sibling-index-ref.html b/tests/wpt/tests/css/css-images/linear-gradient-body-sibling-index-ref.html new file mode 100644 index 00000000000..7325685511a --- /dev/null +++ b/tests/wpt/tests/css/css-images/linear-gradient-body-sibling-index-ref.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<title>CSS Test Reference</title> +<style> + body { + background-position: top left; + background-repeat: no-repeat; + background-size: 100px 100px; + background-image: linear-gradient(blue 40px, yellow); + } +</style> +<body> 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 @@ +<!DOCTYPE html> +<title>CSS Images Test: Linear gradient with sibling-index()</title> +<link rel="help" href="https://drafts.csswg.org/css-images/#linear-gradients"> +<link rel="help" href="https://drafts.csswg.org/css-values-5/#tree-counting"> +<link rel="match" href="linear-gradient-body-sibling-index-ref.html"> +<link rel="assert" content"sibling-index() is 2 for body"> +<style> + body { + background-position: top left; + background-repeat: no-repeat; + background-size: 100px 100px; + background-image: linear-gradient(blue calc(20px * sibling-index()), yellow); + } +</style> +<body> 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 @@ +<!DOCTYPE html> +<title>CSS Test Reference</title> +<style> + div { width: 100px; height: 100px; } +</style> +<div style="background: linear-gradient(blue 20px, yellow)"></div> +<div style="background: linear-gradient(blue 60px, yellow)"></div> 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 @@ +<!DOCTYPE html> +<title>CSS Images Test: Linear gradient with em in calc()</title> +<link rel="help" href="https://drafts.csswg.org/css-images/#linear-gradients"> +<link rel="match" href="linear-gradient-calc-em-units-ref.html"> +<style> + div { + width: 100px; + height: 100px; + background: linear-gradient(blue calc(2em), yellow); + } + #em1 { + font-size: 10px; + } + #em2 { + font-size: 30px; + } +</style> +<div id="em1"></div> +<div id="em2"></div> 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 @@ +<!DOCTYPE html> +<title>CSS Test Reference</title> +<style> + div { width: 100px; height: 100px; } +</style> +<div style="background: linear-gradient(blue 20px, yellow)"></div> +<div style="background: linear-gradient(blue 50px, yellow)"></div> 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 @@ +<!DOCTYPE html> +<title>CSS Images Test: Linear gradient with sibling-index()</title> +<link rel="help" href="https://drafts.csswg.org/css-images/#linear-gradients"> +<link rel="help" href="https://drafts.csswg.org/css-values-5/#tree-counting"> +<link rel="match" href="linear-gradient-sibling-index-ref.html"> +<style> + .grad { + width: 100px; + height: 100px; + background: linear-gradient(blue calc(10px * sibling-index()), yellow); + } +</style> +<div> + <div></div> + <div class="grad"></div> + <div></div> + <div></div> + <div class="grad"></div> +</div> 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 index 11e653e9b7d..15305be2b28 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 @@ <!DOCTYPE html> <title>Custom Functions: Handling cycles</title> -<link rel="help" href="https://drafts.csswg.org/css-mixins-1/#cycles"> -<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/11500"> +<link rel="help" href="https://drafts.csswg.org/css-mixins-1/#evaluating-custom-functions"> +<link rel="help" href="https://drafts.csswg.org/css-values-5/#cyclic-substitution-contexts"> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="resources/utils.js"></script> diff --git a/tests/wpt/tests/css/css-multicol/getclientrects-005.html b/tests/wpt/tests/css/css-multicol/getclientrects-005.html new file mode 100644 index 00000000000..ee60a607dcc --- /dev/null +++ b/tests/wpt/tests/css/css-multicol/getclientrects-005.html @@ -0,0 +1,139 @@ +<!DOCTYPE html> +<title>getClientRects on fragmented table-column and table-column-group</title> +<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org"> +<link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-element-getclientrects"> +<link rel="help" href="https://drafts.csswg.org/css-multicol/"> +<style> + body { + margin: 8px; + } +</style> +<div style="columns:6; gap:20px; inline-size:580px; column-rule:solid; column-fill:auto; block-size:100px; background:yellow;"> + <div style="block-size:110px;"></div> + <div style="display:table; inline-size:100%; border-spacing:5px; border:2px solid; background:pink;"> + <div style="display:table-caption; block-size:100px;"></div> + <div style="display:table-caption; caption-side:bottom; block-size:100px;"></div> + <div id="col1" style="display:table-column; inline-size:15px; background:blue;"></div> + <div id="col2" style="display:table-column; background:lime;"></div> + <div id="group" style="display:table-column-group;"> + <div id="col3" style="display:table-column; inline-size:20px; background:hotpink;"></div> + <div id="col4" style="display:table-column; inline-size:10px; background:cyan;" id="col2"></div> + </div> + <div style="display:table-row-group;"> + <div style="display:table-cell;"> + <div style="block-size:5px;"></div> + </div> + </div> + <div style="display:table-row-group;"> + <div style="display:table-cell;"> + <div style="block-size:240px;"></div> + </div> + <div style="display:table-cell;"></div> + <div style="display:table-cell;"></div> + <div style="display:table-cell;"></div> + </div> + </div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + test(()=> { + let rects = col1.getClientRects(); + assert_equals(rects.length, 3); + + assert_equals(rects[0].left, 215); + assert_equals(rects[0].top, 25); + assert_equals(rects[0].width, 15); + assert_equals(rects[0].height, 83); + + assert_equals(rects[1].left, 315); + assert_equals(rects[1].top, 8); + assert_equals(rects[1].width, 15); + assert_equals(rects[1].height, 100); + + assert_equals(rects[2].left, 415); + assert_equals(rects[2].top, 8); + assert_equals(rects[2].width, 15); + assert_equals(rects[2].height, 67); + }, "#col1"); + + test(()=> { + let rects = col2.getClientRects(); + assert_equals(rects.length, 3); + + assert_equals(rects[0].left, 235); + assert_equals(rects[0].top, 25); + assert_equals(rects[0].width, 10); + assert_equals(rects[0].height, 83); + + assert_equals(rects[1].left, 335); + assert_equals(rects[1].top, 8); + assert_equals(rects[1].width, 10); + assert_equals(rects[1].height, 100); + + assert_equals(rects[2].left, 435); + assert_equals(rects[2].top, 8); + assert_equals(rects[2].width, 10); + assert_equals(rects[2].height, 67); + }, "#col2"); + + test(()=> { + let rects = col3.getClientRects(); + assert_equals(rects.length, 3); + + assert_equals(rects[0].left, 250); + assert_equals(rects[0].top, 25); + assert_equals(rects[0].width, 20); + assert_equals(rects[0].height, 83); + + assert_equals(rects[1].left, 350); + assert_equals(rects[1].top, 8); + assert_equals(rects[1].width, 20); + assert_equals(rects[1].height, 100); + + assert_equals(rects[2].left, 450); + assert_equals(rects[2].top, 8); + assert_equals(rects[2].width, 20); + assert_equals(rects[2].height, 67); + }, "#col3"); + + test(()=> { + let rects = col4.getClientRects(); + assert_equals(rects.length, 3); + + assert_equals(rects[0].left, 275); + assert_equals(rects[0].top, 25); + assert_equals(rects[0].width, 10); + assert_equals(rects[0].height, 83); + + assert_equals(rects[1].left, 375); + assert_equals(rects[1].top, 8); + assert_equals(rects[1].width, 10); + assert_equals(rects[1].height, 100); + + assert_equals(rects[2].left, 475); + assert_equals(rects[2].top, 8); + assert_equals(rects[2].width, 10); + assert_equals(rects[2].height, 67); + }, "#col4"); + + test(()=> { + let rects = group.getClientRects(); + assert_equals(rects.length, 3); + + assert_equals(rects[0].left, 250); + assert_equals(rects[0].top, 25); + assert_equals(rects[0].width, 35); + assert_equals(rects[0].height, 83); + + assert_equals(rects[1].left, 350); + assert_equals(rects[1].top, 8); + assert_equals(rects[1].width, 35); + assert_equals(rects[1].height, 100); + + assert_equals(rects[2].left, 450); + assert_equals(rects[2].top, 8); + assert_equals(rects[2].width, 35); + assert_equals(rects[2].height, 67); + }, "#group"); +</script> diff --git a/tests/wpt/tests/css/css-multicol/getclientrects-006.html b/tests/wpt/tests/css/css-multicol/getclientrects-006.html new file mode 100644 index 00000000000..9f07a7de744 --- /dev/null +++ b/tests/wpt/tests/css/css-multicol/getclientrects-006.html @@ -0,0 +1,139 @@ +<!DOCTYPE html> +<title>getClientRects on fragmented table-column and table-column-group, vertical-rl</title> +<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org"> +<link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-element-getclientrects"> +<link rel="help" href="https://drafts.csswg.org/css-multicol/"> +<style> + body { + margin: 8px; + } +</style> +<div style="writing-mode:vertical-rl; columns:6; gap:20px; inline-size:580px; column-rule:solid; column-fill:auto; block-size:100px; background:yellow;"> + <div style="block-size:110px;"></div> + <div style="display:table; inline-size:100%; border-spacing:5px; border:2px solid; background:pink;"> + <div style="display:table-caption; block-size:100px;"></div> + <div style="display:table-caption; caption-side:bottom; block-size:100px;"></div> + <div id="col1" style="display:table-column; inline-size:15px; background:blue;"></div> + <div id="col2" style="display:table-column; background:lime;"></div> + <div id="group" style="display:table-column-group;"> + <div id="col3" style="display:table-column; inline-size:20px; background:hotpink;"></div> + <div id="col4" style="display:table-column; inline-size:10px; background:cyan;" id="col2"></div> + </div> + <div style="display:table-row-group;"> + <div style="display:table-cell;"> + <div style="block-size:5px;"></div> + </div> + </div> + <div style="display:table-row-group;"> + <div style="display:table-cell;"> + <div style="block-size:240px;"></div> + </div> + <div style="display:table-cell;"></div> + <div style="display:table-cell;"></div> + <div style="display:table-cell;"></div> + </div> + </div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + test(()=> { + let rects = col1.getClientRects(); + assert_equals(rects.length, 3); + + assert_equals(rects[0].left, 8); + assert_equals(rects[0].top, 215); + assert_equals(rects[0].width, 83); + assert_equals(rects[0].height, 15); + + assert_equals(rects[1].left, 8); + assert_equals(rects[1].top, 315); + assert_equals(rects[1].width, 100); + assert_equals(rects[1].height, 15); + + assert_equals(rects[2].left, 41); + assert_equals(rects[2].top, 415); + assert_equals(rects[2].width, 67); + assert_equals(rects[2].height, 15); + }, "#col1"); + + test(()=> { + let rects = col2.getClientRects(); + assert_equals(rects.length, 3); + + assert_equals(rects[0].left, 8); + assert_equals(rects[0].top, 235); + assert_equals(rects[0].width, 83); + assert_equals(rects[0].height, 10); + + assert_equals(rects[1].left, 8); + assert_equals(rects[1].top, 335); + assert_equals(rects[1].width, 100); + assert_equals(rects[1].height, 10); + + assert_equals(rects[2].left, 41); + assert_equals(rects[2].top, 435); + assert_equals(rects[2].width, 67); + assert_equals(rects[2].height, 10); + }, "#col2"); + + test(()=> { + let rects = col3.getClientRects(); + assert_equals(rects.length, 3); + + assert_equals(rects[0].left, 8); + assert_equals(rects[0].top, 250); + assert_equals(rects[0].width, 83); + assert_equals(rects[0].height, 20); + + assert_equals(rects[1].left, 8); + assert_equals(rects[1].top, 350); + assert_equals(rects[1].width, 100); + assert_equals(rects[1].height, 20); + + assert_equals(rects[2].left, 41); + assert_equals(rects[2].top, 450); + assert_equals(rects[2].width, 67); + assert_equals(rects[2].height, 20); + }, "#col3"); + + test(()=> { + let rects = col4.getClientRects(); + assert_equals(rects.length, 3); + + assert_equals(rects[0].left, 8); + assert_equals(rects[0].top, 275); + assert_equals(rects[0].width, 83); + assert_equals(rects[0].height, 10); + + assert_equals(rects[1].left, 8); + assert_equals(rects[1].top, 375); + assert_equals(rects[1].width, 100); + assert_equals(rects[1].height, 10); + + assert_equals(rects[2].left, 41); + assert_equals(rects[2].top, 475); + assert_equals(rects[2].width, 67); + assert_equals(rects[2].height, 10); + }, "#col4"); + + test(()=> { + let rects = group.getClientRects(); + assert_equals(rects.length, 3); + + assert_equals(rects[0].left, 8); + assert_equals(rects[0].top, 250); + assert_equals(rects[0].width, 83); + assert_equals(rects[0].height, 35); + + assert_equals(rects[1].left, 8); + assert_equals(rects[1].top, 350); + assert_equals(rects[1].width, 100); + assert_equals(rects[1].height, 35); + + assert_equals(rects[2].left, 41); + assert_equals(rects[2].top, 450); + assert_equals(rects[2].width, 67); + assert_equals(rects[2].height, 35); + }, "#group"); +</script> diff --git a/tests/wpt/tests/css/css-multicol/getclientrects-007.html b/tests/wpt/tests/css/css-multicol/getclientrects-007.html new file mode 100644 index 00000000000..0791c5b931f --- /dev/null +++ b/tests/wpt/tests/css/css-multicol/getclientrects-007.html @@ -0,0 +1,139 @@ +<!DOCTYPE html> +<title>getClientRects on fragmented table-column and table-column-group, vertical-lr</title> +<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org"> +<link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-element-getclientrects"> +<link rel="help" href="https://drafts.csswg.org/css-multicol/"> +<style> + body { + margin: 8px; + } +</style> +<div style="float:left; writing-mode:vertical-lr; columns:6; gap:20px; inline-size:580px; column-rule:solid; column-fill:auto; block-size:100px; background:yellow;"> + <div style="block-size:110px;"></div> + <div style="display:table; inline-size:100%; border-spacing:5px; border:2px solid; background:pink;"> + <div style="display:table-caption; block-size:100px;"></div> + <div style="display:table-caption; caption-side:bottom; block-size:100px;"></div> + <div id="col1" style="display:table-column; inline-size:15px; background:blue;"></div> + <div id="col2" style="display:table-column; background:lime;"></div> + <div id="group" style="display:table-column-group;"> + <div id="col3" style="display:table-column; inline-size:20px; background:hotpink;"></div> + <div id="col4" style="display:table-column; inline-size:10px; background:cyan;" id="col2"></div> + </div> + <div style="display:table-row-group;"> + <div style="display:table-cell;"> + <div style="block-size:5px;"></div> + </div> + </div> + <div style="display:table-row-group;"> + <div style="display:table-cell;"> + <div style="block-size:240px;"></div> + </div> + <div style="display:table-cell;"></div> + <div style="display:table-cell;"></div> + <div style="display:table-cell;"></div> + </div> + </div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + test(()=> { + let rects = col1.getClientRects(); + assert_equals(rects.length, 3); + + assert_equals(rects[0].top, 215); + assert_equals(rects[0].left, 25); + assert_equals(rects[0].height, 15); + assert_equals(rects[0].width, 83); + + assert_equals(rects[1].top, 315); + assert_equals(rects[1].left, 8); + assert_equals(rects[1].height, 15); + assert_equals(rects[1].width, 100); + + assert_equals(rects[2].top, 415); + assert_equals(rects[2].left, 8); + assert_equals(rects[2].height, 15); + assert_equals(rects[2].width, 67); + }, "#col1"); + + test(()=> { + let rects = col2.getClientRects(); + assert_equals(rects.length, 3); + + assert_equals(rects[0].top, 235); + assert_equals(rects[0].left, 25); + assert_equals(rects[0].height, 10); + assert_equals(rects[0].width, 83); + + assert_equals(rects[1].top, 335); + assert_equals(rects[1].left, 8); + assert_equals(rects[1].height, 10); + assert_equals(rects[1].width, 100); + + assert_equals(rects[2].top, 435); + assert_equals(rects[2].left, 8); + assert_equals(rects[2].height, 10); + assert_equals(rects[2].width, 67); + }, "#col2"); + + test(()=> { + let rects = col3.getClientRects(); + assert_equals(rects.length, 3); + + assert_equals(rects[0].top, 250); + assert_equals(rects[0].left, 25); + assert_equals(rects[0].height, 20); + assert_equals(rects[0].width, 83); + + assert_equals(rects[1].top, 350); + assert_equals(rects[1].left, 8); + assert_equals(rects[1].height, 20); + assert_equals(rects[1].width, 100); + + assert_equals(rects[2].top, 450); + assert_equals(rects[2].left, 8); + assert_equals(rects[2].height, 20); + assert_equals(rects[2].width, 67); + }, "#col3"); + + test(()=> { + let rects = col4.getClientRects(); + assert_equals(rects.length, 3); + + assert_equals(rects[0].top, 275); + assert_equals(rects[0].left, 25); + assert_equals(rects[0].height, 10); + assert_equals(rects[0].width, 83); + + assert_equals(rects[1].top, 375); + assert_equals(rects[1].left, 8); + assert_equals(rects[1].height, 10); + assert_equals(rects[1].width, 100); + + assert_equals(rects[2].top, 475); + assert_equals(rects[2].left, 8); + assert_equals(rects[2].height, 10); + assert_equals(rects[2].width, 67); + }, "#col4"); + + test(()=> { + let rects = group.getClientRects(); + assert_equals(rects.length, 3); + + assert_equals(rects[0].top, 250); + assert_equals(rects[0].left, 25); + assert_equals(rects[0].height, 35); + assert_equals(rects[0].width, 83); + + assert_equals(rects[1].top, 350); + assert_equals(rects[1].left, 8); + assert_equals(rects[1].height, 35); + assert_equals(rects[1].width, 100); + + assert_equals(rects[2].top, 450); + assert_equals(rects[2].left, 8); + assert_equals(rects[2].height, 35); + assert_equals(rects[2].width, 67); + }, "#group"); +</script> diff --git a/tests/wpt/tests/css/css-multicol/getclientrects-008.html b/tests/wpt/tests/css/css-multicol/getclientrects-008.html new file mode 100644 index 00000000000..21505a7e48a --- /dev/null +++ b/tests/wpt/tests/css/css-multicol/getclientrects-008.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<title>getClientRects on monolithic elements and their container</title> +<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org"> +<link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-element-getclientrects"> +<link rel="help" href="https://drafts.csswg.org/css-multicol/"> +<style> + body { + margin: 8px; + } +</style> +<div style="columns:3; column-fill:auto; gap:10px; width:320px; height:100px; background:yellow;"> + <div id="container" style="background:gray;"> + <div id="monolith1" style="contain:size; width:50%; height:250px; background:cyan;"></div> + <div id="monolith2" style="contain:size; width:50%; height:50px; background:black;"></div> + <div id="monolith3" style="contain:size; width:50%; height:250px; background:hotpink;"></div> + </div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + test(()=> { + let rects = monolith1.getClientRects(); + assert_equals(rects.length, 1); + assert_equals(rects[0].left, 8); + assert_equals(rects[0].top, 8); + assert_equals(rects[0].width, 50); + assert_equals(rects[0].height, 250); + }, "#monolith1"); + + test(()=> { + let rects = monolith2.getClientRects(); + assert_equals(rects.length, 1); + assert_equals(rects[0].left, 118); + assert_equals(rects[0].top, 8); + assert_equals(rects[0].width, 50); + assert_equals(rects[0].height, 50); + }, "#monolith2"); + + test(()=> { + let rects = monolith3.getClientRects(); + assert_equals(rects.length, 1); + assert_equals(rects[0].left, 228); + assert_equals(rects[0].top, 8); + assert_equals(rects[0].width, 50); + assert_equals(rects[0].height, 250); + }, "#monolith3"); + + test(()=> { + let rects = container.getClientRects(); + assert_equals(rects.length, 3); + + assert_equals(rects[0].left, 8); + assert_equals(rects[0].top, 8); + assert_equals(rects[0].width, 100); + assert_equals(rects[0].height, 250); + + assert_equals(rects[1].left, 118); + assert_equals(rects[1].top, 8); + assert_equals(rects[1].width, 100); + assert_equals(rects[1].height, 100); + + assert_equals(rects[2].left, 228); + assert_equals(rects[2].top, 8); + assert_equals(rects[2].width, 100); + assert_equals(rects[2].height, 250); + }, "#container"); +</script> diff --git a/tests/wpt/tests/css/css-overflow/scroll-button-activation-without-scroller.html b/tests/wpt/tests/css/css-overflow/scroll-button-activation-without-scroller.html new file mode 100644 index 00000000000..f5951efc419 --- /dev/null +++ b/tests/wpt/tests/css/css-overflow/scroll-button-activation-without-scroller.html @@ -0,0 +1,36 @@ +<!doctype html> +<meta charset="utf-8"> +<title>CSS Test: ::scroll-button() activation without scroller shouldn't crash</title> +<link rel="help" href="https://drafts.csswg.org/css-overflow-5/#scroll-buttons"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<style> +div::scroll-button(inline-start) { + content: ""; + height: 100px; + width: 100px; +} +</style> +<div></div> +<script> + promise_test(async t => { + await new test_driver.Actions() + .pointerMove(50, 50) + .pointerDown() + .pointerUp() + .pointerDown() + .pointerUp() + .send(); + const kTab = '\uE004'; + const kEnter = '\uE007'; + await new test_driver.Actions() + .keyDown(kTab) + .keyUp(kTab) + .keyDown(kEnter) + .keyUp(kEnter) + .send(); + }); +</script> diff --git a/tests/wpt/tests/css/css-overflow/scroll-button-disabled-no-focus.html b/tests/wpt/tests/css/css-overflow/scroll-button-disabled-no-focus.html new file mode 100644 index 00000000000..a8897706874 --- /dev/null +++ b/tests/wpt/tests/css/css-overflow/scroll-button-disabled-no-focus.html @@ -0,0 +1,30 @@ +<!doctype html> +<meta charset="utf-8"> +<title>CSS Test: disabled ::scroll-button() shouldn't be focusable</title> +<link rel="help" href="https://drafts.csswg.org/css-overflow-5/#scroll-buttons"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<style> +div::scroll-button(inline-start) { + content: ""; + background-color: green; +} + +div::scroll-button(inline-start):focus { + background-color: red; +} +</style> +<div id="target"></div> +<script> + promise_test(async t => { + const kTab = '\uE004'; + await new test_driver.Actions() + .keyDown(kTab) + .keyUp(kTab) + .send(); + assert_equals(getComputedStyle(target, "::scroll-button(inline-start)").backgroundColor, "rgb(0, 128, 0)"); + }); +</script> diff --git a/tests/wpt/tests/css/css-overflow/scroll-buttons-appearance-ref.html b/tests/wpt/tests/css/css-overflow/scroll-buttons-appearance-ref.html index 462186407d5..c382f738a6f 100644 --- a/tests/wpt/tests/css/css-overflow/scroll-buttons-appearance-ref.html +++ b/tests/wpt/tests/css/css-overflow/scroll-buttons-appearance-ref.html @@ -7,4 +7,4 @@ <p>Test passes if there are two buttons, the first one using appearance auto and the second using appearance: none.</p> <div></div> -<button>appearance: auto</button><button class="none">appearance: none</button> +<button disabled="true">appearance: auto</button><button disabled="true" class="none">appearance: none</button> diff --git a/tests/wpt/tests/css/css-scroll-anchoring/table-col-and-dead-row-group-crash.html b/tests/wpt/tests/css/css-scroll-anchoring/table-col-and-dead-row-group-crash.html new file mode 100644 index 00000000000..e6e90a63a34 --- /dev/null +++ b/tests/wpt/tests/css/css-scroll-anchoring/table-col-and-dead-row-group-crash.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org"> +<link rel="help" href="https://issues.chromium.org/issues/416061199"> +<div style="display:table;"> + <div style="display:table-column-group;"></div> + <div id="e13" style="display:table-row-group;"> + <div style="height:200vh;"></div> + x + </div> +</div> +<script> + window.scroll(0, 100); + e13.style.display = "none"; +</script> diff --git a/tests/wpt/tests/css/css-text/letter-spacing/letter-spacing-cursive-001.html b/tests/wpt/tests/css/css-text/letter-spacing/letter-spacing-cursive-001.html new file mode 100644 index 00000000000..5700c600981 --- /dev/null +++ b/tests/wpt/tests/css/css-text/letter-spacing/letter-spacing-cursive-001.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<head> +<meta charset="utf-8"> +<title> +CSS Text Test - Letter-spacing should not be applied for Cursive Scripts. +</title> +<link rel="author" title="Sejal Anand" href="mailto:sejalanand@microsoft.com"> +<link rel="help" href="https://www.w3.org/TR/css-text-3/#cursive-tracking"> +<link rel="match" href="reference/letter-spacing-cursive-001-ref.html"> +<meta name="assert" content="Letter-spacing must not be applied to Arabic text, + preserving cursive connections."> +<style> +div { + margin: 1em; + font: 24px serif; + white-space: nowrap; + display: inline-block; + outline: 1px solid gray; +} +.letterspacing { + letter-spacing: 10px; /* Should NOT affect Arabic text */ +} +</style> +</head> +<body> +<p> +The following test will pass if no spacing appears between letters within +Arabic words. Both Arabic texts should display identically. +</p> +<div class="letterspacing"> +<span>مرحباً</span> +</div> +<br> +<div> +<span>مرحباً</span> +</div> +<br> +</body> +</html> diff --git a/tests/wpt/tests/css/css-text/letter-spacing/reference/letter-spacing-cursive-001-ref.html b/tests/wpt/tests/css/css-text/letter-spacing/reference/letter-spacing-cursive-001-ref.html new file mode 100644 index 00000000000..720c9491b4d --- /dev/null +++ b/tests/wpt/tests/css/css-text/letter-spacing/reference/letter-spacing-cursive-001-ref.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<head> +<meta charset="utf-8"> +<title> +CSS Text Test - Letter-spacing should not be applied for Cursive Scripts. +</title> +<link rel="author" title="Sejal Anand" href="mailto:sejalanand@microsoft.com"> +<style> +div { + margin: 1em; + font: 24px serif; + white-space: nowrap; + display: inline-block; + outline: 1px solid gray; +} +</style> +</head> +<body> +<p> +The following test will pass if no spacing appears between letters within +Arabic words. Both Arabic texts should display identically. +</p> +<div> +<span>مرحباً</span> +</div> +<br> +<div> +<span>مرحباً</span> +</div> +<br> +</body> +</html> diff --git a/tests/wpt/tests/css/css-transforms/css-scale-of-clip-path-ref.html b/tests/wpt/tests/css/css-transforms/css-scale-of-clip-path-ref.html new file mode 100644 index 00000000000..d6fa480b040 --- /dev/null +++ b/tests/wpt/tests/css/css-transforms/css-scale-of-clip-path-ref.html @@ -0,0 +1,20 @@ +<!doctype HTML> +<style> +.tile { + width: 620px; + height: 671px; + position: absolute; + clip-path: circle(50%); + background-color: lightblue; +} +#container { + transform: scale(0.25); + position: relative; + left: -200px; + will-change: transform; +} +</style> +<div id=container> + <div class="tile" style="top: 520px;"></div> + <div class="tile" style="top: 688px; left: 390px;"></div> +</div> diff --git a/tests/wpt/tests/css/css-transforms/css-scale-of-clip-path.html b/tests/wpt/tests/css/css-transforms/css-scale-of-clip-path.html new file mode 100644 index 00000000000..dda72c0d139 --- /dev/null +++ b/tests/wpt/tests/css/css-transforms/css-scale-of-clip-path.html @@ -0,0 +1,23 @@ +<!doctype HTML> +<link rel="author" title="Chris Harrelson"> +<link rel="help" href="https://drafts.csswg.org/css-transforms-2/"> +<link rel="match" href="css-scale-of-clip-path-ref.html"> +<meta name=fuzzy content="maxDifference=0-40;totalPixels=0-3000"> +<style> +.tile { + width: 620px; + height: 671px; + position: absolute; + clip-path: circle(50%); + background-color: lightblue; +} +#container { + transform: scale(0.25); + position: relative; + left: -200px; +} +</style> +<div id=container> + <div class="tile" style="top: 520px;"></div> + <div class="tile" style="top: 688px; left: 390px;"></div> +</div> diff --git a/tests/wpt/tests/css/css-transforms/scale-transform-filtered-text-ref.html b/tests/wpt/tests/css/css-transforms/scale-transform-filtered-text-ref.html new file mode 100644 index 00000000000..e8b564ce3dd --- /dev/null +++ b/tests/wpt/tests/css/css-transforms/scale-transform-filtered-text-ref.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<style> + #container { + transform: scale(0.5); + background-color: lightblue; + font-size: 80px; + } + #animated, #content { + will-change: transform; + } +</style> +<div id=container> + <div id=content> + <div id=animated>Text</div> + </div> +</div> diff --git a/tests/wpt/tests/css/css-transforms/scale-transform-filtered-text.html b/tests/wpt/tests/css/css-transforms/scale-transform-filtered-text.html new file mode 100644 index 00000000000..a479b12528a --- /dev/null +++ b/tests/wpt/tests/css/css-transforms/scale-transform-filtered-text.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<link rel="author" href="mailto:chrishtr@chromium.org"> +<link rel="help" href="https://drafts.csswg.org/css-transforms-2/"> +<meta name="assert" content="Text is not blurry under w/filter and animation."> +<link rel="match" href="scale-transform-filtered-text-ref.html"> +<meta name=fuzzy content="maxDifference=0-25;totalPixels=0-500"> +<style> + #container { + transform: scale(0.5); + background-color: lightblue; + font-size: 80px; + } + #content { + filter: blur(0px); + } + #animated { + will-change: transform; + } +</style> +<div id=container> + <div id=content> + <div id=animated>Text</div> + </div> +</div> diff --git a/tests/wpt/tests/css/css-typed-om/the-stylepropertymap/properties/transform.html b/tests/wpt/tests/css/css-typed-om/the-stylepropertymap/properties/transform.html index 7a852545a74..d12714a499c 100644 --- a/tests/wpt/tests/css/css-typed-om/the-stylepropertymap/properties/transform.html +++ b/tests/wpt/tests/css/css-typed-om/the-stylepropertymap/properties/transform.html @@ -18,4 +18,8 @@ runPropertyTests('transform', [ { syntax: '<transform>' }, ]); +runUnsupportedPropertyTests('transform', [ + 'matrix(sibling-index(), 2, 3, 4, 5 ,6)' +]); + </script> diff --git a/tests/wpt/tests/css/css-values/tree-counting/sibling-function-descriptors.tentative.html b/tests/wpt/tests/css/css-values/tree-counting/sibling-function-descriptors.tentative.html index d31d4acf499..76d2ff8ee4d 100644 --- a/tests/wpt/tests/css/css-values/tree-counting/sibling-function-descriptors.tentative.html +++ b/tests/wpt/tests/css/css-values/tree-counting/sibling-function-descriptors.tentative.html @@ -4,7 +4,7 @@ <link rel="help" href="https://github.com/w3c/csswg-drafts/issues/10982"> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> -<style id="test_sheet"> +<style id="page_sheet"> @page { margin: 100px; margin: calc(0px * sibling-index()); @@ -13,33 +13,131 @@ margin: 100px; margin: calc(0px * sibling-count()); } +</style> +<style id="font_face_sheet"> @font-face { font-family: my-font; font-weight: 300; font-weight: calc(max(0 * sibling-index(), 400)); + font-feature-settings: "vert" 2; + font-feature-settings: "vert" calc(max(sibling-index(), 1)); } @font-face { font-family: my-font; font-weight: 300; font-weight: calc(max(0 * sibling-count(), 400)); + font-feature-settings: "vert" 2; + font-feature-settings: "vert" calc(max(sibling-count(), 1)); + } +</style> +<style id="font_palette_sheet"> + @font-palette-values --foo { + font-family: my-font; + base-palette: 1; + base-palette: calc(max(sibling-index(), 2)); + override-colors: 1 green; + override-colors: sibling-index() red; + } + @font-palette-values --foo { + font-family: my-font; + base-palette: 1; + base-palette: calc(max(sibling-count(), 2)); + override-colors: 1 green; + override-colors: sibling-count() red; + } +</style> +<style id="counter_style_sheet"> + @counter-style --foo { + system: fixed 1; + system: fixed calc(max(sibling-index(), 2)); + negative: --pass; + negative: linear-gradient(red calc(20px * sibling-index()), pink); + prefix: --pass; + prefix: linear-gradient(red calc(20px * sibling-index()), pink); + suffix: --pass; + suffix: linear-gradient(red calc(20px * sibling-index()), pink); + range: 1 infinite; + range: calc(max(sibling-index(), 2)) infinite; + pad: 1 --pass; + pad: 1 linear-gradient(red calc(20px * sibling-index()), pink); + pad: calc(max(sibling-index(), 2)) --fail; + symbols: --pass; + symbols: linear-gradient(red calc(20px * sibling-index()), pink); + } + @counter-style --foo { + system: fixed 1; + system: fixed calc(max(sibling-count(), 2)); + negative: --pass; + negative: linear-gradient(green, green); + negative: linear-gradient(red calc(20px * sibling-count()), pink); + prefix: --pass; + prefix: linear-gradient(red calc(20px * sibling-count()), pink); + suffix: --pass; + suffix: linear-gradient(red calc(20px * sibling-count()), pink); + range: 1 infinite; + range: calc(max(sibling-count(), 2)) infinite; + pad: 1 --pass; + pad: 1 linear-gradient(red calc(20px * sibling-count()), pink); + pad: calc(max(sibling-count(), 2)) --fail; + symbols: --pass; + symbols: linear-gradient(red calc(20px * sibling-count()), pink); } </style> <script> - const rules = test_sheet.sheet.cssRules; + const page_rules = page_sheet.sheet.cssRules; test(() => { - assert_equals(rules[0].style.margin, "100px"); + assert_equals(page_rules[0].style.margin, "100px"); }, "sibling-index() should not be allowed in @page properties"); test(() => { - assert_equals(rules[1].style.margin, "100px"); + assert_equals(page_rules[1].style.margin, "100px"); }, "sibling-count() should not be allowed in @page properties"); + const font_face_rules = font_face_sheet.sheet.cssRules; + test(() => { - assert_equals(rules[2].style.fontWeight, "300"); + assert_equals(font_face_rules[0].style.fontWeight, "300"); + assert_equals(font_face_rules[0].style.fontFeatureSettings, "\"vert\" 2"); }, "sibling-index() should not be allowed in @font-face descriptors"); test(() => { - assert_equals(rules[3].style.fontWeight, "300"); + assert_equals(font_face_rules[1].style.fontWeight, "300"); + assert_equals(font_face_rules[1].style.fontFeatureSettings, "\"vert\" 2"); }, "sibling-count() should not be allowed in @font-face descriptors"); + + const font_palette_rules = font_palette_sheet.sheet.cssRules; + + test(() => { + assert_equals(font_palette_rules[0].basePalette, "1"); + assert_equals(font_palette_rules[0].overrideColors, "1 green"); + }, "sibling-index() should not be allowed in @font-palette-values descriptors"); + + test(() => { + assert_equals(font_palette_rules[1].basePalette, "1"); + assert_equals(font_palette_rules[1].overrideColors, "1 green"); + }, "sibling-count() should not be allowed in @font-palette-values descriptors"); + + const counter_style_rules = counter_style_sheet.sheet.cssRules; + + test(() => { + assert_equals(counter_style_rules[0].system, "fixed 1"); + assert_equals(counter_style_rules[0].negative, "--pass"); + assert_equals(counter_style_rules[0].prefix, "--pass"); + assert_equals(counter_style_rules[0].suffix, "--pass"); + assert_equals(counter_style_rules[0].range, "1 infinite"); + assert_equals(counter_style_rules[0].pad, "1 --pass"); + assert_equals(counter_style_rules[0].symbols, "--pass"); + }, "sibling-index() should not be allowed in @counter-style descriptors"); + + test(() => { + assert_equals(counter_style_rules[1].system, "fixed 1"); + assert_equals(counter_style_rules[1].negative, "--pass"); + assert_equals(counter_style_rules[1].prefix, "--pass"); + assert_equals(counter_style_rules[1].suffix, "--pass"); + assert_equals(counter_style_rules[1].range, "1 infinite"); + assert_equals(counter_style_rules[1].pad, "1 --pass"); + assert_equals(counter_style_rules[1].symbols, "--pass"); + }, "sibling-count() should not be allowed in @counter-style descriptors"); + </script> diff --git a/tests/wpt/tests/css/css-values/tree-counting/sibling-index-keyframe-length-value-dynamic.html b/tests/wpt/tests/css/css-values/tree-counting/sibling-index-keyframe-length-value-dynamic.html new file mode 100644 index 00000000000..cbd34602fb3 --- /dev/null +++ b/tests/wpt/tests/css/css-values/tree-counting/sibling-index-keyframe-length-value-dynamic.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<title>CSS Values and Units Test: sibling-index() changing length value during @keyframes animation</title> +<link rel="help" href="https://drafts.csswg.org/css-values-5/#tree-counting"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + @keyframes --anim { + from { + top: calc(100px * sibling-index()); + } + to { + top: 0px; + } + } + #target { + animation: --anim 1000s step-end; + position: absolute; + top: 13px; + width: 100px; + height: 100px; + } +</style> +<div> + <div id="rm"></div> + <div id="target"></div> +</div> +<script> + test(() => { + assert_equals(getComputedStyle(target).top, "200px"); + }, "Initially, the sibling-index() is 2 for #target"); + + test(() => { + rm.remove(); + assert_equals(getComputedStyle(target).top, "100px"); + }, "Removing a preceding sibling of #target reduces the sibling-index()"); + +</script> diff --git a/tests/wpt/tests/css/css-values/tree-counting/sibling-index-keyframe-value-dynamic.html b/tests/wpt/tests/css/css-values/tree-counting/sibling-index-keyframe-value-dynamic.html new file mode 100644 index 00000000000..286e0d3d3e2 --- /dev/null +++ b/tests/wpt/tests/css/css-values/tree-counting/sibling-index-keyframe-value-dynamic.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<title>CSS Values and Units Test: sibling-index() changing value during @keyframes animation</title> +<link rel="help" href="https://drafts.csswg.org/css-values-5/#tree-counting"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + @keyframes --anim { + from { + z-index: sibling-index(); + } + to { + z-index: 1; + } + } + #target { + animation: --anim 1000s step-end; + position: relative; + width: 100px; + height: 100px; + background: red; + } + #abs { + position: absolute; + width: 100px; + height: 100px; + z-index: 3; + background: green; + } +</style> +<p>You should see a green square below.</p> +<div> + <div id="rm"></div> + <div id="abs"></div> + <div id="target"></div> +</div> +<script> + test(() => { + assert_equals(getComputedStyle(target).zIndex, "3"); + }, "Initially, the sibling-index() is 3 for #target"); + + test(() => { + rm.remove(); + assert_equals(getComputedStyle(target).zIndex, "2"); + }, "Removing a preceding sibling of #target reduces the sibling-index()"); + +</script> diff --git a/tests/wpt/tests/css/css-view-transitions/root-preserve3d-crash.html b/tests/wpt/tests/css/css-view-transitions/root-preserve3d-crash.html new file mode 100644 index 00000000000..11232801875 --- /dev/null +++ b/tests/wpt/tests/css/css-view-transitions/root-preserve3d-crash.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1963368"> +<style> + * { + border-style: double !important; + -webkit-transform-style: preserve-3d !important; + } +</style> +<script> + document.addEventListener("DOMContentLoaded", async () => { + document.startViewTransition(undefined) + }) +</script> diff --git a/tests/wpt/tests/css/css-view-transitions/root-replace-crash.html b/tests/wpt/tests/css/css-view-transitions/root-replace-crash.html new file mode 100644 index 00000000000..165eec41b6b --- /dev/null +++ b/tests/wpt/tests/css/css-view-transitions/root-replace-crash.html @@ -0,0 +1,15 @@ +<html> +<head> + <script> + document.addEventListener("DOMContentLoaded", async () => { + const tt = document.createElementNS("http://www.w3.org/1999/xhtml", "tt") + tt.popover = "manual" + const viewTransition = document.startViewTransition(undefined) + await viewTransition.updateCallbackDone + for (let i = 0; i < 2; i++) { + document.replaceChild(tt, document.childNodes[(2324524876 % document.childNodes.length)]) + } + }) + </script> + <head> +</html> diff --git a/tests/wpt/tests/css/css-view-transitions/scoped/crashtests/shadow-dom.html b/tests/wpt/tests/css/css-view-transitions/scoped/crashtests/shadow-dom.html new file mode 100644 index 00000000000..a2faafc1694 --- /dev/null +++ b/tests/wpt/tests/css/css-view-transitions/scoped/crashtests/shadow-dom.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<html class="test-wait"> +<body> +<meter><div id="node1"></div></meter> +<div> + <template shadowrootmode="open"></template> + <div id="node2"></div> +</div> +<div style="display:none"> + <div id="node3"></div> +</div> +<script> + const tr1 = node1.startViewTransition(); + const tr2 = node2.startViewTransition(); + const tr3 = node3.startViewTransition(); + onload = async () => { + await tr1.finished; + await tr2.finished; + await tr3.finished; + document.documentElement.classList.remove('test-wait'); + } +</script> +</body> +</html> diff --git a/tests/wpt/tests/css/css-view-transitions/start-skip-start.html b/tests/wpt/tests/css/css-view-transitions/start-skip-start.html new file mode 100644 index 00000000000..e04979472f1 --- /dev/null +++ b/tests/wpt/tests/css/css-view-transitions/start-skip-start.html @@ -0,0 +1,87 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>Test start skip and then start on a view transition</title> + <link rel="help" href="https://www.w3.org/TR/css-view-transitions-1/"> +</head> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<style> + :root { + view-transition-name: none; + } + #target { + width: 100px; + height: 100px; + background: blue; + contain: paint; + view-transition-name: target; + } + #target.update-1 { + height: 150px; + } + #target.update-2 { + height: 200px; + } +</style> + +<body> + <div id="target"></div> +</body> +<script> + promise_test(async t => { + let rejected_promise_tally = 0; + const target = document.getElementById("target"); + assert_implements(document.startViewTransition, + "Missing document.startViewTransition"); + + const verifyAbortedTransition = (promise) => { + return promise.then( + () => { assert_not_reached('transition aborted') }, + (reason) => { + assert_true(reason instanceof DOMException); + assert_equals(reason.code, DOMException.ABORT_ERR); + rejected_promise_tally++; + }); + }; + + const vt1 = document.startViewTransition(() => { + target.className = 'update-1'; + }); + + vt1.skipTransition(); + const vt2 = document.startViewTransition(() => { + assert_equals(target.className, 'update-1'); + target.className = 'update-2'; + }); + + vt2.skipTransition(); + const vt3 = document.startViewTransition(() => { + assert_equals(target.className, 'update-2'); + }); + + await verifyAbortedTransition(vt1.ready); + await verifyAbortedTransition(vt2.ready); + await vt3.ready; + + assert_equals(rejected_promise_tally, 2, + 'first 2 transitions were skipped'); + const sizeTransformAnimations = document.getAnimations().filter(a => { + return 'height' in a.effect.getKeyframes()[0]; + }); + assert_equals(sizeTransformAnimations.length, 1); + const startingHeight = + sizeTransformAnimations[0].effect.getKeyframes()[0].height; + + assert_equals(startingHeight, '200px', + 'Height change applied before capture'); + + }, 'Synchronously starting a view transition blocks on DOM callback for ' + + 'previously skipped transitions'); + +</script> +</html> diff --git a/tests/wpt/tests/css/css-view-transitions/view-transition-types-mutable-no-document-element-crashtest.html b/tests/wpt/tests/css/css-view-transitions/view-transition-types-mutable-no-document-element-crashtest.html index d5c0abd652c..3ac946b273b 100644 --- a/tests/wpt/tests/css/css-view-transitions/view-transition-types-mutable-no-document-element-crashtest.html +++ b/tests/wpt/tests/css/css-view-transitions/view-transition-types-mutable-no-document-element-crashtest.html @@ -3,6 +3,7 @@ <link rel="help" href="https://www.w3.org/TR/css-transitions-2/"> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> +<script src="/dom/events/scrolling/scroll_support.js"></script> <style> ::view-transition-group(*), @@ -14,14 +15,18 @@ </style> <script> - test(() => { - assert_implements(document.startViewTransition); +promise_test(async () => { + assert_implements(document.startViewTransition); - const { types } = document.startViewTransition(); - document.documentElement.remove(); - types.add("a"); - assert_array_equals([...types], ["a"]); - }, "ViewTransitionTypeSet should not crash when documentElement is null"); + await new Promise((r) => requestAnimationFrame(() => { + waitForCompositorReady().then(r); + })); + + const { types } = document.startViewTransition(); + document.documentElement.remove(); + types.add("a"); + assert_array_equals([...types], ["a"]); +}, "ViewTransitionTypeSet should not crash when documentElement is null"); </script> </html> diff --git a/tests/wpt/tests/css/cssom-view/getBoundingClientRect-newline.html b/tests/wpt/tests/css/cssom-view/getBoundingClientRect-newline.html index 40e29181d6f..ce82b7237d0 100644 --- a/tests/wpt/tests/css/cssom-view/getBoundingClientRect-newline.html +++ b/tests/wpt/tests/css/cssom-view/getBoundingClientRect-newline.html @@ -5,17 +5,14 @@ <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <style> -div { +div[contenteditable] { white-space: pre; - font-family: Ahem; - font-size: 10px; - line-height: 1; + font: 10px/1 Ahem; width: 10ch; } </style> <body> <div contenteditable></div> -</body> <script> function getBoundingClientRect(node, offset) { const range = document.createRange(); @@ -37,3 +34,4 @@ test(function() { assert_less_than(rect6.y, rect7.y); }, 'Range.getBoundingClientRect() should return the first position of the next line when the collapsed range is a newline character'); </script> +</body> diff --git a/tests/wpt/tests/css/cssom-view/resources/scrollIntoView-frame.html b/tests/wpt/tests/css/cssom-view/resources/scrollIntoView-frame.html new file mode 100644 index 00000000000..ee2be2fd3ab --- /dev/null +++ b/tests/wpt/tests/css/cssom-view/resources/scrollIntoView-frame.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<meta name="viewport" content="initial-scale=1"> +<style> +body { + height: 600px; +} +#target { + position: absolute; + top: 400px; + height: 200px; +} +</style> +<body> +<div id="target"></div> +</body> diff --git a/tests/wpt/tests/css/cssom-view/scrollIntoView-container.html b/tests/wpt/tests/css/cssom-view/scrollIntoView-container.html new file mode 100644 index 00000000000..3b241ea2251 --- /dev/null +++ b/tests/wpt/tests/css/cssom-view/scrollIntoView-container.html @@ -0,0 +1,84 @@ +<!DOCTYPE html> +<title>CSSOM View - scrollIntoView container option</title> +<meta charset="utf-8"> +<meta name="viewport" content="initial-scale=1"> +<link rel="author" title="Rob Flack" href="mailto:flackr@chromium.org"> +<link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-element-scrollintoview"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> +.scroller { + overflow: auto; + height: 200px; +} +.spacer { + height: 400px; +} +#target { + height: 200px; +} +</style> +<script> +let setFrameLoaded = null; +let frameLoaded = new Promise(resolve => { + setFrameLoaded = resolve; +}); +</script> +<div id="outer" class="scroller"> + <div class="spacer"></div> + <div id="inner" class="scroller"> + <div class="spacer"></div> + <div id="target"></div> + <iframe id="frame" height="200" src="resources/scrollIntoView-frame.html" onload="setFrameLoaded()"></iframe> + </div> +</div> +<script> +const outer = document.getElementById('outer'); +const inner = document.getElementById('inner'); +const target = document.getElementById('target'); + +function reset() { + outer.scrollTop = 0; + inner.scrollTop = 0; +} + +test(() => { + reset(); + target.scrollIntoView(); + assert_equals(inner.scrollTop, 400, '#inner scrollTop'); + assert_equals(outer.scrollTop, 400, '#outer scrollTop'); +}, `scrollIntoView() defaults to scrolling ancestors`); + +test(() => { + reset(); + target.scrollIntoView({container: 'all'}); + assert_equals(inner.scrollTop, 400, '#inner scrollTop'); + assert_equals(outer.scrollTop, 400, '#outer scrollTop'); +}, `scrollIntoView({container: 'all'}) scrolls ancestors`); + +test(() => { + reset(); + target.scrollIntoView({container: 'nearest'}); + assert_equals(inner.scrollTop, 400, '#inner scrollTop'); + assert_equals(outer.scrollTop, 0, '#outer scrollTop'); +}, `scrollIntoView({container: 'nearest'}) only scrolls nearest scroll container`); + +test(() => { + reset(); + inner.scrollIntoView({container: 'nearest'}); + assert_equals(outer.scrollTop, 400, '#outer scrollTop'); + assert_equals(inner.scrollTop, 0, '#inner scrollTop'); +}, `scrollIntoView({container: 'nearest'}) doesn't stop at itself`); + +promise_test(async () => { + reset(); + await frameLoaded; + const frameDoc = document.getElementById("frame").contentDocument; + const frameTarget = frameDoc.getElementById("target"); + frameTarget.scrollIntoView({container: 'nearest'}); + assert_equals(frameDoc.scrollingElement.scrollTop, 400, 'frame scrollingElement scrollTop'); + assert_equals(inner.scrollTop, 0, '#inner scrollTop'); + assert_equals(outer.scrollTop, 0, '#outer scrollTop'); +}, `scrollIntoView({container: 'nearest'}) doesn't propagate to outer frames`); + +</script> diff --git a/tests/wpt/tests/css/cssom-view/table-client-props.html b/tests/wpt/tests/css/cssom-view/table-client-props.html index 4af06d6bf71..2895bebebbf 100644 --- a/tests/wpt/tests/css/cssom-view/table-client-props.html +++ b/tests/wpt/tests/css/cssom-view/table-client-props.html @@ -43,6 +43,19 @@ <tr><td>`, 26, 34, "Table with collapsed border" ], + [ `<div style="display: flex"> + <table style="width: 20px; height: 30px; + border-width: 1px 2px 3px 4px; border-style: solid; + border-collapse: separate; box-sizing: content-box">`, + 26, 34, + "Flex-level table with separated border" ], + [ `<div style="display: flex"> + <table style="width: 20px; height: 30px; + border-width: 2px 4px 6px 8px; border-style: solid; + border-collapse: collapse; box-sizing: content-box"> + <tr><td>`, + 26, 34, + "Flex-level table with collapsed border" ], [ `<table> <caption style="width: 40px; height: 50px; padding: 1px 2px 3px 4px">`, 46, 54, diff --git a/tests/wpt/tests/css/filter-effects/backdrop-filter-scale-transform-ref.html b/tests/wpt/tests/css/filter-effects/backdrop-filter-scale-transform-ref.html new file mode 100644 index 00000000000..d7faa0e97a1 --- /dev/null +++ b/tests/wpt/tests/css/filter-effects/backdrop-filter-scale-transform-ref.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<style> + #container { + position: relative; + width: 200px; + height: 200px; + transform: scale(1.1); + } + + #backdropfilter { + background: black; + position: relative; + width: 100%; + height: 100%; + } +</style> +<div id=container> + <div id=backdropfilter></div> +</div> + diff --git a/tests/wpt/tests/css/filter-effects/backdrop-filter-scale-transform.html b/tests/wpt/tests/css/filter-effects/backdrop-filter-scale-transform.html new file mode 100644 index 00000000000..813fb95d5ad --- /dev/null +++ b/tests/wpt/tests/css/filter-effects/backdrop-filter-scale-transform.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<link rel="author" href="mailto:chrishtr@chromium.org"> +<link rel="help" href="https://drafts.fxtf.org/filter-effects-2/#BackdropFilterProperty"> +<link rel="match" href="backdrop-filter-scale-transform-ref.html"> +<style> + #container { + position: relative; + width: 200px; + height: 200px; + transform: scale(1.1); + } + + #backdropfilter { + backdrop-filter: invert(100%); + position: relative; + width: 100%; + height: 100%; + } +</style> +<div id=container> + <div id=backdropfilter></div> +</div> + diff --git a/tests/wpt/tests/css/filter-effects/backdrop-filter-transform-popover-crash.html b/tests/wpt/tests/css/filter-effects/backdrop-filter-transform-popover-crash.html new file mode 100644 index 00000000000..582f0c11965 --- /dev/null +++ b/tests/wpt/tests/css/filter-effects/backdrop-filter-transform-popover-crash.html @@ -0,0 +1,17 @@ +<lijnk rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1845184"> +<style> +* { + min-width: 1952643579% !important; + transform: skew(0, 1.8turn) scale(1.5794431017420837e+38) translate(215mm, 62%) scaleY(55498.5); + border-top-left-radius: 98779902.05ch; + backdrop-filter: sepia() invert(870236770%) drop-shadow(16Q -79.35pc 125vw hsla(-1.93deg, 66%, 84%)); +} +</style> +<script> +window.addEventListener("load", () => { + let a = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas") + document.documentElement.appendChild(a) + a.popover = "a" + a.togglePopover(true) +}) +</script> diff --git a/tests/wpt/tests/css/filter-effects/css-filters-opacity-hit-testing.html b/tests/wpt/tests/css/filter-effects/css-filters-opacity-hit-testing.html new file mode 100644 index 00000000000..3b288fca4df --- /dev/null +++ b/tests/wpt/tests/css/filter-effects/css-filters-opacity-hit-testing.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<title>Elements with 'opacity: 0' should respond to hit testing.</title> +<link rel="author" title="Psychpsyo" href="psychpsyo@gmail.com"> +<link rel="help" href="https://drafts.fxtf.org/filter-effects/#FilterProperty"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + div { + width: 100px; + height: 100px; + opacity: 0; + } +</style> +<div></div> +<script> + test(() => { + assert_equals(document.elementFromPoint(50, 50).tagName, "DIV", "element with 'opacity: 0' doesn't respond to hit testing"); + }); +</script> diff --git a/tests/wpt/tests/css/geometry/DOMMatrix-001.html b/tests/wpt/tests/css/geometry/DOMMatrix-001.html index f578da076cb..3436e17ced0 100644 --- a/tests/wpt/tests/css/geometry/DOMMatrix-001.html +++ b/tests/wpt/tests/css/geometry/DOMMatrix-001.html @@ -113,6 +113,8 @@ ['translateX (5px)', 'scale(2 2) translateX(5) translateY(5)', 'scale(2, 2), translateX(5) ,translateY(5)', + 'scale(sign(1em))', + 'scale(sibling-index())', 'translateX(5em)', 'translateX(5ex)', 'translateX(5ch)', @@ -130,10 +132,14 @@ 'translateX(5vmin)', 'translateX(5vmax)', 'translateX(5%)', + 'translateX(calc(10px * sign(1em - 10px)))', + 'translateX(calc(10px * sibling-index()))', 'rotate(5)', 'rotate(5, 5, 5)', 'rotate(5, 5px, 5px)', 'rotate(5deg, 5px, 5px)', + 'rotate(calc(5deg * sign(1em - 10px)))', + 'rotate(calc(5deg * sibling-index()))', ' ', '/**/', '\0', diff --git a/tests/wpt/tests/css/mediaqueries/WEB_FEATURES.yml b/tests/wpt/tests/css/mediaqueries/WEB_FEATURES.yml index 24027360aab..7f15b7fa475 100644 --- a/tests/wpt/tests/css/mediaqueries/WEB_FEATURES.yml +++ b/tests/wpt/tests/css/mediaqueries/WEB_FEATURES.yml @@ -25,3 +25,7 @@ features: - name: update files: - update-media-feature.html +- name: scripting + files: + - scripting.html + - scripting-* diff --git a/tests/wpt/tests/docs/writing-tests/testdriver.md b/tests/wpt/tests/docs/writing-tests/testdriver.md index 67757af63f2..e0fae529875 100644 --- a/tests/wpt/tests/docs/writing-tests/testdriver.md +++ b/tests/wpt/tests/docs/writing-tests/testdriver.md @@ -313,3 +313,11 @@ The module provides access to [Web Bluetooth](https://webbluetoothcg.github.io/w .. js:autofunction:: test_driver.bidi.bluetooth.simulate_preconnected_peripheral .. js:autofunction:: test_driver.bidi.bluetooth.request_device_prompt_updated ``` + +### Emulation ### + +Emulation of browser APIs via [WebDriver BiDi Emulation](https://www.w3.org/TR/webdriver-bidi/#module-emulation). + +```eval_rst +.. js:autofunction:: test_driver.bidi.emulation.set_geolocation_override +``` diff --git a/tests/wpt/tests/dom/events/scrolling/overscroll-deltas.html b/tests/wpt/tests/dom/events/scrolling/overscroll-deltas.tentative.html index e13e9f1cce5..e13e9f1cce5 100644 --- a/tests/wpt/tests/dom/events/scrolling/overscroll-deltas.html +++ b/tests/wpt/tests/dom/events/scrolling/overscroll-deltas.tentative.html diff --git a/tests/wpt/tests/dom/events/scrolling/overscroll-event-fired-to-document.html b/tests/wpt/tests/dom/events/scrolling/overscroll-event-fired-to-document.tentative.html index c054ffca9c4..c054ffca9c4 100644 --- a/tests/wpt/tests/dom/events/scrolling/overscroll-event-fired-to-document.html +++ b/tests/wpt/tests/dom/events/scrolling/overscroll-event-fired-to-document.tentative.html diff --git a/tests/wpt/tests/dom/events/scrolling/overscroll-event-fired-to-element-with-overscroll-behavior.html b/tests/wpt/tests/dom/events/scrolling/overscroll-event-fired-to-element-with-overscroll-behavior.tentative.html index 750080e6568..750080e6568 100644 --- a/tests/wpt/tests/dom/events/scrolling/overscroll-event-fired-to-element-with-overscroll-behavior.html +++ b/tests/wpt/tests/dom/events/scrolling/overscroll-event-fired-to-element-with-overscroll-behavior.tentative.html diff --git a/tests/wpt/tests/dom/events/scrolling/overscroll-event-fired-to-scrolled-element.html b/tests/wpt/tests/dom/events/scrolling/overscroll-event-fired-to-scrolled-element.tentative.html index be4176df59d..be4176df59d 100644 --- a/tests/wpt/tests/dom/events/scrolling/overscroll-event-fired-to-scrolled-element.html +++ b/tests/wpt/tests/dom/events/scrolling/overscroll-event-fired-to-scrolled-element.tentative.html diff --git a/tests/wpt/tests/dom/events/scrolling/overscroll-event-fired-to-window.html b/tests/wpt/tests/dom/events/scrolling/overscroll-event-fired-to-window.tentative.html index ef5ae3daef8..ef5ae3daef8 100644 --- a/tests/wpt/tests/dom/events/scrolling/overscroll-event-fired-to-window.html +++ b/tests/wpt/tests/dom/events/scrolling/overscroll-event-fired-to-window.tentative.html diff --git a/tests/wpt/tests/dom/observable/WEB_FEATURES.yml b/tests/wpt/tests/dom/observable/WEB_FEATURES.yml new file mode 100644 index 00000000000..3e872791165 --- /dev/null +++ b/tests/wpt/tests/dom/observable/WEB_FEATURES.yml @@ -0,0 +1,3 @@ +features: +- name: observable + files: "**" diff --git a/tests/wpt/tests/domparsing/XMLSerializer-serializeToString.html b/tests/wpt/tests/domparsing/XMLSerializer-serializeToString.html index 6c294e464a5..352a62c7d5d 100644 --- a/tests/wpt/tests/domparsing/XMLSerializer-serializeToString.html +++ b/tests/wpt/tests/domparsing/XMLSerializer-serializeToString.html @@ -256,6 +256,10 @@ test(function () { root.setAttributeNS(XMLNS_URI, 'xmlns:foo', ''); assert_equals(serialize(root), '<root xmlns="" xmlns:foo=""/>'); }, 'Check if a prefix bound to an empty namespace URI ("no namespace") serialize'); + +test(function() { + assert_equals(serialize(document.createAttribute("foobar")), "") +}, 'Attribute nodes are serialized as the empty string') </script> </body> </html> diff --git a/tests/wpt/tests/element-timing/resources/element-timing-helpers.js b/tests/wpt/tests/element-timing/resources/element-timing-helpers.js index 2c1bd195ea7..5ff01bfa120 100644 --- a/tests/wpt/tests/element-timing/resources/element-timing-helpers.js +++ b/tests/wpt/tests/element-timing/resources/element-timing-helpers.js @@ -19,7 +19,7 @@ function checkElementInternal(entry, expectedUrl, expectedIdentifier, expectedID assert_greater_than_equal(entry.paintTime, beforeRender, 'paintTime should represent the time when the UA started painting'); // PaintTimingMixin - if ("presentationTime" in entry) { + if ("presentationTime" in entry && entry.presentationTime !== null) { assert_greater_than(entry.presentationTime, entry.paintTime); assert_equals(entry.presentationTime, entry.renderTime); } else { diff --git a/tests/wpt/tests/event-timing/orphan-keydown.html b/tests/wpt/tests/event-timing/orphan-keydown.html new file mode 100644 index 00000000000..0ab105d4d72 --- /dev/null +++ b/tests/wpt/tests/event-timing/orphan-keydown.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing: orphan keydown.</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-actions.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script src=resources/event-timing-test-utils.js></script> + +<body> + <button id='target'>Click me</button> + + <script> + let observedEntries = []; + const map = new Map(); + const events = ['keydown']; + + promise_test(async t => { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + + const callback = (entryList) => {observedEntries = observedEntries.concat(entryList.getEntries().filter(filterAndAddToMap(events, map))); }; + const readyToResolve = () => { return observedEntries.length >= 1; }; + const observerPromise = createPerformanceObserverPromise(['event'], callback, readyToResolve); + + await interactAndObserve('orphan-keydown', document.getElementById('target'), observerPromise); + + assert_equals(observedEntries.length, 1, "Keydown without a keyup should be fired properly."); + assert_greater_than(map.get('keydown'), 0, "Should have a non-trivial interactionId.") + }, "Event Timing: Orphan keydown should be measured as an interaction."); + </script> +</body> +</html> diff --git a/tests/wpt/tests/event-timing/resources/event-timing-test-utils.js b/tests/wpt/tests/event-timing/resources/event-timing-test-utils.js index 59c78ebf39c..afad67bb6fc 100644 --- a/tests/wpt/tests/event-timing/resources/event-timing-test-utils.js +++ b/tests/wpt/tests/event-timing/resources/event-timing-test-utils.js @@ -554,6 +554,17 @@ async function interactAndObserve(interactionType, target, observerPromise, key interactionPromise = textSelectionAndBlockMain(target, 30); break; } + case 'orphan-keydown': { + addListeners(target, ['keydown']); + interactionPromise = new test_driver.Actions() + .pointerMove(0, 0, {origin: target}) + .pointerDown() + .pointerUp() + .addTick() + .keyDown('a') + .send(); + break; + } } return Promise.all([interactionPromise, observerPromise]); } diff --git a/tests/wpt/tests/fedcm/fedcm-disconnect.sub.https.html b/tests/wpt/tests/fedcm/fedcm-disconnect.sub.https.html index 2ea2d4a2599..04fcd272dcf 100644 --- a/tests/wpt/tests/fedcm/fedcm-disconnect.sub.https.html +++ b/tests/wpt/tests/fedcm/fedcm-disconnect.sub.https.html @@ -13,6 +13,7 @@ import {fedcm_test, mark_signed_in, disconnect_options, fedcm_get_and_select_first_account, + fedcm_select_account_promise, request_options_with_mediation_required, alt_manifest_origin, alt_request_options_with_mediation_required, @@ -82,4 +83,20 @@ fedcm_test(async t => { await IdentityCredential.disconnect(disconnect_options("1")); return IdentityCredential.disconnect(alt_disconnect_options("2")); }, 'Disconnect is bound to each IDP'); + +fedcm_test(async t => { + await mark_signed_in(alt_manifest_origin); + // Get at least one connected account that can be disconnected. + await fedcm_get_and_select_first_account(t, alt_request_options_with_mediation_required()); + + // Pending get request. + const credentialPromise = navigator.credentials.get(alt_request_options_with_mediation_required()); + + // Disconnect the one connected account. + await IdentityCredential.disconnect(alt_disconnect_options("1234")); + + // Select an account to resolve the pending get request. + fedcm_select_account_promise(t, 0); + return credentialPromise; +}, 'Test that disconnect succeeds when there is a pending get request and the get request succeeds after the disconnect'); </script> diff --git a/tests/wpt/tests/fetch/api/request/WEB_FEATURES.yml b/tests/wpt/tests/fetch/api/request/WEB_FEATURES.yml new file mode 100644 index 00000000000..69b2ea582a2 --- /dev/null +++ b/tests/wpt/tests/fetch/api/request/WEB_FEATURES.yml @@ -0,0 +1,4 @@ +features: +- name: fetch-priority + files: + - request-init-priority.any.js diff --git a/tests/wpt/tests/fetch/http-cache/pragma-no-cache-with-cache-control.html b/tests/wpt/tests/fetch/http-cache/pragma-no-cache-with-cache-control.html new file mode 100644 index 00000000000..19a80fe5edc --- /dev/null +++ b/tests/wpt/tests/fetch/http-cache/pragma-no-cache-with-cache-control.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTTP Cache: Cache-Control with Pragma: no-cache</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +promise_test(async t => { + // According to https://www.rfc-editor.org/rfc/rfc9111.html#name-pragma + // the pragma header is deprecated. + // When there's a mismatch between pragma and Cache-Control then the latter + // should be respected, and the resource should be cached. + const url = 'resources/cached_pragma_rand.py' + + // First fetch to populate the cache + const response1 = await fetch(url, { cache: 'default' }); + assert_true(response1.ok, 'First fetch should succeed'); + const text1 = await response1.text(); + + // Second fetch should be served from cache + const response2 = await fetch(url, { cache: 'default' }); + assert_true(response2.ok, 'Second fetch should succeed'); + const text2 = await response2.text(); + + assert_equals(text1, text2, 'Responses should be identical, indicating caching'); +}, 'Response with Cache-Control: max-age=2592000, public and Pragma: no-cache should be cached'); +</script> +</body> diff --git a/tests/wpt/tests/fetch/http-cache/resources/cached_pragma_rand.py b/tests/wpt/tests/fetch/http-cache/resources/cached_pragma_rand.py new file mode 100644 index 00000000000..18c7d25159f --- /dev/null +++ b/tests/wpt/tests/fetch/http-cache/resources/cached_pragma_rand.py @@ -0,0 +1,14 @@ +def main(request, response): + # Disable non-standard XSS protection + response.headers.set(b"X-XSS-Protection", b"0") + response.headers.set(b"Content-Type", b"text/html") + + # Set caching headers + # According to rfc9111 Pragma: no-cache is deprecated, so we expect + # Cache-Control to take precedence when there's a mismatch. + response.headers.set(b"Cache-Control", b"max-age=2592000, public") + response.headers.set(b"Pragma", b"no-cache") + + # Include a timestamp to verify caching behavior + import time + response.content = f"Timestamp: {time.time()}".encode('utf-8') diff --git a/tests/wpt/tests/fetch/local-network-access/META.yml b/tests/wpt/tests/fetch/local-network-access/META.yml new file mode 100644 index 00000000000..4c5c6983ed0 --- /dev/null +++ b/tests/wpt/tests/fetch/local-network-access/META.yml @@ -0,0 +1,5 @@ +spec: https://wicg.github.io/local-network-access/ +suggested_reviewers: + - cthomp + - camillelamy + - hchao diff --git a/tests/wpt/tests/fetch/local-network-access/README.md b/tests/wpt/tests/fetch/local-network-access/README.md new file mode 100644 index 00000000000..95066cdcd0b --- /dev/null +++ b/tests/wpt/tests/fetch/local-network-access/README.md @@ -0,0 +1,11 @@ +# Local Network Access tests + +This directory contains tests for Local Network Access' integration with +the Fetch specification. + +See also: + +* [Explainer](https://github.com/explainers-by-googlers/local-network-access) + +Local Network Access replaced [Private Network +Access](https://wicg.github.io/local-network-access/). diff --git a/tests/wpt/tests/fetch/local-network-access/fetch.tentative.https.html b/tests/wpt/tests/fetch/local-network-access/fetch.tentative.https.html new file mode 100644 index 00000000000..9c591f309b7 --- /dev/null +++ b/tests/wpt/tests/fetch/local-network-access/fetch.tentative.https.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>LNA Fetch tests: HTTPS </title> +<body> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/support.sub.js"></script> +<script> + "use strict"; + + promise_test(t => { + const sourceUrl = resolveUrl("resources/fetch-private.html", + sourceResolveOptions({ server: Server.HTTPS_PUBLIC })); + + function checkResult(evt) { + checkTestResult(evt.data, FetchTestResult.SUCCESS); + t.done(); + } + + const promise = new Promise((resolve) => { + window.addEventListener('message', resolve, {once: true}); + }).then(checkResult); + const popup = window.open(sourceUrl); + t.add_cleanup(() => popup.close()); + + return promise; + }, 'LNA Public to private with permission'); + + promise_test(t => { + // TODO(crbug.com/406991278): consider moving permission url param into + // options + const sourceUrl = resolveUrl("resources/fetch-private.html?permission=denied", + sourceResolveOptions({ server: Server.HTTPS_PUBLIC })); + + function checkResult(evt) { + checkTestResult(evt.data, FetchTestResult.FAILURE); + t.done(); + } + + const promise = new Promise((resolve) => { + window.addEventListener('message', resolve, {once: true}); + }).then(checkResult); + const popup = window.open(sourceUrl); + t.add_cleanup(() => popup.close()); + + return promise; + }, 'LNA Public to private with permission denied'); +</script> +</body> diff --git a/tests/wpt/tests/fetch/local-network-access/resources/fetch-private.html b/tests/wpt/tests/fetch/local-network-access/resources/fetch-private.html new file mode 100644 index 00000000000..b96a207ec33 --- /dev/null +++ b/tests/wpt/tests/fetch/local-network-access/resources/fetch-private.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Fetch Private resource</title> + +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="support.sub.js"></script> +<script> +"use strict"; + +// Set the 'local-network-access' permission then attempt to fetch a resource +// in the private address space. +// +// By default, 'local-network-access' permission is set to 'granted'. This can +// be changed by passing in a different value via the 'permission' URL parameter. +// Valid values: +// +// * granted +// * denied +// * prompt +Promise.resolve().then(async () => { + + const window_url = new URL(window.location.href); + let permission_value = 'granted'; + if (window_url.searchParams.has('permission')) { + permission_value = window_url.searchParams.get('permission'); + } + + test_driver.set_test_context(opener); + await test_driver.set_permission({ name: 'local-network-access' }, permission_value); + + const target = { + server: Server.HTTPS_PRIVATE, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }; + const targetUrl = resolveTargetUrl(target); + + fetch(targetUrl) + .then(async function(response) { + const body = await response.text(); + const message = { + ok: response.ok, + type: response.type, + body: body, + }; + opener.postMessage(message, "*"); + }) + .catch(error => { + opener.postMessage({ error: error.toString() }, "*"); + }); +}); +</script> diff --git a/tests/wpt/tests/fetch/local-network-access/resources/support.sub.js b/tests/wpt/tests/fetch/local-network-access/resources/support.sub.js new file mode 100644 index 00000000000..774e34d0a6f --- /dev/null +++ b/tests/wpt/tests/fetch/local-network-access/resources/support.sub.js @@ -0,0 +1,186 @@ +// Maps protocol (without the trailing colon) and address space to port. +const SERVER_PORTS = { + "http": { + "local": {{ports[http][0]}}, + "private": {{ports[http-private][0]}}, + "public": {{ports[http-public][0]}}, + }, + "https": { + "local": {{ports[https][0]}}, + "other-local": {{ports[https][1]}}, + "private": {{ports[https-private][0]}}, + "public": {{ports[https-public][0]}}, + }, + "ws": { + "local": {{ports[ws][0]}}, + }, + "wss": { + "local": {{ports[wss][0]}}, + }, +}; + +// A `Server` is a web server accessible by tests. It has the following shape: +// +// { +// addressSpace: the IP address space of the server ("local", "private" or +// "public"), +// name: a human-readable name for the server, +// port: the port on which the server listens for connections, +// protocol: the protocol (including trailing colon) spoken by the server, +// } +// +// Constants below define the available servers, which can also be accessed +// programmatically with `get()`. +class Server { + // Maps the given `protocol` (without a trailing colon) and `addressSpace` to + // a server. Returns null if no such server exists. + static get(protocol, addressSpace) { + const ports = SERVER_PORTS[protocol]; + if (ports === undefined) { + return null; + } + + const port = ports[addressSpace]; + if (port === undefined) { + return null; + } + + return { + addressSpace, + name: `${protocol}-${addressSpace}`, + port, + protocol: protocol + ':', + }; + } + + static HTTP_LOCAL = Server.get("http", "local"); + static HTTP_PRIVATE = Server.get("http", "private"); + static HTTP_PUBLIC = Server.get("http", "public"); + static HTTPS_LOCAL = Server.get("https", "local"); + static OTHER_HTTPS_LOCAL = Server.get("https", "other-local"); + static HTTPS_PRIVATE = Server.get("https", "private"); + static HTTPS_PUBLIC = Server.get("https", "public"); + static WS_LOCAL = Server.get("ws", "local"); + static WSS_LOCAL = Server.get("wss", "local"); +}; + +// Resolves a URL relative to the current location, returning an absolute URL. +// +// `url` specifies the relative URL, e.g. "foo.html" or "http://foo.example". +// `options`, if defined, should have the following shape: +// +// { +// // Optional. Overrides the protocol of the returned URL. +// protocol, +// +// // Optional. Overrides the port of the returned URL. +// port, +// +// // Extra headers. +// headers, +// +// // Extra search params. +// searchParams, +// } +// +function resolveUrl(url, options) { + const result = new URL(url, window.location); + if (options === undefined) { + return result; + } + + const { port, protocol, headers, searchParams } = options; + if (port !== undefined) { + result.port = port; + } + if (protocol !== undefined) { + result.protocol = protocol; + } + if (headers !== undefined) { + const pipes = []; + for (key in headers) { + pipes.push(`header(${key},${headers[key]})`); + } + result.searchParams.append("pipe", pipes.join("|")); + } + if (searchParams !== undefined) { + for (key in searchParams) { + result.searchParams.append(key, searchParams[key]); + } + } + + return result; +} + +// Computes options to pass to `resolveUrl()` for a source document's URL. +// +// `server` identifies the server from which to load the document. +// `treatAsPublic`, if set to true, specifies that the source document should +// be artificially placed in the `public` address space using CSP. +function sourceResolveOptions({ server, treatAsPublic }) { + const options = {...server}; + if (treatAsPublic) { + options.headers = { "Content-Security-Policy": "treat-as-public-address" }; + } + return options; +} + +// Computes the URL of a target handler configured with the given options. +// +// `server` identifies the server from which to load the resource. +// `behavior` specifies the behavior of the target server. It may contain: +// - `response`: The result of calling one of `ResponseBehavior`'s methods. +// - `redirect`: A URL to which the target should redirect GET requests. +function resolveTargetUrl({ server, behavior }) { + if (server === undefined) { + throw new Error("no server specified."); + } + const options = {...server}; + if (behavior) { + const { response, redirect } = behavior; + options.searchParams = { + ...response, + }; + if (redirect !== undefined) { + options.searchParams.redirect = redirect; + } + } + + return resolveUrl("target.py", options); +} + +// Methods generate behavior specifications for how `resources/target.py` +// should behave upon receiving a regular (non-preflight) request. +const ResponseBehavior = { + // The response should succeed without CORS headers. + default: () => ({}), + + // The response should succeed with CORS headers. + allowCrossOrigin: () => ({ "final-headers": "cors" }), +}; + +const FetchTestResult = { + SUCCESS: { + ok: true, + body: "success", + }, + OPAQUE: { + ok: false, + type: "opaque", + body: "", + }, + FAILURE: { + error: "TypeError: Failed to fetch", + }, +}; + +// Helper function for checking results from fetch tests. +function checkTestResult(actual, expected) { + assert_equals(actual.error, expected.error, "error mismatch"); + assert_equals(actual.ok, expected.ok, "response ok mismatch"); + assert_equals(actual.body, expected.body, "response body mismatch"); + + if (expected.type !== undefined) { + assert_equals(type, expected.type, "response type mismatch"); + } +} diff --git a/tests/wpt/tests/fetch/local-network-access/resources/target.py b/tests/wpt/tests/fetch/local-network-access/resources/target.py new file mode 100644 index 00000000000..eabcdd47517 --- /dev/null +++ b/tests/wpt/tests/fetch/local-network-access/resources/target.py @@ -0,0 +1,105 @@ +# This endpoint responds to requests for a target of a (possible) LNA request. +# +# Its behavior can be configured with various search/GET parameters, all of +# which are optional: +# +# - final-headers: Valid values are: +# - cors: this endpoint responds with valid CORS headers to CORS-enabled +# non-preflight requests. These should be sufficient for non-preflighted +# CORS-enabled requests to succeed. +# - sw: this endpoint responds with a valid Service-Worker header to allow +# for the request to serve as a Service worker script resource. This is +# only valid in conjunction with the cors value above. +# - unspecified: this endpoint responds with no CORS headers to non-preflight +# requests. This should fail CORS-enabled requests, but be sufficient for +# no-CORS requests. +# +# The following parameters only affect non-preflight responses: +# +# - redirect: If set, the response code is set to 301 and the `Location` +# response header is set to this value. +# - mime-type: If set, the `Content-Type` response header is set to this value. +# - file: Specifies a path (relative to this file's directory) to a file. If +# set, the response body is copied from this file. +# - random-js-prefix: If set to any value, the response body is prefixed with +# a Javascript comment line containing a random value. This is useful in +# service worker tests, since service workers are only updated if the new +# script is not byte-for-byte identical with the old script. +# - body: If set and `file` is not, the response body is set to this value. +# + +import os +import random + +from wptserve.utils import isomorphic_encode + +_ACAO = ("Access-Control-Allow-Origin", "*") +_ACAH = ("Access-Control-Allow-Headers", "Service-Worker") + +def _get_response_headers(method, mode, origin): + acam = ("Access-Control-Allow-Methods", method) + + if mode == b"cors": + return [acam, _ACAO] + + if mode == b"cors+sw": + return [acam, _ACAO, _ACAH] + + if mode == b"navigation": + return [ + acam, + ("Access-Control-Allow-Origin", origin), + ("Access-Control-Allow-Credentials", "true"), + ] + + return [] + + +def _is_loaded_in_fenced_frame(request): + return request.GET.get(b"is-loaded-in-fenced-frame") + +def _final_response_body(request): + file_name = None + if file_name is None: + file_name = request.GET.get(b"file") + if file_name is None: + return request.GET.get(b"body") or "success" + + prefix = b"" + if request.GET.get(b"random-js-prefix"): + value = random.randint(0, 1000000000) + prefix = isomorphic_encode("// Random value: {}\n\n".format(value)) + + path = os.path.join(os.path.dirname(isomorphic_encode(__file__)), file_name) + with open(path, 'rb') as f: + contents = f.read() + + return prefix + contents + +def _handle_final_request(request, response): + mode = request.GET.get(b"final-headers") + origin = request.headers.get("Origin") + headers = _get_response_headers(request.method, mode, origin) + + redirect = request.GET.get(b"redirect") + if redirect is not None: + headers.append(("Location", redirect)) + return (301, headers, b"") + + mime_type = request.GET.get(b"mime-type") + if mime_type is not None: + headers.append(("Content-Type", mime_type),) + + if _is_loaded_in_fenced_frame(request): + headers.append(("Supports-Loading-Mode", "fenced-frame")) + + body = _final_response_body(request) + return (headers, body) + + +def main(request, response): + try: + return _handle_final_request(request, response) + except BaseException as e: + # Surface exceptions to the client, where they show up as assertion errors. + return (500, [("X-exception", str(e))], "exception: {}".format(e)) diff --git a/tests/wpt/tests/file-system-access/WEB_FEATURES.yml b/tests/wpt/tests/file-system-access/WEB_FEATURES.yml index 45e40acba1d..7faf67952b5 100644 --- a/tests/wpt/tests/file-system-access/WEB_FEATURES.yml +++ b/tests/wpt/tests/file-system-access/WEB_FEATURES.yml @@ -1,3 +1,7 @@ features: - name: file-system-access files: "**" +- name: origin-private-file-system + files: + - getDirectory.https.any.js + - opaque-origin.https.window.js diff --git a/tests/wpt/tests/html/browsers/windows/auxiliary-browsing-contexts/named-lookup-noopener.html b/tests/wpt/tests/html/browsers/windows/auxiliary-browsing-contexts/named-lookup-noopener.html new file mode 100644 index 00000000000..91cf6ae6ee2 --- /dev/null +++ b/tests/wpt/tests/html/browsers/windows/auxiliary-browsing-contexts/named-lookup-noopener.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<title>Named lookup does not work with noopener</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js"></script> +<link rel="help" href="https://github.com/whatwg/html/issues/11291"> + +<body> +<script> + +promise_test(async t => { + const windowName = 'named-window'; + + const rcHelper = new RemoteContextHelper(); + const rcPopup1 = await rcHelper.addWindow(undefined, { target: windowName, features: 'noopener' }); + const rcPopup2 = await rcHelper.addWindow(undefined, { target: windowName, features: 'noopener' }); + + // If both scripts execute, then the windows are separate, and the test passes. + // If the window is reused, then rcPopup1 will not be able to execute script, and so the test will timeout. + + assert_equals(await rcPopup1.executeScript(() => window.name), windowName); + assert_equals(await rcPopup2.executeScript(() => window.name), windowName); +}, 'Two noopener window.open() calls create separate windows'); + +promise_test(async t => { + const windowName = 'named-window-2'; + + function executorCreator(url) { + const a = document.createElement("a"); + a.href = url; + a.rel = 'noopener'; + a.target = windowName; + document.body.append(a); + a.click(); + } + + const rcHelper = new RemoteContextHelper(); + const rcPopup1 = await rcHelper.createContext({ executorCreator }); + const rcPopup2 = await rcHelper.createContext({ executorCreator }); + + // If both scripts execute, then the windows are separate, and the test passes. + // If the window is reused, then rcPopup1 will not be able to execute script, and so the test will timeout. + + assert_equals(await rcPopup1.executeScript(() => window.name), windowName); + assert_equals(await rcPopup2.executeScript(() => window.name), windowName); +}, 'Two rel=noopener <a href> clicks create separate windows'); +</script> diff --git a/tests/wpt/tests/html/canvas/element/manual/wide-gamut-canvas/WEB_FEATURES.yml b/tests/wpt/tests/html/canvas/element/manual/wide-gamut-canvas/WEB_FEATURES.yml new file mode 100644 index 00000000000..b4d11212a6c --- /dev/null +++ b/tests/wpt/tests/html/canvas/element/manual/wide-gamut-canvas/WEB_FEATURES.yml @@ -0,0 +1,3 @@ +features: +- name: canvas-2d-color-management + files: "**" diff --git a/tests/wpt/tests/html/canvas/element/path-objects/WEB_FEATURES.yml b/tests/wpt/tests/html/canvas/element/path-objects/WEB_FEATURES.yml new file mode 100644 index 00000000000..1bbef5e88d6 --- /dev/null +++ b/tests/wpt/tests/html/canvas/element/path-objects/WEB_FEATURES.yml @@ -0,0 +1,4 @@ +features: +- name: canvas-roundrect + files: + - 2d.path.roundrect.* diff --git a/tests/wpt/tests/html/canvas/element/wide-gamut-canvas/WEB_FEATURES.yml b/tests/wpt/tests/html/canvas/element/wide-gamut-canvas/WEB_FEATURES.yml new file mode 100644 index 00000000000..b4d11212a6c --- /dev/null +++ b/tests/wpt/tests/html/canvas/element/wide-gamut-canvas/WEB_FEATURES.yml @@ -0,0 +1,3 @@ +features: +- name: canvas-2d-color-management + files: "**" diff --git a/tests/wpt/tests/html/canvas/offscreen/manual/wide-gamut-canvas/WEB_FEATURES.yml b/tests/wpt/tests/html/canvas/offscreen/manual/wide-gamut-canvas/WEB_FEATURES.yml new file mode 100644 index 00000000000..b4d11212a6c --- /dev/null +++ b/tests/wpt/tests/html/canvas/offscreen/manual/wide-gamut-canvas/WEB_FEATURES.yml @@ -0,0 +1,3 @@ +features: +- name: canvas-2d-color-management + files: "**" diff --git a/tests/wpt/tests/html/canvas/offscreen/path-objects/WEB_FEATURES.yml b/tests/wpt/tests/html/canvas/offscreen/path-objects/WEB_FEATURES.yml new file mode 100644 index 00000000000..1bbef5e88d6 --- /dev/null +++ b/tests/wpt/tests/html/canvas/offscreen/path-objects/WEB_FEATURES.yml @@ -0,0 +1,4 @@ +features: +- name: canvas-roundrect + files: + - 2d.path.roundrect.* diff --git a/tests/wpt/tests/html/canvas/offscreen/wide-gamut-canvas/WEB_FEATURES.yml b/tests/wpt/tests/html/canvas/offscreen/wide-gamut-canvas/WEB_FEATURES.yml new file mode 100644 index 00000000000..b4d11212a6c --- /dev/null +++ b/tests/wpt/tests/html/canvas/offscreen/wide-gamut-canvas/WEB_FEATURES.yml @@ -0,0 +1,3 @@ +features: +- name: canvas-2d-color-management + files: "**" diff --git a/tests/wpt/tests/html/dom/reflection.js b/tests/wpt/tests/html/dom/reflection.js index b2c3b30aae3..eeecd450fca 100644 --- a/tests/wpt/tests/html/dom/reflection.js +++ b/tests/wpt/tests/html/dom/reflection.js @@ -967,6 +967,7 @@ ReflectionTests.reflects = function(data, idlName, idlObj, domName, domObj) { "previous value", "getAttribute()"); ReflectionHarness.assertEquals(idlObj[idlName], previousIdl, "IDL get"); } else { + var previousValue = domObj.getAttribute(domName); idlObj[idlName] = idlTests[i]; if (data.type == "boolean") { // Special case yay @@ -976,6 +977,11 @@ ReflectionTests.reflects = function(data, idlName, idlObj, domName, domObj) { var expected = idlDomExpected[i] + ""; if (data.isNullable && idlDomExpected[i] === null) { expected = null; + } else if (idlName == "nonce") { + // nonce doesn't reflect the value, as per /content-security-policy/nonce-hiding/ + // tests that confirm that retrieving the nonce value post IDL change does not + // reflect back to the attribute (for security reasons) + expected = previousValue; } ReflectionHarness.assertEquals(domObj.getAttribute(domName), expected, "getAttribute()"); diff --git a/tests/wpt/tests/html/editing/dnd/platform/pointerdown-add-display-none.html b/tests/wpt/tests/html/editing/dnd/platform/pointerdown-add-display-none.html new file mode 100644 index 00000000000..653944e155b --- /dev/null +++ b/tests/wpt/tests/html/editing/dnd/platform/pointerdown-add-display-none.html @@ -0,0 +1,65 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<head> +<title>Test dragging still occurs when pointerdown adds display:none to the dragged element</title> +<style> +.dragging { + display: none; +} + +#dragBox { + width: 200px; + height: 200px; + background-color: #4CAF50; + color: white; + border-radius: 8px; + cursor: grab; + user-select: none; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.inner { + background: rgba(255, 255, 255, 0.2); + padding: 10px; + margin-top: 10px; + border-radius: 4px; +} +</style> +</head> +<body> +<div id="dragBox" draggable="true"> + Drag me + <div class="inner" id="innerButton">Click or press here</div> +</div> +<script> + +promise_test(function() { + return new Promise(r => { + innerButton.addEventListener("pointerdown", function() { + innerButton.classList.add("dragging"); + }); + + dragBox.addEventListener("dragstart", function(e) { + assert_equals(e.target, dragBox); + r(); + }); + + const buttonRect = innerButton.getBoundingClientRect(); + new test_driver.Actions() + .pointerMove(0, 0, {origin: innerButton}) + .pointerDown() + .pointerMove(buttonRect.left + 10, buttonRect.top + 10) + .pointerUp() + .send(); + }); +}, "dragstart should still fire when the dragged element gets display:none in its pointerdown"); +</script> +</body> +</html> diff --git a/tests/wpt/tests/html/embedded-content/the-img-element/WEB_FEATURES.yml b/tests/wpt/tests/html/embedded-content/the-img-element/WEB_FEATURES.yml new file mode 100644 index 00000000000..5716baa5def --- /dev/null +++ b/tests/wpt/tests/html/embedded-content/the-img-element/WEB_FEATURES.yml @@ -0,0 +1,4 @@ +features: +- name: fetch-priority + files: + - attr-img-fetchpriority.html diff --git a/tests/wpt/tests/html/scripting/the-script-element/WEB_FEATURES.yml b/tests/wpt/tests/html/scripting/the-script-element/WEB_FEATURES.yml new file mode 100644 index 00000000000..864bd3398ee --- /dev/null +++ b/tests/wpt/tests/html/scripting/the-script-element/WEB_FEATURES.yml @@ -0,0 +1,4 @@ +features: +- name: fetch-priority + files: + - attr-script-fetchpriority.html diff --git a/tests/wpt/tests/html/semantics/forms/the-select-element/customizable-select/select-input-keyboard-behavior.tentative.html b/tests/wpt/tests/html/semantics/forms/the-select-element/customizable-select/select-input-keyboard-behavior.tentative.html new file mode 100644 index 00000000000..bf3fbab9d18 --- /dev/null +++ b/tests/wpt/tests/html/semantics/forms/the-select-element/customizable-select/select-input-keyboard-behavior.tentative.html @@ -0,0 +1,58 @@ +<!DOCTYPE html> +<link rel=author href="mailto:jarhar@chromium.org"> +<link rel=help href="https://issues.chromium.org/issues/402429384"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="/resources/testdriver-actions.js"></script> + +<style> +select, ::picker(select) { + appearance: base-select; +} +</style> + +<select> + <div id=inputwrapper></div> + <option class=one>one</option> + <option class=two>two</option> +</select> + +<script> +const select = document.querySelector('select'); +const optionOne = select.querySelector('option.one'); +const optionTwo = select.querySelector('option.two'); +const input = document.createElement('input'); +document.getElementById('inputwrapper').appendChild(input); + +function pressKey(key) { + return (new test_driver.Actions() + .keyDown(key) + .keyUp(key)) + .send(); +} +const spaceKey = '\uE00D'; +const arrowDown = '\uE015'; +const arrowUp = '\uE013'; + +promise_test(async () => { + assert_equals(getComputedStyle(select).appearance, 'base-select', + 'appearance: base-select must be supported for this test to run.'); + + select.focus(); + await pressKey(spaceKey); + assert_true(select.matches(':open'), + 'select should be open after pressing space.'); + assert_equals(document.activeElement, input, + 'input should be focused after opening select.'); + + await pressKey(arrowDown); + assert_equals(document.activeElement, optionOne, + 'first option should be focused after arrow down from input.'); + + await pressKey(arrowUp); + assert_equals(document.activeElement, input, + 'input should be focused after arrow up from first option.'); +}, 'Keyboard behavior of <input> in <select>.'); +</script> diff --git a/tests/wpt/tests/html/semantics/the-button-element/interest-target/interesttarget-keyboard-invalidation-ref.html b/tests/wpt/tests/html/semantics/the-button-element/interest-target/interesttarget-keyboard-invalidation-ref.html index 0ba07b16f49..9d520943d5f 100644 --- a/tests/wpt/tests/html/semantics/the-button-element/interest-target/interesttarget-keyboard-invalidation-ref.html +++ b/tests/wpt/tests/html/semantics/the-button-element/interest-target/interesttarget-keyboard-invalidation-ref.html @@ -7,21 +7,24 @@ <button id=hasinterest>Button</button> <button class=otherselector>Button</button> <button class=otherselector>Button</button> -<div id=target>Target</div> +<div popover id=target>Target</div> <style> #hasinterest { - background-color: red; - outline: solid 3px -webkit-focus-ring-color; + background-color: purple; } .otherselector { background-color: green; } #target { background-color: yellow; + inset:auto; + top:50px; + left:0; } </style> <script> + target.showPopover(); hasinterest.focus(); </script> diff --git a/tests/wpt/tests/html/semantics/the-button-element/interest-target/interesttarget-keyboard-invalidation.tentative.html b/tests/wpt/tests/html/semantics/the-button-element/interest-target/interesttarget-keyboard-invalidation.tentative.html index 8c0ebd7a2cd..b6d0ff10932 100644 --- a/tests/wpt/tests/html/semantics/the-button-element/interest-target/interesttarget-keyboard-invalidation.tentative.html +++ b/tests/wpt/tests/html/semantics/the-button-element/interest-target/interesttarget-keyboard-invalidation.tentative.html @@ -13,7 +13,7 @@ <button id=b3 interesttarget=target>Button</button> <button id=b4>Button</button> <button id=b5>Button</button> -<div id=target>Target</div> +<div popover id=target>Target</div> <style> :has-interest { @@ -22,18 +22,24 @@ :has-interest:has-partial-interest { background-color: red; } - :has-partial-interest + button { + /* Test complicated combinators: */ + :has-interest + button { background-color: green; } :root:has(:has-interest) #b5 { background-color: green; } - :target-of-interest:target-of-partial-interest { + :target-of-interest { background-color: yellow; } [interesttarget] { interest-target-delay: 0s; } + #target { + inset:auto; + top:50px; + left:0; + } </style> <script> diff --git a/tests/wpt/tests/html/semantics/the-button-element/interest-target/interesttarget-outline-appearance-ref.html b/tests/wpt/tests/html/semantics/the-button-element/interest-target/interesttarget-outline-appearance-ref.html deleted file mode 100644 index 33b0cf66858..00000000000 --- a/tests/wpt/tests/html/semantics/the-button-element/interest-target/interesttarget-outline-appearance-ref.html +++ /dev/null @@ -1,9 +0,0 @@ -<!DOCTYPE html> -<meta charset="utf-8" /> -<link rel="author" href="mailto:masonf@chromium.org"> - -<button id=non_interesttarget_button>Interesttarget Button</button> - -<script> - document.querySelector('#non_interesttarget_button').focus(); -</script> diff --git a/tests/wpt/tests/html/semantics/the-button-element/interest-target/interesttarget-outline-appearance.tentative.html b/tests/wpt/tests/html/semantics/the-button-element/interest-target/interesttarget-outline-appearance.tentative.html deleted file mode 100644 index 28cb2052a8d..00000000000 --- a/tests/wpt/tests/html/semantics/the-button-element/interest-target/interesttarget-outline-appearance.tentative.html +++ /dev/null @@ -1,16 +0,0 @@ -<!DOCTYPE html> -<meta charset="utf-8" /> -<link rel="author" href="mailto:masonf@chromium.org"> -<link rel="help" href="https://open-ui.org/components/interest-invokers.explainer/" /> -<link rel="mismatch" href="interesttarget-outline-appearance-ref.html"> - -<button interesttarget=target>Interesttarget Button</button> - -<style> - /* Outline style should apply even when the element doesn't have interest. */ - :has-interest { background-color: red; } -</style> - -<script> - document.querySelector('[interesttarget]').focus(); -</script> diff --git a/tests/wpt/tests/html/semantics/the-button-element/interest-target/interesttarget-partial-interest.tentative.html b/tests/wpt/tests/html/semantics/the-button-element/interest-target/interesttarget-partial-interest.tentative.html new file mode 100644 index 00000000000..bc7184d42d2 --- /dev/null +++ b/tests/wpt/tests/html/semantics/the-button-element/interest-target/interesttarget-partial-interest.tentative.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<link rel="author" href="mailto:masonf@chromium.org"> +<link rel="help" href="https://open-ui.org/components/interest-invokers.explainer/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/invoker-utils.js"></script> + +<button id=invoker interesttarget=target>Button</button> +<div id=target popover>Popover with <button id=contained>button</button></div> + +<style> + [interesttarget] { + interest-target-delay: 0s; + } +</style> + +<script> + let expectInterest; + let expectPartial; + let eventCount = 0; + function checkInterest(e) { + const notStr = expectInterest ? "" : "not "; + const event = e instanceof Event ? `${e.type} event` : e; + assert_equals(invoker.matches(':has-interest'),expectInterest,`target should ${notStr}gain interest (${event})`); + assert_equals(invoker.matches(':has-partial-interest'),expectInterest&&expectPartial,`Interest should ${expectPartial ? "" : "not "}be partial (${event})`); + assert_equals(target.matches(':popover-open'),expectInterest,`popover should ${notStr}be open (${event})`); + ++eventCount; + } + + promise_test(async function (t) { + invoker.addEventListener('focus',checkInterest); + target.addEventListener('interest',(e) => { + checkInterest(e); + expectInterest = true; + expectPartial = true; + }); + + expectInterest = false; + expectPartial = false; + await focusOn(invoker); + assert_equals(eventCount,2,'focus and interest should both have fired'); + assert_true(expectInterest,'the interest event should set this'); + checkInterest('before hot key'); + await sendShowInterestHotkey(); + expectPartial = false; + checkInterest('after hot key'); + },'Partial interest timing should not be observable'); +</script> diff --git a/tests/wpt/tests/html/semantics/the-link-element/WEB_FEATURES.yml b/tests/wpt/tests/html/semantics/the-link-element/WEB_FEATURES.yml new file mode 100644 index 00000000000..6a444ee171a --- /dev/null +++ b/tests/wpt/tests/html/semantics/the-link-element/WEB_FEATURES.yml @@ -0,0 +1,4 @@ +features: +- name: fetch-priority + files: + - attr-link-fetchpriority.html diff --git a/tests/wpt/tests/infrastructure/testdriver/bidi/emulation/set_geolocation_override.https.html b/tests/wpt/tests/infrastructure/testdriver/bidi/emulation/set_geolocation_override.https.html index 5cbcf546423..e339f39dcd8 100644 --- a/tests/wpt/tests/infrastructure/testdriver/bidi/emulation/set_geolocation_override.https.html +++ b/tests/wpt/tests/infrastructure/testdriver/bidi/emulation/set_geolocation_override.https.html @@ -13,10 +13,6 @@ descriptor: {name: "geolocation"}, state: "granted", }); - - // Ensure any previously set geolocation emulations are cleared. - await test_driver.bidi.emulation.set_geolocation_override( - {coordinates: null}); }); /** Get the current geolocation or error */ @@ -24,13 +20,13 @@ return new Promise( resolve => window.navigator.geolocation.getCurrentPosition( position => resolve(position.coords.toJSON()), - error => resolve({code: error.code, message: error.message}), + error => resolve({code: error.code}), // Fail fast if geolocation is not available. {timeout: 500} )) } - /** Asserts that relevant coordinate properties match */ + /** Asserts that coordinate or error matches the expected value */ function assert_coordinates_match(actual, expected) { for (const key in expected) { assert_equals(actual[key], expected[key], @@ -49,6 +45,11 @@ }; promise_test(async (t) => { + t.add_cleanup(async () => { + await test_driver.bidi.emulation.set_geolocation_override( + {coordinates: null}); + }); + // Get the initial geolocation (might be error). const initial_coords = await get_current_geolocation(); @@ -66,6 +67,28 @@ await test_driver.bidi.emulation.set_geolocation_override( {coordinates: null}); // Assert coordinates are set to the original value. - assert_coordinates_match(await get_current_geolocation(), initial_coords); + assert_coordinates_match(await get_current_geolocation(), + initial_coords); }, "emulate geolocation and clear override"); + + promise_test(async (t) => { + // Emulate position unavailable. + await test_driver.bidi.emulation.set_geolocation_override({ + error: {type: 'positionUnavailable'}, + }); + + // Get the geolocation after setting the override. + const error = await get_current_geolocation(); + + assert_equals(error.code, 2, + `Geolocation should be unavailable with code POSITION_UNAVAILABLE = 2`); + }, "emulate geolocation position unavailable"); + + promise_test(async (t) => { + assert_throws_js(Error, + () => test_driver.bidi.emulation.set_geolocation_override({ + error: {type: 'positionUnavailable'}, + coordinates: SOME_COORDINATES + }), "Setting both `coordinates` and `error` should fail"); + }, "cannot set `coordinates` and `error` simultaneously"); </script> diff --git a/tests/wpt/tests/input-events/input-events-spin-button-click-on-number-input-delete-document.html b/tests/wpt/tests/input-events/input-events-spin-button-click-on-number-input-delete-document.html index fb3655398db..9e5d9b9ef99 100644 --- a/tests/wpt/tests/input-events/input-events-spin-button-click-on-number-input-delete-document.html +++ b/tests/wpt/tests/input-events/input-events-spin-button-click-on-number-input-delete-document.html @@ -21,7 +21,7 @@ promise_test(async function () { await loadIframe( - "<input type='number' style='width: 100px; height: 20px'>" + "<input type='number' style='width: 100px; height: 20px' value='5'>" ); const inputElement = frame.contentDocument.querySelector("input"); @@ -38,12 +38,14 @@ events.push("change"); }); + assert_equals(inputElement.value, "5", "Original value should be 5"); + // Roughly get the offset to the spin up arrow button's center point within // the iframe's viewport. Note that this is fragile and might need specific // coordinates for each browser and maybe platform. const rect = inputElement.getBoundingClientRect(); - const x = rect.x + rect.width - 10; - const y = rect.y + Math.round(rect.height / 4); + const x = Math.round(rect.x + rect.width - 5); + const y = Math.round(rect.y + (3 * rect.height) / 4); const actions = new test_driver.Actions() .setContext(frame.contentWindow) @@ -52,6 +54,7 @@ .pointerUp(); await actions.send(); + assert_equals(inputElement.value, "6", "Value should increase after clicking the up arrow"); assert_array_equals(events, ['beforeinput']); assert_false(document.body.contains(frame)); }, "Number input should not crash and not fire subsequent events when event handler removes document"); diff --git a/tests/wpt/tests/interfaces/largest-contentful-paint.idl b/tests/wpt/tests/interfaces/largest-contentful-paint.idl index 872ba552b0d..d1630cc7daa 100644 --- a/tests/wpt/tests/interfaces/largest-contentful-paint.idl +++ b/tests/wpt/tests/interfaces/largest-contentful-paint.idl @@ -5,7 +5,6 @@ [Exposed=Window] interface LargestContentfulPaint : PerformanceEntry { - readonly attribute DOMHighResTimeStamp renderTime; readonly attribute DOMHighResTimeStamp loadTime; readonly attribute unsigned long size; readonly attribute DOMString id; @@ -13,3 +12,5 @@ interface LargestContentfulPaint : PerformanceEntry { readonly attribute Element? element; [Default] object toJSON(); }; + +LargestContentfulPaint includes PaintTimingMixin; diff --git a/tests/wpt/tests/interfaces/service-workers.idl b/tests/wpt/tests/interfaces/service-workers.idl index d9ff2f651f8..34af3372401 100644 --- a/tests/wpt/tests/interfaces/service-workers.idl +++ b/tests/wpt/tests/interfaces/service-workers.idl @@ -34,7 +34,7 @@ interface ServiceWorkerRegistration : EventTarget { readonly attribute USVString scope; readonly attribute ServiceWorkerUpdateViaCache updateViaCache; - [NewObject] Promise<undefined> update(); + [NewObject] Promise<ServiceWorkerRegistration> update(); [NewObject] Promise<boolean> unregister(); // event diff --git a/tests/wpt/tests/interfaces/webauthn.idl b/tests/wpt/tests/interfaces/webauthn.idl index a33c85e7bad..7fbe55e6765 100644 --- a/tests/wpt/tests/interfaces/webauthn.idl +++ b/tests/wpt/tests/interfaces/webauthn.idl @@ -311,22 +311,37 @@ enum PublicKeyCredentialHint { partial dictionary AuthenticationExtensionsClientInputs { DOMString appid; }; +partial dictionary AuthenticationExtensionsClientInputsJSON { + DOMString appid; +}; partial dictionary AuthenticationExtensionsClientOutputs { boolean appid; }; +partial dictionary AuthenticationExtensionsClientOutputsJSON { + boolean appid; +}; partial dictionary AuthenticationExtensionsClientInputs { DOMString appidExclude; }; +partial dictionary AuthenticationExtensionsClientInputsJSON { + DOMString appidExclude; +}; partial dictionary AuthenticationExtensionsClientOutputs { boolean appidExclude; }; +partial dictionary AuthenticationExtensionsClientOutputsJSON { + boolean appidExclude; +}; partial dictionary AuthenticationExtensionsClientInputs { boolean credProps; }; +partial dictionary AuthenticationExtensionsClientInputsJSON { + boolean credProps; +}; dictionary CredentialPropertiesOutput { boolean rk; @@ -335,33 +350,57 @@ dictionary CredentialPropertiesOutput { partial dictionary AuthenticationExtensionsClientOutputs { CredentialPropertiesOutput credProps; }; +partial dictionary AuthenticationExtensionsClientOutputsJSON { + CredentialPropertiesOutput credProps; +}; dictionary AuthenticationExtensionsPRFValues { required BufferSource first; BufferSource second; }; +dictionary AuthenticationExtensionsPRFValuesJSON { + required Base64URLString first; + Base64URLString second; +}; dictionary AuthenticationExtensionsPRFInputs { AuthenticationExtensionsPRFValues eval; record<DOMString, AuthenticationExtensionsPRFValues> evalByCredential; }; +dictionary AuthenticationExtensionsPRFInputsJSON { + AuthenticationExtensionsPRFValuesJSON eval; + record<DOMString, AuthenticationExtensionsPRFValuesJSON> evalByCredential; +}; partial dictionary AuthenticationExtensionsClientInputs { AuthenticationExtensionsPRFInputs prf; }; +partial dictionary AuthenticationExtensionsClientInputsJSON { + AuthenticationExtensionsPRFInputsJSON prf; +}; dictionary AuthenticationExtensionsPRFOutputs { boolean enabled; AuthenticationExtensionsPRFValues results; }; +dictionary AuthenticationExtensionsPRFOutputsJSON { + boolean enabled; + AuthenticationExtensionsPRFValuesJSON results; +}; partial dictionary AuthenticationExtensionsClientOutputs { AuthenticationExtensionsPRFOutputs prf; }; +partial dictionary AuthenticationExtensionsClientOutputsJSON { + AuthenticationExtensionsPRFOutputsJSON prf; +}; partial dictionary AuthenticationExtensionsClientInputs { AuthenticationExtensionsLargeBlobInputs largeBlob; }; +partial dictionary AuthenticationExtensionsClientInputsJSON { + AuthenticationExtensionsLargeBlobInputsJSON largeBlob; +}; enum LargeBlobSupport { "required", @@ -373,13 +412,26 @@ dictionary AuthenticationExtensionsLargeBlobInputs { boolean read; BufferSource write; }; +dictionary AuthenticationExtensionsLargeBlobInputsJSON { + DOMString support; + boolean read; + Base64URLString write; +}; partial dictionary AuthenticationExtensionsClientOutputs { AuthenticationExtensionsLargeBlobOutputs largeBlob; }; +partial dictionary AuthenticationExtensionsClientOutputsJSON { + AuthenticationExtensionsLargeBlobOutputsJSON largeBlob; +}; dictionary AuthenticationExtensionsLargeBlobOutputs { boolean supported; ArrayBuffer blob; boolean written; }; +dictionary AuthenticationExtensionsLargeBlobOutputsJSON { + boolean supported; + Base64URLString blob; + boolean written; +}; diff --git a/tests/wpt/tests/largest-contentful-paint/idlharness.html b/tests/wpt/tests/largest-contentful-paint/idlharness.html index 5f5d286b356..0dd006e711c 100644 --- a/tests/wpt/tests/largest-contentful-paint/idlharness.html +++ b/tests/wpt/tests/largest-contentful-paint/idlharness.html @@ -10,7 +10,7 @@ idl_test( ['largest-contentful-paint'], - ['performance-timeline', 'dom', 'hr-time'], + ['performance-timeline', 'dom', 'hr-time', 'paint-timing'], async (idl_array, t) => { idl_array.add_objects({ LargestContentfulPaint: ['lcp'] diff --git a/tests/wpt/tests/largest-contentful-paint/observe-text.html b/tests/wpt/tests/largest-contentful-paint/observe-text.html index 0724a0bb92b..d029d500aa1 100644 --- a/tests/wpt/tests/largest-contentful-paint/observe-text.html +++ b/tests/wpt/tests/largest-contentful-paint/observe-text.html @@ -26,7 +26,7 @@ p { // PaintTimingMixin assert_greater_than_equal(entry.paintTime, beforeRender, 'paintTime should represent the time when the UA started painting'); - if ("presentationTime" in entry) { + if ("presentationTime" in entry && entry.presentationTime !== null) { assert_greater_than(entry.presentationTime, entry.paintTime); assert_equals(entry.presentationTime, entry.renderTime); } else { diff --git a/tests/wpt/tests/largest-contentful-paint/resources/largest-contentful-paint-helpers.js b/tests/wpt/tests/largest-contentful-paint/resources/largest-contentful-paint-helpers.js index a01080c0088..99916f5c5d3 100644 --- a/tests/wpt/tests/largest-contentful-paint/resources/largest-contentful-paint-helpers.js +++ b/tests/wpt/tests/largest-contentful-paint/resources/largest-contentful-paint-helpers.js @@ -55,7 +55,7 @@ function checkImage(entry, expectedUrl, expectedID, expectedSize, timeLowerBound assert_greater_than_equal(entry.paintTime, timeLowerBound, 'paintTime should represent the time when the UA started painting'); // PaintTimingMixin - if ("presentationTime" in entry) { + if ("presentationTime" in entry && entry.presentationTime !== null) { assert_greater_than(entry.presentationTime, entry.paintTime); assert_equals(entry.presentationTime, entry.renderTime); } else { diff --git a/tests/wpt/tests/paint-timing/paint-timing-mixin.html b/tests/wpt/tests/paint-timing/paint-timing-mixin.html index c43b7668b5c..048c985c7c7 100644 --- a/tests/wpt/tests/paint-timing/paint-timing-mixin.html +++ b/tests/wpt/tests/paint-timing/paint-timing-mixin.html @@ -26,7 +26,7 @@ }); const entry = await performance_entry_promise; assert_greater_than(entry.paintTime, reference_time); - if ("presentationTime" in entry) { + if ("presentationTime" in entry && entry.presentationTime !== null) { assert_greater_than(entry.presentationTime, entry.paintTime); assert_equals(entry.presentationTime, entry.startTime); } else { diff --git a/tests/wpt/tests/paint-timing/resources/utils.js b/tests/wpt/tests/paint-timing/resources/utils.js index ed58c957c97..41c76d68266 100644 --- a/tests/wpt/tests/paint-timing/resources/utils.js +++ b/tests/wpt/tests/paint-timing/resources/utils.js @@ -51,7 +51,7 @@ async function test_fcp(label, before_assert_fcp_func) { main.className = 'contentful'; const entry = await assertFirstContentfulPaint(t); if ("paintTime" in entry) { - if ("presentationTime" in entry) { + if ("presentationTime" in entry && entry.presentationTime !== null) { assert_greater_than(entry.presentationTime, entry.paintTime); assert_equals(entry.startTime, entry.presentationTime); } else { diff --git a/tests/wpt/tests/remote-playback/WEB_FEATURES.yml b/tests/wpt/tests/remote-playback/WEB_FEATURES.yml new file mode 100644 index 00000000000..f59321971a7 --- /dev/null +++ b/tests/wpt/tests/remote-playback/WEB_FEATURES.yml @@ -0,0 +1,3 @@ +features: +- name: remote-playback + files: "**" diff --git a/tests/wpt/tests/reporting/resources/report-helper.js b/tests/wpt/tests/reporting/resources/report-helper.js index 5b5438903de..216da22eae8 100644 --- a/tests/wpt/tests/reporting/resources/report-helper.js +++ b/tests/wpt/tests/reporting/resources/report-helper.js @@ -40,7 +40,9 @@ function checkReportExists(reports, type, url) { function getReport(reports, type, document_url, subresource_url) { for (const report of reports) { if (report.type !== type) continue; - if (report.body.documentURL === document_url && report.body.subresourceURL == subresource_url) return report; + if (report.body.documentURL === document_url + && (report.body.subresourceURL == subresource_url + || report.body.blockedURL == subresource_url)) return report; } return null; } diff --git a/tests/wpt/tests/resources/test/tests/functional/api-tests-1.html b/tests/wpt/tests/resources/test/tests/functional/api-tests-1.html index 9de875b0f12..6ec396c221b 100644 --- a/tests/wpt/tests/resources/test/tests/functional/api-tests-1.html +++ b/tests/wpt/tests/resources/test/tests/functional/api-tests-1.html @@ -132,7 +132,13 @@ { assert_less_than(10, 11, "10 is less than 11") } - test(basicAssertApproxEquals, "basic assert_less_than test"); + test(basicAssertLessThan, "basic assert_less_than test"); + + function bigintAssertLessThan() + { + assert_less_than(10n, 11n, "10n is less than 11n") + } + test(bigintAssertLessThan, "bigint assert_less_than test"); function basicAssertGreaterThan() { @@ -140,12 +146,24 @@ } test(basicAssertGreaterThan, "assert_greater_than expected to fail"); + function bigintAssertGreaterThan() + { + assert_greater_than(10n, 11n, "10n is not greater than 11n"); + } + test(bigintAssertGreaterThan, "bigint assert_greater_than expected to fail"); + function basicAssertGreaterThanEqual() { assert_greater_than_equal(10, 10, "10 is greater than or equal to 10") } test(basicAssertGreaterThanEqual, "basic assert_greater_than_equal test"); + function bigintAssertGreaterThanEqual() + { + assert_greater_than_equal(10n, 10n, "10n is greater than or equal to 10n") + } + test(bigintAssertGreaterThanEqual, "bigint assert_greater_than_equal test"); + function basicAssertLessThanEqual() { assert_greater_than_equal('10', 10, "'10' is not a number") @@ -464,6 +482,12 @@ }, { "status_string": "FAIL", + "name": "bigint assert_greater_than expected to fail", + "message": "assert_greater_than: 10n is not greater than 11n expected a number greater than 11n but got 10n", + "properties": {} + }, + { + "status_string": "FAIL", "name": "assert_less_than_equal expected to fail", "message": "assert_greater_than_equal: '10' is not a number expected a number but got a \"string\"", "properties": {} @@ -518,12 +542,24 @@ }, { "status_string": "PASS", + "name": "bigint assert_greater_than_equal test", + "message": null, + "properties": {} + }, + { + "status_string": "PASS", "name": "basic assert_less_than test", "message": null, "properties": {} }, { "status_string": "PASS", + "name": "bigint assert_less_than test", + "message": null, + "properties": {} + }, + { + "status_string": "PASS", "name": "body element fires the onload event set from the attribute", "message": null, "properties": {} @@ -745,13 +781,22 @@ "status": 0 }, { - "assert_name": "assert_approx_equals", + "assert_name": "assert_less_than", "test": "basic assert_less_than test", "args": [ "10", "11", - "1", - "\"10 is approximately (+/- 1) 11\"" + "\"10 is less than 11\"" + ], + "status": 0 + }, + { + "assert_name": "assert_less_than", + "test": "bigint assert_less_than test", + "args": [ + "10n", + "11n", + "\"10n is less than 11n\"" ], "status": 0 }, @@ -766,6 +811,16 @@ "status": 1 }, { + "assert_name": "assert_greater_than", + "test": "bigint assert_greater_than expected to fail", + "args": [ + "10n", + "11n", + "\"10n is not greater than 11n\"" + ], + "status": 1 + }, + { "assert_name": "assert_greater_than_equal", "test": "basic assert_greater_than_equal test", "args": [ @@ -777,6 +832,16 @@ }, { "assert_name": "assert_greater_than_equal", + "test": "bigint assert_greater_than_equal test", + "args": [ + "10n", + "10n", + "\"10n is greater than or equal to 10n\"" + ], + "status": 0 + }, + { + "assert_name": "assert_greater_than_equal", "test": "assert_less_than_equal expected to fail", "args": [ "\"10\"", diff --git a/tests/wpt/tests/resources/testdriver.js b/tests/wpt/tests/resources/testdriver.js index 6e8410b7ea4..992b9e3ab2c 100644 --- a/tests/wpt/tests/resources/testdriver.js +++ b/tests/wpt/tests/resources/testdriver.js @@ -280,8 +280,14 @@ * @param {object} params - Parameters for the command. * @param {null|object} params.coordinates - The optional * geolocation coordinates to set. Matches the - * `emulation.GeolocationCoordinates <https://w3c.github.io/webdriver-bidi/#type-emulation-GeolocationCoordinates>`_ - * value. If null or omitted, the emulation will be removed. + * `emulation.GeolocationCoordinates <https://w3c.github.io/webdriver-bidi/#commands-emulationsetgeolocationoverride>`_ + * value. If null or omitted and the `params.error` is set, the + * emulation will be removed. Mutually exclusive with + * `params.error`. + * @param {object} params.error - The optional + * geolocation error to emulate. Matches the + * `emulation.GeolocationPositionError <https://w3c.github.io/webdriver-bidi/#commands-emulationsetgeolocationoverride>`_ + * value. Mutually exclusive with `params.coordinates`. * @param {null|Array.<(Context)>} [params.contexts] The * optional contexts parameter specifies which browsing contexts * to set the geolocation override on. It should be either an diff --git a/tests/wpt/tests/resources/testharness.js b/tests/wpt/tests/resources/testharness.js index d1d9d61eab8..6ccede34483 100644 --- a/tests/wpt/tests/resources/testharness.js +++ b/tests/wpt/tests/resources/testharness.js @@ -1400,6 +1400,8 @@ return "-0"; } return String(val); + case "bigint": + return String(val) + 'n'; case "object": if (val === null) { return "null"; @@ -1771,20 +1773,25 @@ /** * Assert that ``actual`` is a number less than ``expected``. * - * @param {number} actual - Test value. - * @param {number} expected - Number that ``actual`` must be less than. + * @param {number|bigint} actual - Test value. + * @param {number|bigint} expected - Value that ``actual`` must be less than. * @param {string} [description] - Description of the condition being tested. */ function assert_less_than(actual, expected, description) { /* - * Test if a primitive number is less than another + * Test if a primitive number (or bigint) is less than another */ - assert(typeof actual === "number", + assert(typeof actual === "number" || typeof actual === "bigint", "assert_less_than", description, "expected a number but got a ${type_actual}", {type_actual:typeof actual}); + assert(typeof actual === typeof expected, + "assert_less_than", description, + "expected a ${type_expected} but got a ${type_actual}", + {type_expected:typeof expected, type_actual:typeof actual}); + assert(actual < expected, "assert_less_than", description, "expected a number less than ${expected} but got ${actual}", @@ -1795,20 +1802,25 @@ /** * Assert that ``actual`` is a number greater than ``expected``. * - * @param {number} actual - Test value. - * @param {number} expected - Number that ``actual`` must be greater than. + * @param {number|bigint} actual - Test value. + * @param {number|bigint} expected - Value that ``actual`` must be greater than. * @param {string} [description] - Description of the condition being tested. */ function assert_greater_than(actual, expected, description) { /* - * Test if a primitive number is greater than another + * Test if a primitive number (or bigint) is greater than another */ - assert(typeof actual === "number", + assert(typeof actual === "number" || typeof actual === "bigint", "assert_greater_than", description, "expected a number but got a ${type_actual}", {type_actual:typeof actual}); + assert(typeof actual === typeof expected, + "assert_greater_than", description, + "expected a ${type_expected} but got a ${type_actual}", + {type_expected:typeof expected, type_actual:typeof actual}); + assert(actual > expected, "assert_greater_than", description, "expected a number greater than ${expected} but got ${actual}", @@ -1820,21 +1832,31 @@ * Assert that ``actual`` is a number greater than ``lower`` and less * than ``upper`` but not equal to either. * - * @param {number} actual - Test value. - * @param {number} lower - Number that ``actual`` must be greater than. - * @param {number} upper - Number that ``actual`` must be less than. + * @param {number|bigint} actual - Test value. + * @param {number|bigint} lower - Value that ``actual`` must be greater than. + * @param {number|bigint} upper - Value that ``actual`` must be less than. * @param {string} [description] - Description of the condition being tested. */ function assert_between_exclusive(actual, lower, upper, description) { /* - * Test if a primitive number is between two others + * Test if a primitive number (or bigint) is between two others */ - assert(typeof actual === "number", + assert(typeof lower === typeof upper, + "assert_between_exclusive", description, + "expected lower (${type_lower}) and upper (${type_upper}) types to match (test error)", + {type_lower:typeof lower, type_upper:typeof upper}); + + assert(typeof actual === "number" || typeof actual === "bigint", "assert_between_exclusive", description, "expected a number but got a ${type_actual}", {type_actual:typeof actual}); + assert(typeof actual === typeof lower, + "assert_between_exclusive", description, + "expected a ${type_lower} but got a ${type_actual}", + {type_lower:typeof lower, type_actual:typeof actual}); + assert(actual > lower && actual < upper, "assert_between_exclusive", description, "expected a number greater than ${lower} " + @@ -1846,21 +1868,26 @@ /** * Assert that ``actual`` is a number less than or equal to ``expected``. * - * @param {number} actual - Test value. - * @param {number} expected - Number that ``actual`` must be less + * @param {number|bigint} actual - Test value. + * @param {number|bigint} expected - Value that ``actual`` must be less * than or equal to. * @param {string} [description] - Description of the condition being tested. */ function assert_less_than_equal(actual, expected, description) { /* - * Test if a primitive number is less than or equal to another + * Test if a primitive number (or bigint) is less than or equal to another */ - assert(typeof actual === "number", + assert(typeof actual === "number" || typeof actual === "bigint", "assert_less_than_equal", description, "expected a number but got a ${type_actual}", {type_actual:typeof actual}); + assert(typeof actual === typeof expected, + "assert_less_than_equal", description, + "expected a ${type_expected} but got a ${type_actual}", + {type_expected:typeof expected, type_actual:typeof actual}); + assert(actual <= expected, "assert_less_than_equal", description, "expected a number less than or equal to ${expected} but got ${actual}", @@ -1871,21 +1898,26 @@ /** * Assert that ``actual`` is a number greater than or equal to ``expected``. * - * @param {number} actual - Test value. - * @param {number} expected - Number that ``actual`` must be greater + * @param {number|bigint} actual - Test value. + * @param {number|bigint} expected - Value that ``actual`` must be greater * than or equal to. * @param {string} [description] - Description of the condition being tested. */ function assert_greater_than_equal(actual, expected, description) { /* - * Test if a primitive number is greater than or equal to another + * Test if a primitive number (or bigint) is greater than or equal to another */ - assert(typeof actual === "number", + assert(typeof actual === "number" || typeof actual === "bigint", "assert_greater_than_equal", description, "expected a number but got a ${type_actual}", {type_actual:typeof actual}); + assert(typeof actual === typeof expected, + "assert_greater_than_equal", description, + "expected a ${type_expected} but got a ${type_actual}", + {type_expected:typeof expected, type_actual:typeof actual}); + assert(actual >= expected, "assert_greater_than_equal", description, "expected a number greater than or equal to ${expected} but got ${actual}", @@ -1897,21 +1929,31 @@ * Assert that ``actual`` is a number greater than or equal to ``lower`` and less * than or equal to ``upper``. * - * @param {number} actual - Test value. - * @param {number} lower - Number that ``actual`` must be greater than or equal to. - * @param {number} upper - Number that ``actual`` must be less than or equal to. + * @param {number|bigint} actual - Test value. + * @param {number|bigint} lower - Value that ``actual`` must be greater than or equal to. + * @param {number|bigint} upper - Value that ``actual`` must be less than or equal to. * @param {string} [description] - Description of the condition being tested. */ function assert_between_inclusive(actual, lower, upper, description) { /* - * Test if a primitive number is between to two others or equal to either of them + * Test if a primitive number (or bigint) is between to two others or equal to either of them */ - assert(typeof actual === "number", + assert(typeof lower === typeof upper, + "assert_between_inclusive", description, + "expected lower (${type_lower}) and upper (${type_upper}) types to match (test error)", + {type_lower:typeof lower, type_upper:typeof upper}); + + assert(typeof actual === "number" || typeof actual === "bigint", "assert_between_inclusive", description, "expected a number but got a ${type_actual}", {type_actual:typeof actual}); + assert(typeof actual === typeof lower, + "assert_between_inclusive", description, + "expected a ${type_lower} but got a ${type_actual}", + {type_lower:typeof lower, type_actual:typeof actual}); + assert(actual >= lower && actual <= upper, "assert_between_inclusive", description, "expected a number greater than or equal to ${lower} " + diff --git a/tests/wpt/tests/sanitizer-api/sanitizer-basic-filtering.tentative.html b/tests/wpt/tests/sanitizer-api/sanitizer-basic-filtering.tentative.html index 0b6924b0980..38c764ae181 100644 --- a/tests/wpt/tests/sanitizer-api/sanitizer-basic-filtering.tentative.html +++ b/tests/wpt/tests/sanitizer-api/sanitizer-basic-filtering.tentative.html @@ -273,6 +273,15 @@ for(const group of div.setHTMLUnsafe(testcase.data, config); assert_testcase(div, testcase); }, `setHTMLUnsafe testcase ${group.id}/${index}, "${testcase.data}"`); + + // parseHTML and parseHTMLUnsafe need to allow "html" and "body" for these + // tests to be useful. Update the config, if necessary. + if (config && config["sanitizer"] && config["sanitizer"]["elements"]) { + config["sanitizer"] = new Sanitizer(config["sanitizer"]); + config["sanitizer"].allowElement("body"); + config["sanitizer"].allowElement("html"); + } + test(_ => { assert_testcase( Document.parseHTML(testcase.data, config).body, testcase); diff --git a/tests/wpt/tests/sanitizer-api/sanitizer-parseHTML.tentative.html b/tests/wpt/tests/sanitizer-api/sanitizer-parseHTML.tentative.html new file mode 100644 index 00000000000..c4a31e2eb03 --- /dev/null +++ b/tests/wpt/tests/sanitizer-api/sanitizer-parseHTML.tentative.html @@ -0,0 +1,145 @@ +<!DOCTYPE html> +<head> +<title>Testcases for parseHTML and parseHTMLUnsafe</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="support/html5lib-testcase-support.js"></script> +<!-- + This is a set of basic Sanitizer test cases using the parseHTML and + parseHTMLUnsafe methods. +--> +<script id="all" type="html5lib-testcases"> +#data +text +#document +| <html> +| <head> +| <body> +| "text" + +#data +<div>text +#config +{ "elements": ["html", "body", "div"] } +#document +| <html> +| <body> +| <div> +| "text" + +#data +<div>text +#config +{ "elements": ["body", "div"] } +#document + +#data +<div>text +#config +{ "elements": ["html", "div"] } +#document +| <html> + +</script> +<script id="safe" type="html5lib-testcases"> +#data +<script>hello +#document +| <html> +| <head> +| <body> + +#data +<html onload="2+2"><body onload="3+3"><div>hello +#document +| <html> +| <head> +| <body> +| <div> +| "hello" + +</script> +<script id="unsafe" type="html5lib-testcases"> +#data +<script>hello +#document +| <html> +| <head> +| <script> +| "hello" +| <body> + +#data +<html onload="2+2"><body onload="3+3"><div>hello +#document +| <html> +| onload="2+2" +| <head> +| <body> +| onload="3+3" +| <div> +| "hello" + +</script> +<script id="document" type="html5lib-testcases"> +#data +<!DOCTYPE html> +text +#document +| <!DOCTYPE html "" ""> +| <html> +| <head> +| <body> +| "text" + +</script> +<script> +function test_safe(testcase, index) { + let config = undefined; + try { + config = { sanitizer: JSON.parse(testcase.config) }; + } catch { /* config remains undefined */ } + test(_ => { + assert_testcase(Document.parseHTML(testcase.data, config), testcase); + }, `parseHTML testcase ${index}, "${testcase.data}"`); +} +function test_unsafe(testcase, index) { + let config = undefined; + try { + config = { sanitizer: JSON.parse(testcase.config) }; + } catch { /* config remains undefined */ } + test(_ => { + assert_testcase(Document.parseHTMLUnsafe(testcase.data, config), testcase); + }, `parseHTMLUnsafe testcase ${index}, "${testcase.data}"`); +} + +const all = parse_html5lib_testcases( + document.getElementById("all").textContent); +const safe = parse_html5lib_testcases( + document.getElementById("safe").textContent); +const unsafe = parse_html5lib_testcases( + document.getElementById("unsafe").textContent); +all.forEach(test_safe); +all.forEach(test_unsafe); +safe.forEach(test_safe); +unsafe.forEach(test_unsafe); + + +// DOM only supports Document Type Declarations as children of documents. This +// trips up the assert_testcase implementation, so we'll handle that seperately. +parse_html5lib_testcases( + document.getElementById("document").textContent). + forEach((testcase, index) => { + test(_ => { + const tree = build_node_tree(new Document(), testcase.document); + assert_subtree_equals(Document.parseHTMLUnsafe(testcase.data, {}), tree); + }, `parseHTMLUnsafe full document testcase ${index}, "${testcase.data}"`); + test(_ => { + const tree = build_node_tree(new Document(), testcase.document); + assert_subtree_equals(Document.parseHTML(testcase.data, {}), tree); + }, `parseHTML full document testcase ${index}, "${testcase.data}"`); +}); +</script> +</head> +<body> +</body> diff --git a/tests/wpt/tests/soft-navigation-heuristics/innertext.tentative.html b/tests/wpt/tests/soft-navigation-heuristics/innertext.tentative.html index d40b6047924..7716488f25a 100644 --- a/tests/wpt/tests/soft-navigation-heuristics/innertext.tentative.html +++ b/tests/wpt/tests/soft-navigation-heuristics/innertext.tentative.html @@ -20,13 +20,18 @@ const link = document.getElementById("link"); testSoftNavigation({ addContent: async () => { - document.getElementById("softnav-content").innerText = - "Lorem Ipsum dolor sit amet"; + document.getElementById("softnav-content").innerText = + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, ' + + 'sed do eiusmod tempor incididunt ut labore et dolore magna ' + + 'aliqua. Ut enim ad minim veniam, quis nostrud exercitation ' + + 'ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis ' + + 'aute irure dolor in reprehenderit in voluptate velit esse ' + + 'cillum dolore eu fugiat nulla pariatur. Excepteur sint ' + + 'occaecat cupidatat non proident, sunt in culpa qui officia ' + + 'deserunt mollit anim id est laborum.'; }, link: link, test: "Soft navigation when only innerText was modified"}); </script> </body> </html> - - diff --git a/tests/wpt/tests/soft-navigation-heuristics/interaction-with-paint-before-back.tentative.html b/tests/wpt/tests/soft-navigation-heuristics/interaction-with-paint-before-back.tentative.html index 7b884f2bdb3..5961a6ebcdf 100644 --- a/tests/wpt/tests/soft-navigation-heuristics/interaction-with-paint-before-back.tentative.html +++ b/tests/wpt/tests/soft-navigation-heuristics/interaction-with-paint-before-back.tentative.html @@ -1,73 +1,93 @@ -<!DOCTYPE HTML> +<!doctype html> <html> -<head> -<meta charset="utf-8"> -<script src="/resources/testharness.js"></script> -<script src="/resources/testharnessreport.js"></script> -<script src="/resources/testdriver.js"></script> -<script src="/resources/testdriver-vendor.js"></script> -<script src="resources/soft-navigation-helper.js"></script> -</head> -<body> - <main id=main> - <div> - <a id=link><img src="/images/lcp-256x256.png" id="img"></a> - <a id=not_nav><img src="/images/lcp-16x16.png"></a> + <head> + <meta charset="utf-8" /> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + </head> + <body> + <div id="main"> + <div id="almost_soft_nav_div"> + A click will almost initiate a soft nav, by (1) mutating the DOM by adding an image; (2) + causing the image to be painted. + </div> + <div id="soft_nav_div"> + A click will initiate a soft nav, by (1) mutating the DOM by adding an image; (2) causing + the image to be painted; (3) updating the URL via history.back(), but without causing a hard + navigation. + </div> </div> - </main> - <script> - // Push state a couple of times - history.pushState({}, "", "foobar.html"); - history.pushState({}, "", "anotherOne.html"); + <script> + promise_test( + async (t) => { + // Setup a click handler for 'almost_soft_nav_div', which adds a + // specific, identifiable image 'almost_soft_nav_img' to the document. + document.getElementById("almost_soft_nav_div").addEventListener("click", () => { + const img = new Image(); + img.src = "/images/lcp-133x106.png"; + img.elementTiming = "almost_soft_nav_img"; + document.getElementById("main").appendChild(img); + }); - (async () => { - const link = document.getElementById("link"); - // Trigger a user interaction that doesn't result in a soft navigation, but - // does paint. - await (async () => { - const not_nav = document.getElementById("not_nav"); - let non_soft_nav_click; - const non_soft_nav_click_promise = - new Promise(r => { non_soft_nav_click = r; }); - not_nav.addEventListener("click", () => { - addImageToMain("lcp-133x106.png", "not_soft_nav_image"); - (new PerformanceObserver(non_soft_nav_click)).observe({type: "element"}); - }); - if (test_driver) { - test_driver.click(not_nav); - } - await non_soft_nav_click_promise; - })(); - const url = URL + "?" + counter; - link.addEventListener("click", () => { - // Add an LCP element. - const img = new Image(); - img.src = '/images/lcp-100x500.png' + "?" + Math.random(); - document.getElementById("main").appendChild(img); - history.back(); - }); - promise_test(async t => { - const soft_nav_promise = waitOnSoftNav(); - if (test_driver) { - test_driver.click(link); - } - await soft_nav_promise; - assert_equals( - document.softNavigations, 1, - 'Single Soft Navigation detected'); - const [entries, options] = await new Promise(resolve => { - (new PerformanceObserver((list, obs, options) => resolve( - [list.getEntries(), options]))).observe( - {type: 'soft-navigation', buffered: true}); + // Also set up a click handler for 'soft_nav_div', for adding + // another image, but in addition to what we do for the almost soft nav + // above, also let the handler change the URL, by invoking + // history.back(). We prepare with pushState for the history.back() + // invocation, to avoid triggering a hard navigation. + const test_origin = new URL(location.href).origin; + history.pushState({}, "", "/foo.html"); // We will observe this below. + history.pushState({}, "", "/bar.html"); // Prep for history.back(). + document.getElementById("soft_nav_div").addEventListener("click", () => { + const img = new Image(); + img.src = "/images/lcp-133x106.png"; + document.getElementById("main").appendChild(img); + history.back(); // URL change triggering the soft nav }); - assert_equals(entries.length, 1, - "Performance observer got an entry"); - }, "Ensure that soft navigation entry emitted through a synchronous " + - "event that modified DOM and committed a same document navigation, " + - "and that was preceded by a user intreaction that resulted in a " + - "contentful paint is properly detected."); - })(); - </script> -</body> + // Now, click almost_soft_nav_div, and observe that time image + // was rendered but no soft nav was triggered. + { + const element_timing_promise = new Promise((resolve) => { + new PerformanceObserver(resolve).observe({ type: "element", buffered: true }); + }); + if (test_driver) { + test_driver.click(almost_soft_nav_div); + } + // Returns entries of type PerformanceElementTiming, see + // https://developer.mozilla.org/en-US/docs/Web/API/PerformanceElementTiming. + const entries = (await element_timing_promise).getEntries(); + assert_equals(entries.length, 1); + assert_equals( + entries[0].identifier, + "almost_soft_nav_img", + "Image based on the user interaction was painted.", + ); + assert_equals(document.softNavigations, 0, "No soft navigation detected."); + } + + // Now, click soft_nav_div, and observe the detected soft navigation. + { + const soft_nav_promise = new Promise((resolve) => { + new PerformanceObserver(resolve).observe({ type: "soft-navigation", buffered: true }); + }); + if (test_driver) { + test_driver.click(soft_nav_div); + } + // Returns entries of type SoftNavigationEntry, see + // https://github.com/WICG/soft-navigations/ + const entries = (await soft_nav_promise).getEntries(); + assert_equals(document.softNavigations, 1, "Single soft navigation detected"); + assert_equals(entries.length, 1, "Performance observer got an entry"); + assert_equals(entries[0].name, test_origin + "/foo.html"); + } + }, + "Ensure that soft navigation entry emitted through a synchronous " + + "event that modified DOM and committed a same document navigation, " + + "and that was preceded by a user interaction that resulted in a " + + "contentful paint is properly detected.", + ); + </script> + </body> </html> diff --git a/tests/wpt/tests/soft-navigation-heuristics/smoke/tentative/basic.html b/tests/wpt/tests/soft-navigation-heuristics/smoke/tentative/basic.html new file mode 100644 index 00000000000..9bfedf09b19 --- /dev/null +++ b/tests/wpt/tests/soft-navigation-heuristics/smoke/tentative/basic.html @@ -0,0 +1,67 @@ +<!doctype html> +<html> + <head> + <meta charset="utf-8" /> + <title>Soft Navigation Detection: The Basics.</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script> + // The click handler is triggered by user interaction; it modifies + // the DOM, causing a paint, and also changes the URL. + // This constitutes a soft navigation. + function clickHandler() { + const greeting = document.createElement("div"); + greeting.textContent = "Hello, World."; + document.body.appendChild(greeting); + history.pushState({}, "", "/greeting.html"); + } + </script> + </head> + <body> + <div id="click-target" onclick="clickHandler()">Click here!</div> + + <script> + test(() => { + const observer = new PerformanceObserver(() => {}); + observer.observe({ type: "soft-navigation", buffered: true }); + const records = observer.takeRecords(); + observer.disconnect(); + assert_equals(records.length, 0, "Expecting empty list."); + }, "No soft navigation detection without user interaction."); + + promise_test(async (t) => { + const test_origin = new URL(location.href).origin; + + let entries; + new PerformanceObserver((list, observer) => { + entries = list.getEntries(); + observer.disconnect(); + }).observe({ type: "soft-navigation" }); + + // Initiate the user interaction to trigger the soft navigation. + if (test_driver) { + test_driver.click(document.getElementById("click-target")); + } + + await t.step_wait( + () => entries !== undefined, + "Waiting for entries from PerformanceObserver.", + ); + + // Now check there's one entry, and it's fields. + // The SoftNavigationEntry instance is spec'd in + // https://github.com/WICG/soft-navigations/ + assert_equals(entries.length, 1, "Expecting one soft navigation entry."); + + const expected_url = new URL("/greeting.html", test_origin); + assert_equals( + entries[0].name, + expected_url.toString(), + "Soft navigation should record destination URL as its name.", + ); + }, "Detect soft navigation after a click."); + </script> + </body> +</html> diff --git a/tests/wpt/tests/soft-navigation-heuristics/text-lcp-followed-by-anim-image-softnav-lcp.tentative.html b/tests/wpt/tests/soft-navigation-heuristics/text-lcp-followed-by-anim-image-softnav-lcp.tentative.html index 0615b513e61..b34a6e81a58 100644 --- a/tests/wpt/tests/soft-navigation-heuristics/text-lcp-followed-by-anim-image-softnav-lcp.tentative.html +++ b/tests/wpt/tests/soft-navigation-heuristics/text-lcp-followed-by-anim-image-softnav-lcp.tentative.html @@ -21,12 +21,15 @@ addContent: async () => { const main = document.getElementById("main"); main.removeChild(document.getElementsByTagName("div")[0]); - await addImageToMain("anim-gr.png"); + const img = new Image(500, 500); + img.src = "/images/anim-gr.png?" + Math.random(); + img.id = "imagelcp"; + img.setAttribute("elementtiming", "imagelcp"); + main.appendChild(img); }, link: link, - test: "Test that a text LCP followup by a smaller soft navigation image" - + " LCP properly queues an LCP entry"}); + test: "Test that a text LCP followup by an animaged image " + + " properly queues a soft navigation entry"}); </script> </body> </html> - diff --git a/tests/wpt/tests/soft-navigation-heuristics/visited-link.tentative.html b/tests/wpt/tests/soft-navigation-heuristics/visited-link.tentative.html index 0bb149f00e9..0bb31aaac15 100644 --- a/tests/wpt/tests/soft-navigation-heuristics/visited-link.tentative.html +++ b/tests/wpt/tests/soft-navigation-heuristics/visited-link.tentative.html @@ -24,7 +24,15 @@ })); const main = document.getElementById("main"); const div = document.createElement("div"); - const text = document.createTextNode("Lorem Ipsum"); + const text = document.createTextNode( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, ' + + 'sed do eiusmod tempor incididunt ut labore et dolore magna ' + + 'aliqua. Ut enim ad minim veniam, quis nostrud exercitation ' + + 'ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis ' + + 'aute irure dolor in reprehenderit in voluptate velit esse ' + + 'cillum dolore eu fugiat nulla pariatur. Excepteur sint ' + + 'occaecat cupidatat non proident, sunt in culpa qui officia ' + + 'deserunt mollit anim id est laborum.'); div.appendChild(text); main.appendChild(div); } diff --git a/tests/wpt/tests/speculation-rules/prefetch/resources/basic-service-worker.js b/tests/wpt/tests/speculation-rules/prefetch/resources/basic-service-worker.js index 17fccd448d2..59d7d8bae9e 100644 --- a/tests/wpt/tests/speculation-rules/prefetch/resources/basic-service-worker.js +++ b/tests/wpt/tests/speculation-rules/prefetch/resources/basic-service-worker.js @@ -32,6 +32,13 @@ if (swOption !== 'no-fetch-handler') { if (swOption === 'fetch-handler') { event.respondWith(fetch(event.request)); + } else if (swOption === 'fetch-handler-synthetic') { + const finalUrl = new URL(event.request.url).searchParams.get('location'); + if (finalUrl) { + event.respondWith(Response.redirect(finalUrl)); + } else { + // Fallback to the network. + } } else if (swOption === 'fetch-handler-modify-url') { // The "Sec-Purpose: prefetch" header is dropped in fetch-handler-modify-* // cases in Step 33 of // https://fetch.spec.whatwg.org/#dom-request diff --git a/tests/wpt/tests/speculation-rules/prefetch/resources/counting-executor.py b/tests/wpt/tests/speculation-rules/prefetch/resources/counting-executor.py index cbcbc8eccb9..3511fe4905e 100644 --- a/tests/wpt/tests/speculation-rules/prefetch/resources/counting-executor.py +++ b/tests/wpt/tests/speculation-rules/prefetch/resources/counting-executor.py @@ -21,6 +21,11 @@ def main(request, response): request_count["prefetch" if prefetch else "nonPrefetch"] += 1 request.server.stash.put(uuid, request_count) + if b"location" in request.GET: + response.status = 302 + response.headers.set(b"Location", request.GET[b"location"]) + return + response.content = template( request, open(os.path.join(os.path.dirname(__file__), "executor.sub.html"), "rb").read()) diff --git a/tests/wpt/tests/speculation-rules/prefetch/resources/executor.sub.html b/tests/wpt/tests/speculation-rules/prefetch/resources/executor.sub.html index d27acfe100a..975a3e5092e 100644 --- a/tests/wpt/tests/speculation-rules/prefetch/resources/executor.sub.html +++ b/tests/wpt/tests/speculation-rules/prefetch/resources/executor.sub.html @@ -1,5 +1,8 @@ <!DOCTYPE html> <script src="/common/dispatcher/dispatcher.js" nonce="abc"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="/resources/testdriver-actions.js"></script> <script src="utils.sub.js" nonce="abc"></script> <script nonce="abc"> // For a given string `str` that is escaped by WPT's `.sub.html` or @@ -45,6 +48,39 @@ Object.keys(requestHeaders).forEach(key => { } }); +// Add a link to the page in order to use during the test +function add_link(id, url) { + const link_element = document.createElement("a"); + const link_text = document.createTextNode(url); + link_element.setAttribute("href", url); + link_element.setAttribute("id", id); + link_element.appendChild(link_text); + document.body.appendChild(link_element); +} + +// "id" is the id of the link that we need to hover on in order +// to start the prefetch +async function start_non_eager_prefetch_on_hover(id) { + let target = document.getElementById(id); + + test_driver.set_test_context(window.opener); + // Inject the inputs to run this test. + await new test_driver.Actions().addPointer("mouse").pointerMove(0, 0, {origin: target}).send(); +} + +// "id" is the id of the link that we need to press on in order +// to start the prefetch +async function start_non_eager_prefetch_on_pointerdown(id) { + let target = document.getElementById(id); + + test_driver.set_test_context(window.opener); + // Inject the inputs to run this test. + // Move mouse pointer outside of the anchor so that we don't start the + // navigation before making sure the prefetch request started server-side. + await new test_driver.Actions().addPointer("mouse").pointerMove(0, 0, {origin: target}).pointerDown().pointerMove(0, 0).pointerUp().send(); +} + + // The fetch request's URL sent to the server. window.requestUrl = reverse_html_escape( "{{location[server]}}{{location[path]}}{{location[query]}}"); diff --git a/tests/wpt/tests/speculation-rules/prefetch/tentative/service-worker/redirect.sub.https.html b/tests/wpt/tests/speculation-rules/prefetch/tentative/service-worker/redirect.sub.https.html new file mode 100644 index 00000000000..f56f6bf09b9 --- /dev/null +++ b/tests/wpt/tests/speculation-rules/prefetch/tentative/service-worker/redirect.sub.https.html @@ -0,0 +1,122 @@ +<!DOCTYPE html> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="../../../resources/utils.js"></script> +<script src="../../resources/utils.sub.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> + +<meta name="variant" content="?origin=same-site&sc=in-in&sw=fetch-handler"> +<meta name="variant" content="?origin=same-site&sc=in-in&sw=fetch-handler-to-fallback"> +<meta name="variant" content="?origin=same-site&sc=in-in&sw=fetch-handler-synthetic"> +<meta name="variant" content="?origin=same-site&sc=in-in&sw=no-fetch-handler"> + +<meta name="variant" content="?origin=same-site&sc=in-out&sw=fetch-handler"> +<meta name="variant" content="?origin=same-site&sc=in-out&sw=fetch-handler-to-fallback"> +<meta name="variant" content="?origin=same-site&sc=in-out&sw=fetch-handler-synthetic"> +<meta name="variant" content="?origin=same-site&sc=in-out&sw=no-fetch-handler"> + +<meta name="variant" content="?origin=same-site&sc=out-in&sw=fetch-handler"> +<meta name="variant" content="?origin=same-site&sc=out-in&sw=fetch-handler-to-fallback"> +<meta name="variant" content="?origin=same-site&sc=out-in&sw=no-fetch-handler"> + +<script> +setup(() => assertSpeculationRulesIsSupported()); + +const originOption = new URL(location.href).searchParams.get('origin'); +const swOption = new URL(location.href).searchParams.get('sw'); + +// We use a short name `sc` to avoid long file names https://crbug.com/40469687 +const scopeOption = new URL(location.href).searchParams.get('sc'); + +promise_test(async t => { + const hostname = originOption === 'cross-site' ? '{{hosts[alt][www]}}' + : undefined; + const win = await spawnWindow(t, { protocol: 'https', hostname: hostname }); + + const finalUrl = win.getExecutorURL({ executor: 'counting-executor.py', protocol: 'https', page: 2 }); + const initialUrl = new URL('../../resources/counting-executor.py', location.href); + // Assign a different UUID to track the number of requests to server + // separately for the initialUrl and finalUrl. + initialUrl.searchParams.set('uuid', token()); + initialUrl.searchParams.set('location', finalUrl); + + const swUrl = new URL('../../resources/basic-service-worker.js?sw=' + swOption, location.href).href; + + let sw1; + if (scopeOption === 'in-in' || scopeOption === 'in-out') { + // Register a SW for `initialUrl`. + const reg = await service_worker_unregister_and_register( + t, swUrl + '&sw1', initialUrl); + sw1 = reg.installing; + await wait_for_state(t, reg.installing, 'activated'); + } + + let sw2; + if (scopeOption === 'in-in' || scopeOption === 'out-in') { + // Register a SW for `finalUrl`. + const reg = await service_worker_unregister_and_register( + t, swUrl + '&sw2', finalUrl); + sw2 = reg.installing; + await wait_for_state(t, reg.installing, 'activated'); + } + + // Start speculation rules prefetch and navigate to the URL. + await win.forceSinglePrefetch(initialUrl); + + await win.navigate(initialUrl, {expectedDestinationUrl: finalUrl}); + + const initialRequestCount = await (await fetch(initialUrl + '&check')).json(); + const finalRequestCount = await (await fetch(finalUrl + '&check')).json(); + + const headers = await win.execute_script(() => { + return requestHeaders; + }, []); + + const controllerUrl = await win.execute_script(() => { + return navigator.serviceWorker.controller ? + navigator.serviceWorker.controller.scriptURL : + undefined; + }, []); + + if (sw2) { + // The navigated page should be controlled by the ServiceWorker, + // regardless of whether prefetch is performed/used. + assert_equals(controllerUrl, swUrl + '&sw2'); + } else { + assert_equals(controllerUrl, undefined); + } + + // In any cases prefetch + redirects + ServiceWorker fails and prefetched + // results aren't served. + assert_not_prefetched(headers, "Prefetched result should not be served."); + + if (swOption === 'fetch-handler-synthetic') { + // Synthetic redirect response from ServiceWorker prevents the initial + // prefetch/non-prefetch requests to the server. + assert_equals(initialRequestCount.prefetch, 0, + 'prefetch requests to initial URL should not be sent to the server.'); + assert_equals(initialRequestCount.nonPrefetch, 0, + 'a non-prefetch requests to initial URL should not be sent to the server.'); + } else { + // Otherwise, the initial prefetch request (either with or without + // ServiceWorker interception) anyway reaches the server, and then + // re-requested as a non-prefetch request on navigation. + assert_equals(initialRequestCount.prefetch, 1, + 'prefetch requests to initial URL should be sent to the server.'); + assert_equals(initialRequestCount.nonPrefetch, 1, + 'a non-prefetch requests to initial URL should be sent to the server.'); + } + + // Prefetch requests to the URL after redirects shouldn't be sent to server + // because prefetch should fail immediately on receiving redirects. + assert_equals(finalRequestCount.prefetch, 0, + 'prefetch requests should not be sent to the server.'); + // Instead, a single non-prefetch request should be sent. + assert_equals(finalRequestCount.nonPrefetch, 1, + 'a non-prefetch request should be sent to the server.'); + +}, "Prefetch with ServiceWorker (" + swOption + ")"); +</script> diff --git a/tests/wpt/tests/speculation-rules/speculation-tags/prefetch-eagerness-pointer-down.https.html b/tests/wpt/tests/speculation-rules/speculation-tags/prefetch-eagerness-pointer-down.https.html new file mode 100644 index 00000000000..cbf69cefba6 --- /dev/null +++ b/tests/wpt/tests/speculation-rules/speculation-tags/prefetch-eagerness-pointer-down.https.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<title>Sec-Speculation-Tags request headers</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/utils.js"></script> +<script src="/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js"></script> +<script src="/speculation-rules/prerender/resources/utils.js"></script> +<script src="/speculation-rules/resources/utils.js"></script> +<script src="/speculation-rules/prefetch/resources/utils.sub.js"></script> +<script> +"use strict"; + +setup(() => assertSpeculationRulesIsSupported()); + +promise_test(async t => { + const agent = await spawnWindow(t); + const nextUrl = agent.getExecutorURL({ page: 2 }); + await agent.forceSpeculationRules({ + prefetch: [ + {source: "list", "tag": "conservative", + "eagerness": "conservative", urls: [nextUrl]}, + {source: "list", "tag": "moderate", + "eagerness": "moderate", urls: [nextUrl]} + ] + }); + + const linkId = 'target'; + await agent.execute_script(async (linkId, nextUrl) => { + add_link(linkId, nextUrl); + }, [linkId, nextUrl]); + + await agent.execute_script(async (linkId) => { + await start_non_eager_prefetch_on_pointerdown(linkId); + }, [linkId]); + await agent.navigate(nextUrl); + + const headers = await agent.getRequestHeaders(); + assert_prefetched(headers, "must be prefetched"); + assert_equals(headers.sec_speculation_tags, "\"conservative\", \"moderate\"", "Sec-Speculation-Tags"); +}, "Sec-Speculation-Tags prefetch with eagerness triggered by point down"); + +</script> diff --git a/tests/wpt/tests/speculation-rules/speculation-tags/prefetch-eagerness-pointer-hover.https.html b/tests/wpt/tests/speculation-rules/speculation-tags/prefetch-eagerness-pointer-hover.https.html new file mode 100644 index 00000000000..f5e749b9c6f --- /dev/null +++ b/tests/wpt/tests/speculation-rules/speculation-tags/prefetch-eagerness-pointer-hover.https.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<title>Sec-Speculation-Tags request headers</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/utils.js"></script> +<script src="/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js"></script> +<script src="/speculation-rules/prerender/resources/utils.js"></script> +<script src="/speculation-rules/resources/utils.js"></script> +<script src="/speculation-rules/prefetch/resources/utils.sub.js"></script> +<script> +"use strict"; + +setup(() => assertSpeculationRulesIsSupported()); + +promise_test(async t => { + const agent = await spawnWindow(t); + const nextUrl = agent.getExecutorURL({ page: 2 }); + await agent.forceSpeculationRules({ + prefetch: [ + {source: "list", "tag": "conservative", + "eagerness": "conservative", urls: [nextUrl]}, + {source: "list", "tag": "moderate", + "eagerness": "moderate", urls: [nextUrl]} + ] + }); + + const linkId = 'target'; + await agent.execute_script(async (linkId, nextUrl) => { + add_link(linkId, nextUrl); + }, [linkId, nextUrl]); + + await agent.execute_script(async (linkId) => { + await start_non_eager_prefetch_on_hover(linkId); + }, [linkId]); + + // TODO(crbug.com/381687257): Remove this when 0ms hover trigger is supported. + // Wait for longer than 200 ms for the onhover trigger to fire. + await new Promise(resolve => t.step_timeout(resolve, 500)); + await agent.navigate(nextUrl); + + const headers = await agent.getRequestHeaders(); + assert_prefetched(headers, "must be prefetched"); + assert_equals(headers.sec_speculation_tags, "\"moderate\"", "Sec-Speculation-Tags"); +}, "Sec-Speculation-Tags prefetch with eagerness triggered by point hover"); + +</script> diff --git a/tests/wpt/tests/speech-api/SpeechRecognition-installOnDevice.https.html b/tests/wpt/tests/speech-api/SpeechRecognition-installOnDevice.https.html index 1d1dd35edc2..2f5b359c571 100644 --- a/tests/wpt/tests/speech-api/SpeechRecognition-installOnDevice.https.html +++ b/tests/wpt/tests/speech-api/SpeechRecognition-installOnDevice.https.html @@ -2,17 +2,39 @@ <title>SpeechRecognition installOnDevice</title> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> <script> promise_test(async (t) => { const validLang = "en-US"; const invalidLang = "invalid language code"; - window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; + const validDownloadableLang = "fr-FR"; + window.SpeechRecognition = + window.SpeechRecognition || window.webkitSpeechRecognition; - // Test that it returns a promise. - const validResultPromise = SpeechRecognition.installOnDevice(validLang); + // Attempt to call installOnDevice directly, without a user gesture with a + // language that is downloadable but not installed. + const installWithoutUserGesturePromise = + SpeechRecognition.installOnDevice(validDownloadableLang); + + // Assert that the promise rejects with NotAllowedError. + await promise_rejects_dom( + t, + "NotAllowedError", + window.DOMException, + installWithoutUserGesturePromise, + "SpeechRecognition.installOnDevice() must reject with NotAllowedError if " + + "called without a user gesture." + ); + + // Test that it returns a promise when called with a valid language. + const validResultPromise = test_driver.bless( + "Call SpeechRecognition.installOnDevice with a valid language", + () => SpeechRecognition.installOnDevice(validLang) + ); assert_true( validResultPromise instanceof Promise, - "installOnDevice should return a Promise." + "installOnDevice (with gesture) should return a Promise." ); // Verify the resolved value is a boolean. @@ -22,15 +44,17 @@ promise_test(async (t) => { "The resolved value of the installOnDevice promise should be a boolean." ); - // Verify that the method returns true when called with a supported language code. + // Verify that the method returns true when called with a supported language. assert_equals( validResult, true, - "installOnDevice should resolve with `true` when called with a supported language code." + "installOnDevice should resolve with `true` when called with a " + + "supported language code." ); // Verify that the newly installed language pack is available. - const availableOnDeviceResultPromise = SpeechRecognition.availableOnDevice(validLang); + const availableOnDeviceResultPromise = + SpeechRecognition.availableOnDevice(validLang); assert_true( availableOnDeviceResultPromise instanceof Promise, "availableOnDevice should return a Promise." @@ -42,28 +66,58 @@ promise_test(async (t) => { "The resolved value of the availableOnDevice promise should be a string." ); - assert_true(availableOnDeviceResult === "available", - "The resolved value of the availableOnDevice promise should be available." + assert_true( + availableOnDeviceResult === "available", + "The resolved value of the availableOnDevice promise should be 'available'." ); - // Verify that installing an already installed language pack resolves to true. - const secondResultPromise = SpeechRecognition.installOnDevice(validLang); + // Verify that installing an already installed language resolves to true. + const secondResultPromise = test_driver.bless( + "Call SpeechRecognition.installOnDevice for an already installed language", + () => SpeechRecognition.installOnDevice(validLang) + ); + assert_true( + secondResultPromise instanceof Promise, + "installOnDevice (with gesture, for already installed language) should " + + "return a Promise." + ); const secondResult = await secondResultPromise; + assert_true( + typeof secondResult === "boolean", + "The resolved value of the second installOnDevice promise should be a " + + "boolean." + ); assert_equals( secondResult, true, - "installOnDevice should resolve with `true` if the language is already installed." + "installOnDevice should resolve with `true` if the language is already " + + "installed." ); - // Test that it returns a promise. - const invalidResultPromise = SpeechRecognition.installOnDevice(invalidLang); + // Test that it returns a promise and resolves to false for unsupported lang. + const invalidResultPromise = test_driver.bless( + "Call SpeechRecognition.installOnDevice with an unsupported language code", + () => SpeechRecognition.installOnDevice(invalidLang) + ); + assert_true( + invalidResultPromise instanceof Promise, + "installOnDevice (with gesture, for unsupported language) should return " + + "a Promise." + ); const invalidResult = await invalidResultPromise; + assert_true( + typeof invalidResult === "boolean", + "The resolved value of the installOnDevice promise (unsupported language) " + + "should be a boolean." + ); assert_equals( invalidResult, false, - "installOnDevice should resolve with `false` when called with an unsupported language code." + "installOnDevice should resolve with `false` when called with an " + + "unsupported language code." ); -}, "SpeechRecognition.installOnDevice resolves with a boolean value."); +}, "SpeechRecognition.installOnDevice resolves with a boolean value " + + "(with user gesture)."); promise_test(async (t) => { const iframe = document.createElement("iframe"); @@ -78,7 +132,13 @@ promise_test(async (t) => { t, "InvalidStateError", frameDOMException, - frameSpeechRecognition.installOnDevice("en-US"), + test_driver.bless( + "Call SpeechRecognition.installOnDevice in a detached frame context", + () => { + return frameSpeechRecognition.installOnDevice("en-US"); + } + ) ); -}, "SpeechRecognition.installOnDevice rejects in a detached context."); +}, "SpeechRecognition.installOnDevice rejects in a detached context " + + "(with user gesture)."); </script> diff --git a/tests/wpt/tests/streams/readable-streams/crashtests/garbage-collection.any.js b/tests/wpt/tests/streams/readable-streams/crashtests/garbage-collection.any.js index cf10f7f2e83..6e9d80c4142 100644 --- a/tests/wpt/tests/streams/readable-streams/crashtests/garbage-collection.any.js +++ b/tests/wpt/tests/streams/readable-streams/crashtests/garbage-collection.any.js @@ -14,35 +14,6 @@ promise_test(async () => { await garbageCollect(); }, 'Garbage-collecting a stream along with its reader should not crash'); - -// See https://crbug.com/390646657 for details. -promise_test(async () => { - const written = new WritableStream({ - write(chunk) { - return new Promise(resolve => {}); - } - }).getWriter().write('just nod if you can hear me'); - for (let i = 0; i < 5; ++i) - await garbageCollect(); -}, 'Garbage-collecting a stream writer with a pending write should not crash'); - - -promise_test(async () => { - const closed = new WritableStream({ - write(chunk) { } - }).getWriter().closed; - for (let i = 0; i < 5; ++i) - await garbageCollect(); -}, 'Garbage-collecting a stream writer should not crash with closed promise is retained'); - -promise_test(async () => { - const ready = new WritableStream({ - write(chunk) { } - }, {highWaterMark: 0}).getWriter().ready; - for (let i = 0; i < 5; ++i) - await garbageCollect(); -}, 'Garbage-collecting a stream writer should not crash when backpressure is being applied'); - promise_test(async () => { let reader = new ReadableStream({ pull() { } diff --git a/tests/wpt/tests/streams/writable-streams/crashtests/garbage-collection.any.js b/tests/wpt/tests/streams/writable-streams/crashtests/garbage-collection.any.js new file mode 100644 index 00000000000..a3796881c9f --- /dev/null +++ b/tests/wpt/tests/streams/writable-streams/crashtests/garbage-collection.any.js @@ -0,0 +1,43 @@ +// META: global=window,worker +// META: script=/common/gc.js +'use strict'; + +// See https://crbug.com/390646657 for details. +promise_test(async () => { + const written = new WritableStream({ + write(chunk) { + return new Promise(resolve => {}); + } + }).getWriter().write('just nod if you can hear me'); + for (let i = 0; i < 5; ++i) + await garbageCollect(); +}, 'Garbage-collecting a stream writer with a pending write should not crash'); + +promise_test(async () => { + const closed = new WritableStream({ + write(chunk) { } + }).getWriter().closed; + for (let i = 0; i < 5; ++i) + await garbageCollect(); +}, 'Garbage-collecting a stream writer should not crash with closed promise is retained'); + +promise_test(async () => { + let writer = new WritableStream({ + write(chunk) { return new Promise(resolve => {}); }, + close() { return new Promise(resolve => {}); } + }).getWriter(); + writer.write('is there anyone home?'); + writer.close(); + writer = null; + for (let i = 0; i < 5; ++i) + await garbageCollect(); +}, 'Garbage-collecting a stream writer should not crash with close promise pending'); + +promise_test(async () => { + const ready = new WritableStream({ + write(chunk) { } + }, {highWaterMark: 0}).getWriter().ready; + for (let i = 0; i < 5; ++i) + await garbageCollect(); +}, 'Garbage-collecting a stream writer should not crash when backpressure is being applied'); + diff --git a/tests/wpt/tests/subresource-integrity/tentative/integrity-policy/parsing.https.html b/tests/wpt/tests/subresource-integrity/tentative/integrity-policy/parsing.https.html new file mode 100644 index 00000000000..205854419a7 --- /dev/null +++ b/tests/wpt/tests/subresource-integrity/tentative/integrity-policy/parsing.https.html @@ -0,0 +1,119 @@ +<!doctype html> +<head> + <meta name="timeout" content="long"> + <meta name="variant" content="?type=enforce"> + <meta name="variant" content="?type=report"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/common/dispatcher/dispatcher.js"></script> + <script src="/common/utils.js"></script> + <script src="/reporting/resources/report-helper.js"></script> +</head> +<body> +<script> + + const run_test = (test_case) => { + promise_test(async () => { + const REMOTE_EXECUTOR = + `/common/dispatcher/remote-executor.html?pipe=`; + + let header_name = "Integrity-Policy"; + const params = new URLSearchParams(location.search); + if (params.get('type') === "report") { + if (test_case.expected.blocked) { + return; + } + header_name += "-Report-Only"; + } + const iframe_uuid = token(); + const header = + `header(${header_name},${test_case.header_value})`; + const iframe_url = + `${REMOTE_EXECUTOR}${encodeURIComponent(header)}&uuid=${iframe_uuid}`; + + const iframe = document.createElement('iframe'); + iframe.src = iframe_url; + document.body.appendChild(iframe); + + // Execute code directly from the iframe. + const ctx = new RemoteContext(iframe_uuid); + const result = await ctx.execute_script(async (test_case) => { + const resource_url = "/content-security-policy/resources/ran.js"; + let report_observed_promise; + + // Load a script with no integrity. If there's a policy in place, it + // would be blocked. + const loaded = await new Promise(resolve => { + const script = document.createElement('script'); + script.onload = () => { resolve(true); }; + script.onerror = () => { resolve(false); }; + script.src = resource_url; + document.body.appendChild(script); + }); + return { blocked: !loaded, ran: window.ran }; + }, [test_case]); + assert_equals(!result.blocked, !!result.ran); + assert_equals(result.blocked, test_case.expected.blocked); + }, test_case.description); + }; + + const test_cases = [ + { + description: "Ensure that test is working with a valid destination", + header_value: "blocked-destinations=\\(script\\)", + expected: {blocked: true}, + }, + { + description: "Ensure that test is working with a valid destination and source", + header_value: "blocked-destinations=\\(script\\)\\, sources=\\(inline\\)", + expected: {blocked: true}, + }, + { + description: "Ensure that an empty header does not block", + header_value: "", + expected: {blocked: false}, + }, + { + description: "Ensure that a destination header with a token value does not parse", + header_value: "blocked-destinations=script", + expected: {blocked: false}, + }, + { + description: "Ensure that a destination header with an inner list of strings does not parse", + header_value: 'blocked-destinations=\\("script"\\)', + expected: {blocked: false}, + }, + { + description: "Ensure that a destination header with an inner list of single-quote strings does not parse", + header_value: "blocked-destinations=\\('script'\\)", + expected: {blocked: false}, + }, + { + description: "Ensure that a destination header with an unclosed inner list does not parse", + header_value: "blocked-destinations=\\(script", + expected: {blocked: false}, + }, + { + description: "Ensure that a destination header with a malformed inner list does not parse", + header_value: "blocked-destinations=\\(script\\,style\\)", + expected: {blocked: false}, + }, + { + description: "Ensure that an unknown destination does not enforce a policy", + header_value: "blocked-destinations=\\(style\\)", + expected: {blocked: false}, + }, + { + description: "Ensure that an unknown source causes the policy to not be enforced", + header_value: "blocked-destinations=\\(script\\)\\, sources=\\(telepathy\\)", + expected: {blocked: false}, + }, + { + description: "Ensure that an invalid source causes the policy to not be enforced", + header_value: "blocked-destinations=\\(script\\)\\, sources=\\(invalid", + expected: {blocked: false}, + }, + ]; + test_cases.map(run_test); +</script> + diff --git a/tests/wpt/tests/subresource-integrity/tentative/integrity-policy/script.https.html b/tests/wpt/tests/subresource-integrity/tentative/integrity-policy/script.https.html new file mode 100644 index 00000000000..783374db920 --- /dev/null +++ b/tests/wpt/tests/subresource-integrity/tentative/integrity-policy/script.https.html @@ -0,0 +1,239 @@ +<!doctype html> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/reporting/resources/report-helper.js"></script> + +<body> +<script> + const {ORIGIN} = get_host_info(); + const getAbsoluteUrl = url => { + return new URL(url, window.location.href).href; + } + + const check_report = async (reporting_endpoint, reporting_uuid, iframe_url, url, report_only) => { + const reports = await pollReports(reporting_endpoint, reporting_uuid); + const abs_iframe_url = getAbsoluteUrl(iframe_url); + checkReportExists(reports, 'integrity-violation', abs_iframe_url); + const abs_blocked_url = getAbsoluteUrl(url); + const report = getReport(reports, 'integrity-violation', abs_iframe_url, abs_blocked_url); + assert_not_equals(report, null); + assert_equals(report.body.documentURL, abs_iframe_url); + assert_equals(report.body.blockedURL, abs_blocked_url); + assert_equals(report.body.destination, "script"); + assert_equals(report.body.reportOnly, report_only); + }; + const blob = new Blob([`window.ran=true;`], + { type: 'application/javascript' }); + + const blob_url = URL.createObjectURL(blob); + + // Generated using https://sha2.it/ed25519.html (In Chrome Canary, with Experimental Web Platform Features enabled) + const signature = encodeURIComponent( + 'header(Unencoded-Digest, sha-384=:tqyFpeo21WFM8HDeUtLqH20GUq\/q3D1R6mqTzW3RtyTZ3dAYZJhC1wUcnkgOE2ak:)' + + '|header(Signature-Input, signature=\\("unencoded-digest";sf\\); keyid="JrQLj5P\/89iXES9+vFgrIy29clF9CC\/oPPsw3c5D0bs="; tag="sri")' + + '|header(Signature, signature=:qM19uLskHm2TQG5LJcH/hY0n0BWWzYOJztVWYlwk0cZb3u0JdgUMre1J4Jn8Tma0x2u5/kPBfbXRMbB+X+vTBw==:)'); + + const test_cases = [ + { + description: "Ensure that a script without integrity did not run", + url: "/content-security-policy/resources/ran.js", + cross_origin: true, + integrity: "", + policy_violation: true, + block: true, + endpoints: true, + expected: {blocked: ORIGIN + "/content-security-policy/resources/ran.js", ran: false }, + }, + { + description: "Ensure that a script with unknown integrity algorithm did not run", + url: "/content-security-policy/resources/ran.js", + cross_origin: true, + integrity: "foobar-AAAAAAAAAAAAAAAAAAAa", + policy_violation: true, + block: true, + endpoints: true, + expected: {blocked: ORIGIN + "/content-security-policy/resources/ran.js", ran: false }, + }, + { + description: "Ensure that a script without integrity algorithm runs and gets reported in report-only mode", + url: "/content-security-policy/resources/ran.js", + cross_origin: true, + integrity: "", + policy_violation: true, + block: false, + endpoints: true, + expected: {blocked: ORIGIN + "/content-security-policy/resources/ran.js", ran: true }, + }, + { + description: "Ensure that a no-cors script gets blocked", + url: "/content-security-policy/resources/ran.js", + cross_origin: false, + integrity: "sha384-tqyFpeo21WFM8HDeUtLqH20GUq/q3D1R6mqTzW3RtyTZ3dAYZJhC1wUcnkgOE2ak", + policy_violation: true, + block: true, + endpoints: true, + expected: {blocked: ORIGIN + "/content-security-policy/resources/ran.js", ran: false }, + }, + { + description: "Ensure that ReportingObserver gets called without endpoints", + url: "/content-security-policy/resources/ran.js", + cross_origin: false, + integrity: "sha384-tqyFpeo21WFM8HDeUtLqH20GUq/q3D1R6mqTzW3RtyTZ3dAYZJhC1wUcnkgOE2ak", + policy_violation: true, + block: true, + endpoints: false, + expected: {blocked: ORIGIN + "/content-security-policy/resources/ran.js", ran: false }, + }, + { + description: "Ensure that a script with integrity runs", + url: "/content-security-policy/resources/ran.js", + cross_origin: true, + integrity: "sha384-tqyFpeo21WFM8HDeUtLqH20GUq/q3D1R6mqTzW3RtyTZ3dAYZJhC1wUcnkgOE2ak", + policy_violation: false, + block: true, + endpoints: true, + expected: {blocked: "", ran: true }, + }, + { + description: "Ensure that a script with signature integrity runs", + url: "/content-security-policy/resources/ran.js?pipe=" + signature, + cross_origin: true, + integrity: "ed25519-JrQLj5P/89iXES9+vFgrIy29clF9CC/oPPsw3c5D0bs=", + policy_violation: false, + block: true, + endpoints: true, + expected: {blocked: "", ran: true }, + }, + { + description: "Ensure that a data URI script with no integrity runs", + url: "data:application/javascript,window.ran=true", + cross_origin: true, + integrity: "", + policy_violation: false, + block: true, + endpoints: true, + expected: {blocked: "", ran: true }, + }, + { + description: "Ensure that a no-CORS data URI script with no integrity runs", + url: "data:application/javascript,window.ran=true", + cross_origin: false, + integrity: "", + policy_violation: false, + block: true, + endpoints: true, + expected: {blocked: "", ran: true }, + }, + { + description: "Ensure that a blob URL script with no integrity runs", + url: blob_url, + cross_origin: true, + integrity: "", + policy_violation: false, + block: true, + endpoints: true, + expected: {blocked: "", ran: true }, + }, + { + description: "Ensure that a no-CORS blob URL script with no integrity runs", + url: blob_url, + cross_origin: false, + integrity: "", + policy_violation: false, + block: true, + endpoints: true, + expected: {blocked: "", ran: true }, + } + ]; + test_cases.map(test_case => { + promise_test(async () => { + const REMOTE_EXECUTOR = + `/common/dispatcher/remote-executor.html?pipe=`; + const iframe_uuid = token(); + + const params = new URLSearchParams(location.search); + if (params.get('type') === "report") { + if (test_case.expected.blocked) { + return; + } + header_name += "-Report-Only"; + } + const reporting_uuid_1 = token(); + const reporting_uuid_2 = token(); + const reporting_uuid_3 = token(); + const reporting_endpoint = `${ORIGIN}/reporting/resources/report.py`; + let header = ""; + if (test_case.block) { + header += + `header(Integrity-Policy,blocked-destinations=\\(script\\)\\, endpoints=\\(integrity-endpoint-1 integrity-endpoint-2\\))`; + } + header += + `|header(Integrity-Policy-Report-Only,blocked-destinations=\\(script\\)\\, endpoints=\\(integrity-endpoint-3\\))`; + if (test_case.endpoints) { + header += + `|header(Reporting-Endpoints, integrity-endpoint-1=\"${reporting_endpoint}?reportID=${reporting_uuid_1}\"\\, ` + + `integrity-endpoint-2=\"${reporting_endpoint}?reportID=${reporting_uuid_2}\"\\, ` + + `integrity-endpoint-3=\"${reporting_endpoint}?reportID=${reporting_uuid_3}\")`; + } + const iframe_url = `${REMOTE_EXECUTOR}${encodeURIComponent(header)}&uuid=${iframe_uuid}`; + + const iframe = document.createElement('iframe'); + iframe.src = iframe_url; + document.body.appendChild(iframe); + + // Execute code directly from the iframe. + const ctx = new RemoteContext(iframe_uuid); + const result = await ctx.execute_script(async (test_case) => { + window.ran = false; + let report_observed_promise; + if (test_case.policy_violation) { + report_observed_promise = new Promise(r => { + (new ReportingObserver((reports, observer) => { + reports.forEach(report => { + if (report.body.blockedURL.endsWith(test_case.url)) { + r(report.body); + observer.disconnect(); + } + }); + })).observe('integrity-violation'); + }); + } + + // Load the script + await new Promise(resolve => { + const script = document.createElement('script'); + if (test_case.cross_origin) { + script.crossOrigin="anonymous"; + } + if (test_case.integrity) { + script.integrity = test_case.integrity; + } + script.onload = resolve; + script.onerror = resolve; + script.src = test_case.url; + document.body.appendChild(script); + }); + const report_body = await report_observed_promise; + return { body: report_body, ran: window.ran }; + }, [test_case]); + assert_equals(result.ran, test_case.expected.ran); + if (test_case.policy_violation) { + assert_equals(result.body.blockedURL, test_case.expected.blocked); + assert_true(result.body.documentURL.endsWith(iframe_url)); + assert_equals(result.body.destination, "script"); + assert_equals(result.body.reportOnly, !test_case.block); + } + if (test_case.endpoints && test_case.policy_violation) { + if (test_case.block) { + await check_report(reporting_endpoint, reporting_uuid_1, iframe_url, test_case.url, !test_case.block); + await check_report(reporting_endpoint, reporting_uuid_2, iframe_url, test_case.url, !test_case.block); + } + await check_report(reporting_endpoint, reporting_uuid_3, iframe_url, test_case.url, true); + } + }, test_case.description); + }); +</script> diff --git a/tests/wpt/tests/svg/animations/discard-check-removal-order.html b/tests/wpt/tests/svg/animations/discard-check-removal-order.html deleted file mode 100644 index 2935a69adda..00000000000 --- a/tests/wpt/tests/svg/animations/discard-check-removal-order.html +++ /dev/null @@ -1,52 +0,0 @@ -<!DOCTYPE HTML> -<html> -<title>This tests for the remove order of discard elements.</title> -<link rel="help" href="https://svgwg.org/specs/animations/#DiscardElement"> -<script src="/resources/testharness.js"></script> -<script src="/resources/testharnessreport.js"></script> -<script src="/resources/SVGAnimationTestCase-testharness.js"></script> - -<body> - <div id="removeOrder"></div> - <svg id="svg" width="400" height="400"> - <rect id="rect" width="100" height="100" fill="green" /> - <animate id="animate" href="#rect" attributeName="x" from="0" to="300" dur="10s" /> - </svg> - <script> - const rootSVGElement = document.querySelector('svg'); - - let removedNodes = new Array(); - let callback = function(mutations) { - mutations.forEach(function(mutation) { - let nodeList = mutation.removedNodes; - for (let i = 0; i < nodeList.length; ++i) { - removedNodes.push(nodeList[i].nodeName); - } - }); - }; - let observer = new MutationObserver(callback); - observer.observe(rootSVGElement, { 'childList': true, 'subtree': true }); - - function checkRemovedNodes(array) { - assert_array_equals(removedNodes, array, 'removed nodes'); - } - - function discardElement(id) { - let discard = createSVGElement("discard"); - discard.setAttribute("href", "#" + id); - rootSVGElement.appendChild(discard); - } - - discardElement("animate"); - discardElement("rect"); - - smil_async_test(t => { - runAnimationTest(t, [ - // [animationId, time, sampleCallback] - ['anim', 1, checkRemovedNodes.bind(this, ['animate', 'discard', 'rect', 'discard'])], - ]); - }); - window.animationStartsImmediately = true; - </script> -</body> -</html> diff --git a/tests/wpt/tests/svg/animations/reftests/reference/green-100x100.svg b/tests/wpt/tests/svg/animations/reftests/reference/green-100x100.svg deleted file mode 100644 index 120941444a4..00000000000 --- a/tests/wpt/tests/svg/animations/reftests/reference/green-100x100.svg +++ /dev/null @@ -1,3 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg"> - <rect width="100" height="100" fill="green"/> -</svg> diff --git a/tests/wpt/tests/svg/embedded/WEB_FEATURES.yml b/tests/wpt/tests/svg/embedded/WEB_FEATURES.yml new file mode 100644 index 00000000000..8482e555584 --- /dev/null +++ b/tests/wpt/tests/svg/embedded/WEB_FEATURES.yml @@ -0,0 +1,4 @@ +features: +- name: fetch-priority + files: + - attr-image-fetchpriority.html diff --git a/tests/wpt/tests/svg/idlharness.window.js b/tests/wpt/tests/svg/idlharness.window.js index b363128c25a..57beca77fd4 100644 --- a/tests/wpt/tests/svg/idlharness.window.js +++ b/tests/wpt/tests/svg/idlharness.window.js @@ -42,7 +42,6 @@ const elements = [ 'script', 'animate', 'set', - 'discard', 'animateMotion', 'mpath', 'animateTransform', @@ -111,7 +110,6 @@ idl_test( SVGGElement: ['objects.g'], SVGDefsElement: ['objects.defs'], SVGDescElement: ['objects.desc'], - SVGDiscardElement: ['objects.discard'], SVGTitleElement: ['objects.title'], SVGSymbolElement: ['objects.symbol'], SVGUseElement: ['objects.use'], @@ -168,7 +166,6 @@ idl_test( SVGViewElement: ['objects.view'], SVGScriptElement: ['objects.script'], SVGAnimateElement: ['objects.animate'], - SVGDiscardElement: ['objects.discard'], SVGSetElement: ['objects.set'], SVGAnimateMotionElement: ['objects.animateMotion'], SVGMPathElement: ['objects.mpath'], diff --git a/tests/wpt/tests/svg/scripted/WEB_FEATURES.yml b/tests/wpt/tests/svg/scripted/WEB_FEATURES.yml new file mode 100644 index 00000000000..864bd3398ee --- /dev/null +++ b/tests/wpt/tests/svg/scripted/WEB_FEATURES.yml @@ -0,0 +1,4 @@ +features: +- name: fetch-priority + files: + - attr-script-fetchpriority.html diff --git a/tests/wpt/tests/svg/styling/presentation-attributes-special-cases.html b/tests/wpt/tests/svg/styling/presentation-attributes-special-cases.html index 7bfa001030a..9a5da751ce1 100644 --- a/tests/wpt/tests/svg/styling/presentation-attributes-special-cases.html +++ b/tests/wpt/tests/svg/styling/presentation-attributes-special-cases.html @@ -98,7 +98,7 @@ if (CSS.supports("d", "initial")) { // animation elements. if (CSS.supports("fill", "initial")) { - for (let e of ["animate", "animateMotion", "animateTransform", "discard", "set"]) { + for (let e of ["animate", "animateMotion", "animateTransform", "set"]) { test(function() { assertPresentationAttributeIsNotSupported(e, "fill", "blue", "fill"); }, `fill presentation attribute not supported on ${e}`); diff --git a/tests/wpt/tests/tools/webdriver/webdriver/bidi/modules/emulation.py b/tests/wpt/tests/tools/webdriver/webdriver/bidi/modules/emulation.py index e587a082c03..fd5cf733ab7 100644 --- a/tests/wpt/tests/tools/webdriver/webdriver/bidi/modules/emulation.py +++ b/tests/wpt/tests/tools/webdriver/webdriver/bidi/modules/emulation.py @@ -35,11 +35,16 @@ class Emulation(BidiModule): def set_geolocation_override( self, coordinates: Union[CoordinatesOptions, Undefined] = UNDEFINED, + error: Optional[Dict[str, Any]] = None, contexts: Optional[List[str]] = None, user_contexts: Optional[List[str]] = None, ) -> Mapping[str, Any]: - params: MutableMapping[str, Any] = {"coordinates": coordinates} + params: MutableMapping[str, Any] = {} + if coordinates is not UNDEFINED: + params["coordinates"] = coordinates + if error is not None: + params["error"] = error if contexts is not None: params["contexts"] = contexts if user_contexts is not None: diff --git a/tests/wpt/tests/tools/wptrunner/wptrunner/browsers/android_webview.py b/tests/wpt/tests/tools/wptrunner/wptrunner/browsers/android_webview.py index 0e02d3c02cd..c796be18a73 100644 --- a/tests/wpt/tests/tools/wptrunner/wptrunner/browsers/android_webview.py +++ b/tests/wpt/tests/tools/wptrunner/wptrunner/browsers/android_webview.py @@ -88,7 +88,6 @@ class SystemWebViewShell(ChromeAndroidBrowserBase): def __init__(self, logger, binary, webdriver_binary="chromedriver", adb_binary=None, - remote_queue=None, device_serial=None, webdriver_args=None, stackwalk_binary=None, @@ -96,7 +95,7 @@ class SystemWebViewShell(ChromeAndroidBrowserBase): """Creates a new representation of Chrome. The `binary` argument gives the browser binary to use for testing.""" super().__init__(logger, - webdriver_binary, adb_binary, remote_queue, + webdriver_binary, adb_binary, device_serial, webdriver_args, stackwalk_binary, symbols_path) self.binary = binary diff --git a/tests/wpt/tests/tools/wptrunner/wptrunner/browsers/chrome_android.py b/tests/wpt/tests/tools/wptrunner/wptrunner/browsers/chrome_android.py index 820323e615a..4ed7707c3be 100644 --- a/tests/wpt/tests/tools/wptrunner/wptrunner/browsers/chrome_android.py +++ b/tests/wpt/tests/tools/wptrunner/wptrunner/browsers/chrome_android.py @@ -3,7 +3,7 @@ import mozprocess import subprocess -from .base import cmd_arg, require_arg +from .base import OutputHandler, cmd_arg, require_arg from .base import get_timeout_multiplier # noqa: F401 from .base import WebDriverBrowser # noqa: F401 from .chrome import executor_kwargs as chrome_executor_kwargs @@ -79,10 +79,9 @@ def env_options(): class LogcatRunner: - def __init__(self, logger, browser, remote_queue): + def __init__(self, logger, browser): self.logger = logger self.browser = browser - self.remote_queue = remote_queue def start(self): try: @@ -99,43 +98,31 @@ class LogcatRunner: self.logger.error("Failed to clear logcat buffer") self._cmd = self.browser.logcat_cmd() + self._output_handler = OutputHandler(self.logger, self._cmd) self._proc = mozprocess.ProcessHandler( self._cmd, - processOutputLine=self.on_output, + processOutputLine=self._output_handler, storeOutput=False) self._proc.run() - - def _send_message(self, command, *args): - try: - self.remote_queue.put((command, args)) - except AssertionError: - self.logger.warning("Error when send to remote queue") + self._output_handler.after_process_start(self._proc.pid) + self._output_handler.start() def stop(self, force=False): if self.is_alive(): kill_result = self._proc.kill() if force and kill_result != 0: self._proc.kill(9) + self._output_handler.after_process_stop() def is_alive(self): return hasattr(self._proc, "proc") and self._proc.poll() is None - def on_output(self, line): - data = { - "action": "process_output", - "process": "LOGCAT", - "command": "logcat", - "data": line - } - self._send_message("log", data) - class ChromeAndroidBrowserBase(WebDriverBrowser): def __init__(self, logger, webdriver_binary="chromedriver", adb_binary=None, - remote_queue=None, device_serial=None, webdriver_args=None, stackwalk_binary=None, @@ -148,15 +135,11 @@ class ChromeAndroidBrowserBase(WebDriverBrowser): self.device_serial = device_serial self.stackwalk_binary = stackwalk_binary self.symbols_path = symbols_path - self.remote_queue = remote_queue - - if self.remote_queue is not None: - self.logcat_runner = LogcatRunner(self.logger, self, self.remote_queue) + self.logcat_runner = LogcatRunner(self.logger, self) def setup(self): self.setup_adb_reverse() - if self.remote_queue is not None: - self.logcat_runner.start() + self.logcat_runner.start() def _adb_run(self, args): cmd = [self.adb_binary] @@ -176,8 +159,7 @@ class ChromeAndroidBrowserBase(WebDriverBrowser): super().cleanup() self._adb_run(['forward', '--remove-all']) self._adb_run(['reverse', '--remove-all']) - if self.remote_queue is not None: - self.logcat_runner.stop(force=True) + self.logcat_runner.stop(force=True) def executor_browser(self): cls, kwargs = super().executor_browser() @@ -231,13 +213,12 @@ class ChromeAndroidBrowser(ChromeAndroidBrowserBase): def __init__(self, logger, package_name, webdriver_binary="chromedriver", adb_binary=None, - remote_queue = None, device_serial=None, webdriver_args=None, stackwalk_binary=None, symbols_path=None): super().__init__(logger, - webdriver_binary, adb_binary, remote_queue, + webdriver_binary, adb_binary, device_serial, webdriver_args, stackwalk_binary, symbols_path) self.package_name = package_name diff --git a/tests/wpt/tests/tools/wptrunner/wptrunner/browsers/firefox.py b/tests/wpt/tests/tools/wptrunner/wptrunner/browsers/firefox.py index 494a7514efd..c63bfa2ceeb 100644 --- a/tests/wpt/tests/tools/wptrunner/wptrunner/browsers/firefox.py +++ b/tests/wpt/tests/tools/wptrunner/wptrunner/browsers/firefox.py @@ -220,6 +220,7 @@ def run_info_extras(logger, default_prefs=None, **kwargs): not bool_pref("fission.disableSessionHistoryInParent")), "swgl": bool_pref("gfx.webrender.software"), "privateBrowsing": bool_pref("browser.privatebrowsing.autostart"), + "remoteAsyncEvents": bool_pref("remote.events.async.wheel.enabled"), "incOriginInit": os.environ.get("MOZ_ENABLE_INC_ORIGIN_INIT") == "1", } rv.update(run_info_browser_version(**kwargs)) @@ -250,6 +251,7 @@ def update_properties(): "swgl", "asan", "tsan", + "remoteAsyncEvents", "sessionHistoryInParent", "subsuite"], { "os": ["version"], diff --git a/tests/wpt/tests/tools/wptrunner/wptrunner/executors/asyncactions.py b/tests/wpt/tests/tools/wptrunner/wptrunner/executors/asyncactions.py index 9925a4b511d..8397d7838a3 100644 --- a/tests/wpt/tests/tools/wptrunner/wptrunner/executors/asyncactions.py +++ b/tests/wpt/tests/tools/wptrunner/wptrunner/executors/asyncactions.py @@ -1,4 +1,5 @@ # mypy: allow-untyped-defs +from webdriver.bidi.undefined import UNDEFINED webdriver = None @@ -7,6 +8,22 @@ def do_delayed_imports(): global webdriver import webdriver + +def get_browsing_context_id(context): + """ + :param context: Either a string representing the browsing context id, or a + BiDi serialized window proxy object. In the latter case, the value is + extracted from the serialized object. + :return: The browsing context id. + """ + if isinstance(context, str): + return context + elif isinstance(context, webdriver.bidi.protocol.BidiWindow): + # Context can be a serialized WindowProxy. + return context.browsing_context + raise ValueError("Unexpected context type: %s" % context) + + class BidiBluetoothHandleRequestDevicePrompt: name = "bidi.bluetooth.handle_request_device_prompt" @@ -16,18 +33,10 @@ class BidiBluetoothHandleRequestDevicePrompt: self.protocol = protocol async def __call__(self, payload): - if payload["context"] is None: + if "context" not in payload: raise ValueError("Missing required parameter: context") - context = payload["context"] - if isinstance(context, str): - pass - elif isinstance(context, webdriver.bidi.protocol.BidiWindow): - # Context can be a serialized WindowProxy. - context = context.browsing_context - else: - raise ValueError("Unexpected context type: %s" % context) - + context = get_browsing_context_id(payload["context"]) prompt = payload["prompt"] accept = payload["accept"] device = payload["device"] @@ -42,17 +51,10 @@ class BidiBluetoothSimulateAdapterAction: self.protocol = protocol async def __call__(self, payload): - if payload["context"] is None: + if "context" not in payload: raise ValueError("Missing required parameter: context") - context = payload["context"] - if isinstance(context, str): - pass - elif isinstance(context, webdriver.bidi.protocol.BidiWindow): - # Context can be a serialized WindowProxy. - context = context.browsing_context - else: - raise ValueError("Unexpected context type: %s" % context) + context = get_browsing_context_id(payload["context"]) state = payload["state"] return await self.protocol.bidi_bluetooth.simulate_adapter(context, @@ -68,17 +70,9 @@ class BidiBluetoothSimulatePreconnectedPeripheralAction: self.protocol = protocol async def __call__(self, payload): - if payload["context"] is None: + if "context" not in payload: raise ValueError("Missing required parameter: context") - - context = payload["context"] - if isinstance(context, str): - pass - elif isinstance(context, webdriver.bidi.protocol.BidiWindow): - # Context can be a serialized WindowProxy. - context = context.browsing_context - else: - raise ValueError("Unexpected context type: %s" % context) + context = get_browsing_context_id(payload["context"]) address = payload["address"] name = payload["name"] @@ -97,22 +91,29 @@ class BidiEmulationSetGeolocationOverrideAction: self.protocol = protocol async def __call__(self, payload): - coordinates = payload['coordinates'] + if "error" in payload and "coordinates" in payload: + raise ValueError( + "Params `error` and `coordinates` are mutually exclusive") + + # If `error` is present, set it. Otherwise, do not pass it (error: None). + # Note, unlike `coordinates`, `error` cannot be `UNDEFINED`. It's either + # `None` and it's not passed, or some dict value which is passed. + error = payload['error'] if 'error' in payload else None + # If `error` is present, do not pass `coordinates` (coordinates: UNDEFINED). + # Otherwise, remove emulation (coordinates: None). + coordinates = payload['coordinates'] if 'coordinates' in payload else ( + None if error is None else UNDEFINED) + + if "contexts" not in payload: + raise ValueError("Missing required parameter: contexts") contexts = [] for context in payload["contexts"]: - # Context can be either a browsing context id, or a BiDi serialized window. In the latter case, the - # value is extracted from the serialized object. - if isinstance(context, str): - contexts.append(context) - elif isinstance(context, webdriver.bidi.protocol.BidiWindow): - contexts.append(context.browsing_context) - else: - raise ValueError("Unexpected context type: %s" % context) + contexts.append(get_browsing_context_id(context)) if len(contexts) == 0: raise ValueError("At least one context must be provided") return await self.protocol.bidi_emulation.set_geolocation_override( - coordinates, contexts) + coordinates, error, contexts) class BidiSessionSubscribeAction: @@ -126,17 +127,10 @@ class BidiSessionSubscribeAction: async def __call__(self, payload): events = payload["events"] contexts = None - if payload["contexts"] is not None: + if "contexts" in payload and payload["contexts"] is not None: contexts = [] for context in payload["contexts"]: - # Context can be either a browsing context id, or a BiDi serialized window. In the latter case, the - # value is extracted from the serialized object. - if isinstance(context, str): - contexts.append(context) - elif isinstance(context, webdriver.bidi.protocol.BidiWindow): - contexts.append(context.browsing_context) - else: - raise ValueError("Unexpected context type: %s" % context) + contexts.append(get_browsing_context_id(context)) return await self.protocol.bidi_events.subscribe(events, contexts) diff --git a/tests/wpt/tests/tools/wptrunner/wptrunner/executors/executorwebdriver.py b/tests/wpt/tests/tools/wptrunner/wptrunner/executors/executorwebdriver.py index 87403c2944d..7ca46a05a7b 100644 --- a/tests/wpt/tests/tools/wptrunner/wptrunner/executors/executorwebdriver.py +++ b/tests/wpt/tests/tools/wptrunner/wptrunner/executors/executorwebdriver.py @@ -272,9 +272,9 @@ class WebDriverBidiEmulationProtocolPart(BidiEmulationProtocolPart): def setup(self): self.webdriver = self.parent.webdriver - async def set_geolocation_override(self, coordinates, contexts): + async def set_geolocation_override(self, coordinates, error, contexts): return await self.webdriver.bidi_session.emulation.set_geolocation_override( - coordinates=coordinates, contexts=contexts) + coordinates=coordinates, error=error, contexts=contexts) class WebDriverBidiPermissionsProtocolPart(BidiPermissionsProtocolPart): diff --git a/tests/wpt/tests/tools/wptrunner/wptrunner/executors/protocol.py b/tests/wpt/tests/tools/wptrunner/wptrunner/executors/protocol.py index 833dff45636..16eb3cbb4a5 100644 --- a/tests/wpt/tests/tools/wptrunner/wptrunner/executors/protocol.py +++ b/tests/wpt/tests/tools/wptrunner/wptrunner/executors/protocol.py @@ -5,7 +5,10 @@ import traceback from http.client import HTTPConnection from abc import ABCMeta, abstractmethod -from typing import Any, Awaitable, Callable, ClassVar, List, Mapping, Optional, Tuple, Type +from typing import Any, Awaitable, Callable, ClassVar, List, Mapping, Optional, \ + Tuple, Type, Union + +from webdriver.bidi.undefined import Undefined def merge_dicts(target, source): @@ -452,7 +455,8 @@ class BidiEmulationProtocolPart(ProtocolPart): @abstractmethod async def set_geolocation_override(self, - coordinates: Optional[Mapping[str, Any]], + coordinates: Optional[Union[Mapping[str, Any], Undefined]], + error: Optional[Mapping[str, Any]], contexts: List[str]) -> None: pass diff --git a/tests/wpt/tests/tools/wptrunner/wptrunner/formatters/chromium.py b/tests/wpt/tests/tools/wptrunner/wptrunner/formatters/chromium.py deleted file mode 100644 index 95f53011bfc..00000000000 --- a/tests/wpt/tests/tools/wptrunner/wptrunner/formatters/chromium.py +++ /dev/null @@ -1,338 +0,0 @@ -# mypy: allow-untyped-defs - -import functools -import json -import time - -from collections import defaultdict -from mozlog.formatters import base - -from wptrunner.wptmanifest import serializer - -_escape_heading = functools.partial(serializer.escape, extras="]") - - -class ChromiumFormatter(base.BaseFormatter): # type: ignore - """Formatter to produce results matching the Chromium JSON Test Results format. - https://chromium.googlesource.com/chromium/src/+/master/docs/testing/json_test_results_format.md - - Notably, each test has an "artifacts" field that is a dict consisting of - "log": a list of strings (one per subtest + one for harness status, see - _append_test_message for the format) - "screenshots": a list of strings in the format of "url: base64" - - """ - - def __init__(self): - # Whether the run was interrupted, either by the test runner or user. - self.interrupted = False - - # A map of test status to the number of tests that had that status. - self.num_failures_by_status = defaultdict(int) - - # Start time, expressed as offset since UNIX epoch in seconds. Measured - # from the first `suite_start` event. - self.start_timestamp_seconds = None - - # A map of test names to test start timestamps, expressed in seconds - # since UNIX epoch. Only contains tests that are currently running - # (i.e., have not received the `test_end` event). - self.test_starts = {} - - # Trie of test results. Each directory in the test name is a node in - # the trie and the leaf contains the dict of per-test data. - self.tests = {} - - # Two dictionaries keyed by test name. Values are lists of strings: - # actual metadata content and other messages, respectively. - # See _append_test_message for examples. - self.actual_metadata = defaultdict(list) - self.messages = defaultdict(list) - - # List of tests that have failing subtests. - self.tests_with_subtest_fails = set() - - # Browser log for the current test under execution. - # These logs are from ChromeDriver's stdout/err, so we cannot say for - # sure which test a message is from, but instead we correlate them based - # on timing. - self.browser_log = [] - - def _append_test_message(self, test, subtest, wpt_actual_status, message): - r""" - Appends the message data for a test or subtest. - - :param str test: the name of the test - :param str subtest: the name of the subtest with the message. Will be - None if this is called for a test. - :param str wpt_actual_status: the test status as reported by WPT - :param str message: the string to append to the message for this test - - Example actual_metadata of a test with a subtest: - "[test_name]\n expected: OK\n" - " [subtest_name]\n expected: FAIL\n" - - NOTE: throughout this function we output a key called "expected" but - fill it in with the actual status. This is by design. The goal of this - output is to look exactly like WPT's expectation metadata so that it - can be easily diff-ed. - - Messages are appended verbatim to self.messages[test]. - """ - if subtest: - result = " [%s]\n expected: %s\n" % (_escape_heading(subtest), - wpt_actual_status) - self.actual_metadata[test].append(result) - if message: - self.messages[test].append("%s: %s\n" % (subtest, message)) - else: - # No subtest, so this is the top-level test. The result must be - # prepended to the list, so that it comes before any subtest. - test_name_last_part = test.split("/")[-1] - result = "[%s]\n expected: %s\n" % ( - _escape_heading(test_name_last_part), wpt_actual_status) - self.actual_metadata[test].insert(0, result) - if message: - self.messages[test].insert(0, "Harness: %s\n" % message) - - def _append_artifact(self, cur_dict, artifact_name, artifact_value): - """ - Appends artifacts to the specified dictionary. - :param dict cur_dict: the test leaf dictionary to append to - :param str artifact_name: the name of the artifact - :param str artifact_value: the value of the artifact - """ - assert isinstance(artifact_value, str), "artifact_value must be a str" - if "artifacts" not in cur_dict.keys(): - cur_dict["artifacts"] = defaultdict(list) - cur_dict["artifacts"][artifact_name].append(artifact_value) - - def _store_test_result(self, name, actual, expected, actual_metadata, - messages, wpt_actual, subtest_failure, - duration=None, reftest_screenshots=None): - """ - Stores the result of a single test in |self.tests| - - :param str name: name of the test. - :param str actual: actual status of the test. - :param str expected: expected statuses of the test. - :param list actual_metadata: a list of metadata items. - :param list messages: a list of test messages. - :param str wpt_actual: actual status reported by wpt, may differ from |actual|. - :param bool subtest_failure: whether this test failed because of subtests. - :param Optional[float] duration: time it took in seconds to run this test. - :param Optional[list] reftest_screenshots: see executors/base.py for definition. - """ - # The test name can contain a leading / which will produce an empty - # string in the first position of the list returned by split. We use - # filter(None) to remove such entries. - name_parts = filter(None, name.split("/")) - cur_dict = self.tests - for name_part in name_parts: - cur_dict = cur_dict.setdefault(name_part, {}) - # Splitting and joining the list of statuses here avoids the need for - # recursively postprocessing the |tests| trie at shutdown. We assume the - # number of repetitions is typically small enough for the quadratic - # runtime to not matter. - statuses = cur_dict.get("actual", "").split() - statuses.append(actual) - cur_dict["actual"] = " ".join(statuses) - cur_dict["expected"] = expected - if duration is not None: - # Record the time to run the first invocation only. - cur_dict.setdefault("time", duration) - durations = cur_dict.setdefault("times", []) - durations.append(duration) - if subtest_failure: - self._append_artifact(cur_dict, "wpt_subtest_failure", "true") - if wpt_actual != actual: - self._append_artifact(cur_dict, "wpt_actual_status", wpt_actual) - if wpt_actual == 'CRASH': - for line in self.browser_log: - self._append_artifact(cur_dict, "wpt_crash_log", line) - for metadata in actual_metadata: - self._append_artifact(cur_dict, "wpt_actual_metadata", metadata) - for message in messages: - self._append_artifact(cur_dict, "wpt_log", message) - - # Store screenshots (if any). - for item in reftest_screenshots or []: - if not isinstance(item, dict): - # Skip the relation string. - continue - data = "%s: %s" % (item["url"], item["screenshot"]) - self._append_artifact(cur_dict, "screenshots", data) - - # Figure out if there was a regression, unexpected status, or flake. - # This only happens for tests that were run - if actual != "SKIP": - if actual not in expected: - cur_dict["is_unexpected"] = True - if actual != "PASS": - cur_dict["is_regression"] = True - if len(set(statuses)) > 1: - cur_dict["is_flaky"] = True - - # Update the count of how many tests ran with each status. Only includes - # the first invocation's result in the totals. - if len(statuses) == 1: - self.num_failures_by_status[actual] += 1 - - def _map_status_name(self, status): - """ - Maps a WPT status to a Chromium status. - - Chromium has five main statuses that we have to map to: - CRASH: the test harness crashed - FAIL: the test did not run as expected - PASS: the test ran as expected - SKIP: the test was not run - TIMEOUT: the did not finish in time and was aborted - - :param str status: the string status of a test from WPT - :return: a corresponding string status for Chromium - """ - if status == "OK": - return "PASS" - if status == "NOTRUN": - return "SKIP" - if status == "EXTERNAL-TIMEOUT": - return "TIMEOUT" - if status in ("ERROR", "PRECONDITION_FAILED"): - return "FAIL" - if status == "INTERNAL-ERROR": - return "CRASH" - # Any other status just gets returned as-is. - return status - - def _get_expected_status_from_data(self, actual_status, data): - """ - Gets the expected statuses from a |data| dictionary. - - If there is no expected status in data, the actual status is returned. - This is because mozlog will delete "expected" from |data| if it is the - same as "status". So the presence of "expected" implies that "status" is - unexpected. Conversely, the absence of "expected" implies the "status" - is expected. So we use the "expected" status if it's there or fall back - to the actual status if it's not. - - If the test has multiple statuses, it will have other statuses listed as - "known_intermittent" in |data|. If these exist, they will be added to - the returned status with spaced in between. - - :param str actual_status: the actual status of the test - :param data: a data dictionary to extract expected status from - :return str: the expected statuses as a string - """ - expected_statuses = self._map_status_name(data["expected"]) if "expected" in data else actual_status - if data.get("known_intermittent"): - all_statsues = {self._map_status_name(other_status) for other_status in data["known_intermittent"]} - all_statsues.add(expected_statuses) - expected_statuses = " ".join(sorted(all_statsues)) - return expected_statuses - - def _get_time(self, data): - """Get the timestamp of a message in seconds since the UNIX epoch.""" - maybe_timestamp_millis = data.get("time") - if maybe_timestamp_millis is not None: - return float(maybe_timestamp_millis) / 1000 - return time.time() - - def _time_test(self, test_name, data): - """Time how long a test took to run. - - :param str test_name: the name of the test to time - :param data: a data dictionary to extract the test end timestamp from - :return Optional[float]: a nonnegative duration in seconds or None if - the measurement is unavailable or invalid - """ - test_start = self.test_starts.pop(test_name, None) - if test_start is not None: - # The |data| dictionary only provides millisecond resolution - # anyway, so further nonzero digits are unlikely to be meaningful. - duration = round(self._get_time(data) - test_start, 3) - if duration >= 0: - return duration - return None - - def suite_start(self, data): - if self.start_timestamp_seconds is None: - self.start_timestamp_seconds = self._get_time(data) - if 'run_info' in data: - self.flag_specific = data['run_info'].get('flag_specific', '') - - def test_start(self, data): - test_name = data["test"] - self.test_starts[test_name] = self._get_time(data) - - def test_status(self, data): - test_name = data["test"] - wpt_actual_status = data["status"] - actual_status = self._map_status_name(wpt_actual_status) - expected_statuses = self._get_expected_status_from_data(actual_status, data) - - is_unexpected = actual_status not in expected_statuses - if is_unexpected and test_name not in self.tests_with_subtest_fails: - self.tests_with_subtest_fails.add(test_name) - # We should always get a subtest in the data dict, but it's technically - # possible that it's missing. Be resilient here. - subtest_name = data.get("subtest", "UNKNOWN SUBTEST") - self._append_test_message(test_name, subtest_name, - wpt_actual_status, data.get("message", "")) - - def test_end(self, data): - test_name = data["test"] - # Save the status reported by WPT since we might change it when - # reporting to Chromium. - wpt_actual_status = data["status"] - actual_status = self._map_status_name(wpt_actual_status) - expected_statuses = self._get_expected_status_from_data(actual_status, data) - duration = self._time_test(test_name, data) - subtest_failure = False - if test_name in self.tests_with_subtest_fails: - subtest_failure = True - # Clean up the test list to avoid accumulating too many. - self.tests_with_subtest_fails.remove(test_name) - # This test passed but it has failing subtests. Since we can only - # report a single status to Chromium, we choose FAIL to indicate - # that something about this test did not run correctly. - if actual_status == "PASS": - actual_status = "FAIL" - - self._append_test_message(test_name, None, wpt_actual_status, - data.get("message", "")) - self._store_test_result(test_name, - actual_status, - expected_statuses, - self.actual_metadata[test_name], - self.messages[test_name], - wpt_actual_status, - subtest_failure, - duration, - data.get("extra", {}).get("reftest_screenshots")) - - # Remove the test from dicts to avoid accumulating too many. - self.actual_metadata.pop(test_name) - self.messages.pop(test_name) - - # New test, new browser logs. - self.browser_log = [] - - def shutdown(self, data): - # Create the final result dictionary - final_result = { - # There are some required fields that we just hard-code. - "interrupted": False, - "path_delimiter": "/", - "version": 3, - "seconds_since_epoch": self.start_timestamp_seconds, - "num_failures_by_type": self.num_failures_by_status, - "flag_name": self.flag_specific, - "tests": self.tests - } - return json.dumps(final_result) - - def process_output(self, data): - cmd = data.get("command", "") - if any(c in cmd for c in ["chromedriver", "logcat"]): - self.browser_log.append(data['data']) diff --git a/tests/wpt/tests/tools/wptrunner/wptrunner/formatters/tests/test_chromium.py b/tests/wpt/tests/tools/wptrunner/wptrunner/formatters/tests/test_chromium.py deleted file mode 100644 index bf815d5dc76..00000000000 --- a/tests/wpt/tests/tools/wptrunner/wptrunner/formatters/tests/test_chromium.py +++ /dev/null @@ -1,828 +0,0 @@ -# mypy: ignore-errors - -import json -import sys -from os.path import dirname, join -from io import StringIO - -from mozlog import handlers, structuredlog -import pytest - -sys.path.insert(0, join(dirname(__file__), "..", "..")) -from formatters.chromium import ChromiumFormatter - - -@pytest.fixture -def logger(): - test_logger = structuredlog.StructuredLogger("test_a") - try: - yield test_logger - finally: - # Loggers of the same name share state globally: - # https://searchfox.org/mozilla-central/rev/1c54648c082efdeb08cf6a5e3a8187e83f7549b9/testing/mozbase/mozlog/mozlog/structuredlog.py#195-196 - # - # Resetting the state here ensures the logger will not be shut down in - # the next test. - test_logger.reset_state() - - -def test_chromium_required_fields(logger, capfd): - # Test that the test results contain a handful of required fields. - - # Set up the handler. - output = StringIO() - logger.add_handler(handlers.StreamHandler(output, ChromiumFormatter())) - - # output a bunch of stuff - logger.suite_start(["test-id-1"], run_info={}, time=123) - logger.test_start("test-id-1") - logger.test_end("test-id-1", status="PASS", expected="PASS") - logger.suite_end() - logger.shutdown() - - # check nothing got output to stdout/stderr - # (note that mozlog outputs exceptions during handling to stderr!) - captured = capfd.readouterr() - assert captured.out == "" - assert captured.err == "" - - # check the actual output of the formatter - output.seek(0) - output_obj = json.load(output) - - # Check for existence of required fields - assert "interrupted" in output_obj - assert "path_delimiter" in output_obj - assert "version" in output_obj - assert "num_failures_by_type" in output_obj - assert "tests" in output_obj - - test_obj = output_obj["tests"]["test-id-1"] - assert "actual" in test_obj - assert "expected" in test_obj - - -def test_time_per_test(logger, capfd): - # Test that the formatter measures time per test correctly. - - # Set up the handler. - output = StringIO() - logger.add_handler(handlers.StreamHandler(output, ChromiumFormatter())) - - logger.suite_start(["test-id-1", "test-id-2"], run_info={}, time=50) - logger.test_start("test-id-1", time=100) - logger.test_start("test-id-2", time=200) - logger.test_end("test-id-1", status="PASS", expected="PASS", time=300) - logger.test_end("test-id-2", status="PASS", expected="PASS", time=199) - logger.suite_end() - - logger.suite_start(["test-id-1"], run_info={}, time=400) - logger.test_start("test-id-1", time=500) - logger.test_end("test-id-1", status="PASS", expected="PASS", time=600) - logger.suite_end() - - # Write the final results. - logger.shutdown() - - # check nothing got output to stdout/stderr - # (note that mozlog outputs exceptions during handling to stderr!) - captured = capfd.readouterr() - assert captured.out == "" - assert captured.err == "" - - # check the actual output of the formatter - output.seek(0) - output_obj = json.load(output) - - test1_obj = output_obj["tests"]["test-id-1"] - test2_obj = output_obj["tests"]["test-id-2"] - # Test 1 run 1: 300ms - 100ms = 0.2s - # Test 1 run 2: 600ms - 500ms = 0.1s - assert test1_obj["time"] == pytest.approx(0.2) - assert len(test1_obj["times"]) == 2 - assert test1_obj["times"][0] == pytest.approx(0.2) - assert test1_obj["times"][1] == pytest.approx(0.1) - assert "time" not in test2_obj - assert "times" not in test2_obj - - -def test_chromium_test_name_trie(logger, capfd): - # Ensure test names are broken into directories and stored in a trie with - # test results at the leaves. - - # Set up the handler. - output = StringIO() - logger.add_handler(handlers.StreamHandler(output, ChromiumFormatter())) - - # output a bunch of stuff - logger.suite_start(["/foo/bar/test-id-1", "/foo/test-id-2"], run_info={}, - time=123) - logger.test_start("/foo/bar/test-id-1") - logger.test_end("/foo/bar/test-id-1", status="TIMEOUT", expected="FAIL") - logger.test_start("/foo/test-id-2") - logger.test_end("/foo/test-id-2", status="ERROR", expected="TIMEOUT") - logger.suite_end() - logger.shutdown() - - # check nothing got output to stdout/stderr - # (note that mozlog outputs exceptions during handling to stderr!) - captured = capfd.readouterr() - assert captured.out == "" - assert captured.err == "" - - # check the actual output of the formatter - output.seek(0) - output_obj = json.load(output) - - # Ensure that the test names are broken up by directory name and that the - # results are stored at the leaves. - test_obj = output_obj["tests"]["foo"]["bar"]["test-id-1"] - assert test_obj["actual"] == "TIMEOUT" - assert test_obj["expected"] == "FAIL" - - test_obj = output_obj["tests"]["foo"]["test-id-2"] - # The ERROR status is mapped to FAIL for Chromium - assert test_obj["actual"] == "FAIL" - assert test_obj["expected"] == "TIMEOUT" - - -def test_num_failures_by_type(logger, capfd): - # Test that the number of failures by status type is correctly calculated. - - # Set up the handler. - output = StringIO() - logger.add_handler(handlers.StreamHandler(output, ChromiumFormatter())) - - # Run some tests with different statuses: 3 passes, 1 timeout - logger.suite_start(["t1", "t2", "t3", "t4"], run_info={}, time=123) - logger.test_start("t1") - logger.test_end("t1", status="PASS", expected="PASS") - logger.test_start("t2") - logger.test_end("t2", status="PASS", expected="PASS") - logger.test_start("t3") - logger.test_end("t3", status="PASS", expected="FAIL") - logger.test_start("t4") - logger.test_end("t4", status="TIMEOUT", expected="CRASH") - logger.suite_end() - logger.shutdown() - - # check nothing got output to stdout/stderr - # (note that mozlog outputs exceptions during handling to stderr!) - captured = capfd.readouterr() - assert captured.out == "" - assert captured.err == "" - - # check the actual output of the formatter - output.seek(0) - num_failures_by_type = json.load(output)["num_failures_by_type"] - - # We expect 3 passes and 1 timeout, nothing else. - assert sorted(num_failures_by_type.keys()) == ["PASS", "TIMEOUT"] - assert num_failures_by_type["PASS"] == 3 - assert num_failures_by_type["TIMEOUT"] == 1 - - -def test_subtest_messages(logger, capfd): - # Tests accumulation of test output - - # Set up the handler. - output = StringIO() - logger.add_handler(handlers.StreamHandler(output, ChromiumFormatter())) - - # Run two tests with subtest messages. The subtest name should be included - # in the output. We should also tolerate missing messages and subtest names - # with unusual characters. - logger.suite_start(["t1", "t2"], run_info={}, time=123) - logger.test_start("t1") - logger.test_status("t1", status="FAIL", subtest="t1_a", - message="t1_a_message") - # Subtest name includes a backslash and two closing square brackets. - logger.test_status("t1", status="PASS", subtest=r"t1_\[]]b", - message="t1_b_message") - logger.test_end("t1", status="PASS", expected="PASS") - logger.test_start("t2") - # Subtests with empty messages should not be ignored. - logger.test_status("t2", status="PASS", subtest="t2_a") - # A test-level message will also be appended - logger.test_end("t2", status="TIMEOUT", expected="PASS", - message="t2_message") - logger.suite_end() - logger.shutdown() - - # check nothing got output to stdout/stderr - # (note that mozlog outputs exceptions during handling to stderr!) - captured = capfd.readouterr() - assert captured.out == "" - assert captured.err == "" - - # check the actual output of the formatter - output.seek(0) - output_json = json.load(output) - - t1_artifacts = output_json["tests"]["t1"]["artifacts"] - assert t1_artifacts["wpt_actual_metadata"] == [ - "[t1]\n expected: PASS\n", - " [t1_a]\n expected: FAIL\n", - " [t1_\\\\[\\]\\]b]\n expected: PASS\n", - ] - assert t1_artifacts["wpt_log"] == [ - "t1_a: t1_a_message\n", - # Only humans will read the log, so there's no need to escape - # characters here. - "t1_\\[]]b: t1_b_message\n", - ] - assert t1_artifacts["wpt_subtest_failure"] == ["true"] - t2_artifacts = output_json["tests"]["t2"]["artifacts"] - assert t2_artifacts["wpt_actual_metadata"] == [ - "[t2]\n expected: TIMEOUT\n", - " [t2_a]\n expected: PASS\n", - ] - assert t2_artifacts["wpt_log"] == [ - "Harness: t2_message\n" - ] - assert "wpt_subtest_failure" not in t2_artifacts.keys() - - -def test_subtest_failure(logger, capfd): - # Tests that a test fails if a subtest fails - - # Set up the handler. - output = StringIO() - formatter = ChromiumFormatter() - logger.add_handler(handlers.StreamHandler(output, formatter)) - - # Run a test with some subtest failures. - logger.suite_start(["t1"], run_info={}, time=123) - logger.test_start("t1") - logger.test_status("t1", status="FAIL", subtest="t1_a", - message="t1_a_message") - logger.test_status("t1", status="PASS", subtest="t1_b", - message="t1_b_message") - logger.test_status("t1", status="TIMEOUT", subtest="t1_c", - message="t1_c_message") - - # Make sure the test name was added to the set of tests with subtest fails - assert "t1" in formatter.tests_with_subtest_fails - - # The test status is reported as a pass here because the harness was able to - # run the test to completion. - logger.test_end("t1", status="PASS", expected="PASS", message="top_message") - logger.suite_end() - logger.shutdown() - - # check nothing got output to stdout/stderr - # (note that mozlog outputs exceptions during handling to stderr!) - captured = capfd.readouterr() - assert captured.out == "" - assert captured.err == "" - - # check the actual output of the formatter - output.seek(0) - output_json = json.load(output) - - test_obj = output_json["tests"]["t1"] - t1_artifacts = test_obj["artifacts"] - assert t1_artifacts["wpt_actual_metadata"] == [ - "[t1]\n expected: PASS\n", - " [t1_a]\n expected: FAIL\n", - " [t1_b]\n expected: PASS\n", - " [t1_c]\n expected: TIMEOUT\n", - ] - assert t1_artifacts["wpt_log"] == [ - "Harness: top_message\n", - "t1_a: t1_a_message\n", - "t1_b: t1_b_message\n", - "t1_c: t1_c_message\n", - ] - assert t1_artifacts["wpt_subtest_failure"] == ["true"] - # The status of the test in the output is a failure because subtests failed, - # despite the harness reporting that the test passed. But the harness status - # is logged as an artifact. - assert t1_artifacts["wpt_actual_status"] == ["PASS"] - assert test_obj["actual"] == "FAIL" - assert test_obj["expected"] == "PASS" - # Also ensure that the formatter cleaned up its internal state - assert "t1" not in formatter.tests_with_subtest_fails - - -def test_expected_subtest_failure(logger, capfd): - # Tests that an expected subtest failure does not cause the test to fail - - # Set up the handler. - output = StringIO() - formatter = ChromiumFormatter() - logger.add_handler(handlers.StreamHandler(output, formatter)) - - # Run a test with some expected subtest failures. - logger.suite_start(["t1"], run_info={}, time=123) - logger.test_start("t1") - logger.test_status("t1", status="FAIL", expected="FAIL", subtest="t1_a", - message="t1_a_message") - logger.test_status("t1", status="PASS", subtest="t1_b", - message="t1_b_message") - logger.test_status("t1", status="TIMEOUT", expected="TIMEOUT", subtest="t1_c", - message="t1_c_message") - - # The subtest failures are all expected so this test should not be added to - # the set of tests with subtest failures. - assert "t1" not in formatter.tests_with_subtest_fails - - # The test status is reported as a pass here because the harness was able to - # run the test to completion. - logger.test_end("t1", status="OK", expected="OK") - logger.suite_end() - logger.shutdown() - - # check nothing got output to stdout/stderr - # (note that mozlog outputs exceptions during handling to stderr!) - captured = capfd.readouterr() - assert captured.out == "" - assert captured.err == "" - - # check the actual output of the formatter - output.seek(0) - output_json = json.load(output) - - test_obj = output_json["tests"]["t1"] - assert test_obj["artifacts"]["wpt_actual_metadata"] == [ - "[t1]\n expected: OK\n", - " [t1_a]\n expected: FAIL\n", - " [t1_b]\n expected: PASS\n", - " [t1_c]\n expected: TIMEOUT\n", - ] - assert test_obj["artifacts"]["wpt_log"] == [ - "t1_a: t1_a_message\n", - "t1_b: t1_b_message\n", - "t1_c: t1_c_message\n", - ] - # The status of the test in the output is a pass because the subtest - # failures were all expected. - assert test_obj["actual"] == "PASS" - assert test_obj["expected"] == "PASS" - - -def test_unexpected_subtest_pass(logger, capfd): - # A subtest that unexpectedly passes is considered a failure condition. - - # Set up the handler. - output = StringIO() - formatter = ChromiumFormatter() - logger.add_handler(handlers.StreamHandler(output, formatter)) - - # Run a test with a subtest that is expected to fail but passes. - logger.suite_start(["t1"], run_info={}, time=123) - logger.test_start("t1") - logger.test_status("t1", status="PASS", expected="FAIL", subtest="t1_a", - message="t1_a_message") - - # Since the subtest behaviour is unexpected, it's considered a failure, so - # the test should be added to the set of tests with subtest failures. - assert "t1" in formatter.tests_with_subtest_fails - - # The test status is reported as a pass here because the harness was able to - # run the test to completion. - logger.test_end("t1", status="PASS", expected="PASS") - logger.suite_end() - logger.shutdown() - - # check nothing got output to stdout/stderr - # (note that mozlog outputs exceptions during handling to stderr!) - captured = capfd.readouterr() - assert captured.out == "" - assert captured.err == "" - - # check the actual output of the formatter - output.seek(0) - output_json = json.load(output) - - test_obj = output_json["tests"]["t1"] - t1_artifacts = test_obj["artifacts"] - assert t1_artifacts["wpt_actual_metadata"] == [ - "[t1]\n expected: PASS\n", - " [t1_a]\n expected: PASS\n", - ] - assert t1_artifacts["wpt_log"] == [ - "t1_a: t1_a_message\n", - ] - assert t1_artifacts["wpt_subtest_failure"] == ["true"] - # Since the subtest status is unexpected, we fail the test. But we report - # wpt_actual_status as an artifact - assert t1_artifacts["wpt_actual_status"] == ["PASS"] - assert test_obj["actual"] == "FAIL" - assert test_obj["expected"] == "PASS" - # Also ensure that the formatter cleaned up its internal state - assert "t1" not in formatter.tests_with_subtest_fails - - -def test_expected_test_fail(logger, capfd): - # Check that an expected test-level failure is treated as a Pass - - # Set up the handler. - output = StringIO() - logger.add_handler(handlers.StreamHandler(output, ChromiumFormatter())) - - # Run some tests with different statuses: 3 passes, 1 timeout - logger.suite_start(["t1"], run_info={}, time=123) - logger.test_start("t1") - logger.test_end("t1", status="ERROR", expected="ERROR") - logger.suite_end() - logger.shutdown() - - # check nothing got output to stdout/stderr - # (note that mozlog outputs exceptions during handling to stderr!) - captured = capfd.readouterr() - assert captured.out == "" - assert captured.err == "" - - # check the actual output of the formatter - output.seek(0) - output_json = json.load(output) - - test_obj = output_json["tests"]["t1"] - # The test's actual and expected status should map from "ERROR" to "FAIL" - assert test_obj["actual"] == "FAIL" - assert test_obj["expected"] == "FAIL" - # ..and this test should not be a regression nor unexpected - assert "is_regression" not in test_obj - assert "is_unexpected" not in test_obj - - -def test_unexpected_test_fail(logger, capfd): - # Check that an unexpected test-level failure is marked as unexpected and - # as a regression. - - # Set up the handler. - output = StringIO() - logger.add_handler(handlers.StreamHandler(output, ChromiumFormatter())) - - # Run some tests with different statuses: 3 passes, 1 timeout - logger.suite_start(["t1"], run_info={}, time=123) - logger.test_start("t1") - logger.test_end("t1", status="ERROR", expected="OK") - logger.suite_end() - logger.shutdown() - - # check nothing got output to stdout/stderr - # (note that mozlog outputs exceptions during handling to stderr!) - captured = capfd.readouterr() - assert captured.out == "" - assert captured.err == "" - - # check the actual output of the formatter - output.seek(0) - output_json = json.load(output) - - test_obj = output_json["tests"]["t1"] - # The test's actual and expected status should be mapped, ERROR->FAIL and - # OK->PASS - assert test_obj["actual"] == "FAIL" - assert test_obj["expected"] == "PASS" - # ..and this test should be a regression and unexpected - assert test_obj["is_regression"] is True - assert test_obj["is_unexpected"] is True - - -def test_flaky_test_expected(logger, capfd): - # Check that a flaky test with multiple possible statuses is seen as - # expected if its actual status is one of the possible ones. - - # set up the handler. - output = StringIO() - logger.add_handler(handlers.StreamHandler(output, ChromiumFormatter())) - - # Run a test that is known to be flaky - logger.suite_start(["t1"], run_info={}, time=123) - logger.test_start("t1") - logger.test_end("t1", status="ERROR", expected="OK", known_intermittent=["ERROR", "TIMEOUT"]) - logger.suite_end() - logger.shutdown() - - # check nothing got output to stdout/stderr - # (note that mozlog outputs exceptions during handling to stderr!) - captured = capfd.readouterr() - assert captured.out == "" - assert captured.err == "" - - # check the actual output of the formatter - output.seek(0) - output_json = json.load(output) - - test_obj = output_json["tests"]["t1"] - # The test's statuses are all mapped, changing ERROR->FAIL and OK->PASS - assert test_obj["actual"] == "FAIL" - # All the possible statuses are merged and sorted together into expected. - assert test_obj["expected"] == "FAIL PASS TIMEOUT" - # ...this is not a regression or unexpected because the actual status is one - # of the expected ones - assert "is_regression" not in test_obj - assert "is_unexpected" not in test_obj - - -def test_flaky_test_unexpected(logger, capfd): - # Check that a flaky test with multiple possible statuses is seen as - # unexpected if its actual status is NOT one of the possible ones. - - # set up the handler. - output = StringIO() - logger.add_handler(handlers.StreamHandler(output, ChromiumFormatter())) - - # Run a test that is known to be flaky - logger.suite_start(["t1"], run_info={}, time=123) - logger.test_start("t1") - logger.test_end("t1", status="ERROR", expected="OK", known_intermittent=["TIMEOUT"]) - logger.suite_end() - logger.shutdown() - - # check nothing got output to stdout/stderr - # (note that mozlog outputs exceptions during handling to stderr!) - captured = capfd.readouterr() - assert captured.out == "" - assert captured.err == "" - - # check the actual output of the formatter - output.seek(0) - output_json = json.load(output) - - test_obj = output_json["tests"]["t1"] - # The test's statuses are all mapped, changing ERROR->FAIL and OK->PASS - assert test_obj["actual"] == "FAIL" - # All the possible statuses are merged and sorted together into expected. - assert test_obj["expected"] == "PASS TIMEOUT" - # ...this is a regression and unexpected because the actual status is not - # one of the expected ones - assert test_obj["is_regression"] is True - assert test_obj["is_unexpected"] is True - - -def test_precondition_failed(logger, capfd): - # Check that a failed precondition gets properly handled. - - # set up the handler. - output = StringIO() - logger.add_handler(handlers.StreamHandler(output, ChromiumFormatter())) - - # Run a test with a precondition failure - logger.suite_start(["t1"], run_info={}, time=123) - logger.test_start("t1") - logger.test_end("t1", status="PRECONDITION_FAILED", expected="OK") - logger.suite_end() - logger.shutdown() - - # check nothing got output to stdout/stderr - # (note that mozlog outputs exceptions during handling to stderr!) - captured = capfd.readouterr() - assert captured.out == "" - assert captured.err == "" - - # check the actual output of the formatter - output.seek(0) - output_json = json.load(output) - - test_obj = output_json["tests"]["t1"] - # The precondition failure should map to FAIL status, but we should also - # have an artifact containing the original PRECONDITION_FAILED status. - assert test_obj["actual"] == "FAIL" - assert test_obj["artifacts"]["wpt_actual_status"] == ["PRECONDITION_FAILED"] - # ...this is an unexpected regression because we expected a pass but failed - assert test_obj["is_regression"] is True - assert test_obj["is_unexpected"] is True - - -def test_repeated_test_statuses(logger, capfd): - # Check that the logger outputs all statuses from multiple runs of a test. - - # Set up the handler. - output = StringIO() - logger.add_handler(handlers.StreamHandler(output, ChromiumFormatter())) - - # Run a test suite for the first time. - logger.suite_start(["t1"], run_info={}, time=123) - logger.test_start("t1") - logger.test_end("t1", status="PASS", expected="PASS", known_intermittent=[]) - logger.suite_end() - - # Run the test suite for the second time. - logger.suite_start(["t1"], run_info={}, time=456) - logger.test_start("t1") - logger.test_end("t1", status="FAIL", expected="PASS", known_intermittent=[]) - logger.suite_end() - - # Write the final results. - logger.shutdown() - - # check nothing got output to stdout/stderr - # (note that mozlog outputs exceptions during handling to stderr!) - captured = capfd.readouterr() - assert captured.out == "" - assert captured.err == "" - - # check the actual output of the formatter - output.seek(0) - output_json = json.load(output) - - status_totals = output_json["num_failures_by_type"] - assert status_totals["PASS"] == 1 - # A missing result type is the same as being present and set to zero (0). - assert status_totals.get("FAIL", 0) == 0 - - # The actual statuses are accumulated in a ordered space-separated list. - test_obj = output_json["tests"]["t1"] - assert test_obj["actual"] == "PASS FAIL" - assert test_obj["expected"] == "PASS" - - -def test_flaky_test_detection(logger, capfd): - # Check that the logger detects flakiness for a test run multiple times. - - # Set up the handler. - output = StringIO() - logger.add_handler(handlers.StreamHandler(output, ChromiumFormatter())) - - logger.suite_start(["t1", "t2"], run_info={}) - logger.test_start("t1") - logger.test_start("t2") - logger.test_end("t1", status="FAIL", expected="PASS") - logger.test_end("t2", status="FAIL", expected="FAIL") - logger.suite_end() - - logger.suite_start(["t1", "t2"], run_info={}) - logger.test_start("t1") - logger.test_start("t2") - logger.test_end("t1", status="PASS", expected="PASS") - logger.test_end("t2", status="FAIL", expected="FAIL") - logger.suite_end() - - # Write the final results. - logger.shutdown() - - # check nothing got output to stdout/stderr - # (note that mozlog outputs exceptions during handling to stderr!) - captured = capfd.readouterr() - assert captured.out == "" - assert captured.err == "" - - # check the actual output of the formatter - output.seek(0) - output_json = json.load(output) - - # We consider a test flaky if it runs multiple times and produces more than - # one kind of result. - test1_obj = output_json["tests"]["t1"] - test2_obj = output_json["tests"]["t2"] - assert test1_obj["is_flaky"] is True - assert "is_flaky" not in test2_obj - - -def test_known_intermittent_empty(logger, capfd): - # If the known_intermittent list is empty, we want to ensure we don't append - # any extraneous characters to the output. - - # set up the handler. - output = StringIO() - logger.add_handler(handlers.StreamHandler(output, ChromiumFormatter())) - - # Run a test and include an empty known_intermittent list - logger.suite_start(["t1"], run_info={}, time=123) - logger.test_start("t1") - logger.test_end("t1", status="OK", expected="OK", known_intermittent=[]) - logger.suite_end() - logger.shutdown() - - # check nothing got output to stdout/stderr - # (note that mozlog outputs exceptions during handling to stderr!) - captured = capfd.readouterr() - assert captured.out == "" - assert captured.err == "" - - # check the actual output of the formatter - output.seek(0) - output_json = json.load(output) - - test_obj = output_json["tests"]["t1"] - # Both actual and expected statuses get mapped to Pass. No extra whitespace - # anywhere. - assert test_obj["actual"] == "PASS" - assert test_obj["expected"] == "PASS" - - -def test_known_intermittent_duplicate(logger, capfd): - # We don't want to have duplicate statuses in the final "expected" field. - - # Set up the handler. - output = StringIO() - logger.add_handler(handlers.StreamHandler(output, ChromiumFormatter())) - - # There are two duplications in this input: - # 1. known_intermittent already contains expected; - # 2. both statuses in known_intermittent map to FAIL in Chromium. - # In the end, we should only get one FAIL in Chromium "expected". - logger.suite_start(["t1"], run_info={}, time=123) - logger.test_start("t1") - logger.test_end("t1", status="ERROR", expected="ERROR", known_intermittent=["FAIL", "ERROR"]) - logger.suite_end() - logger.shutdown() - - # Check nothing got output to stdout/stderr. - # (Note that mozlog outputs exceptions during handling to stderr!) - captured = capfd.readouterr() - assert captured.out == "" - assert captured.err == "" - - # Check the actual output of the formatter. - output.seek(0) - output_json = json.load(output) - - test_obj = output_json["tests"]["t1"] - assert test_obj["actual"] == "FAIL" - # No duplicate "FAIL" in "expected". - assert test_obj["expected"] == "FAIL" - - -def test_reftest_screenshots(logger, capfd): - # reftest_screenshots, if present, should be plumbed into artifacts. - - # Set up the handler. - output = StringIO() - logger.add_handler(handlers.StreamHandler(output, ChromiumFormatter())) - - # Run a reftest with reftest_screenshots. - logger.suite_start(["t1"], run_info={}, time=123) - logger.test_start("t1") - logger.test_end("t1", status="FAIL", expected="PASS", extra={ - "reftest_screenshots": [ - {"url": "foo.html", "hash": "HASH1", "screenshot": "DATA1"}, - "!=", - {"url": "foo-ref.html", "hash": "HASH2", "screenshot": "DATA2"}, - ] - }) - logger.suite_end() - logger.shutdown() - - # check nothing got output to stdout/stderr - # (note that mozlog outputs exceptions during handling to stderr!) - captured = capfd.readouterr() - assert captured.out == "" - assert captured.err == "" - - # check the actual output of the formatter - output.seek(0) - output_json = json.load(output) - - test_obj = output_json["tests"]["t1"] - assert test_obj["artifacts"]["screenshots"] == [ - "foo.html: DATA1", - "foo-ref.html: DATA2", - ] - - -def test_process_output_crashing_test(logger, capfd): - """Test that chromedriver logs are preserved for crashing tests""" - - # Set up the handler. - output = StringIO() - logger.add_handler(handlers.StreamHandler(output, ChromiumFormatter())) - - logger.suite_start(["t1", "t2", "t3"], run_info={}, time=123) - - logger.test_start("t1") - logger.process_output(100, "This message should be recorded", "/some/path/to/chromedriver --some-flag") - logger.process_output(101, "This message should not be recorded", "/some/other/process --another-flag") - logger.process_output(100, "This message should also be recorded", "/some/path/to/chromedriver --some-flag") - logger.test_end("t1", status="CRASH", expected="CRASH") - - logger.test_start("t2") - logger.process_output(100, "Another message for the second test", "/some/path/to/chromedriver --some-flag") - logger.test_end("t2", status="CRASH", expected="PASS") - - logger.test_start("t3") - logger.process_output(100, "This test fails", "/some/path/to/chromedriver --some-flag") - logger.process_output(100, "But the output should not be captured", "/some/path/to/chromedriver --some-flag") - logger.process_output(100, "Because it does not crash", "/some/path/to/chromedriver --some-flag") - logger.test_end("t3", status="FAIL", expected="PASS") - - logger.suite_end() - logger.shutdown() - - # check nothing got output to stdout/stderr - # (note that mozlog outputs exceptions during handling to stderr!) - captured = capfd.readouterr() - assert captured.out == "" - assert captured.err == "" - - # check the actual output of the formatter - output.seek(0) - output_json = json.load(output) - - test_obj = output_json["tests"]["t1"] - assert test_obj["artifacts"]["wpt_crash_log"] == [ - "This message should be recorded", - "This message should also be recorded" - ] - - test_obj = output_json["tests"]["t2"] - assert test_obj["artifacts"]["wpt_crash_log"] == [ - "Another message for the second test" - ] - - test_obj = output_json["tests"]["t3"] - assert "wpt_crash_log" not in test_obj["artifacts"] diff --git a/tests/wpt/tests/tools/wptrunner/wptrunner/testdriver-extra.js b/tests/wpt/tests/tools/wptrunner/wptrunner/testdriver-extra.js index ab8b04ba3cd..3c2dd8b42dd 100644 --- a/tests/wpt/tests/tools/wptrunner/wptrunner/testdriver-extra.js +++ b/tests/wpt/tests/tools/wptrunner/wptrunner/testdriver-extra.js @@ -263,11 +263,14 @@ window.test_driver_internal.bidi.emulation.set_geolocation_override = function (params) { + if ('coordinates' in params && 'error' in params) { + throw new Error( + "`coordinates` and `error` are mutually exclusive in set_geolocation_override"); + } + return create_action("bidi.emulation.set_geolocation_override", { // Default to the current window. contexts: [window], - // Default to no coordinates. - coordinates: null, ...(params ?? {}) }); } diff --git a/tests/wpt/tests/tools/wptrunner/wptrunner/testrunner.py b/tests/wpt/tests/tools/wptrunner/wptrunner/testrunner.py index 64668fc470b..2c2da790e90 100644 --- a/tests/wpt/tests/tools/wptrunner/wptrunner/testrunner.py +++ b/tests/wpt/tests/tools/wptrunner/wptrunner/testrunner.py @@ -601,7 +601,7 @@ class TestRunnerManager(threading.Thread): assert self.browser.browser is not None self.browser.browser.cleanup() impl = self.test_implementations[(self.state.subsuite, self.state.test_type)] - browser = impl.browser_cls(self.logger, remote_queue=self.command_queue, + browser = impl.browser_cls(self.logger, **impl.browser_kwargs) browser.setup() self.browser = BrowserManager(self.logger, diff --git a/tests/wpt/tests/tools/wptrunner/wptrunner/wptcommandline.py b/tests/wpt/tests/tools/wptrunner/wptrunner/wptcommandline.py index 647cc7f21b1..7bfeef31717 100644 --- a/tests/wpt/tests/tools/wptrunner/wptrunner/wptcommandline.py +++ b/tests/wpt/tests/tools/wptrunner/wptrunner/wptcommandline.py @@ -11,7 +11,7 @@ from typing import Mapping, Optional from . import config from . import products from . import wpttest -from .formatters import chromium, wptreport, wptscreenshot +from .formatters import wptreport, wptscreenshot def abs_path(path): @@ -418,7 +418,6 @@ scheme host and port.""") "Cache API (default: %s)" % wptscreenshot.DEFAULT_API, {"wptscreenshot"}, "store") - commandline.log_formatters["chromium"] = (chromium.ChromiumFormatter, "Chromium Layout Tests format") commandline.log_formatters["wptreport"] = (wptreport.WptreportFormatter, "wptreport format") commandline.log_formatters["wptscreenshot"] = (wptscreenshot.WptscreenshotFormatter, "wpt.fyi screenshots") diff --git a/tests/wpt/tests/wasm/core/js/simd/simd_select.wast.js b/tests/wpt/tests/wasm/core/js/simd/simd_select.wast.js new file mode 100644 index 00000000000..e997d81f75a --- /dev/null +++ b/tests/wpt/tests/wasm/core/js/simd/simd_select.wast.js @@ -0,0 +1,24 @@ +(function simd_select_wast_js() { + +// simd_select.wast:3 +let $1 = instance("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\x88\x80\x80\x80\x00\x01\x60\x03\x7b\x7b\x7f\x01\x7b\x03\x82\x80\x80\x80\x00\x01\x00\x07\x93\x80\x80\x80\x00\x01\x0f\x73\x65\x6c\x65\x63\x74\x5f\x76\x31\x32\x38\x5f\x69\x33\x32\x00\x00\x0a\x8f\x80\x80\x80\x00\x01\x89\x80\x80\x80\x00\x00\x20\x00\x20\x01\x20\x02\x1b\x0b"); + +// simd_select.wast:9 +run(() => call(instance("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\xa6\x80\x80\x80\x00\x07\x60\x00\x00\x60\x01\x7f\x01\x6f\x60\x01\x6f\x01\x7f\x60\x01\x70\x01\x7f\x60\x02\x6f\x6f\x01\x7f\x60\x02\x70\x70\x01\x7f\x60\x03\x7b\x7b\x7f\x01\x7b\x02\x8b\x81\x80\x80\x00\x06\x06\x6d\x6f\x64\x75\x6c\x65\x0f\x73\x65\x6c\x65\x63\x74\x5f\x76\x31\x32\x38\x5f\x69\x33\x32\x00\x06\x08\x73\x70\x65\x63\x74\x65\x73\x74\x09\x65\x78\x74\x65\x72\x6e\x72\x65\x66\x00\x01\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0c\x69\x73\x5f\x65\x78\x74\x65\x72\x6e\x72\x65\x66\x00\x02\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0a\x69\x73\x5f\x66\x75\x6e\x63\x72\x65\x66\x00\x03\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0c\x65\x71\x5f\x65\x78\x74\x65\x72\x6e\x72\x65\x66\x00\x04\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0a\x65\x71\x5f\x66\x75\x6e\x63\x72\x65\x66\x00\x05\x03\x82\x80\x80\x80\x00\x01\x00\x07\x87\x80\x80\x80\x00\x01\x03\x72\x75\x6e\x00\x06\x0a\xe2\x80\x80\x80\x00\x01\xdc\x80\x80\x80\x00\x00\x02\x40\xfd\x0c\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\xfd\x0c\x05\x00\x00\x00\x06\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00\x00\x41\x01\x10\x00\xfd\x0c\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfd\x4e\xfd\x0c\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\xfd\x23\xfd\x63\x45\x0d\x00\x0f\x0b\x00\x0b", exports($1)), "run", [])); // assert_return(() => call($1, "select_v128_i32", [v128("1 2 3 4"), v128("5 6 7 8"), 1]), v128("1 2 3 4")) + +// simd_select.wast:18 +run(() => call(instance("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\xa6\x80\x80\x80\x00\x07\x60\x00\x00\x60\x01\x7f\x01\x6f\x60\x01\x6f\x01\x7f\x60\x01\x70\x01\x7f\x60\x02\x6f\x6f\x01\x7f\x60\x02\x70\x70\x01\x7f\x60\x03\x7b\x7b\x7f\x01\x7b\x02\x8b\x81\x80\x80\x00\x06\x06\x6d\x6f\x64\x75\x6c\x65\x0f\x73\x65\x6c\x65\x63\x74\x5f\x76\x31\x32\x38\x5f\x69\x33\x32\x00\x06\x08\x73\x70\x65\x63\x74\x65\x73\x74\x09\x65\x78\x74\x65\x72\x6e\x72\x65\x66\x00\x01\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0c\x69\x73\x5f\x65\x78\x74\x65\x72\x6e\x72\x65\x66\x00\x02\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0a\x69\x73\x5f\x66\x75\x6e\x63\x72\x65\x66\x00\x03\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0c\x65\x71\x5f\x65\x78\x74\x65\x72\x6e\x72\x65\x66\x00\x04\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0a\x65\x71\x5f\x66\x75\x6e\x63\x72\x65\x66\x00\x05\x03\x82\x80\x80\x80\x00\x01\x00\x07\x87\x80\x80\x80\x00\x01\x03\x72\x75\x6e\x00\x06\x0a\xe2\x80\x80\x80\x00\x01\xdc\x80\x80\x80\x00\x00\x02\x40\xfd\x0c\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\xfd\x0c\x05\x00\x00\x00\x06\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00\x00\x41\x00\x10\x00\xfd\x0c\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfd\x4e\xfd\x0c\x05\x00\x00\x00\x06\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00\x00\xfd\x23\xfd\x63\x45\x0d\x00\x0f\x0b\x00\x0b", exports($1)), "run", [])); // assert_return(() => call($1, "select_v128_i32", [v128("1 2 3 4"), v128("5 6 7 8"), 0]), v128("5 6 7 8")) + +// simd_select.wast:27 +run(() => call(instance("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\xa6\x80\x80\x80\x00\x07\x60\x00\x00\x60\x01\x7f\x01\x6f\x60\x01\x6f\x01\x7f\x60\x01\x70\x01\x7f\x60\x02\x6f\x6f\x01\x7f\x60\x02\x70\x70\x01\x7f\x60\x03\x7b\x7b\x7f\x01\x7b\x02\x8b\x81\x80\x80\x00\x06\x06\x6d\x6f\x64\x75\x6c\x65\x0f\x73\x65\x6c\x65\x63\x74\x5f\x76\x31\x32\x38\x5f\x69\x33\x32\x00\x06\x08\x73\x70\x65\x63\x74\x65\x73\x74\x09\x65\x78\x74\x65\x72\x6e\x72\x65\x66\x00\x01\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0c\x69\x73\x5f\x65\x78\x74\x65\x72\x6e\x72\x65\x66\x00\x02\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0a\x69\x73\x5f\x66\x75\x6e\x63\x72\x65\x66\x00\x03\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0c\x65\x71\x5f\x65\x78\x74\x65\x72\x6e\x72\x65\x66\x00\x04\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0a\x65\x71\x5f\x66\x75\x6e\x63\x72\x65\x66\x00\x05\x03\x82\x80\x80\x80\x00\x01\x00\x07\x87\x80\x80\x80\x00\x01\x03\x72\x75\x6e\x00\x06\x0a\xe2\x80\x80\x80\x00\x01\xdc\x80\x80\x80\x00\x00\x02\x40\xfd\x0c\x00\x00\x80\x3f\x00\x00\x00\x40\x00\x00\x40\x40\x00\x00\x80\x40\xfd\x0c\x00\x00\xa0\x40\x00\x00\xc0\x40\x00\x00\xe0\x40\x00\x00\x00\x41\x41\x7f\x10\x00\xfd\x0c\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfd\x4e\xfd\x0c\x00\x00\x80\x3f\x00\x00\x00\x40\x00\x00\x40\x40\x00\x00\x80\x40\xfd\x23\xfd\x63\x45\x0d\x00\x0f\x0b\x00\x0b", exports($1)), "run", [])); // assert_return(() => call($1, "select_v128_i32", [v128("1_065_353_216 1_073_741_824 1_077_936_128 1_082_130_432"), v128("1_084_227_584 1_086_324_736 1_088_421_888 1_090_519_040"), -1]), v128("1. 2. 3. 4.")) + +// simd_select.wast:36 +run(() => call(instance("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\xa6\x80\x80\x80\x00\x07\x60\x00\x00\x60\x01\x7f\x01\x6f\x60\x01\x6f\x01\x7f\x60\x01\x70\x01\x7f\x60\x02\x6f\x6f\x01\x7f\x60\x02\x70\x70\x01\x7f\x60\x03\x7b\x7b\x7f\x01\x7b\x02\x8b\x81\x80\x80\x00\x06\x06\x6d\x6f\x64\x75\x6c\x65\x0f\x73\x65\x6c\x65\x63\x74\x5f\x76\x31\x32\x38\x5f\x69\x33\x32\x00\x06\x08\x73\x70\x65\x63\x74\x65\x73\x74\x09\x65\x78\x74\x65\x72\x6e\x72\x65\x66\x00\x01\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0c\x69\x73\x5f\x65\x78\x74\x65\x72\x6e\x72\x65\x66\x00\x02\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0a\x69\x73\x5f\x66\x75\x6e\x63\x72\x65\x66\x00\x03\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0c\x65\x71\x5f\x65\x78\x74\x65\x72\x6e\x72\x65\x66\x00\x04\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0a\x65\x71\x5f\x66\x75\x6e\x63\x72\x65\x66\x00\x05\x03\x82\x80\x80\x80\x00\x01\x00\x07\x87\x80\x80\x80\x00\x01\x03\x72\x75\x6e\x00\x06\x0a\xe2\x80\x80\x80\x00\x01\xdc\x80\x80\x80\x00\x00\x02\x40\xfd\x0c\x00\x00\xc0\xbf\x00\x00\x20\xc0\x00\x00\x60\xc0\x00\x00\x90\xc0\xfd\x0c\x00\x00\x18\x41\x00\x00\x08\x41\x00\x00\xf0\x40\x00\x00\xd0\x40\x41\x00\x10\x00\xfd\x0c\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfd\x4e\xfd\x0c\x00\x00\x18\x41\x00\x00\x08\x41\x00\x00\xf0\x40\x00\x00\xd0\x40\xfd\x23\xfd\x63\x45\x0d\x00\x0f\x0b\x00\x0b", exports($1)), "run", [])); // assert_return(() => call($1, "select_v128_i32", [v128("-1_077_936_128 -1_071_644_672 -1_067_450_368 -1_064_304_640"), v128("1_092_091_904 1_091_043_328 1_089_470_464 1_087_373_312"), 0]), v128("9.5 8.5 7.5 6.5")) + +// simd_select.wast:45 +run(() => call(instance("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\xa6\x80\x80\x80\x00\x07\x60\x00\x00\x60\x01\x7f\x01\x6f\x60\x01\x6f\x01\x7f\x60\x01\x70\x01\x7f\x60\x02\x6f\x6f\x01\x7f\x60\x02\x70\x70\x01\x7f\x60\x03\x7b\x7b\x7f\x01\x7b\x02\x8b\x81\x80\x80\x00\x06\x06\x6d\x6f\x64\x75\x6c\x65\x0f\x73\x65\x6c\x65\x63\x74\x5f\x76\x31\x32\x38\x5f\x69\x33\x32\x00\x06\x08\x73\x70\x65\x63\x74\x65\x73\x74\x09\x65\x78\x74\x65\x72\x6e\x72\x65\x66\x00\x01\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0c\x69\x73\x5f\x65\x78\x74\x65\x72\x6e\x72\x65\x66\x00\x02\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0a\x69\x73\x5f\x66\x75\x6e\x63\x72\x65\x66\x00\x03\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0c\x65\x71\x5f\x65\x78\x74\x65\x72\x6e\x72\x65\x66\x00\x04\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0a\x65\x71\x5f\x66\x75\x6e\x63\x72\x65\x66\x00\x05\x03\x82\x80\x80\x80\x00\x01\x00\x07\x87\x80\x80\x80\x00\x01\x03\x72\x75\x6e\x00\x06\x0a\xe3\x80\x80\x80\x00\x01\xdd\x80\x80\x80\x00\x00\x02\x40\xfd\x0c\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\xfd\x0c\x10\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x41\xfb\x00\x10\x00\xfd\x0c\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfd\x4e\xfd\x0c\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\xfd\x23\xfd\x63\x45\x0d\x00\x0f\x0b\x00\x0b", exports($1)), "run", [])); // assert_return(() => call($1, "select_v128_i32", [v128("67_305_985 134_678_021 202_050_057 269_422_093"), v128("219_025_168 151_653_132 84_281_096 16_909_060"), 123]), v128("1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16")) + +// simd_select.wast:54 +run(() => call(instance("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\xa6\x80\x80\x80\x00\x07\x60\x00\x00\x60\x01\x7f\x01\x6f\x60\x01\x6f\x01\x7f\x60\x01\x70\x01\x7f\x60\x02\x6f\x6f\x01\x7f\x60\x02\x70\x70\x01\x7f\x60\x03\x7b\x7b\x7f\x01\x7b\x02\x8b\x81\x80\x80\x00\x06\x06\x6d\x6f\x64\x75\x6c\x65\x0f\x73\x65\x6c\x65\x63\x74\x5f\x76\x31\x32\x38\x5f\x69\x33\x32\x00\x06\x08\x73\x70\x65\x63\x74\x65\x73\x74\x09\x65\x78\x74\x65\x72\x6e\x72\x65\x66\x00\x01\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0c\x69\x73\x5f\x65\x78\x74\x65\x72\x6e\x72\x65\x66\x00\x02\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0a\x69\x73\x5f\x66\x75\x6e\x63\x72\x65\x66\x00\x03\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0c\x65\x71\x5f\x65\x78\x74\x65\x72\x6e\x72\x65\x66\x00\x04\x08\x73\x70\x65\x63\x74\x65\x73\x74\x0a\x65\x71\x5f\x66\x75\x6e\x63\x72\x65\x66\x00\x05\x03\x82\x80\x80\x80\x00\x01\x00\x07\x87\x80\x80\x80\x00\x01\x03\x72\x75\x6e\x00\x06\x0a\xe2\x80\x80\x80\x00\x01\xdc\x80\x80\x80\x00\x00\x02\x40\xfd\x0c\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\xfd\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x41\x00\x10\x00\xfd\x0c\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfd\x4e\xfd\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfd\x23\xfd\x63\x45\x0d\x00\x0f\x0b\x00\x0b", exports($1)), "run", [])); // assert_return(() => call($1, "select_v128_i32", [v128("16_843_009 16_843_009 16_843_009 16_843_009"), v128("0 0 0 0"), 0]), v128("0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0")) +reinitializeRegistry(); +})(); diff --git a/tests/wpt/tests/wasm/core/simd/simd_select.wast.js.html b/tests/wpt/tests/wasm/core/simd/simd_select.wast.js.html new file mode 100644 index 00000000000..5d50138374b --- /dev/null +++ b/tests/wpt/tests/wasm/core/simd/simd_select.wast.js.html @@ -0,0 +1,16 @@ +<!doctype html> +<html> + <head> + <meta charset="UTF-8"> + <title>WebAssembly Web Platform Test</title> + </head> + <body> + + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="../js/harness/async_index.js"></script> + + <div id=log></div> + <script src="../js/simd/simd_select.wast.js"></script> + </body> +</html> diff --git a/tests/wpt/tests/webdriver/tests/bidi/__init__.py b/tests/wpt/tests/webdriver/tests/bidi/__init__.py index 955335ea87e..587dc91a938 100644 --- a/tests/wpt/tests/webdriver/tests/bidi/__init__.py +++ b/tests/wpt/tests/webdriver/tests/bidi/__init__.py @@ -132,7 +132,7 @@ def assert_handle(obj: Mapping[str, Any], should_contain_handle: bool) -> None: assert "handle" not in obj, f"Result should not contain `handle`. Actual: {obj}" -async def create_console_api_message(bidi_session, context: str, text: str): +async def create_console_api_message(bidi_session, context: Any, text: str): await bidi_session.script.call_function( function_declaration="""(text) => console.log(text)""", arguments=[{"type": "string", "value": text}], @@ -142,7 +142,7 @@ async def create_console_api_message(bidi_session, context: str, text: str): return text -async def get_device_pixel_ratio(bidi_session, context: str) -> float: +async def get_device_pixel_ratio(bidi_session, context: Any) -> float: result = await bidi_session.script.call_function( function_declaration="""() => { return window.devicePixelRatio; @@ -167,7 +167,7 @@ async def get_element_dimensions(bidi_session, context, element): return remote_mapping_to_dict(result["value"]) -async def get_viewport_dimensions(bidi_session, context: str, +async def get_viewport_dimensions(bidi_session, context: Any, with_scrollbar: bool = True, quirk_mode: bool = False): if with_scrollbar: expression = """ @@ -198,7 +198,7 @@ async def get_viewport_dimensions(bidi_session, context: str, return remote_mapping_to_dict(result["value"]) -async def get_document_dimensions(bidi_session, context: str): +async def get_document_dimensions(bidi_session, context: Any): expression = """ ({ height: document.documentElement.scrollHeight, diff --git a/tests/wpt/tests/webdriver/tests/bidi/browsing_context/set_viewport/user_contexts.py b/tests/wpt/tests/webdriver/tests/bidi/browsing_context/set_viewport/user_contexts.py index 64c26163734..fa1e748d6b2 100644 --- a/tests/wpt/tests/webdriver/tests/bidi/browsing_context/set_viewport/user_contexts.py +++ b/tests/wpt/tests/webdriver/tests/bidi/browsing_context/set_viewport/user_contexts.py @@ -1,11 +1,13 @@ import pytest +from webdriver.bidi.modules.script import ContextTarget from webdriver.bidi.undefined import UNDEFINED from ... import get_device_pixel_ratio, get_viewport_dimensions - pytestmark = pytest.mark.asyncio +CONTEXT_LOAD_EVENT = "browsingContext.load" + async def test_set_to_user_context(bidi_session, new_tab, create_user_context): user_context = await create_user_context() @@ -55,6 +57,48 @@ async def test_set_to_user_context(bidi_session, new_tab, create_user_context): ) +async def test_set_to_user_context_window_open( + bidi_session, + new_tab, + create_user_context, + inline, + subscribe_events, + wait_for_event, + wait_for_future_safe, +): + user_context = await create_user_context() + context_in_user_context_1 = await bidi_session.browsing_context.create( + user_context=user_context, type_hint="tab" + ) + + test_viewport = {"width": 250, "height": 300} + await bidi_session.browsing_context.set_viewport( + user_contexts=[user_context], viewport=test_viewport + ) + assert ( + await get_viewport_dimensions(bidi_session, context_in_user_context_1) + == test_viewport + ) + + await subscribe_events(events=[CONTEXT_LOAD_EVENT]) + + # Assert that tabs opened via window.open in the same user context + # successfully load and have the right viewport set. + on_load = wait_for_event(CONTEXT_LOAD_EVENT) + result = await bidi_session.script.evaluate( + await_promise=False, + expression=f"""window.open('{inline("popup")}')""", + target=ContextTarget(context_in_user_context_1["context"]), + ) + event = await wait_for_future_safe(on_load) + + contexts = await bidi_session.browsing_context.get_tree(root=event["context"]) + assert len(contexts) == 1 + popup_context = contexts[0] + + assert await get_viewport_dimensions(bidi_session, popup_context) == test_viewport + + async def test_set_to_default_user_context(bidi_session, new_tab, create_user_context): user_context = await create_user_context() context_in_user_context_1 = await bidi_session.browsing_context.create( diff --git a/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/__init__.py b/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/__init__.py index 33dcca90f04..5e1fd1aa863 100644 --- a/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/__init__.py +++ b/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/__init__.py @@ -1,28 +1 @@ -from webdriver.bidi.modules.script import ContextTarget - -from ... import remote_mapping_to_dict - - TEST_COORDINATES = {"latitude": 10, "longitude": 15, "accuracy": 0.5} - - -async def get_current_geolocation(bidi_session, context): - # Per geolocation spec, the geolocation coordinates are returned - # only for an active browsing context. It might be required to - # re-activate the previously active tab in the test. - await bidi_session.browsing_context.activate(context=context["context"]) - - result = await bidi_session.script.call_function( - function_declaration="""() => - new Promise( - resolve => window.navigator.geolocation.getCurrentPosition( - position => resolve(position.coords.toJSON()), - error => resolve({code: error.code, message: error.message}), - {timeout: 500} - )) - """, - target=ContextTarget(context["context"]), - await_promise=True, - ) - - return remote_mapping_to_dict(result["value"]) diff --git a/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/conftest.py b/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/conftest.py index a90895c74ed..5fb9451f0a2 100644 --- a/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/conftest.py +++ b/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/conftest.py @@ -1,6 +1,35 @@ import pytest_asyncio -from ... import get_context_origin +from webdriver.bidi.modules.script import ContextTarget + +from ... import get_context_origin, remote_mapping_to_dict + + +@pytest_asyncio.fixture +async def get_current_geolocation(bidi_session, configuration): + async def get_current_geolocation(context): + # Per geolocation spec, the geolocation coordinates are returned + # only for an active browsing context. It might be required to + # re-activate the previously active tab in the test. + await bidi_session.browsing_context.activate(context=context["context"]) + + result = await bidi_session.script.call_function( + function_declaration="""(multiplier) => + new Promise( + resolve => window.navigator.geolocation.getCurrentPosition( + position => resolve(position.coords.toJSON()), + error => resolve({code: error.code}), + {timeout: 500 * multiplier} + )) + """, + arguments=[{"type": "number", "value": configuration["timeout_multiplier"]}], + target=ContextTarget(context["context"]), + await_promise=True, + ) + + return remote_mapping_to_dict(result["value"]) + + return get_current_geolocation @pytest_asyncio.fixture diff --git a/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/contexts.py b/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/contexts.py index 8a0e43475da..068bb804e73 100644 --- a/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/contexts.py +++ b/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/contexts.py @@ -2,14 +2,14 @@ import pytest from webdriver.bidi.modules.emulation import CoordinatesOptions -from . import get_current_geolocation, TEST_COORDINATES +from . import TEST_COORDINATES pytestmark = pytest.mark.asyncio async def test_contexts( - bidi_session, new_tab, top_context, url, set_geolocation_permission + bidi_session, new_tab, top_context, url, get_current_geolocation, set_geolocation_permission ): test_url = url("/common/blank.html") await bidi_session.browsing_context.navigate( @@ -24,12 +24,10 @@ async def test_contexts( ) await set_geolocation_permission(new_tab) - default_coordinates = await get_current_geolocation(bidi_session, new_tab) + default_coordinates = await get_current_geolocation(new_tab) assert default_coordinates != TEST_COORDINATES - assert ( - await get_current_geolocation(bidi_session, top_context) == default_coordinates - ) + assert await get_current_geolocation(top_context) == default_coordinates # Set geolocation override. await bidi_session.emulation.set_geolocation_override( @@ -41,24 +39,20 @@ async def test_contexts( ), ) - assert await get_current_geolocation(bidi_session, new_tab) == TEST_COORDINATES - assert ( - await get_current_geolocation(bidi_session, top_context) == default_coordinates - ) + assert await get_current_geolocation(new_tab) == TEST_COORDINATES + assert await get_current_geolocation(top_context) == default_coordinates # Reset geolocation override. await bidi_session.emulation.set_geolocation_override( contexts=[new_tab["context"]], coordinates=None ) - assert await get_current_geolocation(bidi_session, new_tab) == default_coordinates - assert ( - await get_current_geolocation(bidi_session, top_context) == default_coordinates - ) + assert await get_current_geolocation(new_tab) == default_coordinates + assert await get_current_geolocation(top_context) == default_coordinates async def test_multiple_contexts( - bidi_session, new_tab, url, set_geolocation_permission + bidi_session, new_tab, url, get_current_geolocation, set_geolocation_permission ): new_context = await bidi_session.browsing_context.create(type_hint="tab") test_url = url("/common/blank.html") @@ -74,12 +68,10 @@ async def test_multiple_contexts( ) await set_geolocation_permission(new_tab) - default_coordinates = await get_current_geolocation(bidi_session, new_tab) + default_coordinates = await get_current_geolocation(new_tab) assert default_coordinates != TEST_COORDINATES - assert ( - await get_current_geolocation(bidi_session, new_context) == default_coordinates - ) + assert await get_current_geolocation(new_context) == default_coordinates # Set geolocation override. await bidi_session.emulation.set_geolocation_override( @@ -91,8 +83,8 @@ async def test_multiple_contexts( ), ) - assert await get_current_geolocation(bidi_session, new_tab) == TEST_COORDINATES - assert await get_current_geolocation(bidi_session, new_context) == TEST_COORDINATES + assert await get_current_geolocation(new_tab) == TEST_COORDINATES + assert await get_current_geolocation(new_context) == TEST_COORDINATES # Reset geolocation override. await bidi_session.emulation.set_geolocation_override( @@ -101,7 +93,5 @@ async def test_multiple_contexts( # The new coordinates can be different from the initial ones if the position # was not available at the beginning. - assert await get_current_geolocation(bidi_session, new_tab) != TEST_COORDINATES - assert ( - await get_current_geolocation(bidi_session, new_context) != TEST_COORDINATES - ) + assert await get_current_geolocation(new_tab) != TEST_COORDINATES + assert await get_current_geolocation(new_context) != TEST_COORDINATES diff --git a/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/coordinates.py b/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/coordinates.py index b23354e9e42..ea4fe643c3e 100644 --- a/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/coordinates.py +++ b/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/coordinates.py @@ -4,7 +4,6 @@ from webdriver.bidi.modules.emulation import CoordinatesOptions from webdriver.bidi.modules.script import ContextTarget from ... import remote_mapping_to_dict -from . import get_current_geolocation pytestmark = pytest.mark.asyncio @@ -22,7 +21,12 @@ pytestmark = pytest.mark.asyncio ], ) async def test_get_current_position( - bidi_session, new_tab, url, set_geolocation_permission, test_coordinates + bidi_session, + new_tab, + url, + get_current_geolocation, + set_geolocation_permission, + test_coordinates, ): test_url = url("/common/blank.html") await bidi_session.browsing_context.navigate( @@ -32,7 +36,7 @@ async def test_get_current_position( ) await set_geolocation_permission(new_tab) - default_coordinates = await get_current_geolocation(bidi_session, new_tab) + default_coordinates = await get_current_geolocation(new_tab) # Set default accuracy value. if "accuracy" not in test_coordinates: @@ -44,7 +48,7 @@ async def test_get_current_position( contexts=[new_tab["context"]], coordinates=test_coordinates ) - assert await get_current_geolocation(bidi_session, new_tab) == test_coordinates + assert await get_current_geolocation(new_tab) == test_coordinates async def test_watch_position( @@ -132,7 +136,7 @@ async def test_watch_position( async def test_persists_on_reload( - bidi_session, url, new_tab, set_geolocation_permission + bidi_session, url, new_tab, get_current_geolocation, set_geolocation_permission ): test_url = url("/common/blank.html") await bidi_session.browsing_context.navigate( @@ -153,17 +157,17 @@ async def test_persists_on_reload( ), ) - assert await get_current_geolocation(bidi_session, new_tab) == test_coordinates + assert await get_current_geolocation(new_tab) == test_coordinates await bidi_session.browsing_context.reload( context=new_tab["context"], wait="complete" ) - assert await get_current_geolocation(bidi_session, new_tab) == test_coordinates + assert await get_current_geolocation(new_tab) == test_coordinates async def test_persists_on_navigation( - bidi_session, url, new_tab, set_geolocation_permission + bidi_session, url, new_tab, get_current_geolocation, set_geolocation_permission ): test_url = url("/common/blank.html") await bidi_session.browsing_context.navigate( @@ -184,7 +188,7 @@ async def test_persists_on_navigation( ), ) - assert await get_current_geolocation(bidi_session, new_tab) == test_coordinates + assert await get_current_geolocation(new_tab) == test_coordinates await bidi_session.browsing_context.navigate( context=new_tab["context"], @@ -192,4 +196,24 @@ async def test_persists_on_navigation( wait="complete", ) - assert await get_current_geolocation(bidi_session, new_tab) == test_coordinates + assert await get_current_geolocation(new_tab) == test_coordinates + + +async def test_reset_without_override( + bidi_session, new_tab, url, get_current_geolocation, set_geolocation_permission +): + test_url = url("/common/blank.html") + await bidi_session.browsing_context.navigate( + context=new_tab["context"], + url=test_url, + wait="complete", + ) + await set_geolocation_permission(new_tab) + + default_coordinates = await get_current_geolocation(new_tab) + + await bidi_session.emulation.set_geolocation_override( + contexts=[new_tab["context"]], coordinates=None + ) + + assert await get_current_geolocation(new_tab) == default_coordinates diff --git a/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/error.py b/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/error.py new file mode 100644 index 00000000000..27c23dc7811 --- /dev/null +++ b/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/error.py @@ -0,0 +1,123 @@ +import pytest + +from webdriver.bidi.modules.script import ContextTarget + +from ... import remote_mapping_to_dict + +pytestmark = pytest.mark.asyncio + +ERROR = {"type": "positionUnavailable"} +EXPECTED_ERROR = {"code": 2} + + +async def test_get_current_position(bidi_session, new_tab, url, + get_current_geolocation, set_geolocation_permission): + test_url = url("/common/blank.html") + await bidi_session.browsing_context.navigate( + context=new_tab["context"], + url=test_url, + wait="complete", + ) + await set_geolocation_permission(new_tab) + + await bidi_session.emulation.set_geolocation_override( + contexts=[new_tab["context"]], error=ERROR + ) + + assert await get_current_geolocation(new_tab) == EXPECTED_ERROR + + +async def test_watch_position( + bidi_session, + new_tab, + url, + subscribe_events, + wait_for_event, + wait_for_future_safe, + set_geolocation_permission, +): + await subscribe_events(["script.message"]) + + test_url = url("/common/blank.html") + await bidi_session.browsing_context.navigate( + context=new_tab["context"], + url=test_url, + wait="complete", + ) + await set_geolocation_permission(new_tab) + + await bidi_session.emulation.set_geolocation_override( + contexts=[new_tab["context"]], + error=ERROR + ) + + on_script_message = wait_for_event("script.message") + await bidi_session.browsing_context.activate(context=new_tab["context"]) + await bidi_session.script.call_function( + arguments=[{"type": "channel", "value": {"channel": "channel_name"}}], + function_declaration="""(channel) => + window.navigator.geolocation.watchPosition( + (result) => channel("unexpected result"), + (error) => channel({code: error.code}) + ) + """, + target=ContextTarget(new_tab["context"]), + await_promise=False, + ) + event_data = await wait_for_future_safe(on_script_message) + + assert remote_mapping_to_dict(event_data["data"]["value"]) == EXPECTED_ERROR + + +async def test_persists_on_reload( + bidi_session, url, new_tab, get_current_geolocation, set_geolocation_permission +): + test_url = url("/common/blank.html") + await bidi_session.browsing_context.navigate( + context=new_tab["context"], + url=test_url, + wait="complete", + ) + await set_geolocation_permission(new_tab) + + # Set geolocation override. + await bidi_session.emulation.set_geolocation_override( + contexts=[new_tab["context"]], + error=ERROR, + ) + + assert await get_current_geolocation(new_tab) == EXPECTED_ERROR + + await bidi_session.browsing_context.reload( + context=new_tab["context"], wait="complete" + ) + + assert await get_current_geolocation(new_tab) == EXPECTED_ERROR + + +async def test_persists_on_navigation( + bidi_session, url, new_tab, get_current_geolocation, set_geolocation_permission +): + test_url = url("/common/blank.html") + await bidi_session.browsing_context.navigate( + context=new_tab["context"], + url=test_url, + wait="complete", + ) + await set_geolocation_permission(new_tab) + + # Set geolocation override. + await bidi_session.emulation.set_geolocation_override( + contexts=[new_tab["context"]], + error=ERROR, + ) + + assert await get_current_geolocation(new_tab) == EXPECTED_ERROR + + await bidi_session.browsing_context.navigate( + context=new_tab["context"], + url=url("/webdriver/tests/support/html/default.html"), + wait="complete", + ) + + assert await get_current_geolocation(new_tab) == EXPECTED_ERROR diff --git a/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/invalid.py b/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/invalid.py index e804848e0be..2334d37517d 100644 --- a/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/invalid.py +++ b/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/invalid.py @@ -2,7 +2,6 @@ import pytest import webdriver.bidi.error as error from webdriver.bidi.modules.emulation import CoordinatesOptions -from webdriver.bidi.undefined import UNDEFINED pytestmark = pytest.mark.asyncio @@ -75,7 +74,7 @@ async def test_params_contexts_iframe(bidi_session, new_tab, get_test_page): ) -@pytest.mark.parametrize("value", [UNDEFINED, False, 42, "foo", []]) +@pytest.mark.parametrize("value", [False, 42, "foo", []]) async def test_params_coordinates_invalid_type(bidi_session, top_context, value): with pytest.raises(error.InvalidArgumentException): await bidi_session.emulation.set_geolocation_override( @@ -333,3 +332,60 @@ async def test_params_user_contexts_entry_invalid_value(bidi_session, value): }, user_contexts=[value], ) + + +async def test_params_coordinates_and_error(bidi_session, top_context): + with pytest.raises(error.InvalidArgumentException): + await bidi_session.emulation.set_geolocation_override( + contexts=[top_context["context"]], + coordinates={ + "latitude": 10, + "longitude": 10, + }, + error={"type": "positionUnavailable"} + ) + + +async def test_params_no_coordinates_no_error(bidi_session, top_context): + with pytest.raises(error.InvalidArgumentException): + await bidi_session.emulation.set_geolocation_override( + contexts=[top_context["context"]], + ) + + +@pytest.mark.parametrize("value", [False, 42, "foo", []]) +async def test_params_error_invalid_type(bidi_session, top_context, value): + with pytest.raises(error.InvalidArgumentException): + await bidi_session.emulation.set_geolocation_override( + contexts=[top_context["context"]], + error=value, + ) + + +async def test_params_error_empty_object(bidi_session, top_context): + with pytest.raises(error.InvalidArgumentException): + await bidi_session.emulation.set_geolocation_override( + contexts=[top_context["context"]], + error={}, + ) + + +@pytest.mark.parametrize("value", [None, False, 42, {}, []]) +async def test_params_error_type_invalid_type(bidi_session, top_context, value): + with pytest.raises(error.InvalidArgumentException): + await bidi_session.emulation.set_geolocation_override( + contexts=[top_context["context"]], + error={ + "type": value + }, + ) + + +async def test_params_error_type_invalid_value(bidi_session, top_context): + with pytest.raises(error.InvalidArgumentException): + await bidi_session.emulation.set_geolocation_override( + contexts=[top_context["context"]], + error={ + "type": "unknownError", + }, + ) diff --git a/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/user_contexts.py b/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/user_contexts.py index 008dee5698d..b3038bb5958 100644 --- a/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/user_contexts.py +++ b/tests/wpt/tests/webdriver/tests/bidi/emulation/set_geolocation_override/user_contexts.py @@ -2,14 +2,19 @@ import pytest from webdriver.bidi.modules.emulation import CoordinatesOptions -from . import get_current_geolocation, TEST_COORDINATES +from . import TEST_COORDINATES pytestmark = pytest.mark.asyncio async def test_user_contexts( - bidi_session, url, create_user_context, new_tab, set_geolocation_permission + bidi_session, + url, + create_user_context, + new_tab, + get_current_geolocation, + set_geolocation_permission, ): user_context = await create_user_context() context_in_user_context_1 = await bidi_session.browsing_context.create( @@ -29,12 +34,10 @@ async def test_user_contexts( await set_geolocation_permission(new_tab) await set_geolocation_permission(new_tab, user_context) - default_coordinates = await get_current_geolocation( - bidi_session, context_in_user_context_1 - ) + default_coordinates = await get_current_geolocation(context_in_user_context_1) assert default_coordinates != TEST_COORDINATES - assert await get_current_geolocation(bidi_session, new_tab) == default_coordinates + assert await get_current_geolocation(new_tab) == default_coordinates # Set geolocation override. await bidi_session.emulation.set_geolocation_override( @@ -46,11 +49,8 @@ async def test_user_contexts( ), ) - assert ( - await get_current_geolocation(bidi_session, context_in_user_context_1) - == TEST_COORDINATES - ) - assert await get_current_geolocation(bidi_session, new_tab) == default_coordinates + assert await get_current_geolocation(context_in_user_context_1) == TEST_COORDINATES + assert await get_current_geolocation(new_tab) == default_coordinates # Create a new context in the user context. context_in_user_context_2 = await bidi_session.browsing_context.create( @@ -62,14 +62,16 @@ async def test_user_contexts( wait="complete", ) - assert ( - await get_current_geolocation(bidi_session, context_in_user_context_2) - == TEST_COORDINATES - ) + assert await get_current_geolocation(context_in_user_context_2) == TEST_COORDINATES async def test_set_to_default_user_context( - bidi_session, new_tab, create_user_context, url, set_geolocation_permission + bidi_session, + new_tab, + create_user_context, + url, + get_current_geolocation, + set_geolocation_permission, ): user_context = await create_user_context() context_in_user_context_1 = await bidi_session.browsing_context.create( @@ -89,7 +91,7 @@ async def test_set_to_default_user_context( await set_geolocation_permission(new_tab) await set_geolocation_permission(new_tab, user_context) - default_coordinates = await get_current_geolocation(bidi_session, new_tab) + default_coordinates = await get_current_geolocation(new_tab) assert default_coordinates != TEST_COORDINATES await bidi_session.emulation.set_geolocation_override( @@ -103,10 +105,9 @@ async def test_set_to_default_user_context( # Make sure that the geolocation changes are only applied to the context associated with default user context. assert ( - await get_current_geolocation(bidi_session, context_in_user_context_1) - == default_coordinates + await get_current_geolocation(context_in_user_context_1) == default_coordinates ) - assert await get_current_geolocation(bidi_session, new_tab) == TEST_COORDINATES + assert await get_current_geolocation(new_tab) == TEST_COORDINATES # Create a new context in the default context. context_in_default_context_2 = await bidi_session.browsing_context.create( @@ -120,8 +121,7 @@ async def test_set_to_default_user_context( ) assert ( - await get_current_geolocation(bidi_session, context_in_default_context_2) - == TEST_COORDINATES + await get_current_geolocation(context_in_default_context_2) == TEST_COORDINATES ) # Reset geolocation override. @@ -131,7 +131,11 @@ async def test_set_to_default_user_context( async def test_set_to_multiple_user_contexts( - bidi_session, create_user_context, url, set_geolocation_permission + bidi_session, + create_user_context, + url, + get_current_geolocation, + set_geolocation_permission, ): user_context_1 = await create_user_context() context_in_user_context_1 = await bidi_session.browsing_context.create( @@ -164,18 +168,17 @@ async def test_set_to_multiple_user_contexts( ), ) - assert ( - await get_current_geolocation(bidi_session, context_in_user_context_1) - == TEST_COORDINATES - ) - assert ( - await get_current_geolocation(bidi_session, context_in_user_context_2) - == TEST_COORDINATES - ) + assert await get_current_geolocation(context_in_user_context_1) == TEST_COORDINATES + assert await get_current_geolocation(context_in_user_context_2) == TEST_COORDINATES async def test_set_to_user_context_and_then_to_context( - bidi_session, create_user_context, url, new_tab, set_geolocation_permission + bidi_session, + create_user_context, + url, + new_tab, + get_current_geolocation, + set_geolocation_permission, ): user_context = await create_user_context() context_in_user_context_1 = await bidi_session.browsing_context.create( @@ -195,9 +198,7 @@ async def test_set_to_user_context_and_then_to_context( await set_geolocation_permission(new_tab) await set_geolocation_permission(new_tab, user_context) - default_coordinates = await get_current_geolocation( - bidi_session, context_in_user_context_1 - ) + default_coordinates = await get_current_geolocation(context_in_user_context_1) assert default_coordinates != TEST_COORDINATES @@ -222,7 +223,7 @@ async def test_set_to_user_context_and_then_to_context( ), ) assert ( - await get_current_geolocation(bidi_session, context_in_user_context_1) + await get_current_geolocation(context_in_user_context_1) == new_geolocation_coordinates ) @@ -232,7 +233,7 @@ async def test_set_to_user_context_and_then_to_context( # Make sure that after reload the geolocation is still updated. assert ( - await get_current_geolocation(bidi_session, context_in_user_context_1) + await get_current_geolocation(context_in_user_context_1) == new_geolocation_coordinates ) @@ -246,10 +247,7 @@ async def test_set_to_user_context_and_then_to_context( wait="complete", ) # Make sure that the geolocation override for the user context is applied. - assert ( - await get_current_geolocation(bidi_session, context_in_user_context_2) - == TEST_COORDINATES - ) + assert await get_current_geolocation(context_in_user_context_2) == TEST_COORDINATES await bidi_session.emulation.set_geolocation_override( contexts=[context_in_user_context_1["context"]], @@ -258,6 +256,5 @@ async def test_set_to_user_context_and_then_to_context( # Make sure that the geolocation override was reset. assert ( - await get_current_geolocation(bidi_session, context_in_user_context_1) - == default_coordinates + await get_current_geolocation(context_in_user_context_1) == default_coordinates ) diff --git a/tests/wpt/tests/webnn/conformance_tests/conv2d.https.any.js b/tests/wpt/tests/webnn/conformance_tests/conv2d.https.any.js index 9fe246c9395..45fecb2b40c 100644 --- a/tests/wpt/tests/webnn/conformance_tests/conv2d.https.any.js +++ b/tests/wpt/tests/webnn/conformance_tests/conv2d.https.any.js @@ -1105,6 +1105,1011 @@ const conv2dTests = [ } } } + }, + + // float16 tests + { + 'name': + 'conv2d float16 4D both input and filter non-constant tensors default options', + 'graph': { + 'inputs': { + 'conv2dInput': { + 'data': [ + 0.6123046875, 0.8857421875, 0.13671875, + 0.564453125, 0.896484375, 0.367919921875, + 0.68115234375, 0.047943115234375, 0.33349609375, + 0.1988525390625, 0.41162109375, 0.079345703125, + 0.42724609375, 0.53564453125, 0.59130859375, + 0.2841796875, 0.414794921875, 0.0269012451171875, + 0.362060546875, 0.99462890625, 0.07183837890625, + 0.1220703125, 0.84228515625, 0.453857421875, + 0.21533203125 + ], + 'descriptor': {shape: [1, 1, 5, 5], dataType: 'float16'} + }, + 'conv2dFilter': { + 'data': [ + 0.38037109375, 0.52783203125, 0.219482421875, 0.366943359375, + 0.33984375, 0.419921875, 0.380615234375, 0.1944580078125, + 0.56884765625 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'} + } + }, + 'operators': [{ + 'name': 'conv2d', + 'arguments': [{'input': 'conv2dInput'}, {'filter': 'conv2dFilter'}], + 'outputs': 'conv2dOutput' + }], + 'expectedOutputs': { + 'conv2dOutput': { + 'data': [ + 1.5322265625, 1.357421875, 1.3642578125, 1.0712890625, 1.1259765625, + 1.4716796875, 1.0791015625, 1.1552734375, 1.6572265625 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'conv2d float16 4D both input and filter constant tensors default options', + 'graph': { + 'inputs': { + 'conv2dInput': { + 'data': [ + 0.6123046875, 0.8857421875, 0.13671875, + 0.564453125, 0.896484375, 0.367919921875, + 0.68115234375, 0.047943115234375, 0.33349609375, + 0.1988525390625, 0.41162109375, 0.079345703125, + 0.42724609375, 0.53564453125, 0.59130859375, + 0.2841796875, 0.414794921875, 0.0269012451171875, + 0.362060546875, 0.99462890625, 0.07183837890625, + 0.1220703125, 0.84228515625, 0.453857421875, + 0.21533203125 + ], + 'descriptor': {shape: [1, 1, 5, 5], dataType: 'float16'}, + 'constant': true + }, + 'conv2dFilter': { + 'data': [ + 0.38037109375, 0.52783203125, 0.219482421875, 0.366943359375, + 0.33984375, 0.419921875, 0.380615234375, 0.1944580078125, + 0.56884765625 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'conv2d', + 'arguments': [{'input': 'conv2dInput'}, {'filter': 'conv2dFilter'}], + 'outputs': 'conv2dOutput' + }], + 'expectedOutputs': { + 'conv2dOutput': { + 'data': [ + 1.5322265625, 1.357421875, 1.3642578125, 1.0712890625, 1.1259765625, + 1.4716796875, 1.0791015625, 1.1552734375, 1.6572265625 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'} + } + } + } + }, + { + 'name': 'conv2d float16 4D input and filter tensors default options', + 'graph': { + 'inputs': { + 'conv2dInput': { + 'data': [ + 0.6123046875, 0.8857421875, 0.13671875, + 0.564453125, 0.896484375, 0.367919921875, + 0.68115234375, 0.047943115234375, 0.33349609375, + 0.1988525390625, 0.41162109375, 0.079345703125, + 0.42724609375, 0.53564453125, 0.59130859375, + 0.2841796875, 0.414794921875, 0.0269012451171875, + 0.362060546875, 0.99462890625, 0.07183837890625, + 0.1220703125, 0.84228515625, 0.453857421875, + 0.21533203125 + ], + 'descriptor': {shape: [1, 1, 5, 5], dataType: 'float16'} + }, + 'conv2dFilter': { + 'data': [ + 0.38037109375, 0.52783203125, 0.219482421875, 0.366943359375, + 0.33984375, 0.419921875, 0.380615234375, 0.1944580078125, + 0.56884765625 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'conv2d', + 'arguments': [{'input': 'conv2dInput'}, {'filter': 'conv2dFilter'}], + 'outputs': 'conv2dOutput' + }], + 'expectedOutputs': { + 'conv2dOutput': { + 'data': [ + 1.5322265625, 1.357421875, 1.3642578125, 1.0712890625, 1.1259765625, + 1.4716796875, 1.0791015625, 1.1552734375, 1.6572265625 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'} + } + } + } + }, + { + 'name': 'conv2d float16 4D input and filter tensors options.padding', + 'graph': { + 'inputs': { + 'conv2dInput': { + 'data': [ + 0.6123046875, 0.8857421875, 0.13671875, + 0.564453125, 0.896484375, 0.367919921875, + 0.68115234375, 0.047943115234375, 0.33349609375, + 0.1988525390625, 0.41162109375, 0.079345703125, + 0.42724609375, 0.53564453125, 0.59130859375, + 0.2841796875, 0.414794921875, 0.0269012451171875, + 0.362060546875, 0.99462890625, 0.07183837890625, + 0.1220703125, 0.84228515625, 0.453857421875, + 0.21533203125 + ], + 'descriptor': {shape: [1, 1, 5, 5], dataType: 'float16'} + }, + 'conv2dFilter': { + 'data': [ + 0.38037109375, 0.52783203125, 0.219482421875, 0.366943359375, + 0.33984375, 0.419921875, 0.380615234375, 0.1944580078125, + 0.56884765625 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'conv2d', + 'arguments': [ + {'input': 'conv2dInput'}, {'filter': 'conv2dFilter'}, + {'options': {'padding': [1, 1, 1, 1]}} + ], + 'outputs': 'conv2dOutput' + }], + 'expectedOutputs': { + 'conv2dOutput': { + 'data': [ + 1.0390625, 0.8828125, 1.06640625, 0.814453125, + 0.67724609375, 1.0537109375, 1.5322265625, 1.357421875, + 1.3642578125, 1.1962890625, 0.80810546875, 1.0712890625, + 1.1259765625, 1.4716796875, 0.96044921875, 0.5888671875, + 1.0791015625, 1.1552734375, 1.6572265625, 1.201171875, + 0.316650390625, 0.75439453125, 0.77294921875, 0.97314453125, + 0.90234375 + ], + 'descriptor': {shape: [1, 1, 5, 5], dataType: 'float16'} + } + } + } + }, + { + 'name': 'conv2d float16 4D input and filter tensors options.strides', + 'graph': { + 'inputs': { + 'conv2dInput': { + 'data': [ + 0.6123046875, 0.8857421875, 0.13671875, + 0.564453125, 0.896484375, 0.367919921875, + 0.68115234375, 0.047943115234375, 0.33349609375, + 0.1988525390625, 0.41162109375, 0.079345703125, + 0.42724609375, 0.53564453125, 0.59130859375, + 0.2841796875, 0.414794921875, 0.0269012451171875, + 0.362060546875, 0.99462890625, 0.07183837890625, + 0.1220703125, 0.84228515625, 0.453857421875, + 0.21533203125 + ], + 'descriptor': {shape: [1, 1, 5, 5], dataType: 'float16'} + }, + 'conv2dFilter': { + 'data': [ + 0.38037109375, 0.52783203125, 0.219482421875, 0.366943359375, + 0.33984375, 0.419921875, 0.380615234375, 0.1944580078125, + 0.56884765625 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'conv2d', + 'arguments': [ + {'input': 'conv2dInput'}, {'filter': 'conv2dFilter'}, + {'options': {'strides': [2, 2]}} + ], + 'outputs': 'conv2dOutput' + }], + 'expectedOutputs': { + 'conv2dOutput': { + 'data': [1.5322265625, 1.3642578125, 1.0791015625, 1.6572265625], + 'descriptor': {shape: [1, 1, 2, 2], dataType: 'float16'} + } + } + } + }, + { + 'name': 'conv2d float16 4D input and filter tensors options.dilations', + 'graph': { + 'inputs': { + 'conv2dInput': { + 'data': [ + 0.6123046875, 0.8857421875, 0.13671875, + 0.564453125, 0.896484375, 0.367919921875, + 0.68115234375, 0.047943115234375, 0.33349609375, + 0.1988525390625, 0.41162109375, 0.079345703125, + 0.42724609375, 0.53564453125, 0.59130859375, + 0.2841796875, 0.414794921875, 0.0269012451171875, + 0.362060546875, 0.99462890625, 0.07183837890625, + 0.1220703125, 0.84228515625, 0.453857421875, + 0.21533203125 + ], + 'descriptor': {shape: [1, 1, 5, 5], dataType: 'float16'} + }, + 'conv2dFilter': { + 'data': [ + 0.38037109375, 0.52783203125, 0.219482421875, 0.366943359375, + 0.33984375, 0.419921875, 0.380615234375, 0.1944580078125, + 0.56884765625 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'conv2d', + 'arguments': [ + {'input': 'conv2dInput'}, {'filter': 'conv2dFilter'}, + {'options': {'dilations': [2, 2]}} + ], + 'outputs': 'conv2dOutput' + }], + 'expectedOutputs': { + 'conv2dOutput': { + 'data': [1.3603515625], + 'descriptor': {shape: [1, 1, 1, 1], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'depthwise conv2d float16 4D input and filter tensors options.groups= input_channels', + 'graph': { + 'inputs': { + 'conv2dInput': { + 'data': [ + 0.8447265625, 0.943359375, 0.65576171875, 0.6982421875, + 0.99951171875, 0.2366943359375, 0.367431640625, 0.261962890625, + 0.62548828125, 0.84033203125, 0.37841796875, 0.454345703125, + 0.253173828125, 0.578125, 0.54150390625, 0.37841796875 + ], + 'descriptor': {shape: [1, 4, 2, 2], dataType: 'float16'} + }, + 'conv2dFilter': { + 'data': [ + 0.272216796875, 0.28125, 0.8544921875, 0.1796875, 0.7763671875, + 0.51416015625, 0.63720703125, 0.1280517578125, 0.83740234375, + 0.57275390625, 0.09857177734375, 0.5927734375, 0.58984375, + 0.96923828125, 0.2318115234375, 0.1480712890625 + ], + 'descriptor': {shape: [4, 1, 2, 2], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'conv2d', + 'arguments': [ + {'input': 'conv2dInput'}, {'filter': 'conv2dFilter'}, + {'options': {'groups': 4}} + ], + 'outputs': 'conv2dOutput' + }], + 'expectedOutputs': { + 'conv2dOutput': { + 'data': [1.1806640625, 1.1650390625, 1.3115234375, 0.89111328125], + 'descriptor': {shape: [1, 4, 1, 1], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'conv2d float16 4D input and filter tensors options.inputLayout=\'nchw\'', + 'graph': { + 'inputs': { + 'conv2dInput': { + 'data': [ + 0.7529296875, 0.751953125, 0.5947265625, 0.21630859375, + 0.07586669921875, 0.151123046875, 0.12127685546875, 0.53662109375, + 0.59375, 0.9912109375, 0.363037109375, 0.92919921875, + 0.227294921875, 0.54150390625, 0.08447265625, 0.6767578125, + 0.619140625, 0.392822265625 + ], + 'descriptor': {shape: [2, 1, 3, 3], dataType: 'float16'} + }, + 'conv2dFilter': { + 'data': [ + 0.1453857421875, 0.96728515625, 0.10833740234375, 0.3203125, + 0.6953125, 0.50732421875, 0.0814208984375, 0.5302734375, + 0.30712890625, 0.432373046875, 0.98486328125, 0.42822265625 + ], + 'descriptor': {shape: [3, 1, 2, 2], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'conv2d', + 'arguments': [ + {'input': 'conv2dInput'}, {'filter': 'conv2dFilter'}, + {'options': {'inputLayout': 'nchw'}} + ], + 'outputs': 'conv2dOutput' + }], + 'expectedOutputs': { + 'conv2dOutput': { + 'data': [ + 0.884765625, 0.7412109375, 0.289794921875, 0.405517578125, + 0.962890625, 0.91064453125, 0.4833984375, 0.488037109375, + 0.8017578125, 0.62744140625, 0.448486328125, 0.87158203125, + 0.693359375, 1.037109375, 0.82861328125, 0.353271484375, + 1.1787109375, 0.8125, 0.81640625, 0.67822265625, + 0.9169921875, 1.0830078125, 1.2353515625, 0.98095703125 + ], + 'descriptor': {shape: [2, 3, 2, 2], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'conv2d float16 4D input and filter tensors options.inputLayout=\'nhwc\'', + 'graph': { + 'inputs': { + 'conv2dInput': { + 'data': [ + 0.7529296875, 0.751953125, 0.5947265625, 0.21630859375, + 0.07586669921875, 0.151123046875, 0.12127685546875, 0.53662109375, + 0.59375, 0.9912109375, 0.363037109375, 0.92919921875, + 0.227294921875, 0.54150390625, 0.08447265625, 0.6767578125, + 0.619140625, 0.392822265625 + ], + 'descriptor': {shape: [2, 3, 3, 1], dataType: 'float16'} + }, + 'conv2dFilter': { + 'data': [ + 0.1453857421875, 0.96728515625, 0.10833740234375, 0.3203125, + 0.6953125, 0.50732421875, 0.0814208984375, 0.5302734375, + 0.30712890625, 0.432373046875, 0.98486328125, 0.42822265625 + ], + 'descriptor': {shape: [3, 1, 2, 2], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'conv2d', + 'arguments': [ + {'input': 'conv2dInput'}, {'filter': 'conv2dFilter'}, + {'options': {'inputLayout': 'nhwc'}} + ], + 'outputs': 'conv2dOutput' + }], + 'expectedOutputs': { + 'conv2dOutput': { + 'data': [ + 0.884765625, 0.962890625, 0.8017578125, 0.7412109375, + 0.91064453125, 0.62744140625, 0.289794921875, 0.4833984375, + 0.448486328125, 0.405517578125, 0.488037109375, 0.87158203125, + 0.693359375, 1.1787109375, 0.9169921875, 1.037109375, + 0.8125, 1.0830078125, 0.82861328125, 0.81640625, + 1.2353515625, 0.353271484375, 0.67822265625, 0.98095703125 + ], + 'descriptor': {shape: [2, 2, 2, 3], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'conv2d float16 4D input and filter tensors options.filterLayout=\'oihw\'', + 'graph': { + 'inputs': { + 'conv2dInput': { + 'data': [ + 0.7529296875, 0.751953125, 0.5947265625, 0.21630859375, + 0.07586669921875, 0.151123046875, 0.12127685546875, 0.53662109375, + 0.59375, 0.9912109375, 0.363037109375, 0.92919921875, + 0.227294921875, 0.54150390625, 0.08447265625, 0.6767578125, + 0.619140625, 0.392822265625 + ], + 'descriptor': {shape: [2, 1, 3, 3], dataType: 'float16'} + }, + 'conv2dFilter': { + 'data': [ + 0.1453857421875, 0.96728515625, 0.10833740234375, 0.3203125, + 0.6953125, 0.50732421875, 0.0814208984375, 0.5302734375, + 0.30712890625, 0.432373046875, 0.98486328125, 0.42822265625 + ], + 'descriptor': {shape: [3, 1, 2, 2], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'conv2d', + 'arguments': [ + {'input': 'conv2dInput'}, {'filter': 'conv2dFilter'}, + {'options': {'filterLayout': 'oihw'}} + ], + 'outputs': 'conv2dOutput' + }], + 'expectedOutputs': { + 'conv2dOutput': { + 'data': [ + 0.884765625, 0.7412109375, 0.289794921875, 0.405517578125, + 0.962890625, 0.91064453125, 0.4833984375, 0.488037109375, + 0.8017578125, 0.62744140625, 0.448486328125, 0.87158203125, + 0.693359375, 1.037109375, 0.82861328125, 0.353271484375, + 1.1787109375, 0.8125, 0.81640625, 0.67822265625, + 0.9169921875, 1.0830078125, 1.2353515625, 0.98095703125 + ], + 'descriptor': {shape: [2, 3, 2, 2], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'conv2d float16 4D input and filter tensors options.filterLayout=\'hwio\'', + 'graph': { + 'inputs': { + 'conv2dInput': { + 'data': [ + 0.7529296875, 0.751953125, 0.5947265625, 0.21630859375, + 0.07586669921875, 0.151123046875, 0.12127685546875, 0.53662109375, + 0.59375, 0.9912109375, 0.363037109375, 0.92919921875, + 0.227294921875, 0.54150390625, 0.08447265625, 0.6767578125, + 0.619140625, 0.392822265625 + ], + 'descriptor': {shape: [2, 1, 3, 3], dataType: 'float16'} + }, + 'conv2dFilter': { + 'data': [ + 0.1453857421875, 0.6953125, 0.30712890625, 0.96728515625, + 0.50732421875, 0.432373046875, 0.10833740234375, 0.0814208984375, + 0.98486328125, 0.3203125, 0.5302734375, 0.42822265625 + ], + 'descriptor': {shape: [2, 2, 1, 3], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'conv2d', + 'arguments': [ + {'input': 'conv2dInput'}, {'filter': 'conv2dFilter'}, + {'options': {'filterLayout': 'hwio'}} + ], + 'outputs': 'conv2dOutput' + }], + 'expectedOutputs': { + 'conv2dOutput': { + 'data': [ + 0.884765625, 0.7412109375, 0.289794921875, 0.405517578125, + 0.962890625, 0.91064453125, 0.4833984375, 0.488037109375, + 0.8017578125, 0.62744140625, 0.448486328125, 0.87158203125, + 0.693359375, 1.037109375, 0.82861328125, 0.353271484375, + 1.1787109375, 0.8125, 0.81640625, 0.67822265625, + 0.9169921875, 1.0830078125, 1.2353515625, 0.98095703125 + ], + 'descriptor': {shape: [2, 3, 2, 2], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'conv2d float16 4D input and filter tensors options.filterLayout=\'ohwi\'', + 'graph': { + 'inputs': { + 'conv2dInput': { + 'data': [ + 0.7529296875, 0.751953125, 0.5947265625, 0.21630859375, + 0.07586669921875, 0.151123046875, 0.12127685546875, 0.53662109375, + 0.59375, 0.9912109375, 0.363037109375, 0.92919921875, + 0.227294921875, 0.54150390625, 0.08447265625, 0.6767578125, + 0.619140625, 0.392822265625 + ], + 'descriptor': {shape: [2, 1, 3, 3], dataType: 'float16'} + }, + 'conv2dFilter': { + 'data': [ + 0.1453857421875, 0.96728515625, 0.10833740234375, 0.3203125, + 0.6953125, 0.50732421875, 0.0814208984375, 0.5302734375, + 0.30712890625, 0.432373046875, 0.98486328125, 0.42822265625 + ], + 'descriptor': {shape: [3, 2, 2, 1], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'conv2d', + 'arguments': [ + {'input': 'conv2dInput'}, {'filter': 'conv2dFilter'}, + {'options': {'filterLayout': 'ohwi'}} + ], + 'outputs': 'conv2dOutput' + }], + 'expectedOutputs': { + 'conv2dOutput': { + 'data': [ + 0.884765625, 0.7412109375, 0.289794921875, 0.405517578125, + 0.962890625, 0.91064453125, 0.4833984375, 0.488037109375, + 0.8017578125, 0.62744140625, 0.448486328125, 0.87158203125, + 0.693359375, 1.037109375, 0.82861328125, 0.353271484375, + 1.1787109375, 0.8125, 0.81640625, 0.67822265625, + 0.9169921875, 1.0830078125, 1.2353515625, 0.98095703125 + ], + 'descriptor': {shape: [2, 3, 2, 2], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'conv2d float16 4D input and filter tensors options.filterLayout=\'ihwo\'', + 'graph': { + 'inputs': { + 'conv2dInput': { + 'data': [ + 0.7529296875, 0.751953125, 0.5947265625, 0.21630859375, + 0.07586669921875, 0.151123046875, 0.12127685546875, 0.53662109375, + 0.59375, 0.9912109375, 0.363037109375, 0.92919921875, + 0.227294921875, 0.54150390625, 0.08447265625, 0.6767578125, + 0.619140625, 0.392822265625 + ], + 'descriptor': {shape: [2, 1, 3, 3], dataType: 'float16'} + }, + 'conv2dFilter': { + 'data': [ + 0.1453857421875, 0.6953125, 0.30712890625, 0.96728515625, + 0.50732421875, 0.432373046875, 0.10833740234375, 0.0814208984375, + 0.98486328125, 0.3203125, 0.5302734375, 0.42822265625 + ], + 'descriptor': {shape: [1, 2, 2, 3], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'conv2d', + 'arguments': [ + {'input': 'conv2dInput'}, {'filter': 'conv2dFilter'}, + {'options': {'filterLayout': 'ihwo'}} + ], + 'outputs': 'conv2dOutput' + }], + 'expectedOutputs': { + 'conv2dOutput': { + 'data': [ + 0.884765625, 0.7412109375, 0.289794921875, 0.405517578125, + 0.962890625, 0.91064453125, 0.4833984375, 0.488037109375, + 0.8017578125, 0.62744140625, 0.448486328125, 0.87158203125, + 0.693359375, 1.037109375, 0.82861328125, 0.353271484375, + 1.1787109375, 0.8125, 0.81640625, 0.67822265625, + 0.9169921875, 1.0830078125, 1.2353515625, 0.98095703125 + ], + 'descriptor': {shape: [2, 3, 2, 2], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'conv2d float16 4D input and filter tensors options.inputLayout=\'nhwc\' and options.filterLayout=\'oihw\'', + 'graph': { + 'inputs': { + 'conv2dInput': { + 'data': [ + 0.7529296875, 0.751953125, 0.5947265625, 0.21630859375, + 0.07586669921875, 0.151123046875, 0.12127685546875, 0.53662109375, + 0.59375, 0.9912109375, 0.363037109375, 0.92919921875, + 0.227294921875, 0.54150390625, 0.08447265625, 0.6767578125, + 0.619140625, 0.392822265625 + ], + 'descriptor': {shape: [2, 3, 3, 1], dataType: 'float16'} + }, + 'conv2dFilter': { + 'data': [ + 0.1453857421875, 0.96728515625, 0.10833740234375, 0.3203125, + 0.6953125, 0.50732421875, 0.0814208984375, 0.5302734375, + 0.30712890625, 0.432373046875, 0.98486328125, 0.42822265625 + ], + 'descriptor': {shape: [3, 1, 2, 2], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'conv2d', + 'arguments': [ + {'input': 'conv2dInput'}, {'filter': 'conv2dFilter'}, + {'options': {'inputLayout': 'nhwc', 'filterLayout': 'oihw'}} + ], + 'outputs': 'conv2dOutput' + }], + 'expectedOutputs': { + 'conv2dOutput': { + 'data': [ + 0.884765625, 0.962890625, 0.8017578125, 0.7412109375, + 0.91064453125, 0.62744140625, 0.289794921875, 0.4833984375, + 0.448486328125, 0.405517578125, 0.488037109375, 0.87158203125, + 0.693359375, 1.1787109375, 0.9169921875, 1.037109375, + 0.8125, 1.0830078125, 0.82861328125, 0.81640625, + 1.2353515625, 0.353271484375, 0.67822265625, 0.98095703125 + ], + 'descriptor': {shape: [2, 2, 2, 3], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'conv2d float16 4D input and filter tensors options.inputLayout=\'nhwc\' and options.filterLayout=\'hwio\'', + 'graph': { + 'inputs': { + 'conv2dInput': { + 'data': [ + 0.7529296875, 0.751953125, 0.5947265625, 0.21630859375, + 0.07586669921875, 0.151123046875, 0.12127685546875, 0.53662109375, + 0.59375, 0.9912109375, 0.363037109375, 0.92919921875, + 0.227294921875, 0.54150390625, 0.08447265625, 0.6767578125, + 0.619140625, 0.392822265625 + ], + 'descriptor': {shape: [2, 3, 3, 1], dataType: 'float16'} + }, + 'conv2dFilter': { + 'data': [ + 0.1453857421875, 0.6953125, 0.30712890625, 0.96728515625, + 0.50732421875, 0.432373046875, 0.10833740234375, 0.0814208984375, + 0.98486328125, 0.3203125, 0.5302734375, 0.42822265625 + ], + 'descriptor': {shape: [2, 2, 1, 3], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'conv2d', + 'arguments': [ + {'input': 'conv2dInput'}, {'filter': 'conv2dFilter'}, + {'options': {'inputLayout': 'nhwc', 'filterLayout': 'hwio'}} + ], + 'outputs': 'conv2dOutput' + }], + 'expectedOutputs': { + 'conv2dOutput': { + 'data': [ + 0.884765625, 0.962890625, 0.8017578125, 0.7412109375, + 0.91064453125, 0.62744140625, 0.289794921875, 0.4833984375, + 0.448486328125, 0.405517578125, 0.488037109375, 0.87158203125, + 0.693359375, 1.1787109375, 0.9169921875, 1.037109375, + 0.8125, 1.0830078125, 0.82861328125, 0.81640625, + 1.2353515625, 0.353271484375, 0.67822265625, 0.98095703125 + ], + 'descriptor': {shape: [2, 2, 2, 3], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'conv2d float16 4D input and filter tensors options.inputLayout=\'nhwc\' and options.filterLayout=\'ohwi\'', + 'graph': { + 'inputs': { + 'conv2dInput': { + 'data': [ + 0.7529296875, 0.751953125, 0.5947265625, 0.21630859375, + 0.07586669921875, 0.151123046875, 0.12127685546875, 0.53662109375, + 0.59375, 0.9912109375, 0.363037109375, 0.92919921875, + 0.227294921875, 0.54150390625, 0.08447265625, 0.6767578125, + 0.619140625, 0.392822265625 + ], + 'descriptor': {shape: [2, 3, 3, 1], dataType: 'float16'} + }, + 'conv2dFilter': { + 'data': [ + 0.1453857421875, 0.96728515625, 0.10833740234375, 0.3203125, + 0.6953125, 0.50732421875, 0.0814208984375, 0.5302734375, + 0.30712890625, 0.432373046875, 0.98486328125, 0.42822265625 + ], + 'descriptor': {shape: [3, 2, 2, 1], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'conv2d', + 'arguments': [ + {'input': 'conv2dInput'}, {'filter': 'conv2dFilter'}, + {'options': {'inputLayout': 'nhwc', 'filterLayout': 'ohwi'}} + ], + 'outputs': 'conv2dOutput' + }], + 'expectedOutputs': { + 'conv2dOutput': { + 'data': [ + 0.884765625, 0.962890625, 0.8017578125, 0.7412109375, + 0.91064453125, 0.62744140625, 0.289794921875, 0.4833984375, + 0.448486328125, 0.405517578125, 0.488037109375, 0.87158203125, + 0.693359375, 1.1787109375, 0.9169921875, 1.037109375, + 0.8125, 1.0830078125, 0.82861328125, 0.81640625, + 1.2353515625, 0.353271484375, 0.67822265625, 0.98095703125 + ], + 'descriptor': {shape: [2, 2, 2, 3], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'conv2d float16 4D input and filter tensors options.inputLayout=\'nhwc\' and options.filterLayout=\'ihwo\'', + 'graph': { + 'inputs': { + 'conv2dInput': { + 'data': [ + 0.7529296875, 0.751953125, 0.5947265625, 0.21630859375, + 0.07586669921875, 0.151123046875, 0.12127685546875, 0.53662109375, + 0.59375, 0.9912109375, 0.363037109375, 0.92919921875, + 0.227294921875, 0.54150390625, 0.08447265625, 0.6767578125, + 0.619140625, 0.392822265625 + ], + 'descriptor': {shape: [2, 3, 3, 1], dataType: 'float16'} + }, + 'conv2dFilter': { + 'data': [ + 0.1453857421875, 0.6953125, 0.30712890625, 0.96728515625, + 0.50732421875, 0.432373046875, 0.10833740234375, 0.0814208984375, + 0.98486328125, 0.3203125, 0.5302734375, 0.42822265625 + ], + 'descriptor': {shape: [1, 2, 2, 3], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'conv2d', + 'arguments': [ + {'input': 'conv2dInput'}, {'filter': 'conv2dFilter'}, + {'options': {'inputLayout': 'nhwc', 'filterLayout': 'ihwo'}} + ], + 'outputs': 'conv2dOutput' + }], + 'expectedOutputs': { + 'conv2dOutput': { + 'data': [ + 0.884765625, 0.962890625, 0.8017578125, 0.7412109375, + 0.91064453125, 0.62744140625, 0.289794921875, 0.4833984375, + 0.448486328125, 0.405517578125, 0.488037109375, 0.87158203125, + 0.693359375, 1.1787109375, 0.9169921875, 1.037109375, + 0.8125, 1.0830078125, 0.82861328125, 0.81640625, + 1.2353515625, 0.353271484375, 0.67822265625, 0.98095703125 + ], + 'descriptor': {shape: [2, 2, 2, 3], dataType: 'float16'} + } + } + } + }, + { + 'name': 'conv2d float16 4D input and filter tensors 1D options.bias', + 'graph': { + 'inputs': { + 'conv2dInput': { + 'data': [ + 0.7529296875, 0.751953125, 0.5947265625, 0.21630859375, + 0.07586669921875, 0.151123046875, 0.12127685546875, 0.53662109375, + 0.59375, 0.9912109375, 0.363037109375, 0.92919921875, + 0.227294921875, 0.54150390625, 0.08447265625, 0.6767578125, + 0.619140625, 0.392822265625 + ], + 'descriptor': {shape: [2, 1, 3, 3], dataType: 'float16'} + }, + 'conv2dFilter': { + 'data': [ + 0.1453857421875, 0.96728515625, 0.10833740234375, 0.3203125, + 0.6953125, 0.50732421875, 0.0814208984375, 0.5302734375, + 0.30712890625, 0.432373046875, 0.98486328125, 0.42822265625 + ], + 'descriptor': {shape: [3, 1, 2, 2], dataType: 'float16'}, + 'constant': true + }, + 'conv2dBias': { + 'data': [0.8134765625, 0.83935546875, 0.494384765625], + 'descriptor': {shape: [3], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'conv2d', + 'arguments': [ + {'input': 'conv2dInput'}, {'filter': 'conv2dFilter'}, + {'options': {'bias': 'conv2dBias'}} + ], + 'outputs': 'conv2dOutput' + }], + 'expectedOutputs': { + 'conv2dOutput': { + 'data': [ + 1.6982421875, 1.5546875, 1.103515625, 1.21875, + 1.8017578125, 1.75, 1.322265625, 1.3271484375, + 1.2958984375, 1.1220703125, 0.94287109375, 1.3662109375, + 1.5068359375, 1.8505859375, 1.6416015625, 1.1669921875, + 2.017578125, 1.65234375, 1.6552734375, 1.517578125, + 1.4111328125, 1.5771484375, 1.7294921875, 1.4755859375 + ], + 'descriptor': {shape: [2, 3, 2, 2], dataType: 'float16'} + } + } + } + }, + { + 'name': 'conv2d float16 4D input and filter tensors all options', + 'graph': { + 'inputs': { + 'conv2dInput': { + 'data': [ + 0.0997314453125, 0.53759765625, 0.3056640625, + 0.72216796875, 0.5068359375, 0.32373046875, + 0.8720703125, 0.2098388671875, 0.50537109375, + 0.0268707275390625, 0.5498046875, 0.0643310546875, + 0.156005859375, 0.11968994140625, 0.0361328125, + 0.414306640625, 0.24169921875, 0.67724609375, + 0.208740234375, 0.10980224609375, 0.7451171875, + 0.744140625, 0.787109375, 0.5888671875, + 0.11016845703125, 0.904296875, 0.11724853515625, + 0.85107421875, 0.424560546875, 0.0253753662109375, + 0.74755859375, 0.464599609375, 0.0304107666015625, + 0.42431640625, 0.384765625, 0.75830078125, + 0.990234375, 0.03717041015625, 0.014495849609375, + 0.826171875, 0.210693359375, 0.65673828125, + 0.258544921875, 0.480224609375, 0.97021484375, + 0.296875, 0.75244140625, 0.029632568359375, + 0.09027099609375, 0.7783203125 + ], + 'descriptor': {shape: [1, 2, 5, 5], dataType: 'float16'} + }, + 'conv2dFilter': { + 'data': [ + 0.638671875, 0.07763671875, 0.129150390625, 0.456298828125, + 0.404296875, 0.59423828125, 0.1424560546875, 0.90380859375 + ], + 'descriptor': {shape: [2, 2, 1, 2], dataType: 'float16'}, + 'constant': true + }, + 'conv2dBias': { + 'data': [0.54248046875, 0.8408203125], + 'descriptor': {shape: [2], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'conv2d', + 'arguments': [ + {'input': 'conv2dInput'}, {'filter': 'conv2dFilter'}, { + 'options': { + 'padding': [1, 0, 0, 1], + 'strides': [1, 1], + 'dilations': [1, 1], + 'groups': 2, + 'inputLayout': 'nchw', + 'filterLayout': 'hwio', + 'bias': 'conv2dBias' + } + } + ], + 'outputs': 'conv2dOutput' + }], + 'expectedOutputs': { + 'conv2dOutput': { + 'data': [ + 0.6591796875, 0.80322265625, 0.76904296875, 0.90673828125, + 0.74755859375, 0.9306640625, 1.3076171875, 0.98779296875, + 1.27734375, 0.876953125, 1.09375, 1.1748046875, + 0.82177734375, 0.92236328125, 0.57421875, 1.103515625, + 0.7978515625, 0.9609375, 0.7236328125, 0.60986328125, + 1.2451171875, 1.197265625, 1.404296875, 0.94384765625, + 0.6572265625, 1.484375, 1.6796875, 1.73046875, + 1.1162109375, 0.85595703125, 1.8291015625, 1.5419921875, + 1.501953125, 1.4853515625, 1.0712890625, 2.45703125, + 1.5126953125, 1.072265625, 1.8046875, 1.361328125, + 2.0703125, 1.55859375, 1.4384765625, 2.380859375, + 1.4814453125, 2.013671875, 1.4833984375, 1.1796875, + 2.078125, 1.37890625 + ], + 'descriptor': {shape: [1, 2, 5, 5], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'conv2d float16 4D input and filter tensors, both negative input tensor and options.bias', + 'graph': { + 'inputs': { + 'conv2dInput': { + 'data': [ + -0.80712890625, -0.8837890625, -0.77001953125, + -0.564453125, -0.397216796875, -0.1083984375, + -0.5517578125, -0.3955078125, -0.057586669921875, + -0.5146484375, -0.2132568359375, -0.95068359375, + -0.80419921875, -0.86474609375, -0.9609375, + -0.326416015625, -0.06884765625, -0.3203125, + -0.269287109375, -0.343017578125, -0.89892578125, + -0.90380859375, -0.63671875, -0.20068359375, + -0.787109375, -0.3466796875, -0.060028076171875, + -0.14990234375, -0.6484375, -0.8935546875, + -0.81494140625, -0.642578125, -0.032745361328125, + -0.6611328125, -0.58447265625, -0.09918212890625, + -0.166015625, -0.95068359375, -0.30517578125, + -0.62109375, -0.5400390625, -0.420166015625, + -0.188232421875, -0.35888671875, -0.71142578125, + -0.375, -0.7109375, -0.360595703125, + -0.546875, -0.032257080078125 + ], + 'descriptor': {shape: [1, 2, 5, 5], dataType: 'float16'} + }, + 'conv2dFilter': { + 'data': [ + 0.638671875, 0.07763671875, 0.129150390625, 0.456298828125, + 0.404296875, 0.59423828125, 0.1424560546875, 0.90380859375 + ], + 'descriptor': {shape: [2, 2, 1, 2], dataType: 'float16'}, + 'constant': true + }, + 'conv2dBias': { + 'data': [-0.375, -0.436279296875], + 'descriptor': {shape: [2], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'conv2d', + 'arguments': [ + {'input': 'conv2dInput'}, {'filter': 'conv2dFilter'}, { + 'options': { + 'padding': [1, 0, 0, 1], + 'groups': 2, + 'filterLayout': 'hwio', + 'bias': 'conv2dBias' + } + } + ], + 'outputs': 'conv2dOutput' + }], + 'expectedOutputs': { + 'conv2dOutput': { + 'data': [ + -0.8271484375, -0.841796875, -0.7666015625, -0.65966796875, + -0.53564453125, -1.126953125, -1.318359375, -1.107421875, + -0.88330078125, -0.8369140625, -0.7373046875, -1.27734375, + -1.0830078125, -0.96484375, -1.091796875, -0.77587890625, + -1.1591796875, -1.16796875, -1.208984375, -1.126953125, + -1.0849609375, -0.91650390625, -0.900390625, -0.78466796875, + -0.912109375, -0.69677734375, -0.607421875, -1.111328125, + -1.62890625, -0.96728515625, -1.5556640625, -0.9208984375, + -1.3603515625, -1.8154296875, -0.85302734375, -1.001953125, + -1.458984375, -1.5810546875, -1.4970703125, -0.8505859375, + -1.220703125, -1.302734375, -1.0859375, -1.599609375, + -0.9072265625, -1.53515625, -1.302734375, -1.3232421875, + -1.142578125, -0.5107421875 + ], + 'descriptor': {shape: [1, 2, 5, 5], dataType: 'float16'} + } + } + } } ]; diff --git a/tests/wpt/tests/webnn/conformance_tests/conv_transpose2d.https.any.js b/tests/wpt/tests/webnn/conformance_tests/conv_transpose2d.https.any.js index 67ce5dbaf65..a17df911e44 100644 --- a/tests/wpt/tests/webnn/conformance_tests/conv_transpose2d.https.any.js +++ b/tests/wpt/tests/webnn/conformance_tests/conv_transpose2d.https.any.js @@ -1578,6 +1578,1670 @@ const convTranspose2dTests = [ } } } + }, + + // float16 tests + { + 'name': + 'convTranspose2d float16 4D both input and filter non-constant tensors default options', + 'graph': { + 'inputs': { + 'convTranspose2dInput': { + 'data': [ + 0.58740234375, 0.60791015625, 0.0172882080078125, 0.261474609375 + ], + 'descriptor': {shape: [1, 1, 2, 2], dataType: 'float16'} + }, + 'convTranspose2dFilter': { + 'data': [ + 0.329345703125, 0.5869140625, 0.297119140625, 0.003337860107421875 + ], + 'descriptor': {shape: [1, 1, 2, 2], dataType: 'float16'} + } + }, + 'operators': [{ + 'name': 'convTranspose2d', + 'arguments': [ + {'input': 'convTranspose2dInput'}, {'filter': 'convTranspose2dFilter'} + ], + 'outputs': 'convTranspose2dOutput' + }], + 'expectedOutputs': { + 'convTranspose2dOutput': { + 'data': [ + 0.1934814453125, 0.544921875, 0.356689453125, 0.18017578125, + 0.27880859375, 0.155517578125, 0.005138397216796875, + 0.0777587890625, 0.0008726119995117188 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'convTranspose2d float16 4D both input and filter constant tensors default options', + 'graph': { + 'inputs': { + 'convTranspose2dInput': { + 'data': [ + 0.58740234375, 0.60791015625, 0.0172882080078125, 0.261474609375 + ], + 'descriptor': {shape: [1, 1, 2, 2], dataType: 'float16'}, + 'constant': true + }, + 'convTranspose2dFilter': { + 'data': [ + 0.329345703125, 0.5869140625, 0.297119140625, 0.003337860107421875 + ], + 'descriptor': {shape: [1, 1, 2, 2], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'convTranspose2d', + 'arguments': [ + {'input': 'convTranspose2dInput'}, {'filter': 'convTranspose2dFilter'} + ], + 'outputs': 'convTranspose2dOutput' + }], + 'expectedOutputs': { + 'convTranspose2dOutput': { + 'data': [ + 0.1934814453125, 0.544921875, 0.356689453125, 0.18017578125, + 0.27880859375, 0.155517578125, 0.005138397216796875, + 0.0777587890625, 0.0008726119995117188 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'convTranspose2d float16 4D input and filter tensors default options', + 'graph': { + 'inputs': { + 'convTranspose2dInput': { + 'data': [ + 0.58740234375, 0.60791015625, 0.0172882080078125, 0.261474609375 + ], + 'descriptor': {shape: [1, 1, 2, 2], dataType: 'float16'} + }, + 'convTranspose2dFilter': { + 'data': [ + 0.329345703125, 0.5869140625, 0.297119140625, 0.003337860107421875 + ], + 'descriptor': {shape: [1, 1, 2, 2], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'convTranspose2d', + 'arguments': [ + {'input': 'convTranspose2dInput'}, {'filter': 'convTranspose2dFilter'} + ], + 'outputs': 'convTranspose2dOutput' + }], + 'expectedOutputs': { + 'convTranspose2dOutput': { + 'data': [ + 0.1934814453125, 0.544921875, 0.356689453125, 0.18017578125, + 0.27880859375, 0.155517578125, 0.005138397216796875, + 0.0777587890625, 0.0008726119995117188 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'convTranspose2d float16 4D input and filter tensors options.padding', + 'graph': { + 'inputs': { + 'convTranspose2dInput': { + 'data': [ + 0.58740234375, 0.60791015625, 0.0172882080078125, 0.261474609375 + ], + 'descriptor': {shape: [1, 1, 2, 2], dataType: 'float16'} + }, + 'convTranspose2dFilter': { + 'data': [ + 0.329345703125, 0.5869140625, 0.297119140625, 0.003337860107421875 + ], + 'descriptor': {shape: [1, 1, 2, 2], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'convTranspose2d', + 'arguments': [ + {'input': 'convTranspose2dInput'}, + {'filter': 'convTranspose2dFilter'}, + {'options': {'padding': [1, 1, 1, 1]}} + ], + 'outputs': 'convTranspose2dOutput' + }], + 'expectedOutputs': { + 'convTranspose2dOutput': { + 'data': [0.27880859375], + 'descriptor': {shape: [1, 1, 1, 1], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'convTranspose2d float16 input tensors options.padding is the same upper padding', + 'graph': { + 'inputs': { + 'convTranspose2dInput': { + 'data': [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5], + 'descriptor': {shape: [1, 3, 3, 1], dataType: 'float16'} + }, + 'convTranspose2dFilter': { + 'data': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + 'descriptor': {shape: [2, 3, 3, 1], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'convTranspose2d', + 'arguments': [ + {'input': 'convTranspose2dInput'}, + {'filter': 'convTranspose2dFilter'}, { + 'options': { + 'outputSizes': [6, 6], + 'groups': 1, + 'strides': [2, 2], + 'dilations': [1, 1], + 'padding': [0, 1, 0, 1], + 'filterLayout': 'ohwi', + 'inputLayout': 'nhwc' + } + } + ], + 'outputs': 'convTranspose2dOutput' + }], + 'expectedOutputs': { + 'convTranspose2dOutput': { + 'data': [ + 0.5, 0.5, 0.5, 0.5, 1, 1, 0.5, 0.5, 1, 1, 0.5, 0.5, + 0.5, 0.5, 0.5, 0.5, 1, 1, 0.5, 0.5, 1, 1, 0.5, 0.5, + 1, 1, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, + 0.5, 0.5, 0.5, 0.5, 1, 1, 0.5, 0.5, 1, 1, 0.5, 0.5, + 1, 1, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, + 0.5, 0.5, 0.5, 0.5, 1, 1, 0.5, 0.5, 1, 1, 0.5, 0.5 + ], + 'descriptor': {shape: [1, 6, 6, 2], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'convTranspose2d float16 4D input and filter tensors options.strides', + 'graph': { + 'inputs': { + 'convTranspose2dInput': { + 'data': [ + 0.056060791015625, 0.71142578125, 0.65283203125, 0.38623046875, + 0.386962890625, 0.9462890625, 0.095703125, 0.92333984375, + 0.63623046875 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'} + }, + 'convTranspose2dFilter': { + 'data': [ + 0.861328125, 0.626953125, 0.63671875, 0.83837890625, + 0.11883544921875, 0.9921875, 0.32861328125, 0.8740234375, + 0.720703125, 0.97998046875, 0.06170654296875, 0.322021484375, 0.75, + 0.39306640625, 0.1380615234375, 0.283935546875, 0.423583984375, + 0.1448974609375 + ], + 'descriptor': {shape: [1, 2, 3, 3], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'convTranspose2d', + 'arguments': [ + {'input': 'convTranspose2dInput'}, + {'filter': 'convTranspose2dFilter'}, {'options': {'strides': [3, 2]}} + ], + 'outputs': 'convTranspose2dOutput' + }], + 'expectedOutputs': { + 'convTranspose2dOutput': { + 'data': [ + 0.04827880859375, + 0.03515625, + 0.6484375, + 0.446044921875, + 1.015625, + 0.4091796875, + 0.415771484375, + 0.0469970703125, + 0.00666046142578125, + 0.65185546875, + 0.08453369140625, + 1.2529296875, + 0.07757568359375, + 0.64794921875, + 0.0184173583984375, + 0.04901123046875, + 0.274169921875, + 0.62158203125, + 0.72705078125, + 0.57080078125, + 0.470458984375, + 0.332763671875, + 0.2421875, + 0.5791015625, + 0.2425537109375, + 1.0615234375, + 0.59326171875, + 0.6025390625, + 0.32373046875, + 0.0458984375, + 0.70751953125, + 0.045989990234375, + 1.177734375, + 0.1124267578125, + 0.93896484375, + 0.126953125, + 0.337646484375, + 0.405517578125, + 0.338134765625, + 0.58984375, + 0.8271484375, + 0.68212890625, + 0.08245849609375, + 0.05999755859375, + 0.8564453125, + 0.5791015625, + 1.1357421875, + 0.39892578125, + 0.405029296875, + 0.08026123046875, + 0.01137542724609375, + 0.869140625, + 0.1097412109375, + 1.44921875, + 0.07562255859375, + 0.63134765625, + 0.031463623046875, + 0.0836181640625, + 0.372314453125, + 0.80712890625, + 0.87451171875, + 0.55615234375, + 0.45849609375, + 0.054931640625, + 0.003459930419921875, + 0.71533203125, + 0.043914794921875, + 0.86865234375, + 0.040283203125, + 0.210205078125, + 0.04205322265625, + 0.02203369140625, + 0.54150390625, + 0.279541015625, + 0.587890625, + 0.256591796875, + 0.09014892578125, + 0.0159149169921875, + 0.02374267578125, + 0.2100830078125, + 0.30126953125, + 0.288330078125, + 0.276611328125, + 0.0946044921875, + 0.37841796875, + 0.023834228515625, + 0.50341796875, + 0.0238800048828125, + 1.0517578125, + 0.058380126953125, + 0.3046875, + 0.28955078125, + 0.15185546875, + 0.343505859375, + 0.152099609375, + 0.76318359375, + 0.3720703125, + 0.130615234375, + 0.10968017578125, + 0.16357421875, + 0.1658935546875, + 0.1639404296875, + 0.32470703125, + 0.40087890625, + 0.1370849609375, + 0.09381103515625, + 0.0059051513671875, + 0.935546875, + 0.056976318359375, + 0.9208984375, + 0.03924560546875, + 0.204833984375, + 0.07177734375, + 0.037628173828125, + 0.70556640625, + 0.363037109375, + 0.6044921875, + 0.25, + 0.08782958984375, + 0.0271759033203125, + 0.04052734375, + 0.276123046875, + 0.39111328125, + 0.314453125, + 0.26953125, + 0.0921630859375 + ], + 'descriptor': {shape: [1, 2, 9, 7], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'convTranspose2d float16 4D input and filter tensors options.dilations', + 'graph': { + 'inputs': { + 'convTranspose2dInput': { + 'data': [ + 0.319580078125, 0.97607421875, 0.4130859375, 0.479736328125, + 0.767578125, 0.908203125, 0.62060546875, 0.658203125, 0.6552734375 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'} + }, + 'convTranspose2dFilter': { + 'data': [0.68359375, 0.96435546875, 0.8271484375, 0.5771484375], + 'descriptor': {shape: [1, 1, 2, 2], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'convTranspose2d', + 'arguments': [ + {'input': 'convTranspose2dInput'}, + {'filter': 'convTranspose2dFilter'}, + {'options': {'dilations': [2, 2]}} + ], + 'outputs': 'convTranspose2dOutput' + }], + 'expectedOutputs': { + 'convTranspose2dOutput': { + 'data': [ + 0.218505859375, 0.66748046875, 0.59033203125, 0.94140625, + 0.3984375, 0.327880859375, 0.52490234375, 1.0830078125, + 0.740234375, 0.8759765625, 0.6884765625, 1.2568359375, + 1.572265625, 1.1982421875, 0.8701171875, 0.396728515625, + 0.634765625, 1.0283203125, 0.443115234375, 0.52392578125, + 0.51318359375, 0.54443359375, 0.900390625, 0.3798828125, + 0.378173828125 + ], + 'descriptor': {shape: [1, 1, 5, 5], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'convTranspose2d float16 4D input and filter tensors options.outputPadding', + 'graph': { + 'inputs': { + 'convTranspose2dInput': { + 'data': [ + 0.056060791015625, 0.71142578125, 0.65283203125, 0.38623046875, + 0.386962890625, 0.9462890625, 0.095703125, 0.92333984375, + 0.63623046875 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'} + }, + 'convTranspose2dFilter': { + 'data': [ + 0.861328125, 0.626953125, 0.63671875, 0.83837890625, + 0.11883544921875, 0.9921875, 0.32861328125, 0.8740234375, + 0.720703125, 0.97998046875, 0.06170654296875, 0.322021484375, 0.75, + 0.39306640625, 0.1380615234375, 0.283935546875, 0.423583984375, + 0.1448974609375 + ], + 'descriptor': {shape: [1, 2, 3, 3], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'convTranspose2d', + 'arguments': [ + {'input': 'convTranspose2dInput'}, + {'filter': 'convTranspose2dFilter'}, + {'options': {'strides': [3, 2], 'outputPadding': [1, 1]}} + ], + 'outputs': 'convTranspose2dOutput' + }], + 'expectedOutputs': { + 'convTranspose2dOutput': { + 'data': [ + 0.04827880859375, + 0.03515625, + 0.6484375, + 0.446044921875, + 1.015625, + 0.4091796875, + 0.415771484375, + 0, + 0.0469970703125, + 0.00666046142578125, + 0.65185546875, + 0.08453369140625, + 1.2529296875, + 0.07757568359375, + 0.64794921875, + 0, + 0.0184173583984375, + 0.04901123046875, + 0.274169921875, + 0.62158203125, + 0.72705078125, + 0.57080078125, + 0.470458984375, + 0, + 0.332763671875, + 0.2421875, + 0.5791015625, + 0.2425537109375, + 1.0615234375, + 0.59326171875, + 0.6025390625, + 0, + 0.32373046875, + 0.0458984375, + 0.70751953125, + 0.045989990234375, + 1.177734375, + 0.1124267578125, + 0.93896484375, + 0, + 0.126953125, + 0.337646484375, + 0.405517578125, + 0.338134765625, + 0.58984375, + 0.8271484375, + 0.68212890625, + 0, + 0.08245849609375, + 0.05999755859375, + 0.8564453125, + 0.5791015625, + 1.1357421875, + 0.39892578125, + 0.405029296875, + 0, + 0.08026123046875, + 0.01137542724609375, + 0.869140625, + 0.1097412109375, + 1.44921875, + 0.07562255859375, + 0.63134765625, + 0, + 0.031463623046875, + 0.0836181640625, + 0.372314453125, + 0.80712890625, + 0.87451171875, + 0.55615234375, + 0.45849609375, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.054931640625, + 0.003459930419921875, + 0.71533203125, + 0.043914794921875, + 0.86865234375, + 0.040283203125, + 0.210205078125, + 0, + 0.04205322265625, + 0.02203369140625, + 0.54150390625, + 0.279541015625, + 0.587890625, + 0.256591796875, + 0.09014892578125, + 0, + 0.0159149169921875, + 0.02374267578125, + 0.2100830078125, + 0.30126953125, + 0.288330078125, + 0.276611328125, + 0.0946044921875, + 0, + 0.37841796875, + 0.023834228515625, + 0.50341796875, + 0.0238800048828125, + 1.0517578125, + 0.058380126953125, + 0.3046875, + 0, + 0.28955078125, + 0.15185546875, + 0.343505859375, + 0.152099609375, + 0.76318359375, + 0.3720703125, + 0.130615234375, + 0, + 0.10968017578125, + 0.16357421875, + 0.1658935546875, + 0.1639404296875, + 0.32470703125, + 0.40087890625, + 0.1370849609375, + 0, + 0.09381103515625, + 0.0059051513671875, + 0.935546875, + 0.056976318359375, + 0.9208984375, + 0.03924560546875, + 0.204833984375, + 0, + 0.07177734375, + 0.037628173828125, + 0.70556640625, + 0.363037109375, + 0.6044921875, + 0.25, + 0.08782958984375, + 0, + 0.0271759033203125, + 0.04052734375, + 0.276123046875, + 0.39111328125, + 0.314453125, + 0.26953125, + 0.0921630859375, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + 'descriptor': {shape: [1, 2, 10, 8], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'convTranspose2d float16 4D input and filter tensors options.outputSizes', + 'graph': { + 'inputs': { + 'convTranspose2dInput': { + 'data': [ + 0.056060791015625, 0.71142578125, 0.65283203125, 0.38623046875, + 0.386962890625, 0.9462890625, 0.095703125, 0.92333984375, + 0.63623046875 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'} + }, + 'convTranspose2dFilter': { + 'data': [ + 0.861328125, 0.626953125, 0.63671875, 0.83837890625, + 0.11883544921875, 0.9921875, 0.32861328125, 0.8740234375, + 0.720703125, 0.97998046875, 0.06170654296875, 0.322021484375, 0.75, + 0.39306640625, 0.1380615234375, 0.283935546875, 0.423583984375, + 0.1448974609375 + ], + 'descriptor': {shape: [1, 2, 3, 3], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'convTranspose2d', + 'arguments': [ + {'input': 'convTranspose2dInput'}, + {'filter': 'convTranspose2dFilter'}, + {'options': {'strides': [3, 2], 'outputSizes': [10, 8]}} + ], + 'outputs': 'convTranspose2dOutput' + }], + 'expectedOutputs': { + 'convTranspose2dOutput': { + 'data': [ + 0.04827880859375, + 0.03515625, + 0.6484375, + 0.446044921875, + 1.015625, + 0.4091796875, + 0.415771484375, + 0, + 0.0469970703125, + 0.00666046142578125, + 0.65185546875, + 0.08453369140625, + 1.2529296875, + 0.07757568359375, + 0.64794921875, + 0, + 0.0184173583984375, + 0.04901123046875, + 0.274169921875, + 0.62158203125, + 0.72705078125, + 0.57080078125, + 0.470458984375, + 0, + 0.332763671875, + 0.2421875, + 0.5791015625, + 0.2425537109375, + 1.0615234375, + 0.59326171875, + 0.6025390625, + 0, + 0.32373046875, + 0.0458984375, + 0.70751953125, + 0.045989990234375, + 1.177734375, + 0.1124267578125, + 0.93896484375, + 0, + 0.126953125, + 0.337646484375, + 0.405517578125, + 0.338134765625, + 0.58984375, + 0.8271484375, + 0.68212890625, + 0, + 0.08245849609375, + 0.05999755859375, + 0.8564453125, + 0.5791015625, + 1.1357421875, + 0.39892578125, + 0.405029296875, + 0, + 0.08026123046875, + 0.01137542724609375, + 0.869140625, + 0.1097412109375, + 1.44921875, + 0.07562255859375, + 0.63134765625, + 0, + 0.031463623046875, + 0.0836181640625, + 0.372314453125, + 0.80712890625, + 0.87451171875, + 0.55615234375, + 0.45849609375, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.054931640625, + 0.003459930419921875, + 0.71533203125, + 0.043914794921875, + 0.86865234375, + 0.040283203125, + 0.210205078125, + 0, + 0.04205322265625, + 0.02203369140625, + 0.54150390625, + 0.279541015625, + 0.587890625, + 0.256591796875, + 0.09014892578125, + 0, + 0.0159149169921875, + 0.02374267578125, + 0.2100830078125, + 0.30126953125, + 0.288330078125, + 0.276611328125, + 0.0946044921875, + 0, + 0.37841796875, + 0.023834228515625, + 0.50341796875, + 0.0238800048828125, + 1.0517578125, + 0.058380126953125, + 0.3046875, + 0, + 0.28955078125, + 0.15185546875, + 0.343505859375, + 0.152099609375, + 0.76318359375, + 0.3720703125, + 0.130615234375, + 0, + 0.10968017578125, + 0.16357421875, + 0.1658935546875, + 0.1639404296875, + 0.32470703125, + 0.40087890625, + 0.1370849609375, + 0, + 0.09381103515625, + 0.0059051513671875, + 0.935546875, + 0.056976318359375, + 0.9208984375, + 0.03924560546875, + 0.204833984375, + 0, + 0.07177734375, + 0.037628173828125, + 0.70556640625, + 0.363037109375, + 0.6044921875, + 0.25, + 0.08782958984375, + 0, + 0.0271759033203125, + 0.04052734375, + 0.276123046875, + 0.39111328125, + 0.314453125, + 0.26953125, + 0.0921630859375, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + 'descriptor': {shape: [1, 2, 10, 8], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'convTranspose2d float16 4D input and filter tensors options.inputLayout=nchw', + 'graph': { + 'inputs': { + 'convTranspose2dInput': { + 'data': [ + 0.056060791015625, 0.71142578125, 0.65283203125, 0.38623046875, + 0.386962890625, 0.9462890625, 0.095703125, 0.92333984375, + 0.63623046875 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'} + }, + 'convTranspose2dFilter': { + 'data': [ + 0.861328125, 0.626953125, 0.63671875, 0.83837890625, + 0.11883544921875, 0.9921875, 0.32861328125, 0.8740234375, + 0.720703125, 0.97998046875, 0.06170654296875, 0.322021484375, 0.75, + 0.39306640625, 0.1380615234375, 0.283935546875, 0.423583984375, + 0.1448974609375 + ], + 'descriptor': {shape: [1, 2, 3, 3], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'convTranspose2d', + 'arguments': [ + {'input': 'convTranspose2dInput'}, + {'filter': 'convTranspose2dFilter'}, + {'options': {'inputLayout': 'nchw'}} + ], + 'outputs': 'convTranspose2dOutput' + }], + 'expectedOutputs': { + 'convTranspose2dOutput': { + 'data': [ + 0.04827880859375, + 0.64794921875, + 1.0439453125, + 0.8623046875, + 0.415771484375, + 0.379638671875, + 1.1787109375, + 1.9912109375, + 1.623046875, + 1.25, + 0.424560546875, + 1.5087890625, + 3.287109375, + 2.56640625, + 1.814453125, + 0.2071533203125, + 1.25, + 1.666015625, + 2.09765625, + 1.3134765625, + 0.031463623046875, + 0.386962890625, + 1.0849609375, + 1.2216796875, + 0.45849609375, + 0.054931640625, + 0.70068359375, + 0.70166015625, + 0.269287109375, + 0.210205078125, + 0.420654296875, + 0.95849609375, + 1.8525390625, + 0.53759765625, + 0.394775390625, + 0.3994140625, + 1.578125, + 2.12109375, + 1.1416015625, + 0.43017578125, + 0.181396484375, + 1.00390625, + 1.341796875, + 0.83447265625, + 0.2249755859375, + 0.0271759033203125, + 0.302734375, + 0.58544921875, + 0.4033203125, + 0.0921630859375 + ], + 'descriptor': {shape: [1, 2, 5, 5], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'convTranspose2d float16 4D input and filter tensors options.inputLayout=nhwc', + 'graph': { + 'inputs': { + 'convTranspose2dInput': { + 'data': [ + 0.056060791015625, 0.71142578125, 0.65283203125, 0.38623046875, + 0.386962890625, 0.9462890625, 0.095703125, 0.92333984375, + 0.63623046875 + ], + 'descriptor': {shape: [1, 3, 3, 1], dataType: 'float16'} + }, + 'convTranspose2dFilter': { + 'data': [ + 0.861328125, 0.626953125, 0.63671875, 0.83837890625, + 0.11883544921875, 0.9921875, 0.32861328125, 0.8740234375, + 0.720703125, 0.97998046875, 0.06170654296875, 0.322021484375, 0.75, + 0.39306640625, 0.1380615234375, 0.283935546875, 0.423583984375, + 0.1448974609375 + ], + 'descriptor': {shape: [1, 2, 3, 3], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'convTranspose2d', + 'arguments': [ + {'input': 'convTranspose2dInput'}, + {'filter': 'convTranspose2dFilter'}, + {'options': {'inputLayout': 'nhwc'}} + ], + 'outputs': 'convTranspose2dOutput' + }], + 'expectedOutputs': { + 'convTranspose2dOutput': { + 'data': [ + 0.04827880859375, + 0.054931640625, + 0.64794921875, + 0.70068359375, + 1.0439453125, + 0.70166015625, + 0.8623046875, + 0.269287109375, + 0.415771484375, + 0.210205078125, + 0.379638671875, + 0.420654296875, + 1.1787109375, + 0.95849609375, + 1.9912109375, + 1.8525390625, + 1.623046875, + 0.53759765625, + 1.25, + 0.394775390625, + 0.424560546875, + 0.3994140625, + 1.5087890625, + 1.578125, + 3.287109375, + 2.12109375, + 2.56640625, + 1.1416015625, + 1.814453125, + 0.43017578125, + 0.2071533203125, + 0.181396484375, + 1.25, + 1.00390625, + 1.666015625, + 1.341796875, + 2.09765625, + 0.83447265625, + 1.3134765625, + 0.2249755859375, + 0.031463623046875, + 0.0271759033203125, + 0.386962890625, + 0.302734375, + 1.0849609375, + 0.58544921875, + 1.2216796875, + 0.4033203125, + 0.45849609375, + 0.0921630859375 + ], + 'descriptor': {shape: [1, 5, 5, 2], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'convTranspose2d float16 4D input and filter tensors options.filterLayout=iohw', + 'graph': { + 'inputs': { + 'convTranspose2dInput': { + 'data': [ + 0.056060791015625, 0.71142578125, 0.65283203125, 0.38623046875, + 0.386962890625, 0.9462890625, 0.095703125, 0.92333984375, + 0.63623046875 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'} + }, + 'convTranspose2dFilter': { + 'data': [ + 0.861328125, 0.626953125, 0.63671875, 0.83837890625, + 0.11883544921875, 0.9921875, 0.32861328125, 0.8740234375, + 0.720703125, 0.97998046875, 0.06170654296875, 0.322021484375, 0.75, + 0.39306640625, 0.1380615234375, 0.283935546875, 0.423583984375, + 0.1448974609375 + ], + 'descriptor': {shape: [1, 2, 3, 3], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'convTranspose2d', + 'arguments': [ + {'input': 'convTranspose2dInput'}, + {'filter': 'convTranspose2dFilter'}, + {'options': {'filterLayout': 'iohw'}} + ], + 'outputs': 'convTranspose2dOutput' + }], + 'expectedOutputs': { + 'convTranspose2dOutput': { + 'data': [ + 0.04827880859375, + 0.64794921875, + 1.0439453125, + 0.8623046875, + 0.415771484375, + 0.379638671875, + 1.1787109375, + 1.9912109375, + 1.623046875, + 1.25, + 0.424560546875, + 1.5087890625, + 3.287109375, + 2.56640625, + 1.814453125, + 0.2071533203125, + 1.25, + 1.666015625, + 2.09765625, + 1.3134765625, + 0.031463623046875, + 0.386962890625, + 1.0849609375, + 1.2216796875, + 0.45849609375, + 0.054931640625, + 0.70068359375, + 0.70166015625, + 0.269287109375, + 0.210205078125, + 0.420654296875, + 0.95849609375, + 1.8525390625, + 0.53759765625, + 0.394775390625, + 0.3994140625, + 1.578125, + 2.12109375, + 1.1416015625, + 0.43017578125, + 0.181396484375, + 1.00390625, + 1.341796875, + 0.83447265625, + 0.2249755859375, + 0.0271759033203125, + 0.302734375, + 0.58544921875, + 0.4033203125, + 0.0921630859375 + ], + 'descriptor': {shape: [1, 2, 5, 5], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'convTranspose2d float16 4D input and filter tensors options.filterLayout=hwoi', + 'graph': { + 'inputs': { + 'convTranspose2dInput': { + 'data': [ + 0.056060791015625, 0.71142578125, 0.65283203125, 0.38623046875, + 0.386962890625, 0.9462890625, 0.095703125, 0.92333984375, + 0.63623046875 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'} + }, + 'convTranspose2dFilter': { + 'data': [ + 0.861328125, 0.97998046875, 0.626953125, 0.06170654296875, + 0.63671875, 0.322021484375, 0.83837890625, 0.75, 0.11883544921875, + 0.39306640625, 0.9921875, 0.1380615234375, 0.32861328125, + 0.283935546875, 0.8740234375, 0.423583984375, 0.720703125, + 0.1448974609375 + ], + 'descriptor': {shape: [3, 3, 2, 1], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'convTranspose2d', + 'arguments': [ + {'input': 'convTranspose2dInput'}, + {'filter': 'convTranspose2dFilter'}, + {'options': {'filterLayout': 'hwoi'}} + ], + 'outputs': 'convTranspose2dOutput' + }], + 'expectedOutputs': { + 'convTranspose2dOutput': { + 'data': [ + 0.04827880859375, + 0.64794921875, + 1.0439453125, + 0.8623046875, + 0.415771484375, + 0.379638671875, + 1.1787109375, + 1.9912109375, + 1.623046875, + 1.25, + 0.424560546875, + 1.5087890625, + 3.287109375, + 2.56640625, + 1.814453125, + 0.2071533203125, + 1.25, + 1.666015625, + 2.09765625, + 1.3134765625, + 0.031463623046875, + 0.386962890625, + 1.0849609375, + 1.2216796875, + 0.45849609375, + 0.054931640625, + 0.70068359375, + 0.70166015625, + 0.269287109375, + 0.210205078125, + 0.420654296875, + 0.95849609375, + 1.8525390625, + 0.53759765625, + 0.394775390625, + 0.3994140625, + 1.578125, + 2.12109375, + 1.1416015625, + 0.43017578125, + 0.181396484375, + 1.00390625, + 1.341796875, + 0.83447265625, + 0.2249755859375, + 0.0271759033203125, + 0.302734375, + 0.58544921875, + 0.4033203125, + 0.0921630859375 + ], + 'descriptor': {shape: [1, 2, 5, 5], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'convTranspose2d float16 4D input and filter tensors options.filterLayout=ohwi', + 'graph': { + 'inputs': { + 'convTranspose2dInput': { + 'data': [ + 0.056060791015625, 0.71142578125, 0.65283203125, 0.38623046875, + 0.386962890625, 0.9462890625, 0.095703125, 0.92333984375, + 0.63623046875 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'} + }, + 'convTranspose2dFilter': { + 'data': [ + 0.861328125, 0.626953125, 0.63671875, 0.83837890625, + 0.11883544921875, 0.9921875, 0.32861328125, 0.8740234375, + 0.720703125, 0.97998046875, 0.06170654296875, 0.322021484375, 0.75, + 0.39306640625, 0.1380615234375, 0.283935546875, 0.423583984375, + 0.1448974609375 + ], + 'descriptor': {shape: [2, 3, 3, 1], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'convTranspose2d', + 'arguments': [ + {'input': 'convTranspose2dInput'}, + {'filter': 'convTranspose2dFilter'}, + {'options': {'filterLayout': 'ohwi'}} + ], + 'outputs': 'convTranspose2dOutput' + }], + 'expectedOutputs': { + 'convTranspose2dOutput': { + 'data': [ + 0.04827880859375, + 0.64794921875, + 1.0439453125, + 0.8623046875, + 0.415771484375, + 0.379638671875, + 1.1787109375, + 1.9912109375, + 1.623046875, + 1.25, + 0.424560546875, + 1.5087890625, + 3.287109375, + 2.56640625, + 1.814453125, + 0.2071533203125, + 1.25, + 1.666015625, + 2.09765625, + 1.3134765625, + 0.031463623046875, + 0.386962890625, + 1.0849609375, + 1.2216796875, + 0.45849609375, + 0.054931640625, + 0.70068359375, + 0.70166015625, + 0.269287109375, + 0.210205078125, + 0.420654296875, + 0.95849609375, + 1.8525390625, + 0.53759765625, + 0.394775390625, + 0.3994140625, + 1.578125, + 2.12109375, + 1.1416015625, + 0.43017578125, + 0.181396484375, + 1.00390625, + 1.341796875, + 0.83447265625, + 0.2249755859375, + 0.0271759033203125, + 0.302734375, + 0.58544921875, + 0.4033203125, + 0.0921630859375 + ], + 'descriptor': {shape: [1, 2, 5, 5], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'convTranspose2d float16 4D input and filter tensors options.inputLayout=nhwc options.filterLayout=iohw', + 'graph': { + 'inputs': { + 'convTranspose2dInput': { + 'data': [ + 0.056060791015625, 0.71142578125, 0.65283203125, 0.38623046875, + 0.386962890625, 0.9462890625, 0.095703125, 0.92333984375, + 0.63623046875 + ], + 'descriptor': {shape: [1, 3, 3, 1], dataType: 'float16'} + }, + 'convTranspose2dFilter': { + 'data': [ + 0.861328125, 0.626953125, 0.63671875, 0.83837890625, + 0.11883544921875, 0.9921875, 0.32861328125, 0.8740234375, + 0.720703125, 0.97998046875, 0.06170654296875, 0.322021484375, 0.75, + 0.39306640625, 0.1380615234375, 0.283935546875, 0.423583984375, + 0.1448974609375 + ], + 'descriptor': {shape: [1, 2, 3, 3], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'convTranspose2d', + 'arguments': [ + {'input': 'convTranspose2dInput'}, + {'filter': 'convTranspose2dFilter'}, + {'options': {'inputLayout': 'nhwc', 'filterLayout': 'iohw'}} + ], + 'outputs': 'convTranspose2dOutput' + }], + 'expectedOutputs': { + 'convTranspose2dOutput': { + 'data': [ + 0.04827880859375, + 0.054931640625, + 0.64794921875, + 0.70068359375, + 1.0439453125, + 0.70166015625, + 0.8623046875, + 0.269287109375, + 0.415771484375, + 0.210205078125, + 0.379638671875, + 0.420654296875, + 1.1787109375, + 0.95849609375, + 1.9912109375, + 1.8525390625, + 1.623046875, + 0.53759765625, + 1.25, + 0.394775390625, + 0.424560546875, + 0.3994140625, + 1.5087890625, + 1.578125, + 3.287109375, + 2.12109375, + 2.56640625, + 1.1416015625, + 1.814453125, + 0.43017578125, + 0.2071533203125, + 0.181396484375, + 1.25, + 1.00390625, + 1.666015625, + 1.341796875, + 2.09765625, + 0.83447265625, + 1.3134765625, + 0.2249755859375, + 0.031463623046875, + 0.0271759033203125, + 0.386962890625, + 0.302734375, + 1.0849609375, + 0.58544921875, + 1.2216796875, + 0.4033203125, + 0.45849609375, + 0.0921630859375 + ], + 'descriptor': {shape: [1, 5, 5, 2], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'convTranspose2d float16 4D input and filter tensors options.inputLayout=nhwc options.filterLayout=hwoi', + 'graph': { + 'inputs': { + 'convTranspose2dInput': { + 'data': [ + 0.056060791015625, 0.71142578125, 0.65283203125, 0.38623046875, + 0.386962890625, 0.9462890625, 0.095703125, 0.92333984375, + 0.63623046875 + ], + 'descriptor': {shape: [1, 3, 3, 1], dataType: 'float16'} + }, + 'convTranspose2dFilter': { + 'data': [ + 0.861328125, 0.97998046875, 0.626953125, 0.06170654296875, + 0.63671875, 0.322021484375, 0.83837890625, 0.75, 0.11883544921875, + 0.39306640625, 0.9921875, 0.1380615234375, 0.32861328125, + 0.283935546875, 0.8740234375, 0.423583984375, 0.720703125, + 0.1448974609375 + ], + 'descriptor': {shape: [3, 3, 2, 1], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'convTranspose2d', + 'arguments': [ + {'input': 'convTranspose2dInput'}, + {'filter': 'convTranspose2dFilter'}, + {'options': {'inputLayout': 'nhwc', 'filterLayout': 'hwoi'}} + ], + 'outputs': 'convTranspose2dOutput' + }], + 'expectedOutputs': { + 'convTranspose2dOutput': { + 'data': [ + 0.04827880859375, + 0.054931640625, + 0.64794921875, + 0.70068359375, + 1.0439453125, + 0.70166015625, + 0.8623046875, + 0.269287109375, + 0.415771484375, + 0.210205078125, + 0.379638671875, + 0.420654296875, + 1.1787109375, + 0.95849609375, + 1.9912109375, + 1.8525390625, + 1.623046875, + 0.53759765625, + 1.25, + 0.394775390625, + 0.424560546875, + 0.3994140625, + 1.5087890625, + 1.578125, + 3.287109375, + 2.12109375, + 2.56640625, + 1.1416015625, + 1.814453125, + 0.43017578125, + 0.2071533203125, + 0.181396484375, + 1.25, + 1.00390625, + 1.666015625, + 1.341796875, + 2.09765625, + 0.83447265625, + 1.3134765625, + 0.2249755859375, + 0.031463623046875, + 0.0271759033203125, + 0.386962890625, + 0.302734375, + 1.0849609375, + 0.58544921875, + 1.2216796875, + 0.4033203125, + 0.45849609375, + 0.0921630859375 + ], + 'descriptor': {shape: [1, 5, 5, 2], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'convTranspose2d float16 4D input and filter tensors options.inputLayout=nhwc options.filterLayout=ohwi', + 'graph': { + 'inputs': { + 'convTranspose2dInput': { + 'data': [ + 0.056060791015625, 0.71142578125, 0.65283203125, 0.38623046875, + 0.386962890625, 0.9462890625, 0.095703125, 0.92333984375, + 0.63623046875 + ], + 'descriptor': {shape: [1, 3, 3, 1], dataType: 'float16'} + }, + 'convTranspose2dFilter': { + 'data': [ + 0.861328125, 0.626953125, 0.63671875, 0.83837890625, + 0.11883544921875, 0.9921875, 0.32861328125, 0.8740234375, + 0.720703125, 0.97998046875, 0.06170654296875, 0.322021484375, 0.75, + 0.39306640625, 0.1380615234375, 0.283935546875, 0.423583984375, + 0.1448974609375 + ], + 'descriptor': {shape: [2, 3, 3, 1], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'convTranspose2d', + 'arguments': [ + {'input': 'convTranspose2dInput'}, + {'filter': 'convTranspose2dFilter'}, + {'options': {'inputLayout': 'nhwc', 'filterLayout': 'ohwi'}} + ], + 'outputs': 'convTranspose2dOutput' + }], + 'expectedOutputs': { + 'convTranspose2dOutput': { + 'data': [ + 0.04827880859375, + 0.054931640625, + 0.64794921875, + 0.70068359375, + 1.0439453125, + 0.70166015625, + 0.8623046875, + 0.269287109375, + 0.415771484375, + 0.210205078125, + 0.379638671875, + 0.420654296875, + 1.1787109375, + 0.95849609375, + 1.9912109375, + 1.8525390625, + 1.623046875, + 0.53759765625, + 1.25, + 0.394775390625, + 0.424560546875, + 0.3994140625, + 1.5087890625, + 1.578125, + 3.287109375, + 2.12109375, + 2.56640625, + 1.1416015625, + 1.814453125, + 0.43017578125, + 0.2071533203125, + 0.181396484375, + 1.25, + 1.00390625, + 1.666015625, + 1.341796875, + 2.09765625, + 0.83447265625, + 1.3134765625, + 0.2249755859375, + 0.031463623046875, + 0.0271759033203125, + 0.386962890625, + 0.302734375, + 1.0849609375, + 0.58544921875, + 1.2216796875, + 0.4033203125, + 0.45849609375, + 0.0921630859375 + ], + 'descriptor': {shape: [1, 5, 5, 2], dataType: 'float16'} + } + } + } + }, + { + 'name': 'convTranspose2d float16 4D input and filter tensors options.bias', + 'graph': { + 'inputs': { + 'convTranspose2dInput': { + 'data': [ + 0.1109619140625, 0.8681640625, 0.734375, 0.4306640625, + 0.59814453125, 0.12322998046875, 0.1611328125, 0.08837890625, + 0.291015625 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'} + }, + 'convTranspose2dFilter': { + 'data': [ + 0.6162109375, 0.26220703125, 0.794921875, 0.873046875, 0.8310546875, + 0.85498046875, 0.55517578125, 0.84033203125, 0.85302734375 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'}, + 'constant': true + }, + 'convTranspose2dBias': { + 'data': [0.45166015625], + 'descriptor': {shape: [1], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'convTranspose2d', + 'arguments': [ + {'input': 'convTranspose2dInput'}, + {'filter': 'convTranspose2dFilter'}, + {'options': {'bias': 'convTranspose2dBias'}} + ], + 'outputs': 'convTranspose2dOutput' + }], + 'expectedOutputs': { + 'convTranspose2dOutput': { + 'data': [ + 0.52001953125, 1.015625, 1.2197265625, 1.333984375, + 1.03515625, 0.81396484375, 1.783203125, 2.484375, + 2.3125, 1.177734375, 0.98876953125, 2.00390625, + 2.986328125, 2.5703125, 1.4150390625, 0.83154296875, + 1.3564453125, 1.85546875, 1.3828125, 0.8056640625, + 0.541015625, 0.63623046875, 0.82470703125, 0.771484375, + 0.69970703125 + ], + 'descriptor': {shape: [1, 1, 5, 5], dataType: 'float16'} + } + } + } + }, + { + 'name': + 'convTranspose2d float16 4D input and filter tensors, both negative input tensor and options.bias', + 'graph': { + 'inputs': { + 'convTranspose2dInput': { + 'data': [ + -0.10888671875, -0.298095703125, -0.390869140625, -0.5625, + -0.732421875, -0.84228515625, -0.305908203125, -0.9765625, + -0.01416015625 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'} + }, + 'convTranspose2dFilter': { + 'data': [ + 0.6162109375, 0.26220703125, 0.794921875, 0.873046875, 0.8310546875, + 0.85498046875, 0.55517578125, 0.84033203125, 0.85302734375 + ], + 'descriptor': {shape: [1, 1, 3, 3], dataType: 'float16'}, + 'constant': true + }, + 'convTranspose2dBias': { + 'data': [-0.845703125], + 'descriptor': {shape: [1], dataType: 'float16'}, + 'constant': true + } + }, + 'operators': [{ + 'name': 'convTranspose2d', + 'arguments': [ + {'input': 'convTranspose2dInput'}, + {'filter': 'convTranspose2dFilter'}, + {'options': {'bias': 'convTranspose2dBias'}} + ], + 'outputs': 'convTranspose2dOutput' + }], + 'expectedOutputs': { + 'convTranspose2dOutput': { + 'data': [ + -0.91259765625, -1.0576171875, -1.2509765625, -1.185546875, + -1.15625, -1.287109375, -1.794921875, -2.685546875, + -2.228515625, -1.849609375, -1.5859375, -2.890625, + -3.73828125, -3.53515625, -1.91015625, -1.4248046875, + -2.83203125, -3.494140625, -3.025390625, -1.576171875, + -1.015625, -1.64453125, -1.935546875, -1.6904296875, + -0.85791015625 + ], + 'descriptor': {shape: [1, 1, 5, 5], dataType: 'float16'} + } + } + } } ]; diff --git a/tests/wpt/tests/webnn/conformance_tests/log.https.any.js b/tests/wpt/tests/webnn/conformance_tests/log.https.any.js index 011beef53ac..8ed807b3401 100644 --- a/tests/wpt/tests/webnn/conformance_tests/log.https.any.js +++ b/tests/wpt/tests/webnn/conformance_tests/log.https.any.js @@ -14,11 +14,8 @@ // MLOperand log(MLOperand input); -const getLogPrecisionTolerance = (graphResources) => { - const toleranceValueDict = {float32: 1 / 1024, float16: 1 / 1024}; - const expectedDataType = - getExpectedDataTypeOfSingleOutput(graphResources.expectedOutputs); - return {metricType: 'ATOL', value: toleranceValueDict[expectedDataType]}; +const getLogPrecisionTolerance = () => { + return {metricType: 'ULP', value: 8}; }; const logTests = [ diff --git a/tests/wpt/tests/webnn/conformance_tests/pooling.https.any.js b/tests/wpt/tests/webnn/conformance_tests/pooling.https.any.js index f385aab1f15..8f81ff565d2 100644 --- a/tests/wpt/tests/webnn/conformance_tests/pooling.https.any.js +++ b/tests/wpt/tests/webnn/conformance_tests/pooling.https.any.js @@ -34,46 +34,6 @@ // MLOperand maxPool2d( // MLOperand input, optional MLPool2dOptions options = {}); - -const getPoolingOperatorsPrecisionTolerance = (graphResources) => { - const args = graphResources.operators[0].arguments; - const inputShape = - graphResources.inputs[args[0][Object.keys(args[0])[0]]].descriptor.shape; - const options = - args.length === 2 ? {...args[1][Object.keys(args[1])[0]]} : {}; - let height; - let width; - - if (options.windowDimensions) { - height = options.windowDimensions[0]; - width = options.windowDimensions[1]; - } else { - // If not present, the window dimensions are assumed to be the height and - // width dimensions of the input shape - if (options.layout && options.layout === 'nhwc') { - height = inputShape[1]; - width = inputShape[2]; - } else { - // nhwc layout of input - height = inputShape[2]; - width = inputShape[3]; - } - } - - const tolerance = height * width + 2; - const toleranceDict = { - averagePool2d: {float32: tolerance, float16: tolerance}, - l2Pool2d: {float32: tolerance, float16: tolerance}, - maxPool2d: {float32: 0, float16: 0}, - }; - const expectedDataType = - getExpectedDataTypeOfSingleOutput(graphResources.expectedOutputs); - return { - metricType: 'ULP', - value: toleranceDict[graphResources.operators[0].name][expectedDataType] - }; -}; - const poolingOperatorsTests = [ // averagePool2d tests { @@ -2345,8 +2305,7 @@ const poolingOperatorsTests = [ if (navigator.ml) { poolingOperatorsTests.forEach((test) => { - webnn_conformance_test( - buildAndExecuteGraph, getPoolingOperatorsPrecisionTolerance, test); + webnn_conformance_test(buildAndExecuteGraph, getPrecisionTolerance, test); }); } else { test(() => assert_implements(navigator.ml, 'missing navigator.ml')); diff --git a/tests/wpt/tests/webnn/conformance_tests/qdq_subgraph.https.any.js b/tests/wpt/tests/webnn/conformance_tests/qdq_subgraph.https.any.js index 3b59c3bb49d..aa816cce7fb 100644 --- a/tests/wpt/tests/webnn/conformance_tests/qdq_subgraph.https.any.js +++ b/tests/wpt/tests/webnn/conformance_tests/qdq_subgraph.https.any.js @@ -535,12 +535,12 @@ const subgraphTests = [ {'input': 'transposeOutput'}, {'scale': 'outputScale', 'zeroPoint': 'outputZeroPoint'} ], - 'outputs': 'quantizedtransposeOutput' + 'outputs': 'quantizedTransposeOutput' }, { 'name': 'dequantizeLinear', 'arguments': [ - {'input': 'quantizedtransposeOutput'}, + {'input': 'quantizedTransposeOutput'}, {'scale': 'outputScale', 'zeroPoint': 'outputZeroPoint'} ], 'outputs': 'output' @@ -619,12 +619,12 @@ const subgraphTests = [ {'input': 'tanhOutput'}, {'scale': 'outputScale', 'zeroPoint': 'outputZeroPoint'} ], - 'outputs': 'quantizedtanhOutput' + 'outputs': 'quantizedTanhOutput' }, { 'name': 'dequantizeLinear', 'arguments': [ - {'input': 'quantizedtanhOutput'}, + {'input': 'quantizedTanhOutput'}, {'scale': 'outputScale', 'zeroPoint': 'outputZeroPoint'} ], 'outputs': 'output' @@ -702,12 +702,12 @@ const subgraphTests = [ {'input': 'sigmoidOutput'}, {'scale': 'outputScale', 'zeroPoint': 'outputZeroPoint'} ], - 'outputs': 'quantizedsigmoidOutput' + 'outputs': 'quantizedSigmoidOutput' }, { 'name': 'dequantizeLinear', 'arguments': [ - {'input': 'quantizedsigmoidOutput'}, + {'input': 'quantizedSigmoidOutput'}, {'scale': 'outputScale', 'zeroPoint': 'outputZeroPoint'} ], 'outputs': 'output' @@ -788,12 +788,12 @@ const subgraphTests = [ {'input': 'leakyReluOutput'}, {'scale': 'outputScale', 'zeroPoint': 'outputZeroPoint'} ], - 'outputs': 'quantizedleakyReluOutput' + 'outputs': 'quantizedLeakyReluOutput' }, { 'name': 'dequantizeLinear', 'arguments': [ - {'input': 'quantizedleakyReluOutput'}, + {'input': 'quantizedLeakyReluOutput'}, {'scale': 'outputScale', 'zeroPoint': 'outputZeroPoint'} ], 'outputs': 'output' @@ -924,6 +924,337 @@ const subgraphTests = [ } } }, + { + 'name': 'quantized elu', + 'graph': { + 'inputs': { + 'input': { + 'data': [ + 1.6811466217041016, 0.0479511022567749, 0.33355462551116943, + -0.1988269537687301, -0.0041167140007019, -0.0634240251779556, + ], + 'descriptor': {shape: [2, 3], dataType: 'float32'}, + 'constant': false + }, + 'inputScale': { + 'data': [0.003921568859368563], + 'descriptor': {shape: [1], dataType: 'float32'}, + 'constant': true + }, + 'inputZeroPoint': { + 'data': [0], + 'descriptor': {shape: [1], dataType: 'int8'}, + 'constant': true + }, + 'outputScale': { + 'data': [0.003921568859368563], + 'descriptor': {shape: [1], dataType: 'float32'}, + 'constant': true + }, + 'outputZeroPoint': { + 'data': [0], + 'descriptor': {shape: [1], dataType: 'int8'}, + 'constant': true + }, + }, + 'operators': [ + { + 'name': 'quantizeLinear', + 'arguments': [ + {'input': 'input'}, + {'scale': 'inputScale', 'zeroPoint': 'inputZeroPoint'} + ], + 'outputs': 'quantizedInput' + }, + { + 'name': 'dequantizeLinear', + 'arguments': [ + {'input': 'quantizedInput'}, + {'scale': 'inputScale', 'zeroPoint': 'inputZeroPoint'} + ], + 'outputs': 'dequantizedInput' + }, + { + 'name': 'elu', + 'arguments': [{'input': 'dequantizedInput'}], + 'outputs': 'eluOutput' + }, + { + 'name': 'quantizeLinear', + 'arguments': [ + {'input': 'eluOutput'}, + {'scale': 'outputScale', 'zeroPoint': 'outputZeroPoint'} + ], + 'outputs': 'quantizedEluOutput' + }, + { + 'name': 'dequantizeLinear', + 'arguments': [ + {'input': 'quantizedEluOutput'}, + {'scale': 'outputScale', 'zeroPoint': 'outputZeroPoint'} + ], + 'outputs': 'output' + } + ], + 'expectedOutputs': { + 'output': { + 'data': [ + 0.49803924560546875, 0.0470588281750679, 0.3333333432674408, + -0.18039216101169586, -0.003921568859368563, -0.062745101749897, + ], + 'descriptor': {shape: [2, 3], dataType: 'float32'} + } + } + } + }, + { + 'name': 'quantized averagePool2d', + 'graph': { + 'inputs': { + 'input': { + 'data': [ + -2.549168109893799, -4.794857501983643, + 8.413617134094238, 6.108623504638672 + ], + 'descriptor': {shape: [1, 2, 2, 1], dataType: 'float32'}, + 'constant': false + }, + 'inputScale': { + 'data': [0.343092918395996], + 'descriptor': {shape: [1], dataType: 'float32'}, + 'constant': true + }, + 'inputZeroPoint': { + 'data': [-128], + 'descriptor': {shape: [1], dataType: 'int8'}, + 'constant': true + }, + 'outputScale': { + 'data': [0.343092918395996], + 'descriptor': {shape: [1], dataType: 'float32'}, + 'constant': true + }, + 'outputZeroPoint': { + 'data': [-128], + 'descriptor': {shape: [1], dataType: 'int8'}, + 'constant': true + }, + }, + 'operators': [ + { + 'name': 'quantizeLinear', + 'arguments': [ + {'input': 'input'}, + {'scale': 'inputScale', 'zeroPoint': 'inputZeroPoint'} + ], + 'outputs': 'quantizedInput' + }, + { + 'name': 'dequantizeLinear', + 'arguments': [ + {'input': 'quantizedInput'}, + {'scale': 'inputScale', 'zeroPoint': 'inputZeroPoint'} + ], + 'outputs': 'dequantizedInput' + }, + { + 'name': 'averagePool2d', + 'arguments': [{'input': 'dequantizedInput'}, {'options': {'layout': 'nhwc'}}], + 'outputs': 'averagePool2dOutput' + }, + { + 'name': 'quantizeLinear', + 'arguments': [ + {'input': 'averagePool2dOutput'}, + {'scale': 'outputScale', 'zeroPoint': 'outputZeroPoint'} + ], + 'outputs': 'quantizedAveragePool2dOutput' + }, + { + 'name': 'dequantizeLinear', + 'arguments': [ + {'input': 'quantizedAveragePool2dOutput'}, + {'scale': 'outputScale', 'zeroPoint': 'outputZeroPoint'} + ], + 'outputs': 'output' + } + ], + 'expectedOutputs': { + 'output': { + 'data': [ + 3.774022102355957, + ], + 'descriptor': {shape: [1, 1, 1, 1], dataType: 'float32'} + } + } + } + }, + { + 'name': 'quantized maxPool2d', + 'graph': { + 'inputs': { + 'input': { + 'data': [ + -2.549168109893799, -4.794857501983643, + 8.413617134094238, 6.108623504638672 + ], + 'descriptor': {shape: [1, 2, 2, 1], dataType: 'float32'}, + 'constant': false + }, + 'inputScale': { + 'data': [0.343092918395996], + 'descriptor': {shape: [1], dataType: 'float32'}, + 'constant': true + }, + 'inputZeroPoint': { + 'data': [-128], + 'descriptor': {shape: [1], dataType: 'int8'}, + 'constant': true + }, + 'outputScale': { + 'data': [0.343092918395996], + 'descriptor': {shape: [1], dataType: 'float32'}, + 'constant': true + }, + 'outputZeroPoint': { + 'data': [-128], + 'descriptor': {shape: [1], dataType: 'int8'}, + 'constant': true + }, + }, + 'operators': [ + { + 'name': 'quantizeLinear', + 'arguments': [ + {'input': 'input'}, + {'scale': 'inputScale', 'zeroPoint': 'inputZeroPoint'} + ], + 'outputs': 'quantizedInput' + }, + { + 'name': 'dequantizeLinear', + 'arguments': [ + {'input': 'quantizedInput'}, + {'scale': 'inputScale', 'zeroPoint': 'inputZeroPoint'} + ], + 'outputs': 'dequantizedInput' + }, + { + 'name': 'maxPool2d', + 'arguments': [{'input': 'dequantizedInput'}, {'options': {'layout': 'nhwc'}}], + 'outputs': 'maxPool2dOutput' + }, + { + 'name': 'quantizeLinear', + 'arguments': [ + {'input': 'maxPool2dOutput'}, + {'scale': 'outputScale', 'zeroPoint': 'outputZeroPoint'} + ], + 'outputs': 'quantizedMaxPool2dOutput' + }, + { + 'name': 'dequantizeLinear', + 'arguments': [ + {'input': 'quantizedMaxPool2dOutput'}, + {'scale': 'outputScale', 'zeroPoint': 'outputZeroPoint'} + ], + 'outputs': 'output' + } + ], + 'expectedOutputs': { + 'output': { + 'data': [ + 8.577322959899902, + ], + 'descriptor': {shape: [1, 1, 1, 1], dataType: 'float32'} + } + } + } + }, + { + 'name': 'quantized reshape', + 'graph': { + 'inputs': { + 'input': { + 'data': [ + 1.6811466217041016, 0.0479511022567749, 0.33355462551116943, + -0.1988269537687301, -0.0041167140007019, -0.0634240251779556, + ], + 'descriptor': {shape: [2, 3], dataType: 'float32'}, + 'constant': false + }, + 'inputScale': { + 'data': [0.003921568859368563], + 'descriptor': {shape: [1], dataType: 'float32'}, + 'constant': true + }, + 'inputZeroPoint': { + 'data': [16], + 'descriptor': {shape: [1], dataType: 'int8'}, + 'constant': true + }, + 'outputScale': { + 'data': [0.003921568859368563], + 'descriptor': {shape: [1], dataType: 'float32'}, + 'constant': true + }, + 'outputZeroPoint': { + 'data': [16], + 'descriptor': {shape: [1], dataType: 'int8'}, + 'constant': true + }, + }, + 'operators': [ + { + 'name': 'quantizeLinear', + 'arguments': [ + {'input': 'input'}, + {'scale': 'inputScale', 'zeroPoint': 'inputZeroPoint'} + ], + 'outputs': 'quantizedInput' + }, + { + 'name': 'dequantizeLinear', + 'arguments': [ + {'input': 'quantizedInput'}, + {'scale': 'inputScale', 'zeroPoint': 'inputZeroPoint'} + ], + 'outputs': 'dequantizedInput' + }, + { + 'name': 'reshape', + 'arguments': [{'input': 'dequantizedInput'}, {'newShape': [3, 2]}], + 'outputs': 'reshapeOutput' + }, + { + 'name': 'quantizeLinear', + 'arguments': [ + {'input': 'reshapeOutput'}, + {'scale': 'outputScale', 'zeroPoint': 'outputZeroPoint'} + ], + 'outputs': 'quantizedReshapeOutput' + }, + { + 'name': 'dequantizeLinear', + 'arguments': [ + {'input': 'quantizedReshapeOutput'}, + {'scale': 'outputScale', 'zeroPoint': 'outputZeroPoint'} + ], + 'outputs': 'output' + } + ], + 'expectedOutputs': { + 'output': { + 'data': [ + 0.43529415130615234, 0.0470588281750679, + 0.3333333432674408, -0.20000001788139343, + -0.003921568859368563, -0.062745101749897, + ], + 'descriptor': {shape: [3, 2], dataType: 'float32'} + } + } + } + }, ]; if (navigator.ml) { diff --git a/tests/wpt/tests/webnn/conformance_tests/sign.https.any.js b/tests/wpt/tests/webnn/conformance_tests/sign.https.any.js index 4c3a330f850..004c03bdf13 100644 --- a/tests/wpt/tests/webnn/conformance_tests/sign.https.any.js +++ b/tests/wpt/tests/webnn/conformance_tests/sign.https.any.js @@ -122,7 +122,8 @@ const signTests = [ 'graph': { 'inputs': { 'signInput': { - 'data': [-1, 0, 1, 2], + // int32 range: [/* -(2**31) */ -2147483648, /* 2**31 - 1 */ 2147483647] + 'data': [-2147483648, 0, 2147483646, 2147483647], 'descriptor': {shape: [2, 2], dataType: 'int32'} } }, @@ -166,7 +167,8 @@ const signTests = [ 'graph': { 'inputs': { 'signInput': { - 'data': [-1, 0, 1, 2, -2, -1, 0, 1], + // int8 range: [/* -(2**7) */ -128, /* 2**7 - 1 */ 127] + 'data': [-128, 0, 1, 2, -2, -1, 0, 127], 'descriptor': {shape: [1, 2, 2, 2], dataType: 'int8'} } }, diff --git a/tests/wpt/tests/webnn/resources/utils.js b/tests/wpt/tests/webnn/resources/utils.js index 50d7911a918..9d5cfc70c10 100644 --- a/tests/wpt/tests/webnn/resources/utils.js +++ b/tests/wpt/tests/webnn/resources/utils.js @@ -59,6 +59,13 @@ const getPrecisionTolerance = (graphResources, intermediateOperands) => { op, graphResources, intermediateOperands) .value; break; + case 'averagePool2d': + case 'maxPool2d': + case 'l2Pool2d': + toleranceValue += getPoolingOperatorsPrecisionTolerance( + op, graphResources, intermediateOperands) + .value; + break; default: const operatorTolerance = operatorToleranceDict[op.name]?.[expectedDataType]; @@ -1006,6 +1013,53 @@ const getConv2dPrecisionTolerance = return {metricType: 'ULP', value: toleranceValueDict[expectedDataType]}; }; +const getPoolingOperatorsPrecisionTolerance = + (op, graphResources, intermediateOperands) => { + const args = op.arguments; + const operatorName = op.name; + const {inputs} = graphResources; + let inputShape; + const inputIndex = args[0][Object.keys(args[0])[0]]; + if (inputs[inputIndex]) { + inputShape = inputs[inputIndex].descriptor.shape; + } else { + inputShape = intermediateOperands[inputIndex].shape; + } + const options = + args.length === 2 ? {...args[1][Object.keys(args[1])[0]]} : {}; + let height; + let width; + + if (options.windowDimensions) { + height = options.windowDimensions[0]; + width = options.windowDimensions[1]; + } else { + // If not present, the window dimensions are assumed to be the height + // and width dimensions of the input shape + if (options.layout && options.layout === 'nhwc') { + height = inputShape[1]; + width = inputShape[2]; + } else { + // nhwc layout of input + height = inputShape[2]; + width = inputShape[3]; + } + } + + const tolerance = height * width + 2; + const toleranceDict = { + averagePool2d: {float32: tolerance, float16: tolerance}, + l2Pool2d: {float32: tolerance, float16: tolerance}, + maxPool2d: {float32: 0, float16: 0}, + }; + const expectedDataType = + getExpectedDataTypeOfSingleOutput(graphResources.expectedOutputs); + return { + metricType: 'ULP', + value: toleranceDict[operatorName][expectedDataType] + }; +}; + const getInstanceNormPrecisionTolerance = (graphResources) => { // according to // https://github.com/web-platform-tests/wpt/pull/43891#discussion_r1457026316 diff --git a/tests/wpt/tests/webrtc-encoded-transform/tentative/RTCEncodedAudioFrame-clone.https.html b/tests/wpt/tests/webrtc-encoded-transform/tentative/RTCEncodedAudioFrame-clone.https.html index 9f07713d443..c93f8b3e541 100644 --- a/tests/wpt/tests/webrtc-encoded-transform/tentative/RTCEncodedAudioFrame-clone.https.html +++ b/tests/wpt/tests/webrtc-encoded-transform/tentative/RTCEncodedAudioFrame-clone.https.html @@ -37,7 +37,7 @@ promise_test(async t => { const original = result.value; let clone = structuredClone(original); assert_equals(original.timestamp, clone.timestamp); - assert_equals(original.getMetadata().absCaptureTime, clone.getMetadata().absCaptureTime); + assert_equals(original.getMetadata().captureTime, clone.getMetadata().captureTime); assert_array_equals(Array.from(original.data), Array.from(clone.data)); await writer2.write(clone); resolve(); diff --git a/tests/wpt/tests/webrtc-encoded-transform/tentative/RTCEncodedAudioFrame-metadata.https.html b/tests/wpt/tests/webrtc-encoded-transform/tentative/RTCEncodedAudioFrame-metadata.https.html index 435e1c06783..df4577c5614 100644 --- a/tests/wpt/tests/webrtc-encoded-transform/tentative/RTCEncodedAudioFrame-metadata.https.html +++ b/tests/wpt/tests/webrtc-encoded-transform/tentative/RTCEncodedAudioFrame-metadata.https.html @@ -39,7 +39,7 @@ promise_test(async t => { assert_true(original.getMetadata().hasOwnProperty('receiveTime')); assert_true(original.getMetadata().receiveTime > 0); assert_equals(original.getMetadata().rtpTimestamp, newFrame.getMetadata().rtpTimestamp); - assert_equals(original.getMetadata().absCaptureTime, newFrame.getMetadata().absCaptureTime); + assert_equals(original.getMetadata().captureTime, newFrame.getMetadata().captureTime); assert_equals(original.getMetadata().receiveTime, newFrame.getMetadata().receiveTime); assert_array_equals(Array.from(original.data), Array.from(newFrame.data)); await writer2.write(newFrame); @@ -83,7 +83,7 @@ promise_test(async t => { assert_not_equals(original.getMetadata().rtpTimestamp, newFrame.getMetadata().rtpTimestamp); assert_equals(newMetadata.rtpTimestamp, newFrame.getMetadata().rtpTimestamp); assert_equals(original.getMetadata().receiveTime, newFrame.getMetadata().receiveTime); - assert_equals(original.getMetadata().absCaptureTime, newFrame.getMetadata().absCaptureTime); + assert_equals(original.getMetadata().captureTime, newFrame.getMetadata().captureTime); assert_array_equals(Array.from(original.data), Array.from(newFrame.data)); await writer2.write(newFrame); resolve(); diff --git a/tests/wpt/tests/webrtc-encoded-transform/tentative/RTCPeerConnection-insertable-streams.js b/tests/wpt/tests/webrtc-encoded-transform/tentative/RTCPeerConnection-insertable-streams.js index 0bf820acde4..f3873e1de4b 100644 --- a/tests/wpt/tests/webrtc-encoded-transform/tentative/RTCPeerConnection-insertable-streams.js +++ b/tests/wpt/tests/webrtc-encoded-transform/tentative/RTCPeerConnection-insertable-streams.js @@ -30,7 +30,7 @@ function areMetadataEqual(metadata1, metadata2, type) { metadata1.payloadType == metadata2.payloadType && areArraysEqual( metadata1.contributingSources, metadata2.contributingSources) && - metadata1.absCaptureTime == metadata2.absCaptureTime && + metadata1.captureTime == metadata2.captureTime && metadata1.frameId === metadata2.frameId && areArraysEqual(metadata1.dependencies, metadata2.dependencies) && metadata1.spatialIndex === metadata2.spatialIndex && diff --git a/tests/wpt/webgl/meta/conformance/context/premultiplyalpha-test.html.ini b/tests/wpt/webgl/meta/conformance/context/premultiplyalpha-test.html.ini deleted file mode 100644 index ffc971bc861..00000000000 --- a/tests/wpt/webgl/meta/conformance/context/premultiplyalpha-test.html.ini +++ /dev/null @@ -1,8 +0,0 @@ -[premultiplyalpha-test.html] - bug: https://github.com/servo/servo/issues/21132 - - [WebGL test #62] - expected: FAIL - - [WebGL test #69] - expected: FAIL |