mirror of
https://github.com/zebrajr/ladybird.git
synced 2025-12-06 00:19:53 +01:00
Compare commits
421 Commits
1c10421316
...
f54793315c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f54793315c | ||
|
|
4bcb34d7a0 | ||
|
|
f846b6f2d9 | ||
|
|
55636432e9 | ||
|
|
8a02acbab6 | ||
|
|
5b9469786e | ||
|
|
3ef55f8859 | ||
|
|
6f9d297c3c | ||
|
|
75d49c4b55 | ||
|
|
e1344afff3 | ||
|
|
d234e9ee71 | ||
|
|
8b1f2e4e24 | ||
|
|
cd3ef805c4 | ||
|
|
13e1881bf7 | ||
|
|
1e0b56586b | ||
|
|
9ded35f98f | ||
|
|
583164e412 | ||
|
|
019c529c07 | ||
|
|
b7ecdad685 | ||
|
|
35c6d52d7d | ||
|
|
2492c07430 | ||
|
|
1796089f29 | ||
|
|
62781f4818 | ||
|
|
5706831328 | ||
|
|
2f7797f854 | ||
|
|
25d78f7c8e | ||
|
|
7b5940d27d | ||
|
|
30729feebb | ||
|
|
991ab62dd7 | ||
|
|
c2ca712406 | ||
|
|
9c06d58b2e | ||
|
|
6671cbef41 | ||
|
|
cdcbbcf48b | ||
|
|
667354fd12 | ||
|
|
fae2888103 | ||
|
|
7e20b21879 | ||
|
|
3708ac5599 | ||
|
|
5e529fc603 | ||
|
|
7683f1285f | ||
|
|
643f0de422 | ||
|
|
95f239a357 | ||
|
|
20f9510687 | ||
|
|
6261c433a3 | ||
|
|
231336f137 | ||
|
|
d299df24ac | ||
|
|
11ec7c9cea | ||
|
|
9dae1acc31 | ||
|
|
a7d13b107e | ||
|
|
59ce6c9b41 | ||
|
|
4c7ffc0552 | ||
|
|
e967631763 | ||
|
|
fdb85a330e | ||
|
|
fb05063dde | ||
|
|
3fb678b376 | ||
|
|
e4e18ca84b | ||
|
|
35254d17d1 | ||
|
|
418f1575b0 | ||
|
|
14dba82202 | ||
|
|
32cfb4e7d8 | ||
|
|
964c0ddde9 | ||
|
|
8fcba173e0 | ||
|
|
244f086709 | ||
|
|
d2b2d57387 | ||
|
|
498e59f71d | ||
|
|
3ca4ff6037 | ||
|
|
b0e02af040 | ||
|
|
676171bccd | ||
|
|
7f37889ff1 | ||
|
|
95d23d02f1 | ||
|
|
5384f84550 | ||
|
|
d67dc23960 | ||
|
|
822fcc39de | ||
|
|
6cf22c424e | ||
|
|
dc10c28b57 | ||
|
|
1216a2f952 | ||
|
|
7450da5556 | ||
|
|
997d6ee75a | ||
|
|
d3568d9467 | ||
|
|
e5d5ff952c | ||
|
|
823deacc27 | ||
|
|
ffcd3a4bb2 | ||
|
|
93fde59892 | ||
|
|
9e4c87ab85 | ||
|
|
cb1719aa81 | ||
|
|
4471e8c0ec | ||
|
|
3be6b957f8 | ||
|
|
45727d7a58 | ||
|
|
412467cc70 | ||
|
|
d9e663fc44 | ||
|
|
e81fece123 | ||
|
|
122f97c68d | ||
|
|
7745d9209d | ||
|
|
49f088275e | ||
|
|
bf0219d798 | ||
|
|
16b296c090 | ||
|
|
c75f134eec | ||
|
|
9f44fcbded | ||
|
|
d17e7fd921 | ||
|
|
4cf0a77d38 | ||
|
|
fcde0f66c8 | ||
|
|
9c960c3307 | ||
|
|
e9495d0ba0 | ||
|
|
ccf4b3f6e9 | ||
|
|
b1c9a872bc | ||
|
|
27ed536540 | ||
|
|
9117f661a8 | ||
|
|
e5a6b76a40 | ||
|
|
86e236519d | ||
|
|
e94ab24e66 | ||
|
|
0a03cc1cf7 | ||
|
|
d52ceec1bf | ||
|
|
aa8c28eb24 | ||
|
|
d3941cd83d | ||
|
|
6ff7e4bfac | ||
|
|
8d9a493b1b | ||
|
|
e176249db8 | ||
|
|
ee587cfec4 | ||
|
|
3ebaa0cd3f | ||
|
|
3d0b8cc30c | ||
|
|
29ab9c5fd5 | ||
|
|
e11da1f85f | ||
|
|
e8238b4098 | ||
|
|
5456072d48 | ||
|
|
dfe59b8a4f | ||
|
|
0ff330c906 | ||
|
|
dd052832c1 | ||
|
|
6b34003c2c | ||
|
|
6caa2f99aa | ||
|
|
0f9fa47352 | ||
|
|
5e645929a7 | ||
|
|
31b72c4799 | ||
|
|
7e238cd724 | ||
|
|
dfbad09315 | ||
|
|
523e7e2ffa | ||
|
|
27742ef26d | ||
|
|
9da723b5c6 | ||
|
|
4dbae64dce | ||
|
|
beb1d60714 | ||
|
|
6f50c35d68 | ||
|
|
e4de6c0d05 | ||
|
|
c0d08b68af | ||
|
|
e1ff1e2095 | ||
|
|
9312a9f86f | ||
|
|
44fa9566a8 | ||
|
|
892c7d980e | ||
|
|
b712caf855 | ||
|
|
3a38040c82 | ||
|
|
b77f658c83 | ||
|
|
c591f8c14f | ||
|
|
33285467a8 | ||
|
|
db41ea8117 | ||
|
|
1368744d33 | ||
|
|
2fa84f1683 | ||
|
|
887537b061 | ||
|
|
e2adce84e7 | ||
|
|
4b00a61479 | ||
|
|
df543cf31a | ||
|
|
5381146e85 | ||
|
|
59a1184469 | ||
|
|
12716dccf0 | ||
|
|
64f438857b | ||
|
|
2a68087dfc | ||
|
|
73b01a975a | ||
|
|
76dadd45d6 | ||
|
|
c8f345356e | ||
|
|
823dd11b67 | ||
|
|
84762021b8 | ||
|
|
18477b0d84 | ||
|
|
8854bb62c6 | ||
|
|
ebd802f6fc | ||
|
|
633fc45e0f | ||
|
|
5f963e1c52 | ||
|
|
9dceb06992 | ||
|
|
01947ded23 | ||
|
|
5abb5d555a | ||
|
|
d733bf54cc | ||
|
|
d65f0e4490 | ||
|
|
3e7061da40 | ||
|
|
008699c129 | ||
|
|
5632a52531 | ||
|
|
fb258639d1 | ||
|
|
cd4ac4f30f | ||
|
|
7ce4abe330 | ||
|
|
8c8961171c | ||
|
|
6c71960425 | ||
|
|
d4deafe5fe | ||
|
|
a21d247b0e | ||
|
|
ba78e3d4be | ||
|
|
e3a56ef913 | ||
|
|
e197ab8ff6 | ||
|
|
86725de23d | ||
|
|
5294559f7b | ||
|
|
a056b26e56 | ||
|
|
4f684bb4c9 | ||
|
|
3593c3b687 | ||
|
|
b10f2993b3 | ||
|
|
9df6ccf772 | ||
|
|
1c00279488 | ||
|
|
017e8a5b8d | ||
|
|
dbf041aa98 | ||
|
|
f34361950c | ||
|
|
49a46522d0 | ||
|
|
15518f119c | ||
|
|
85e8d2ba38 | ||
|
|
4ede2cdf18 | ||
|
|
d0bfb85c22 | ||
|
|
62ae4e878f | ||
|
|
9753b8e62c | ||
|
|
5ee1031b89 | ||
|
|
b50b89b4a8 | ||
|
|
b28a97b399 | ||
|
|
5b2a71a712 | ||
|
|
0b715b20a2 | ||
|
|
8417d74328 | ||
|
|
94c788f2e0 | ||
|
|
bb7d5747e7 | ||
|
|
2e6988d681 | ||
|
|
fc5cdd69a0 | ||
|
|
e78cb71eb3 | ||
|
|
fd2f3b1f03 | ||
|
|
0b45a68423 | ||
|
|
43b06cbbdd | ||
|
|
d32f99b16f | ||
|
|
1977a976da | ||
|
|
2d331b9176 | ||
|
|
670cbccb4c | ||
|
|
3c9ddc9b7f | ||
|
|
e0a75d5084 | ||
|
|
2fd424ccb6 | ||
|
|
865699066e | ||
|
|
40bc1c0eb5 | ||
|
|
bfa86f7961 | ||
|
|
abe536652f | ||
|
|
bd8c6c1431 | ||
|
|
5fd0586125 | ||
|
|
976912f3e9 | ||
|
|
e93f44112d | ||
|
|
61c36e2865 | ||
|
|
f8c4043460 | ||
|
|
8af6da64a6 | ||
|
|
e6ddc995a7 | ||
|
|
868c29545a | ||
|
|
c4e56cc845 | ||
|
|
96b34ea744 | ||
|
|
f1571c4217 | ||
|
|
2c78fd5b89 | ||
|
|
f49cf75d44 | ||
|
|
5b9a36b172 | ||
|
|
a4184fda1f | ||
|
|
6afd39b16a | ||
|
|
b8bbebd3ff | ||
|
|
4ebe43af58 | ||
|
|
39d42b7b73 | ||
|
|
3005cc30b4 | ||
|
|
3d2874bc4e | ||
|
|
b949c8ea47 | ||
|
|
5c1bf5c3f6 | ||
|
|
2b941731a7 | ||
|
|
8dcbe69eb6 | ||
|
|
2c13a2a68c | ||
|
|
35763ffe53 | ||
|
|
59bea36a59 | ||
|
|
7db73118e9 | ||
|
|
0f295e8989 | ||
|
|
0516c414d4 | ||
|
|
13f551612c | ||
|
|
eb44cca5bd | ||
|
|
b99c0c6a7f | ||
|
|
553a7a9278 | ||
|
|
494fcc40ac | ||
|
|
d17f666a8c | ||
|
|
9e064bd3ff | ||
|
|
e8a9d23f92 | ||
|
|
80b629578e | ||
|
|
66a36050bd | ||
|
|
d3ca038b2c | ||
|
|
2ac4544a81 | ||
|
|
509c86dca0 | ||
|
|
edb60e38bd | ||
|
|
2404f95e03 | ||
|
|
e9036c7c75 | ||
|
|
55bcdcf824 | ||
|
|
831e471444 | ||
|
|
9cd23e3ae5 | ||
|
|
fd31fbd84b | ||
|
|
fbc6a4c96f | ||
|
|
85239fb1da | ||
|
|
8284a99f0a | ||
|
|
28451b16c9 | ||
|
|
ca9d107a1a | ||
|
|
ab3eb9adab | ||
|
|
afd170d16c | ||
|
|
b15f4424f9 | ||
|
|
d08915a0cd | ||
|
|
ddf60ebe9e | ||
|
|
93d3ebfd59 | ||
|
|
7ba496b798 | ||
|
|
c619c90e23 | ||
|
|
dab994c4f3 | ||
|
|
884b7fcbf5 | ||
|
|
eeef370902 | ||
|
|
08641c9e15 | ||
|
|
303ebc0a67 | ||
|
|
26b82986c4 | ||
|
|
03be70087d | ||
|
|
755a576013 | ||
|
|
ad41f053b8 | ||
|
|
06a57a280d | ||
|
|
ef4f01ea44 | ||
|
|
2f83356c0f | ||
|
|
91925db9ca | ||
|
|
95e26819d9 | ||
|
|
0e30de82cc | ||
|
|
4c97b336c3 | ||
|
|
41b4292447 | ||
|
|
c9811af3b5 | ||
|
|
d5fe7f7a98 | ||
|
|
eeb5446c1b | ||
|
|
7bccd65b4a | ||
|
|
07c86542b6 | ||
|
|
692195ae88 | ||
|
|
11a1e97e40 | ||
|
|
34857ba554 | ||
|
|
98f91a3fcc | ||
|
|
7fb65283c2 | ||
|
|
b1801c0bc9 | ||
|
|
7ee8645b9c | ||
|
|
dfa796a4e4 | ||
|
|
25a5ed94d6 | ||
|
|
127208f3d6 | ||
|
|
1a3635cda5 | ||
|
|
9c7202e3f3 | ||
|
|
0e82ab2966 | ||
|
|
01c5b6f74f | ||
|
|
fb64be2f78 | ||
|
|
383dd28217 | ||
|
|
ced862c460 | ||
|
|
cdbf4f49e1 | ||
|
|
cc2c8e8615 | ||
|
|
62c00712fa | ||
|
|
24a7eac4ab | ||
|
|
e73e0b3c92 | ||
|
|
4b989b8efd | ||
|
|
25a47ceb1b | ||
|
|
c4eef822de | ||
|
|
9b8f6b8108 | ||
|
|
fc9233f198 | ||
|
|
27cb5d8c1e | ||
|
|
188384710a | ||
|
|
9cd0f9c445 | ||
|
|
b684bc0a9d | ||
|
|
2af071380e | ||
|
|
9651969708 | ||
|
|
3708fc6aa7 | ||
|
|
29fb63c928 | ||
|
|
c23ed104e5 | ||
|
|
5df216218b | ||
|
|
e55060ef6e | ||
|
|
b08ecc0cd1 | ||
|
|
d4df0e1db9 | ||
|
|
373b2838db | ||
|
|
980e715668 | ||
|
|
92c0cbc453 | ||
|
|
33d2959a4c | ||
|
|
d99f663b1a | ||
|
|
8138c2f48b | ||
|
|
ddb35dcb5f | ||
|
|
d6f3f5fd51 | ||
|
|
77237af33f | ||
|
|
6a6f747701 | ||
|
|
d065171791 | ||
|
|
0fb9ba1e3a | ||
|
|
26c1dea22a | ||
|
|
3716db1c61 | ||
|
|
5178d1ebe3 | ||
|
|
35fd3bda79 | ||
|
|
65ba5acf9d | ||
|
|
8fffce07df | ||
|
|
21932661c2 | ||
|
|
163e8e5b44 | ||
|
|
42eaea1043 | ||
|
|
3516a2344f | ||
|
|
411aed96ab | ||
|
|
187d02c45d | ||
|
|
e433dee543 | ||
|
|
62e52640d0 | ||
|
|
d349e91339 | ||
|
|
0d5136ae5c | ||
|
|
05f3bd0fa8 | ||
|
|
0bdb831c68 | ||
|
|
2f5481284d | ||
|
|
61185d98aa | ||
|
|
207f313b4b | ||
|
|
4853e2ffb1 | ||
|
|
881ef21d40 | ||
|
|
f706c883eb | ||
|
|
9e838cffb4 | ||
|
|
81aeee3fb4 | ||
|
|
1ffb0ca311 | ||
|
|
70c46e081d | ||
|
|
0e4450f4b3 | ||
|
|
5a7b0a07cb | ||
|
|
44d2a74eeb | ||
|
|
460ffcbe1d | ||
|
|
18199f8f2a | ||
|
|
74aa7e8a82 | ||
|
|
e6ac064a34 | ||
|
|
bd4e3fd3e0 | ||
|
|
701ef22952 | ||
|
|
078bc1a471 | ||
|
|
37cffba30e | ||
|
|
df7abe1dc2 | ||
|
|
0afa93e639 | ||
|
|
a3e973970a | ||
|
|
1a640b1d95 | ||
|
|
a76f420207 | ||
|
|
755c8d8cd6 | ||
|
|
7462c10ee2 | ||
|
|
b47f8f94fe | ||
|
|
d13b4f3e39 | ||
|
|
e7a3c4dbad |
|
|
@ -7,4 +7,4 @@ dnf install -y git gh
|
|||
# Ladybird dev dependencies
|
||||
dnf install -y autoconf-archive automake ccache cmake curl google-noto-sans-mono-fonts liberation-sans-fonts \
|
||||
libglvnd-devel libtool nasm ninja-build patchelf perl-FindBin perl-IPC-Cmd perl-lib qt6-qtbase-devel \
|
||||
qt6-qtmultimedia-devel qt6-qttools-devel qt6-qtwayland-devel tar unzip zip zlib-ng-compat-static
|
||||
qt6-qttools-devel qt6-qtwayland-devel tar unzip zip zlib-ng-compat-static
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ install_llvm_key() {
|
|||
### Install packages
|
||||
|
||||
apt update -y
|
||||
apt install -y lsb-release git python3 autoconf autoconf-archive automake build-essential cmake libdrm-dev libgl1-mesa-dev libtool nasm ninja-build pkg-config qt6-base-dev qt6-tools-dev-tools qt6-multimedia-dev qt6-wayland ccache fonts-liberation2 zip unzip curl tar
|
||||
apt install -y lsb-release git python3 autoconf autoconf-archive automake build-essential cmake libdrm-dev libgl1-mesa-dev libtool nasm ninja-build pkg-config qt6-base-dev qt6-tools-dev-tools qt6-wayland ccache fonts-liberation2 zip unzip curl tar
|
||||
### Ensure new enough host compiler is available
|
||||
|
||||
VERSION="0.0.0"
|
||||
|
|
|
|||
23
.github/actions/setup/action.yml
vendored
23
.github/actions/setup/action.yml
vendored
|
|
@ -53,13 +53,6 @@ runs:
|
|||
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 100
|
||||
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100
|
||||
|
||||
# FIXME: https://github.com/WebAssembly/wabt/issues/2533
|
||||
# wabt doesn't have binary releases for arm64 Linux
|
||||
curl -f -L -o wabt-1.0.35-ubuntu-20.04.tar.gz https://github.com/WebAssembly/wabt/releases/download/1.0.35/wabt-1.0.35-ubuntu-20.04.tar.gz
|
||||
tar -xzf ./wabt-1.0.35-ubuntu-20.04.tar.gz
|
||||
rm ./wabt-1.0.35-ubuntu-20.04.tar.gz
|
||||
echo "${{ github.workspace }}/wabt-1.0.35/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: 'Select latest Xcode'
|
||||
if: ${{ inputs.os == 'macOS' || inputs.os == 'Android' }}
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
|
|
@ -117,7 +110,21 @@ runs:
|
|||
run: |
|
||||
set -e
|
||||
brew update
|
||||
brew install autoconf autoconf-archive automake bash ccache coreutils libtool llvm@20 nasm ninja pkg-config qt unzip wabt
|
||||
brew install autoconf autoconf-archive automake bash ccache coreutils libtool llvm@20 nasm ninja pkg-config qt unzip
|
||||
|
||||
- name: 'Install wabt'
|
||||
shell: bash
|
||||
run: |
|
||||
if ${{ inputs.os == 'Linux' }} ; then
|
||||
curl -f -L -o wabt-1.0.35.tar.gz https://github.com/WebAssembly/wabt/releases/download/1.0.35/wabt-1.0.35-ubuntu-20.04.tar.gz
|
||||
else
|
||||
curl -f -L -o wabt-1.0.35.tar.gz https://github.com/WebAssembly/wabt/releases/download/1.0.35/wabt-1.0.35-macos-14.tar.gz
|
||||
fi
|
||||
|
||||
tar -xzf ./wabt-1.0.35.tar.gz
|
||||
rm ./wabt-1.0.35.tar.gz
|
||||
|
||||
echo "${{ github.workspace }}/wabt-1.0.35/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: 'Set required environment variables'
|
||||
if: ${{ inputs.os == 'Linux' && inputs.arch == 'arm64' }}
|
||||
|
|
|
|||
5
.github/workflows/ci-flatpak.yml
vendored
5
.github/workflows/ci-flatpak.yml
vendored
|
|
@ -23,15 +23,16 @@ jobs:
|
|||
&& contains(github.event.pull_request.labels.*.name, 'flatpak')
|
||||
name: Flatpak ${{ matrix.arch }}
|
||||
|
||||
# FIXME: Temporarily run on GitHub runners until Blacksmith runners have overlay redirect_dir enabled.
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: ['x86_64']
|
||||
runner_labels: ['["blacksmith-8vcpu-ubuntu-2404"]']
|
||||
runner_labels: ['["ubuntu-24.04"]']
|
||||
|
||||
include:
|
||||
- arch: 'aarch64'
|
||||
runner_labels: '["blacksmith-8vcpu-ubuntu-2404-arm"]'
|
||||
runner_labels: '["ubuntu-24.04-arm"]'
|
||||
|
||||
secrets: inherit
|
||||
uses: ./.github/workflows/flatpak-template.yml
|
||||
|
|
|
|||
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
|
|
@ -44,6 +44,13 @@ jobs:
|
|||
clang_plugins: false
|
||||
runner_labels: '["blacksmith-16vcpu-ubuntu-2404"]'
|
||||
|
||||
- os_name: 'Linux'
|
||||
arch: 'x86_64'
|
||||
build_preset: 'All_Debug'
|
||||
toolchain: 'Clang'
|
||||
clang_plugins: false
|
||||
runner_labels: '["blacksmith-16vcpu-ubuntu-2404"]'
|
||||
|
||||
secrets: inherit
|
||||
uses: ./.github/workflows/lagom-template.yml
|
||||
with:
|
||||
|
|
|
|||
2
.github/workflows/flatpak-template.yml
vendored
2
.github/workflows/flatpak-template.yml
vendored
|
|
@ -24,7 +24,5 @@ jobs:
|
|||
with:
|
||||
bundle: Ladybird.flatpak
|
||||
manifest-path: Meta/CMake/flatpak/org.ladybird.Ladybird.json
|
||||
cache: 'true'
|
||||
arch: ${{ inputs.arch }}
|
||||
# Note: default cache key is 'flatpak-builder-${arch}-${sha256(manifestPath)}'
|
||||
upload-artifact: 'true'
|
||||
|
|
|
|||
48
.github/workflows/js-and-wasm-artifacts.yml
vendored
48
.github/workflows/js-and-wasm-artifacts.yml
vendored
|
|
@ -49,6 +49,19 @@ jobs:
|
|||
with:
|
||||
ref: ${{ inputs.reference_to_build }}
|
||||
|
||||
- name: 'Determine build commit hash'
|
||||
id: build-commit
|
||||
shell: bash
|
||||
run: |
|
||||
echo "sha=$(git rev-parse HEAD)" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: 'Use latest custom actions'
|
||||
if: ${{ inputs.reference_to_build != '' && github.sha != steps.build-commit.outputs.sha }}
|
||||
shell: bash
|
||||
run: |
|
||||
git fetch origin master
|
||||
git checkout origin/master -- .github/actions
|
||||
|
||||
- name: "Set up environment"
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
|
|
@ -102,6 +115,17 @@ jobs:
|
|||
run: |
|
||||
cpack
|
||||
|
||||
# Inject the COMMIT file for older builds (before commit 5c5de0e30e04).
|
||||
for package in ladybird-*.tar.gz; do
|
||||
if ! tar -tzf "${package}" | grep -qx COMMIT; then
|
||||
echo "${{ steps.build-commit.outputs.sha }}" > COMMIT
|
||||
gunzip "${package}"
|
||||
tar --append --file="${package%.gz}" COMMIT
|
||||
gzip "${package%.gz}"
|
||||
rm COMMIT
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Save Caches
|
||||
uses: ./.github/actions/cache-save
|
||||
with:
|
||||
|
|
@ -115,34 +139,38 @@ jobs:
|
|||
- name: Sanity-check the js repl
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
tar -xvzf Build/distribution/ladybird-js-${{ matrix.package_type }}.tar.gz
|
||||
./bin/js -c "console.log('Hello, World\!');" > js-repl-out.txt
|
||||
if ! grep -q "\"Hello, World\!\"" js-repl-out.txt; then
|
||||
path="Build/distribution/ladybird-js-${{ matrix.package_type }}.tar.gz"
|
||||
if [ -f "${path}" ]; then
|
||||
tar -xvzf "${path}"
|
||||
bin/js -c "console.log('Hello, World\!');" > js-repl-out.txt
|
||||
if ! grep -q "\"Hello, World\!\"" js-repl-out.txt; then
|
||||
echo "Sanity check failed: \"Hello, World\!\" not found in output."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Sanity-check the wasm repl
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
tar -xvzf Build/distribution/ladybird-wasm-${{ matrix.package_type }}.tar.gz
|
||||
./bin/wasm -e run_sanity_check -w ${{ github.workspace }}/Libraries/LibWasm/Tests/CI/ci-sanity-check.wasm > wasm-repl-out.txt
|
||||
if ! grep -q "Hello, World\!" wasm-repl-out.txt; then
|
||||
path="Build/distribution/ladybird-wasm-${{ matrix.package_type }}.tar.gz"
|
||||
if [ -f "${path}" ]; then
|
||||
tar -xvzf "${path}"
|
||||
bin/wasm -e run_sanity_check -w ${{ github.workspace }}/Libraries/LibWasm/Tests/CI/ci-sanity-check.wasm > wasm-repl-out.txt
|
||||
if ! grep -q "Hello, World\!" wasm-repl-out.txt; then
|
||||
echo "Sanity check failed: Hello, World\! not found in output."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Upload js package
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: ladybird-js-${{ matrix.package_type }}
|
||||
path: Build/distribution/ladybird-js-*.tar.gz
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload wasm package
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: ladybird-wasm-${{ matrix.package_type }}
|
||||
path: Build/distribution/ladybird-wasm-*.tar.gz
|
||||
|
|
|
|||
27
.github/workflows/js-and-wasm-benchmarks.yml
vendored
27
.github/workflows/js-and-wasm-benchmarks.yml
vendored
|
|
@ -53,7 +53,6 @@ jobs:
|
|||
sudo apt-get install -y python3-venv
|
||||
|
||||
- name: 'Download JS repl artifact'
|
||||
id: download-js-artifact
|
||||
uses: dawidd6/action-download-artifact@v11
|
||||
with:
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
|
|
@ -73,15 +72,16 @@ jobs:
|
|||
commit_hash=$(cat js-repl/COMMIT)
|
||||
echo "sha=${commit_hash}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: 'Download Wasm repl artifact'
|
||||
id: download-wasm-artifact
|
||||
- name: 'Download wasm repl artifact'
|
||||
uses: dawidd6/action-download-artifact@v11
|
||||
with:
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
name: ladybird-wasm-${{ matrix.package_type }}
|
||||
path: wasm-repl
|
||||
if_no_artifact_found: warn
|
||||
|
||||
- name: 'Extract Wasm repl'
|
||||
- name: 'Extract wasm repl'
|
||||
if: ${{ hashFiles('wasm-repl/*.tar.gz') != '' }}
|
||||
shell: bash
|
||||
run: |
|
||||
cd wasm-repl
|
||||
|
|
@ -95,20 +95,27 @@ jobs:
|
|||
source .venv/bin/activate
|
||||
python3 -m pip install -r requirements.txt
|
||||
|
||||
run_options="--iterations=5"
|
||||
run_options='--iterations=5'
|
||||
|
||||
js_path="${{ github.workspace }}/js-repl/bin/js"
|
||||
run_options="${run_options} --executable=${js_path}"
|
||||
|
||||
wasm_path="${{ github.workspace }}/wasm-repl/bin/wasm"
|
||||
if [ -x "${wasm_path}" ]; then
|
||||
run_options="${run_options} --wasm-executable=${wasm_path}"
|
||||
else
|
||||
echo "Wasm repl not found; skipping wasm benchmarks."
|
||||
fi
|
||||
|
||||
if [ "${{ github.event.workflow_run.event }}" = "workflow_dispatch" ]; then
|
||||
# Upstream was run manually; we might run into failing benchmarks as a result of older builds.
|
||||
run_options="${run_options} --continue-on-failure"
|
||||
fi
|
||||
|
||||
./run.py \
|
||||
--executable=${{ github.workspace }}/js-repl/bin/js \
|
||||
--wasm-executable=${{ github.workspace }}/wasm-repl/bin/wasm \
|
||||
${run_options}
|
||||
./run.py ${run_options}
|
||||
|
||||
- name: 'Save results as an artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: js-benchmarks-results-${{ matrix.os_name }}-${{ matrix.arch }}
|
||||
path: js-benchmarks/results.json
|
||||
|
|
|
|||
6
.github/workflows/lagom-template.yml
vendored
6
.github/workflows/lagom-template.yml
vendored
|
|
@ -203,15 +203,15 @@ jobs:
|
|||
UBSAN_OPTIONS: 'log_path="${{ github.workspace }}/ubsan.log"'
|
||||
|
||||
- name: Test
|
||||
if: ${{ inputs.build_preset != 'Fuzzers' && !contains(inputs.build_preset, 'Sanitizer') }}
|
||||
if: ${{ inputs.build_preset != 'Fuzzers' && inputs.build_preset != 'All_Debug' && !contains(inputs.build_preset, 'Sanitizer') }}
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: ctest --output-on-failure --test-dir Build --timeout 1800
|
||||
env:
|
||||
TESTS_ONLY: 1
|
||||
|
||||
- name: Upload LibWeb Test Artifacts
|
||||
if: ${{ always() && inputs.build_preset != 'Fuzzers' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ always() && inputs.build_preset != 'Fuzzers' && inputs.build_preset != 'All_Debug' }}
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: libweb-test-artifacts-${{ inputs.os_name }}-${{ inputs.build_preset }}-${{ inputs.toolchain }}-${{ inputs.clang-plugins }}
|
||||
path: ${{ github.workspace }}/Build/Lagom/Tests/LibWeb/test-web/test-dumps/
|
||||
|
|
|
|||
2
.github/workflows/lint-code.yml
vendored
2
.github/workflows/lint-code.yml
vendored
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
|
||||
- name: Install JS Dependencies
|
||||
shell: bash
|
||||
run: npm install -g prettier@3.3.3
|
||||
run: npm install -g prettier@3.6.2
|
||||
|
||||
- name: Lint
|
||||
run: ${{ github.workspace }}/Meta/lint-ci.sh
|
||||
|
|
|
|||
39
.github/workflows/lint-commits.yml
vendored
39
.github/workflows/lint-commits.yml
vendored
|
|
@ -24,31 +24,36 @@ jobs:
|
|||
error: "Commit message contains CRLF line breaks (only unix-style LF linebreaks are allowed)",
|
||||
},
|
||||
{
|
||||
pattern: /^.+(\r?\n(\r?\n.*)*)?$/,
|
||||
line: 2,
|
||||
pattern: /^$/,
|
||||
error: "Empty line between commit title and body is missing",
|
||||
},
|
||||
{
|
||||
pattern: /^.{0,72}(?:\r?\n(?:(.{0,72})|(.*?([a-z]+:\/\/)?(([a-zA-Z0-9_]|-)+\.)+[a-z]{2,}(:\d+)?([a-zA-Z_0-9@:%\+.~\?&/=]|-)+).*?))*$/,
|
||||
error: "Commit message lines are too long (maximum allowed is 72 characters, except for URLs)",
|
||||
},
|
||||
{
|
||||
pattern: /^((?!^Merge branch )[\s\S])*$/,
|
||||
line: 1,
|
||||
pattern: /^(?!Merge branch )/,
|
||||
error: "Commit is a git merge commit, use the rebase command instead",
|
||||
},
|
||||
{
|
||||
pattern: /^\S.*?\S: .+/,
|
||||
line: 1,
|
||||
pattern: /^(Revert "|\S+: )/,
|
||||
error: "Missing category in commit title (if this is a fix up of a previous commit, it should be squashed)",
|
||||
},
|
||||
{
|
||||
line: 1,
|
||||
pattern: /^\S.*?: [A-Z0-9]/,
|
||||
error: "First word of commit after the subsystem is not capitalized",
|
||||
},
|
||||
{
|
||||
pattern: /^.+[^.\n](\r?\n.*)*$/,
|
||||
line: 1,
|
||||
pattern: /[^.]$/,
|
||||
error: "Commit title ends in a period",
|
||||
},
|
||||
{
|
||||
pattern: /^((?!Signed-off-by: )[\s\S])*$/,
|
||||
pattern: /^(.{0,72}|\s*[a-z]+:\/\/([a-z0-9\-]+\.)+[a-z]{2,}(:\d+)?(\/[a-zA-Z_0-9@:%+.~?&=\-]+)*)$/,
|
||||
error: "Commit message lines are too long (maximum allowed is 72 characters, except for URLs on their own line)",
|
||||
},
|
||||
{
|
||||
pattern: /^(?!Signed-off-by: )/,
|
||||
error: "Commit body contains a Signed-off-by tag",
|
||||
},
|
||||
];
|
||||
|
|
@ -69,14 +74,20 @@ jobs:
|
|||
if (author !== null && excludedBotIds.includes(author.id)) {
|
||||
continue;
|
||||
}
|
||||
const commitErrors = [];
|
||||
for (const { pattern, error } of rules) {
|
||||
if (!pattern.test(message)) {
|
||||
commitErrors.push(error);
|
||||
let commitErrors = [];
|
||||
message.split("\n").forEach((line, index) => {
|
||||
for (const { line: ruleLine, pattern, error } of rules) {
|
||||
if (ruleLine !== undefined && ruleLine !== index + 1) {
|
||||
continue;
|
||||
}
|
||||
if (!pattern.test(line)) {
|
||||
commitErrors.push(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (commitErrors.length > 0) {
|
||||
const title = message.split("\n")[0];
|
||||
commitErrors = [...new Set(commitErrors)]; // remove duplicates
|
||||
errors.push([`${title} (${sha}):`, ...commitErrors].join("\n "));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
.github/workflows/merge-conflict-labeler.yml
vendored
3
.github/workflows/merge-conflict-labeler.yml
vendored
|
|
@ -13,9 +13,12 @@ on:
|
|||
jobs:
|
||||
auto-labeler:
|
||||
runs-on: blacksmith-2vcpu-ubuntu-2404
|
||||
if: github.repository == 'LadybirdBrowser/ladybird'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: eps1lon/actions-label-merge-conflict@v3
|
||||
with:
|
||||
|
|
|
|||
6
.github/workflows/nightly-lagom.yml
vendored
6
.github/workflows/nightly-lagom.yml
vendored
|
|
@ -81,15 +81,17 @@ jobs:
|
|||
flatpak:
|
||||
if: github.repository == 'LadybirdBrowser/ladybird'
|
||||
name: Flatpak ${{ matrix.arch }}
|
||||
|
||||
# FIXME: Temporarily run on GitHub runners until Blacksmith runners have overlay redirect_dir enabled.
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [ 'x86_64' ]
|
||||
runner_labels: [ '["blacksmith-8vcpu-ubuntu-2404"]' ]
|
||||
runner_labels: [ '["ubuntu-24.04"]' ]
|
||||
|
||||
include:
|
||||
- arch: 'aarch64'
|
||||
runner_labels: '["blacksmith-8vcpu-ubuntu-2404-arm"]'
|
||||
runner_labels: '["ubuntu-24.04-arm"]'
|
||||
|
||||
secrets: inherit
|
||||
uses: ./.github/workflows/flatpak-template.yml
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"bracketSpacing": true,
|
||||
"endOfLine": "lf",
|
||||
"insertPragma": false,
|
||||
"printWidth": 100,
|
||||
"printWidth": 120,
|
||||
"quoteProps": "as-needed",
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
|
|
|
|||
31
AK/Checked.h
31
AK/Checked.h
|
|
@ -348,14 +348,9 @@ public:
|
|||
{
|
||||
#if __has_builtin(__builtin_add_overflow_p)
|
||||
return __builtin_add_overflow_p(u, v, (T)0);
|
||||
#elif __has_builtin(__builtin_add_overflow)
|
||||
#else
|
||||
T result;
|
||||
return __builtin_add_overflow(u, v, &result);
|
||||
#else
|
||||
Checked checked;
|
||||
checked = u;
|
||||
checked += v;
|
||||
return checked.has_overflow();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
@ -364,14 +359,9 @@ public:
|
|||
{
|
||||
#if __has_builtin(__builtin_sub_overflow_p)
|
||||
return __builtin_sub_overflow_p(u, v, (T)0);
|
||||
#elif __has_builtin(__builtin_sub_overflow)
|
||||
#else
|
||||
T result;
|
||||
return __builtin_sub_overflow(u, v, &result);
|
||||
#else
|
||||
Checked checked;
|
||||
checked = u;
|
||||
checked -= v;
|
||||
return checked.has_overflow();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
@ -404,27 +394,12 @@ public:
|
|||
{
|
||||
#if __has_builtin(__builtin_mul_overflow_p)
|
||||
return __builtin_mul_overflow_p(u, v, (T)0);
|
||||
#elif __has_builtin(__builtin_mul_overflow)
|
||||
#else
|
||||
T result;
|
||||
return __builtin_mul_overflow(u, v, &result);
|
||||
#else
|
||||
Checked checked;
|
||||
checked = u;
|
||||
checked *= v;
|
||||
return checked.has_overflow();
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename U, typename V, typename X>
|
||||
[[nodiscard]] static constexpr bool multiplication_would_overflow(U u, V v, X x)
|
||||
{
|
||||
Checked checked;
|
||||
checked = u;
|
||||
checked *= v;
|
||||
checked *= x;
|
||||
return checked.has_overflow();
|
||||
}
|
||||
|
||||
private:
|
||||
T m_value {};
|
||||
bool m_overflow { false };
|
||||
|
|
|
|||
|
|
@ -276,11 +276,13 @@
|
|||
# define ASAN_UNPOISON_MEMORY_REGION(addr, size) __asan_unpoison_memory_region(addr, size)
|
||||
# define LSAN_REGISTER_ROOT_REGION(base, size) __lsan_register_root_region(base, size)
|
||||
# define LSAN_UNREGISTER_ROOT_REGION(base, size) __lsan_unregister_root_region(base, size)
|
||||
# define LSAN_IGNORE_OBJECT(base) __lsan_ignore_object(base)
|
||||
#else
|
||||
# define ASAN_POISON_MEMORY_REGION(addr, size)
|
||||
# define ASAN_UNPOISON_MEMORY_REGION(addr, size)
|
||||
# define LSAN_REGISTER_ROOT_REGION(base, size)
|
||||
# define LSAN_UNREGISTER_ROOT_REGION(base, size)
|
||||
# define LSAN_IGNORE_OBJECT(base)
|
||||
#endif
|
||||
|
||||
#if __has_feature(blocks)
|
||||
|
|
|
|||
22
AK/Span.h
22
AK/Span.h
|
|
@ -354,6 +354,24 @@ public:
|
|||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename TargetType>
|
||||
ALWAYS_INLINE constexpr Span<TargetType> reinterpret()
|
||||
{
|
||||
if constexpr (sizeof(T) % sizeof(TargetType) != 0)
|
||||
VERIFY((size() * sizeof(T)) % sizeof(TargetType) == 0);
|
||||
|
||||
return Span<TargetType> { reinterpret_cast<TargetType*>(data()), (size() * sizeof(T)) / sizeof(TargetType) };
|
||||
}
|
||||
|
||||
template<typename TargetType>
|
||||
ALWAYS_INLINE constexpr Span<TargetType const> reinterpret() const
|
||||
{
|
||||
if constexpr (sizeof(T) % sizeof(TargetType) != 0)
|
||||
VERIFY((size() * sizeof(T)) % sizeof(TargetType) == 0);
|
||||
|
||||
return Span<TargetType const> { reinterpret_cast<TargetType const*>(data()), (size() * sizeof(T)) / sizeof(TargetType) };
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
|
@ -381,14 +399,14 @@ template<typename T>
|
|||
requires(IsTrivial<T>)
|
||||
ReadonlyBytes to_readonly_bytes(Span<T> span)
|
||||
{
|
||||
return ReadonlyBytes { static_cast<void const*>(span.data()), span.size() * sizeof(T) };
|
||||
return span.template reinterpret<u8 const>();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires(IsTrivial<T> && !IsConst<T>)
|
||||
Bytes to_bytes(Span<T> span)
|
||||
{
|
||||
return Bytes { static_cast<void*>(span.data()), span.size() * sizeof(T) };
|
||||
return span.template reinterpret<u8>();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -248,17 +248,16 @@ Duration now_time_from_filetime()
|
|||
Duration now_time_from_query_performance_counter()
|
||||
{
|
||||
static LARGE_INTEGER ticks_per_second;
|
||||
// FIXME: Limit to microseconds for now, but could probably use nanos?
|
||||
static float ticks_per_microsecond;
|
||||
static f64 ticks_per_nanosecond;
|
||||
if (ticks_per_second.QuadPart == 0) {
|
||||
QueryPerformanceFrequency(&ticks_per_second);
|
||||
VERIFY(ticks_per_second.QuadPart != 0);
|
||||
ticks_per_microsecond = static_cast<float>(ticks_per_second.QuadPart) / 1'000'000.0F;
|
||||
ticks_per_nanosecond = static_cast<f64>(ticks_per_second.QuadPart) / 1'000'000'000.0;
|
||||
}
|
||||
|
||||
LARGE_INTEGER now_time {};
|
||||
QueryPerformanceCounter(&now_time);
|
||||
return Duration::from_microseconds(static_cast<i64>(now_time.QuadPart / ticks_per_microsecond));
|
||||
return Duration::from_nanoseconds(static_cast<i64>(now_time.QuadPart / ticks_per_nanosecond));
|
||||
}
|
||||
|
||||
Duration now_time_from_clock(int clock_id)
|
||||
|
|
|
|||
14
AK/Time.h
14
AK/Time.h
|
|
@ -14,6 +14,7 @@
|
|||
#include <AK/String.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/Types.h>
|
||||
#include <math.h>
|
||||
|
||||
#ifdef AK_OS_WINDOWS
|
||||
# include <time.h>
|
||||
|
|
@ -213,6 +214,19 @@ private:
|
|||
|
||||
public:
|
||||
[[nodiscard]] constexpr static Duration from_seconds(i64 seconds) { return Duration(seconds, 0); }
|
||||
[[nodiscard]] constexpr static Duration from_seconds_f64(f64 seconds)
|
||||
{
|
||||
VERIFY(!isnan(seconds));
|
||||
if (seconds >= static_cast<f64>(NumericLimits<i64>::max()))
|
||||
return from_seconds(NumericLimits<i64>::max());
|
||||
if (seconds <= static_cast<f64>(NumericLimits<i64>::min()))
|
||||
return from_seconds(NumericLimits<i64>::min());
|
||||
i64 integer_seconds = static_cast<i64>(seconds);
|
||||
if (static_cast<f64>(integer_seconds) > seconds)
|
||||
integer_seconds--;
|
||||
u32 integer_nanoseconds = static_cast<u32>((seconds - static_cast<f64>(integer_seconds)) * 1'000'000'000);
|
||||
return Duration(integer_seconds, integer_nanoseconds);
|
||||
}
|
||||
[[nodiscard]] constexpr static Duration from_nanoseconds(i64 nanoseconds)
|
||||
{
|
||||
i64 seconds = sane_mod(nanoseconds, 1'000'000'000);
|
||||
|
|
|
|||
|
|
@ -83,15 +83,6 @@ struct Traits<T> : public DefaultTraits<T> {
|
|||
static constexpr bool is_trivially_serializable() { return Traits<UnderlyingType<T>>::is_trivially_serializable(); }
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
requires(Detail::IsPointerOfType<char, T>) struct Traits<T> : public DefaultTraits<T> {
|
||||
static unsigned hash(T const value) { return string_hash(value, strlen(value)); }
|
||||
static constexpr bool equals(T const a, T const b) { return strcmp(a, b); }
|
||||
static constexpr bool is_trivial() { return true; }
|
||||
// NOTE: Trivial types always have fast equality checks.
|
||||
static constexpr bool may_have_slow_equality_check() { return false; }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#if USING_AK_GLOBALLY
|
||||
|
|
|
|||
38
AK/Windows.h
38
AK/Windows.h
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2024, stasoid <stasoid@yahoo.com>
|
||||
* Copyright (c) 2025, ayeteadoe <ayeteadoe@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
|
@ -9,6 +10,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Assertions.h>
|
||||
#include <AK/Platform.h>
|
||||
|
||||
#ifdef AK_OS_WINDOWS // needed for Swift
|
||||
|
|
@ -18,4 +20,40 @@
|
|||
# undef IN
|
||||
# pragma comment(lib, "ws2_32.lib")
|
||||
# include <io.h>
|
||||
# include <stdlib.h>
|
||||
|
||||
inline void initiate_wsa()
|
||||
{
|
||||
WSADATA wsa;
|
||||
WORD version = MAKEWORD(2, 2);
|
||||
int rc = WSAStartup(version, &wsa);
|
||||
VERIFY(rc == 0 && wsa.wVersion == version);
|
||||
}
|
||||
|
||||
inline void terminate_wsa()
|
||||
{
|
||||
int rc = WSACleanup();
|
||||
VERIFY(rc == 0);
|
||||
}
|
||||
|
||||
static void invalid_parameter_handler(wchar_t const*, wchar_t const*, wchar_t const*, unsigned int, uintptr_t)
|
||||
{
|
||||
}
|
||||
|
||||
inline void override_crt_invalid_parameter_handler()
|
||||
{
|
||||
// Make _get_osfhandle return -1 instead of crashing on invalid fd in release (debug still __debugbreak's)
|
||||
_set_invalid_parameter_handler(invalid_parameter_handler);
|
||||
}
|
||||
|
||||
inline void windows_init()
|
||||
{
|
||||
initiate_wsa();
|
||||
override_crt_invalid_parameter_handler();
|
||||
}
|
||||
|
||||
inline void windows_shutdown()
|
||||
{
|
||||
terminate_wsa();
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<title>About URLs</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>List of About URLs</h1>
|
||||
<ul>
|
||||
<li><a href="about:about">about:about</a></li>
|
||||
<li><a href="about:newtab">about:newtab</a></li>
|
||||
<li><a href="about:processes">about:processes</a></li>
|
||||
<li><a href="about:settings">about:settings</a></li>
|
||||
<li><a href="about:version">about:version</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<title>About URLs</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>List of About URLs</h1>
|
||||
<ul>
|
||||
<li><a href="about:about">about:about</a></li>
|
||||
<li><a href="about:newtab">about:newtab</a></li>
|
||||
<li><a href="about:processes">about:processes</a></li>
|
||||
<li><a href="about:settings">about:settings</a></li>
|
||||
<li><a href="about:version">about:version</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>New Tab</title>
|
||||
<style>
|
||||
html {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>New Tab</title>
|
||||
<style>
|
||||
html {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -300,10 +300,7 @@
|
|||
<body>
|
||||
<header>
|
||||
<picture>
|
||||
<source
|
||||
srcset="resource://icons/128x128/app-browser.png"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
/>
|
||||
<source srcset="resource://icons/128x128/app-browser.png" media="(prefers-color-scheme: dark)" />
|
||||
<img src="resource://icons/128x128/app-browser-dark.png" />
|
||||
</picture>
|
||||
<h1>Ladybird Settings</h1>
|
||||
|
|
@ -350,9 +347,7 @@
|
|||
|
||||
<div class="card-group">
|
||||
<label for="autocomplete-engine">Search Suggestions</label>
|
||||
<p class="description">
|
||||
Select the engine that will provide search suggestions.
|
||||
</p>
|
||||
<p class="description">Select the engine that will provide search suggestions.</p>
|
||||
<select id="autocomplete-engine">
|
||||
<option value="">Disable search suggestions</option>
|
||||
<hr />
|
||||
|
|
@ -379,9 +374,7 @@
|
|||
<div class="card-body">
|
||||
<div class="card-group">
|
||||
<div class="inline-container">
|
||||
<label for="global-privacy-control-toggle">
|
||||
Enable Global Privacy Control
|
||||
</label>
|
||||
<label for="global-privacy-control-toggle">Enable Global Privacy Control</label>
|
||||
<input id="global-privacy-control-toggle" type="checkbox" switch />
|
||||
</div>
|
||||
<p class="description">Tell websites not to sell or share your data.</p>
|
||||
|
|
@ -471,11 +464,7 @@
|
|||
<div class="form-group">
|
||||
<label for="search-custom-url">Search URL</label>
|
||||
<p class="description">Use %s as a placeholder for the search query</p>
|
||||
<input
|
||||
id="search-custom-url"
|
||||
type="url"
|
||||
placeholder="https://example.com/search?q=%s"
|
||||
/>
|
||||
<input id="search-custom-url" type="url" placeholder="https://example.com/search?q=%s" />
|
||||
</div>
|
||||
<div class="dialog-controls">
|
||||
<button id="search-custom-add" class="primary-button">Add Engine</button>
|
||||
|
|
@ -502,9 +491,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<button id="site-settings-remove-all" class="secondary-button">
|
||||
Remove All Sites
|
||||
</button>
|
||||
<button id="site-settings-remove-all" class="secondary-button">Remove All Sites</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,6 @@
|
|||
const defaultZoomLevelDropdown = document.querySelector("#default-zoom-level");
|
||||
|
||||
const zoomLevelFactors = [
|
||||
1 / 3.0,
|
||||
0.5,
|
||||
2 / 3.0,
|
||||
0.75,
|
||||
0.8,
|
||||
0.9,
|
||||
1.0,
|
||||
1.1,
|
||||
1.25,
|
||||
1.5,
|
||||
1.75,
|
||||
2.0,
|
||||
2.5,
|
||||
3.0,
|
||||
4.0,
|
||||
5.0,
|
||||
];
|
||||
const zoomLevelFactors = [1 / 3.0, 0.5, 2 / 3.0, 0.75, 0.8, 0.9, 1.0, 1.1, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0, 4.0, 5.0];
|
||||
|
||||
const zoomLevelFactorMap = zoomLevelFactors.map(factor => ({
|
||||
factor,
|
||||
|
|
|
|||
|
|
@ -1,65 +1,67 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Index of %path%</title>
|
||||
<style>
|
||||
html {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Index of %path%</title>
|
||||
<style>
|
||||
html {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
header {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
header {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
display: inline;
|
||||
}
|
||||
h1 {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:focus,
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:focus,
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
table {
|
||||
font-family: monospace;
|
||||
}
|
||||
table {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.folder,
|
||||
.file,
|
||||
.open-parent {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 5px;
|
||||
background-size: contain;
|
||||
}
|
||||
.folder,
|
||||
.file,
|
||||
.open-parent {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 5px;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.folder {
|
||||
background-image: url('resource://icons/32x32/filetype-folder.png');
|
||||
}
|
||||
.folder {
|
||||
background-image: url("resource://icons/32x32/filetype-folder.png");
|
||||
}
|
||||
|
||||
.file {
|
||||
background-image: url('resource://icons/32x32/filetype-unknown.png');
|
||||
}
|
||||
.file {
|
||||
background-image: url("resource://icons/32x32/filetype-unknown.png");
|
||||
}
|
||||
|
||||
.open-parent {
|
||||
background-image: url('resource://icons/16x16/open-parent-directory.png');
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<span class="folder" style="width: 24px; height: 24px;"></span>
|
||||
<h1>Index of %path%</h1>
|
||||
</header>
|
||||
<p><a href="%parent_url%"><span class="open-parent"></span>Open Parent Directory</a></p>
|
||||
<hr>
|
||||
%contents%
|
||||
<hr>
|
||||
</body>
|
||||
.open-parent {
|
||||
background-image: url("resource://icons/16x16/open-parent-directory.png");
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<span class="folder" style="width: 24px; height: 24px"></span>
|
||||
<h1>Index of %path%</h1>
|
||||
</header>
|
||||
<p>
|
||||
<a href="%parent_url%"><span class="open-parent"></span>Open Parent Directory</a>
|
||||
</p>
|
||||
<hr />
|
||||
%contents%
|
||||
<hr />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,58 +1,58 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Error!</title>
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
font-family: system-ui, sans-serif;
|
||||
}
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
svg {
|
||||
height: 64px;
|
||||
width: auto;
|
||||
stroke: currentColor;
|
||||
fill: none;
|
||||
stroke-width: 1.5;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
p {
|
||||
font-size: 1rem;
|
||||
color: #555;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17.5 21.5">
|
||||
<path d="M11.75.75h-9c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-13l-5-5z"/>
|
||||
<path d="M10.75.75v4c0 1.1.9 2 2 2h4M5.75 9.75v2M11.75 9.75v2M5.75 16.75c1-2.67 5-2.67 6 0"/>
|
||||
</svg>
|
||||
<h1>Failed to load %failed_url%</h1>
|
||||
</header>
|
||||
<p>%error_message%</p>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Error!</title>
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
font-family: system-ui, sans-serif;
|
||||
}
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
svg {
|
||||
height: 64px;
|
||||
width: auto;
|
||||
stroke: currentColor;
|
||||
fill: none;
|
||||
stroke-width: 1.5;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
p {
|
||||
font-size: 1rem;
|
||||
color: #555;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17.5 21.5">
|
||||
<path d="M11.75.75h-9c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-13l-5-5z" />
|
||||
<path d="M10.75.75v4c0 1.1.9 2 2 2h4M5.75 9.75v2M11.75 9.75v2M5.75 16.75c1-2.67 5-2.67 6 0" />
|
||||
</svg>
|
||||
<h1>Failed to load %failed_url%</h1>
|
||||
</header>
|
||||
<p>%error_message%</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,58 +1,58 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>About %browser_name%</title>
|
||||
<style>
|
||||
html {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
img {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
height: 48px;
|
||||
}
|
||||
th {
|
||||
text-align: right;
|
||||
}
|
||||
td {
|
||||
font-family: monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<picture>
|
||||
<source srcset="resource://icons/128x128/app-browser.png" media="(prefers-color-scheme: dark)">
|
||||
<img src="resource://icons/128x128/app-browser-dark.png">
|
||||
</picture>
|
||||
<h1>About %browser_name%</h1>
|
||||
</header>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Version:</th>
|
||||
<td>%browser_version%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Arch:</th>
|
||||
<td>%arch_name%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Operating System:</th>
|
||||
<td>%os_name%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>User Agent:</th>
|
||||
<td>%user_agent%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Command Line:</th>
|
||||
<td>%command_line%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Executable Path:</th>
|
||||
<td>%executable_path%</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>About %browser_name%</title>
|
||||
<style>
|
||||
html {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
img {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
height: 48px;
|
||||
}
|
||||
th {
|
||||
text-align: right;
|
||||
}
|
||||
td {
|
||||
font-family: monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<picture>
|
||||
<source srcset="resource://icons/128x128/app-browser.png" media="(prefers-color-scheme: dark)" />
|
||||
<img src="resource://icons/128x128/app-browser-dark.png" />
|
||||
</picture>
|
||||
<h1>About %browser_name%</h1>
|
||||
</header>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Version:</th>
|
||||
<td>%browser_version%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Arch:</th>
|
||||
<td>%arch_name%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Operating System:</th>
|
||||
<td>%os_name%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>User Agent:</th>
|
||||
<td>%user_agent%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Command Line:</th>
|
||||
<td>%command_line%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Executable Path:</th>
|
||||
<td>%executable_path%</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -82,6 +82,18 @@
|
|||
"VCPKG_OVERLAY_TRIPLETS": "${fileDir}/Meta/CMake/vcpkg/debug-triplets"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "All_Debug",
|
||||
"inherits": "unix_base",
|
||||
"displayName": "All Debug Config",
|
||||
"description": "All Debug Unix build",
|
||||
"binaryDir": "${fileDir}/Build/alldebug",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"VCPKG_OVERLAY_TRIPLETS": "${fileDir}/Meta/CMake/vcpkg/debug-triplets",
|
||||
"ENABLE_ALL_THE_DEBUG_MACROS": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Windows_Experimental",
|
||||
"inherits": "windows_base",
|
||||
|
|
|
|||
|
|
@ -9,9 +9,6 @@ We currently use gcc-14 and clang-20 in our CI pipeline. If these versions are n
|
|||
|
||||
CMake 3.25 or newer must be available in $PATH.
|
||||
|
||||
> [!NOTE]
|
||||
> In all of the below lists of packages, the Qt6 multimedia package is not needed if your Linux system supports PulseAudio.
|
||||
|
||||
---
|
||||
|
||||
### Debian/Ubuntu:
|
||||
|
|
@ -67,34 +64,34 @@ sudo apt update && sudo apt install g++-14 libstdc++-14-dev
|
|||
|
||||
#### Audio support:
|
||||
|
||||
- Recommendation: Install PulseAudio development package:
|
||||
- Install PulseAudio development package:
|
||||
|
||||
```bash
|
||||
sudo apt install libpulse-dev
|
||||
```
|
||||
|
||||
- Alternative: Install Qt6's multimedia package:
|
||||
|
||||
```bash
|
||||
sudo apt install qt6-multimedia-dev
|
||||
```
|
||||
|
||||
### Arch Linux/Manjaro:
|
||||
|
||||
```
|
||||
sudo pacman -S --needed autoconf-archive automake base-devel ccache cmake curl libgl nasm ninja qt6-base qt6-multimedia qt6-tools qt6-wayland ttf-liberation tar unzip zip
|
||||
sudo pacman -S --needed autoconf-archive automake base-devel ccache cmake curl libgl nasm ninja qt6-base qt6-tools qt6-wayland ttf-liberation tar unzip zip
|
||||
```
|
||||
|
||||
Optionally, install the PulseAudio headers for audio playback support:
|
||||
|
||||
```
|
||||
sudo pacman -S libpulse
|
||||
```
|
||||
|
||||
### Fedora or derivatives:
|
||||
|
||||
```
|
||||
sudo dnf install autoconf-archive automake ccache cmake curl git libdrm-devel liberation-sans-fonts libglvnd-devel libtool nasm ninja-build patchelf perl-FindBin perl-IPC-Cmd perl-lib perl-Time-Piece qt6-qtbase-devel qt6-qtmultimedia-devel qt6-qttools-devel qt6-qtwayland-devel tar unzip zip zlib-ng-compat-static
|
||||
sudo dnf install autoconf-archive automake ccache cmake curl git libdrm-devel liberation-sans-fonts libglvnd-devel libtool nasm ninja-build patchelf perl-FindBin perl-IPC-Cmd perl-lib perl-Time-Piece qt6-qtbase-devel qt6-qttools-devel qt6-qtwayland-devel tar unzip zip zlib-ng-compat-static
|
||||
```
|
||||
|
||||
### openSUSE:
|
||||
|
||||
```
|
||||
sudo zypper install autoconf-archive automake ccache cmake curl gcc14 gcc14-c++ git liberation-fonts libglvnd-devel libtool nasm ninja qt6-base-devel qt6-multimedia-devel qt6-tools-devel qt6-wayland-devel tar unzip zip
|
||||
sudo zypper install autoconf-archive automake ccache cmake curl gcc14 gcc14-c++ git liberation-fonts libglvnd-devel libtool nasm ninja qt6-base-devel qt6-tools-devel qt6-wayland-devel tar unzip zip
|
||||
```
|
||||
|
||||
If one or more of the base repository packages are flagged as having an out-of-date version during the build process, you may need add the `devel:tools:building` repository. For example, on Leap 15.6, the `autoconf` package might be version 2.69, whereas the `gperf` package requires 2.70 to build.
|
||||
|
|
@ -119,11 +116,10 @@ Nothing to do.
|
|||
> sudo zypper install autoconf-2.72-80.d_t_b.1.noarch
|
||||
```
|
||||
|
||||
It is currently recommended to install the `libpulse-devel` package to avoid runtime dynamic linking issues. If issues persist you may need to remove the `qt6-multimedia-devel` package to avoid linking issues.
|
||||
It is necessary to install the `libpulse-devel` package to enable audio playback:
|
||||
|
||||
```
|
||||
sudo zypper install libpulse-devel
|
||||
sudo zypper remove qt6-multimedia-devel
|
||||
```
|
||||
|
||||
The build process requires at least python3.7; openSUSE Leap only features Python 3.6 as default, so it is recommendable to install the package `python312` and create a virtual environment (venv) in this case.
|
||||
|
|
@ -141,7 +137,7 @@ This virtual environment can be created once and reused in future shell sessions
|
|||
|
||||
```
|
||||
sudo xbps-install -Su # (optional) ensure packages are up to date to avoid "Transaction aborted due to unresolved dependencies."
|
||||
sudo xbps-install -S git bash gcc python3 curl cmake zip unzip linux-headers make pkg-config autoconf automake autoconf-archive nasm MesaLib-devel ninja qt6-base-devel qt6-multimedia-devel qt6-tools-devel qt6-wayland-devel
|
||||
sudo xbps-install -S git bash gcc python3 curl cmake zip unzip linux-headers make pkg-config autoconf automake autoconf-archive nasm MesaLib-devel ninja qt6-base-devel qt6-tools-devel qt6-wayland-devel
|
||||
```
|
||||
|
||||
### NixOS or with Nix:
|
||||
|
|
@ -209,7 +205,7 @@ Or, download a version of Gradle >= 8.0.0, and run the ``gradlew`` program in ``
|
|||
### FreeBSD
|
||||
|
||||
```
|
||||
pkg install autoconf-archive automake autoconf bash cmake curl gmake gn libtool libxcb libxkbcommon libX11 libXrender libXi nasm ninja patchelf pkgconf python3 qt6-base qt6-multimedia unzip zip
|
||||
pkg install autoconf-archive automake autoconf bash cmake curl gmake gn libdrm libtool libxcb libxkbcommon libX11 libXrender libXi nasm ninja patchelf pkgconf python3 qt6-base unzip zip
|
||||
```
|
||||
|
||||
## Build steps
|
||||
|
|
|
|||
|
|
@ -36,12 +36,6 @@ Run ``./Meta/ladybird.py run ladybird`` at least once to generate the ``compile_
|
|||
|
||||
- clangd has a tendency to crash when stressing bleeding edge compiler features. You can usually just restart it via the command palette. If that doesn't help, close currently open C++ files and/or switch branches before restarting, which helps sometimes.
|
||||
|
||||
### DSL syntax highlighting
|
||||
|
||||
There's a syntax highlighter extension for domain specific language files (.idl, .ipc) called "SerenityOS DSL Syntax Highlight", available [here](https://marketplace.visualstudio.com/items?itemName=kleinesfilmroellchen.serenity-dsl-syntaxhighlight) or [here](https://open-vsx.org/extension/kleinesfilmroellchen/serenity-dsl-syntaxhighlight).
|
||||
The extension provides syntax highlighting for these files, [Web IDL](https://webidl.spec.whatwg.org/), and LibJS's
|
||||
serialization format (no extension) as output by js with the -d option.
|
||||
|
||||
### Microsoft C/C++ tools
|
||||
|
||||
Note that enabling the extension in the same workspace as the clangd and clang-format extensions will cause conflicts.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
add_subdirectory(LibCompress)
|
||||
add_subdirectory(LibCrypto)
|
||||
add_subdirectory(LibDatabase)
|
||||
add_subdirectory(LibDiff)
|
||||
add_subdirectory(LibDNS)
|
||||
add_subdirectory(LibGC)
|
||||
|
|
|
|||
|
|
@ -13,9 +13,6 @@
|
|||
|
||||
#if defined(AK_OS_MACOS) || defined(AK_OS_IOS)
|
||||
# include <crt_externs.h>
|
||||
#elif defined(AK_OS_FREEBSD)
|
||||
// FIXME: Remove this once FreeBSD exports environ from libc
|
||||
extern "C" __attribute__((weak)) char** environ;
|
||||
#elif !defined(AK_OS_WINDOWS)
|
||||
extern "C" char** environ;
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ int EventLoopImplementationWindows::exec()
|
|||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
size_t EventLoopImplementationWindows::pump(PumpMode)
|
||||
size_t EventLoopImplementationWindows::pump(PumpMode pump_mode)
|
||||
{
|
||||
auto& event_queue = ThreadEventQueue::current();
|
||||
auto* thread_data = ThreadData::the();
|
||||
|
|
@ -115,7 +115,9 @@ size_t EventLoopImplementationWindows::pump(PumpMode)
|
|||
event_handles.append(entry.key.handle);
|
||||
|
||||
bool has_pending_events = event_queue.has_pending_events();
|
||||
int timeout = has_pending_events ? 0 : INFINITE;
|
||||
DWORD timeout = 0;
|
||||
if (!has_pending_events && pump_mode == PumpMode::WaitForEvents)
|
||||
timeout = INFINITE;
|
||||
DWORD result = WaitForMultipleObjects(event_count, event_handles.data(), FALSE, timeout);
|
||||
if (result == WAIT_TIMEOUT) {
|
||||
// FIXME: This verification sometimes fails with ERROR_INVALID_HANDLE, but when I check
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ void PosixSocketHelper::close()
|
|||
|
||||
// shutdown is required for another end to receive FD_CLOSE
|
||||
shutdown(m_fd, SD_BOTH);
|
||||
MUST(System::close(m_fd));
|
||||
closesocket(m_fd);
|
||||
m_fd = -1;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -129,6 +129,27 @@ ByteString StandardPaths::videos_directory()
|
|||
return LexicalPath::canonicalized_path(builder.to_byte_string());
|
||||
}
|
||||
|
||||
ByteString StandardPaths::cache_directory()
|
||||
{
|
||||
#if defined(AK_OS_WINDOWS) || defined(AK_OS_HAIKU)
|
||||
return user_data_directory();
|
||||
#else
|
||||
if (auto cache_directory = get_environment_if_not_empty("XDG_CACHE_HOME"sv); cache_directory.has_value())
|
||||
return LexicalPath::canonicalized_path(*cache_directory);
|
||||
|
||||
StringBuilder builder;
|
||||
builder.append(home_directory());
|
||||
|
||||
# if defined(AK_OS_MACOS)
|
||||
builder.append("/Library/Caches"sv);
|
||||
# else
|
||||
builder.append("/.cache"sv);
|
||||
# endif
|
||||
|
||||
return LexicalPath::canonicalized_path(builder.to_byte_string());
|
||||
#endif
|
||||
}
|
||||
|
||||
ByteString StandardPaths::config_directory()
|
||||
{
|
||||
StringBuilder builder;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ public:
|
|||
static ByteString pictures_directory();
|
||||
static ByteString videos_directory();
|
||||
static ByteString tempfile_directory();
|
||||
static ByteString cache_directory();
|
||||
static ByteString config_directory();
|
||||
static ByteString user_data_directory();
|
||||
static Vector<ByteString> system_data_directories();
|
||||
|
|
|
|||
|
|
@ -870,4 +870,27 @@ ErrorOr<void> set_close_on_exec(int fd, bool enabled)
|
|||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<size_t> transfer_file_through_pipe(int source_fd, int target_fd, size_t source_offset, size_t source_length)
|
||||
{
|
||||
#if defined(AK_OS_LINUX)
|
||||
auto sent = ::splice(source_fd, reinterpret_cast<off_t*>(&source_offset), target_fd, nullptr, source_length, SPLICE_F_MOVE | SPLICE_F_NONBLOCK);
|
||||
if (sent < 0)
|
||||
return Error::from_syscall("send_file_to_pipe"sv, errno);
|
||||
return sent;
|
||||
#else
|
||||
static auto page_size = PAGE_SIZE;
|
||||
|
||||
// mmap requires the offset to be page-aligned, so we must handle that here.
|
||||
auto aligned_source_offset = (source_offset / page_size) * page_size;
|
||||
auto offset_adjustment = source_offset - aligned_source_offset;
|
||||
auto mapped_source_length = source_length + offset_adjustment;
|
||||
|
||||
// FIXME: We could use MappedFile here if we update it to support offsets and not auto-close the source fd.
|
||||
auto* mapped = TRY(mmap(nullptr, mapped_source_length, PROT_READ, MAP_SHARED, source_fd, aligned_source_offset));
|
||||
ScopeGuard guard { [&]() { (void)munmap(mapped, mapped_source_length); } };
|
||||
|
||||
return TRY(write(target_fd, { static_cast<u8*>(mapped) + offset_adjustment, source_length }));
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,4 +190,6 @@ bool is_socket(int fd);
|
|||
ErrorOr<void> sleep_ms(u32 milliseconds);
|
||||
ErrorOr<void> set_close_on_exec(int fd, bool enabled);
|
||||
|
||||
ErrorOr<size_t> transfer_file_through_pipe(int source_fd, int target_fd, size_t source_offset, size_t source_length);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,24 +23,6 @@ namespace Core::System {
|
|||
|
||||
int windows_socketpair(SOCKET socks[2], int make_overlapped);
|
||||
|
||||
static void invalid_parameter_handler(wchar_t const*, wchar_t const*, wchar_t const*, unsigned int, uintptr_t)
|
||||
{
|
||||
}
|
||||
|
||||
static int init_crt_and_wsa()
|
||||
{
|
||||
WSADATA wsa;
|
||||
WORD version = MAKEWORD(2, 2);
|
||||
int rc = WSAStartup(version, &wsa);
|
||||
VERIFY(!rc && wsa.wVersion == version);
|
||||
|
||||
// Make _get_osfhandle return -1 instead of crashing on invalid fd in release (debug still __debugbreak's)
|
||||
_set_invalid_parameter_handler(invalid_parameter_handler);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static auto dummy = init_crt_and_wsa();
|
||||
|
||||
ErrorOr<int> open(StringView path, int options, mode_t mode)
|
||||
{
|
||||
ByteString str = path;
|
||||
|
|
@ -403,4 +385,14 @@ ErrorOr<void> kill(pid_t pid, int signal)
|
|||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<size_t> transfer_file_through_pipe(int source_fd, int target_fd, size_t source_offset, size_t source_length)
|
||||
{
|
||||
(void)source_fd;
|
||||
(void)target_fd;
|
||||
(void)source_offset;
|
||||
(void)source_length;
|
||||
|
||||
return Error::from_string_literal("FIXME: Implement System::transfer_file_through_pipe on Windows (for HTTP disk cache)");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
8
Libraries/LibDatabase/CMakeLists.txt
Normal file
8
Libraries/LibDatabase/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
set(SOURCES
|
||||
Database.cpp
|
||||
)
|
||||
|
||||
find_package(SQLite3 REQUIRED)
|
||||
|
||||
ladybird_lib(LibDatabase database EXPLICIT_SYMBOL_EXPORT)
|
||||
target_link_libraries(LibDatabase PRIVATE LibCore SQLite::SQLite3)
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
* Copyright (c) 2022-2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
|
@ -8,12 +8,11 @@
|
|||
#include <AK/String.h>
|
||||
#include <AK/Time.h>
|
||||
#include <LibCore/Directory.h>
|
||||
#include <LibCore/StandardPaths.h>
|
||||
#include <LibWebView/Database.h>
|
||||
#include <LibDatabase/Database.h>
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
namespace WebView {
|
||||
namespace Database {
|
||||
|
||||
static constexpr StringView sql_error(int error_code)
|
||||
{
|
||||
|
|
@ -39,13 +38,25 @@ static constexpr StringView sql_error(int error_code)
|
|||
} \
|
||||
})
|
||||
|
||||
ErrorOr<NonnullRefPtr<Database>> Database::create()
|
||||
{
|
||||
// FIXME: Move this to a generic "Ladybird data directory" helper.
|
||||
auto database_path = ByteString::formatted("{}/Ladybird", Core::StandardPaths::user_data_directory());
|
||||
TRY(Core::Directory::create(database_path, Core::Directory::CreateDirectories::Yes));
|
||||
#define ENUMERATE_SQL_TYPES \
|
||||
__ENUMERATE_TYPE(String) \
|
||||
__ENUMERATE_TYPE(UnixDateTime) \
|
||||
__ENUMERATE_TYPE(i8) \
|
||||
__ENUMERATE_TYPE(i16) \
|
||||
__ENUMERATE_TYPE(i32) \
|
||||
__ENUMERATE_TYPE(long) \
|
||||
__ENUMERATE_TYPE(long long) \
|
||||
__ENUMERATE_TYPE(u8) \
|
||||
__ENUMERATE_TYPE(u16) \
|
||||
__ENUMERATE_TYPE(u32) \
|
||||
__ENUMERATE_TYPE(unsigned long) \
|
||||
__ENUMERATE_TYPE(unsigned long long) \
|
||||
__ENUMERATE_TYPE(bool)
|
||||
|
||||
auto database_file = ByteString::formatted("{}/Ladybird.db", database_path);
|
||||
ErrorOr<NonnullRefPtr<Database>> Database::create(ByteString const& directory, StringView name)
|
||||
{
|
||||
TRY(Core::Directory::create(directory, Core::Directory::CreateDirectories::Yes));
|
||||
auto database_file = ByteString::formatted("{}/{}.db", directory, name);
|
||||
|
||||
sqlite3* m_database { nullptr };
|
||||
SQL_TRY(sqlite3_open(database_file.characters(), &m_database));
|
||||
|
|
@ -67,7 +78,7 @@ Database::~Database()
|
|||
sqlite3_close(m_database);
|
||||
}
|
||||
|
||||
ErrorOr<Database::StatementID> Database::prepare_statement(StringView statement)
|
||||
ErrorOr<StatementID> Database::prepare_statement(StringView statement)
|
||||
{
|
||||
sqlite3_stmt* prepared_statement { nullptr };
|
||||
SQL_TRY(sqlite3_prepare_v2(m_database, statement.characters_without_null_termination(), static_cast<int>(statement.length()), &prepared_statement, nullptr));
|
||||
|
|
@ -111,18 +122,21 @@ void Database::apply_placeholder(StatementID statement_id, int index, ValueType
|
|||
StringView string { value };
|
||||
SQL_MUST(sqlite3_bind_text(statement, index, string.characters_without_null_termination(), static_cast<int>(string.length()), SQLITE_TRANSIENT));
|
||||
} else if constexpr (IsSame<ValueType, UnixDateTime>) {
|
||||
SQL_MUST(sqlite3_bind_int64(statement, index, value.offset_to_epoch().to_milliseconds()));
|
||||
} else if constexpr (IsSame<ValueType, int>) {
|
||||
SQL_MUST(sqlite3_bind_int(statement, index, value));
|
||||
} else if constexpr (IsSame<ValueType, bool>) {
|
||||
SQL_MUST(sqlite3_bind_int(statement, index, static_cast<int>(value)));
|
||||
apply_placeholder(statement_id, index, value.offset_to_epoch().to_milliseconds());
|
||||
} else if constexpr (IsIntegral<ValueType>) {
|
||||
if constexpr (sizeof(ValueType) <= sizeof(int))
|
||||
SQL_MUST(sqlite3_bind_int(statement, index, static_cast<int>(value)));
|
||||
else
|
||||
SQL_MUST(sqlite3_bind_int64(statement, index, static_cast<sqlite3_int64>(value)));
|
||||
} else {
|
||||
static_assert(DependentFalse<ValueType>);
|
||||
}
|
||||
}
|
||||
|
||||
template void Database::apply_placeholder(StatementID, int, String const&);
|
||||
template void Database::apply_placeholder(StatementID, int, UnixDateTime const&);
|
||||
template void Database::apply_placeholder(StatementID, int, int const&);
|
||||
template void Database::apply_placeholder(StatementID, int, bool const&);
|
||||
#define __ENUMERATE_TYPE(type) \
|
||||
template DATABASE_API void Database::apply_placeholder(StatementID, int, type const&);
|
||||
ENUMERATE_SQL_TYPES
|
||||
#undef __ENUMERATE_TYPE
|
||||
|
||||
template<typename ValueType>
|
||||
ValueType Database::result_column(StatementID statement_id, int column)
|
||||
|
|
@ -133,20 +147,21 @@ ValueType Database::result_column(StatementID statement_id, int column)
|
|||
auto const* text = reinterpret_cast<char const*>(sqlite3_column_text(statement, column));
|
||||
return MUST(String::from_utf8(StringView { text, strlen(text) }));
|
||||
} else if constexpr (IsSame<ValueType, UnixDateTime>) {
|
||||
auto milliseconds = sqlite3_column_int64(statement, column);
|
||||
auto milliseconds = result_column<sqlite3_int64>(statement_id, column);
|
||||
return UnixDateTime::from_milliseconds_since_epoch(milliseconds);
|
||||
} else if constexpr (IsSame<ValueType, int>) {
|
||||
return sqlite3_column_int(statement, column);
|
||||
} else if constexpr (IsSame<ValueType, bool>) {
|
||||
return static_cast<bool>(sqlite3_column_int(statement, column));
|
||||
} else if constexpr (IsIntegral<ValueType>) {
|
||||
if constexpr (sizeof(ValueType) <= sizeof(int))
|
||||
return static_cast<ValueType>(sqlite3_column_int(statement, column));
|
||||
else
|
||||
return static_cast<ValueType>(sqlite3_column_int64(statement, column));
|
||||
} else {
|
||||
static_assert(DependentFalse<ValueType>);
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
template String Database::result_column(StatementID, int);
|
||||
template UnixDateTime Database::result_column(StatementID, int);
|
||||
template int Database::result_column(StatementID, int);
|
||||
template bool Database::result_column(StatementID, int);
|
||||
#define __ENUMERATE_TYPE(type) \
|
||||
template DATABASE_API type Database::result_column(StatementID, int);
|
||||
ENUMERATE_SQL_TYPES
|
||||
#undef __ENUMERATE_TYPE
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
* Copyright (c) 2022-2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
* Copyright (c) 2023, Jelle Raaijmakers <jelle@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
|
|
@ -13,19 +13,18 @@
|
|||
#include <AK/RefCounted.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibWebView/Forward.h>
|
||||
#include <LibDatabase/Forward.h>
|
||||
|
||||
struct sqlite3;
|
||||
struct sqlite3_stmt;
|
||||
|
||||
namespace WebView {
|
||||
namespace Database {
|
||||
|
||||
class WEBVIEW_API Database : public RefCounted<Database> {
|
||||
class DATABASE_API Database : public RefCounted<Database> {
|
||||
public:
|
||||
static ErrorOr<NonnullRefPtr<Database>> create();
|
||||
static ErrorOr<NonnullRefPtr<Database>> create(ByteString const& directory, StringView name);
|
||||
~Database();
|
||||
|
||||
using StatementID = size_t;
|
||||
using OnResult = Function<void(StatementID)>;
|
||||
|
||||
ErrorOr<StatementID> prepare_statement(StringView statement);
|
||||
18
Libraries/LibDatabase/Forward.h
Normal file
18
Libraries/LibDatabase/Forward.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Types.h>
|
||||
#include <LibDatabase/Export.h>
|
||||
|
||||
namespace Database {
|
||||
|
||||
class Database;
|
||||
|
||||
using StatementID = size_t;
|
||||
|
||||
}
|
||||
114
Libraries/LibDevTools/Actors/AccessibilityActor.cpp
Normal file
114
Libraries/LibDevTools/Actors/AccessibilityActor.cpp
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibDevTools/Actors/AccessibilityActor.h>
|
||||
#include <LibDevTools/Actors/AccessibilityWalkerActor.h>
|
||||
#include <LibDevTools/Actors/TabActor.h>
|
||||
#include <LibDevTools/DevToolsDelegate.h>
|
||||
#include <LibDevTools/DevToolsServer.h>
|
||||
|
||||
namespace DevTools {
|
||||
|
||||
NonnullRefPtr<AccessibilityActor> AccessibilityActor::create(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab)
|
||||
{
|
||||
return adopt_ref(*new AccessibilityActor(devtools, move(name), move(tab)));
|
||||
}
|
||||
|
||||
AccessibilityActor::AccessibilityActor(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab)
|
||||
: Actor(devtools, move(name))
|
||||
, m_tab(move(tab))
|
||||
{
|
||||
}
|
||||
|
||||
AccessibilityActor::~AccessibilityActor() = default;
|
||||
|
||||
void AccessibilityActor::enable()
|
||||
{
|
||||
if (m_enabled)
|
||||
return;
|
||||
|
||||
m_enabled = true;
|
||||
|
||||
JsonObject init_event;
|
||||
init_event.set("type"sv, "init"sv);
|
||||
send_message(move(init_event));
|
||||
}
|
||||
|
||||
void AccessibilityActor::handle_message(Message const& message)
|
||||
{
|
||||
if (message.type == "bootstrap"sv) {
|
||||
JsonObject bootstrap;
|
||||
bootstrap.set("enabled"sv, m_enabled);
|
||||
|
||||
JsonObject response;
|
||||
response.set("state"sv, move(bootstrap));
|
||||
send_response(message, move(response));
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type == "getSimulator"sv) {
|
||||
// FIXME: This would return a SimulatorActor for applying visual filters over the whole viewport.
|
||||
// For now, return null.
|
||||
JsonObject response;
|
||||
response.set("simulator"sv, JsonValue {});
|
||||
send_response(message, move(response));
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type == "getTraits"sv) {
|
||||
JsonObject traits;
|
||||
traits.set("tabbingOrder"sv, true);
|
||||
|
||||
JsonObject response;
|
||||
response.set("traits"sv, move(traits));
|
||||
send_response(message, move(response));
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type == "getWalker"sv) {
|
||||
if (auto tab = m_tab.strong_ref()) {
|
||||
devtools().delegate().inspect_accessibility_tree(tab->description(),
|
||||
async_handler<AccessibilityActor>(message, [](auto& self, auto accessibility_tree, auto& response) {
|
||||
if (!AccessibilityWalkerActor::is_suitable_for_accessibility_inspection(accessibility_tree)) {
|
||||
dbgln_if(DEVTOOLS_DEBUG, "Did not receive a suitable accessibility tree: {}", accessibility_tree);
|
||||
return;
|
||||
}
|
||||
|
||||
self.received_accessibility_tree(response, move(accessibility_tree.as_object()));
|
||||
}));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
send_unrecognized_packet_type_error(message);
|
||||
}
|
||||
|
||||
void AccessibilityActor::received_accessibility_tree(JsonObject& response, JsonObject accessibility_tree)
|
||||
{
|
||||
auto& walker_actor = devtools().register_actor<AccessibilityWalkerActor>(m_tab, move(accessibility_tree));
|
||||
m_walker = walker_actor;
|
||||
|
||||
JsonObject walker;
|
||||
walker.set("actor"sv, walker_actor.name());
|
||||
|
||||
response.set("walker"sv, move(walker));
|
||||
}
|
||||
|
||||
RefPtr<TabActor> AccessibilityActor::tab_for(WeakPtr<AccessibilityActor> const& weak_accessibility)
|
||||
{
|
||||
if (auto accessibility = weak_accessibility.strong_ref())
|
||||
return accessibility->m_tab.strong_ref();
|
||||
return {};
|
||||
}
|
||||
|
||||
RefPtr<AccessibilityWalkerActor> AccessibilityActor::walker_for(WeakPtr<AccessibilityActor> const& weak_accessibility)
|
||||
{
|
||||
if (auto accessibility = weak_accessibility.strong_ref())
|
||||
return accessibility->m_walker.strong_ref();
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
37
Libraries/LibDevTools/Actors/AccessibilityActor.h
Normal file
37
Libraries/LibDevTools/Actors/AccessibilityActor.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibDevTools/Actor.h>
|
||||
|
||||
namespace DevTools {
|
||||
|
||||
class DEVTOOLS_API AccessibilityActor final : public Actor {
|
||||
public:
|
||||
static constexpr auto base_name = "accessibility"sv;
|
||||
|
||||
static NonnullRefPtr<AccessibilityActor> create(DevToolsServer&, String name, WeakPtr<TabActor>);
|
||||
virtual ~AccessibilityActor() override;
|
||||
|
||||
static RefPtr<TabActor> tab_for(WeakPtr<AccessibilityActor> const&);
|
||||
static RefPtr<AccessibilityWalkerActor> walker_for(WeakPtr<AccessibilityActor> const&);
|
||||
|
||||
void enable();
|
||||
|
||||
private:
|
||||
AccessibilityActor(DevToolsServer&, String name, WeakPtr<TabActor>);
|
||||
|
||||
virtual void handle_message(Message const&) override;
|
||||
|
||||
void received_accessibility_tree(JsonObject& response, JsonObject accessibility_tree);
|
||||
|
||||
WeakPtr<TabActor> m_tab;
|
||||
WeakPtr<AccessibilityWalkerActor> m_walker;
|
||||
bool m_enabled { false };
|
||||
};
|
||||
|
||||
}
|
||||
160
Libraries/LibDevTools/Actors/AccessibilityNodeActor.cpp
Normal file
160
Libraries/LibDevTools/Actors/AccessibilityNodeActor.cpp
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Enumerate.h>
|
||||
#include <LibDevTools/Actors/AccessibilityNodeActor.h>
|
||||
#include <LibDevTools/Actors/AccessibilityWalkerActor.h>
|
||||
|
||||
namespace DevTools {
|
||||
|
||||
NonnullRefPtr<AccessibilityNodeActor> AccessibilityNodeActor::create(DevToolsServer& devtools, String name, NodeIdentifier node_identifier, WeakPtr<AccessibilityWalkerActor> walker)
|
||||
{
|
||||
return adopt_ref(*new AccessibilityNodeActor(devtools, move(name), move(node_identifier), move(walker)));
|
||||
}
|
||||
|
||||
AccessibilityNodeActor::AccessibilityNodeActor(DevToolsServer& devtools, String name, NodeIdentifier node_identifier, WeakPtr<AccessibilityWalkerActor> walker)
|
||||
: Actor(devtools, move(name))
|
||||
, m_node_identifier(node_identifier)
|
||||
, m_walker(move(walker))
|
||||
{
|
||||
}
|
||||
|
||||
AccessibilityNodeActor::~AccessibilityNodeActor() = default;
|
||||
|
||||
void AccessibilityNodeActor::handle_message(Message const& message)
|
||||
{
|
||||
if (message.type == "audit"sv) {
|
||||
// FIXME: Implement accessibility audits.
|
||||
JsonObject audit;
|
||||
|
||||
JsonObject response;
|
||||
response.set("type"sv, "audited"sv);
|
||||
response.set("audit"sv, audit);
|
||||
send_response(message, move(response));
|
||||
|
||||
// For whatever reason, we need to send this a second time with no `type`.
|
||||
JsonObject second_response;
|
||||
second_response.set("audit"sv, audit);
|
||||
send_message(move(second_response));
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type == "children"sv) {
|
||||
if (auto walker = m_walker.strong_ref()) {
|
||||
auto ancestor_node = walker->accessibility_node(name());
|
||||
if (!ancestor_node.has_value()) {
|
||||
send_unknown_actor_error(message, name());
|
||||
return;
|
||||
}
|
||||
|
||||
JsonArray children;
|
||||
if (auto child_nodes = ancestor_node->node.get_array("children"sv); child_nodes.has_value()) {
|
||||
child_nodes->for_each([&](JsonValue const& child) {
|
||||
children.must_append(walker->serialize_node(child.as_object()));
|
||||
});
|
||||
}
|
||||
|
||||
JsonObject response;
|
||||
response.set("children"sv, move(children));
|
||||
send_response(message, move(response));
|
||||
return;
|
||||
}
|
||||
send_unknown_actor_error(message, name());
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type == "getRelations"sv) {
|
||||
if (auto walker = m_walker.strong_ref()) {
|
||||
auto accessibility_node = walker->accessibility_node(name());
|
||||
if (!accessibility_node.has_value()) {
|
||||
send_unknown_actor_error(message, name());
|
||||
return;
|
||||
}
|
||||
|
||||
auto root_node = walker->root_accessibility_node();
|
||||
|
||||
JsonArray relations;
|
||||
|
||||
auto report_relation = [&](StringView type, Node const& node) {
|
||||
JsonArray targets;
|
||||
MUST(targets.append(walker->serialize_node(node.node)));
|
||||
|
||||
JsonObject relation;
|
||||
relation.set("targets"sv, move(targets));
|
||||
relation.set("type"sv, type);
|
||||
|
||||
MUST(relations.append(move(relation)));
|
||||
};
|
||||
|
||||
// For the root node, list itself as an "embeds" relation.
|
||||
if (root_node.has_value() && root_node->identifier == accessibility_node->identifier)
|
||||
report_relation("embeds"sv, root_node.value());
|
||||
|
||||
// For all nodes, list the root as the "containing document" relation.
|
||||
report_relation("containing document"sv, root_node.value());
|
||||
|
||||
// FIXME: Figure out what other relations we need to report here.
|
||||
|
||||
JsonObject response;
|
||||
response.set("relations"sv, move(relations));
|
||||
send_response(message, move(response));
|
||||
return;
|
||||
}
|
||||
send_unknown_actor_error(message, name());
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type == "hydrate"sv) {
|
||||
auto accessibility_node = AccessibilityWalkerActor::accessibility_node_for(m_walker, name());
|
||||
if (!accessibility_node.has_value()) {
|
||||
send_unknown_actor_error(message, name());
|
||||
return;
|
||||
}
|
||||
|
||||
auto parent_node = AccessibilityWalkerActor::parent_of_accessibility_node_for(m_walker, *accessibility_node);
|
||||
|
||||
auto const& node_json = accessibility_node->node;
|
||||
auto dom_node_type = parse_dom_node_type(node_json.get_string("type"sv).value());
|
||||
|
||||
auto index_in_parent = 0;
|
||||
if (parent_node.has_value()) {
|
||||
if (auto parent_children = parent_node->node.get_array("children"sv); parent_children.has_value()) {
|
||||
for (auto const& [index, child] : enumerate(parent_children->values())) {
|
||||
if (child.as_object().get_i64("id"sv) == accessibility_node->identifier.id) {
|
||||
index_in_parent = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Populate these.
|
||||
JsonArray actions;
|
||||
JsonObject attributes;
|
||||
JsonArray states;
|
||||
|
||||
JsonObject properties;
|
||||
properties.set("actions"sv, move(actions));
|
||||
properties.set("attributes"sv, move(attributes));
|
||||
properties.set("description"sv, node_json.get_string("description"sv).value_or({}));
|
||||
properties.set("domNodeType"sv, to_underlying(dom_node_type));
|
||||
properties.set("indexInParent"sv, index_in_parent);
|
||||
// FIXME: Value of the accesskey attribute
|
||||
properties.set("keyboardShortcut"sv, ""sv);
|
||||
properties.set("states"sv, move(states));
|
||||
// FIXME: Implement
|
||||
properties.set("value"sv, ""sv);
|
||||
|
||||
JsonObject response;
|
||||
response.set("properties"sv, move(properties));
|
||||
send_response(message, move(response));
|
||||
return;
|
||||
}
|
||||
|
||||
send_unrecognized_packet_type_error(message);
|
||||
}
|
||||
|
||||
}
|
||||
35
Libraries/LibDevTools/Actors/AccessibilityNodeActor.h
Normal file
35
Libraries/LibDevTools/Actors/AccessibilityNodeActor.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibDevTools/Actor.h>
|
||||
#include <LibDevTools/Actors/NodeActor.h>
|
||||
#include <LibDevTools/Forward.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
|
||||
namespace DevTools {
|
||||
|
||||
class DEVTOOLS_API AccessibilityNodeActor final : public Actor {
|
||||
public:
|
||||
static constexpr auto base_name = "accessibility-node"sv;
|
||||
|
||||
static NonnullRefPtr<AccessibilityNodeActor> create(DevToolsServer&, String name, NodeIdentifier, WeakPtr<AccessibilityWalkerActor>);
|
||||
virtual ~AccessibilityNodeActor() override;
|
||||
|
||||
NodeIdentifier const& node_identifier() const { return m_node_identifier; }
|
||||
WeakPtr<AccessibilityWalkerActor> const& walker() const { return m_walker; }
|
||||
|
||||
private:
|
||||
AccessibilityNodeActor(DevToolsServer&, String name, NodeIdentifier, WeakPtr<AccessibilityWalkerActor>);
|
||||
|
||||
virtual void handle_message(Message const&) override;
|
||||
|
||||
NodeIdentifier m_node_identifier;
|
||||
WeakPtr<AccessibilityWalkerActor> m_walker;
|
||||
};
|
||||
|
||||
}
|
||||
227
Libraries/LibDevTools/Actors/AccessibilityWalkerActor.cpp
Normal file
227
Libraries/LibDevTools/Actors/AccessibilityWalkerActor.cpp
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibDevTools/Actors/AccessibilityNodeActor.h>
|
||||
#include <LibDevTools/Actors/AccessibilityWalkerActor.h>
|
||||
#include <LibDevTools/Actors/TabActor.h>
|
||||
#include <LibDevTools/DevToolsServer.h>
|
||||
|
||||
namespace DevTools {
|
||||
|
||||
NonnullRefPtr<AccessibilityWalkerActor> AccessibilityWalkerActor::create(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab, JsonObject accessibility_tree)
|
||||
{
|
||||
return adopt_ref(*new AccessibilityWalkerActor(devtools, move(name), move(tab), move(accessibility_tree)));
|
||||
}
|
||||
|
||||
AccessibilityWalkerActor::AccessibilityWalkerActor(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab, JsonObject accessibility_tree)
|
||||
: Actor(devtools, move(name))
|
||||
, m_tab(move(tab))
|
||||
, m_accessibility_tree(move(accessibility_tree))
|
||||
{
|
||||
populate_accessibility_tree_cache();
|
||||
}
|
||||
|
||||
AccessibilityWalkerActor::~AccessibilityWalkerActor() = default;
|
||||
|
||||
void AccessibilityWalkerActor::handle_message(Message const& message)
|
||||
{
|
||||
if (message.type == "children"sv) {
|
||||
JsonArray children;
|
||||
MUST(children.append(serialize_root()));
|
||||
|
||||
JsonObject response;
|
||||
response.set("children"sv, move(children));
|
||||
send_response(message, move(response));
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type == "hideTabbingOrder"sv) {
|
||||
// Blank response is expected.
|
||||
send_response(message, JsonObject {});
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type == "highlightAccessible"sv) {
|
||||
// FIXME: Highlight things.
|
||||
JsonObject response;
|
||||
response.set("value"sv, false);
|
||||
send_response(message, move(response));
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type == "unhighlight"sv) {
|
||||
// FIXME: Unhighlight things.
|
||||
send_response(message, JsonObject {});
|
||||
return;
|
||||
}
|
||||
|
||||
send_unrecognized_packet_type_error(message);
|
||||
}
|
||||
|
||||
bool AccessibilityWalkerActor::is_suitable_for_accessibility_inspection(JsonValue const& node)
|
||||
{
|
||||
if (!node.is_object())
|
||||
return false;
|
||||
|
||||
auto const& object = node.as_object();
|
||||
|
||||
if (!object.has_string("type"sv) || !object.has_string("role"sv))
|
||||
return false;
|
||||
|
||||
if (!object.has_i64("id"sv))
|
||||
return false;
|
||||
|
||||
if (auto text = object.get_string("text"sv); text.has_value()) {
|
||||
if (AK::StringUtils::is_whitespace(*text))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
JsonValue AccessibilityWalkerActor::serialize_root() const
|
||||
{
|
||||
return serialize_node(m_accessibility_tree);
|
||||
}
|
||||
|
||||
JsonValue AccessibilityWalkerActor::serialize_node(JsonObject const& node) const
|
||||
{
|
||||
auto tab = m_tab.strong_ref();
|
||||
if (!tab)
|
||||
return {};
|
||||
|
||||
auto actor = node.get_string("actor"sv);
|
||||
if (!actor.has_value())
|
||||
return {};
|
||||
|
||||
auto name = node.get_string("name"sv).value_or(""_string);
|
||||
auto type = node.get_string("type"sv).release_value();
|
||||
auto role = node.get_string("role"sv).release_value();
|
||||
|
||||
auto child_count = 0u;
|
||||
if (auto children = node.get_array("children"sv); children.has_value())
|
||||
child_count = children->size();
|
||||
|
||||
JsonObject serialized;
|
||||
serialized.set("actor"sv, actor.release_value());
|
||||
serialized.set("name"sv, move(name));
|
||||
serialized.set("role"sv, move(role));
|
||||
serialized.set("useChildTargetToFetchChildren"sv, false);
|
||||
serialized.set("childCount"sv, child_count);
|
||||
serialized.set("checks"sv, JsonObject {});
|
||||
return serialized;
|
||||
}
|
||||
|
||||
Optional<Node> AccessibilityWalkerActor::accessibility_node_for(WeakPtr<AccessibilityWalkerActor> const& weak_walker, StringView actor)
|
||||
{
|
||||
if (auto walker = weak_walker.strong_ref())
|
||||
return walker->accessibility_node(actor);
|
||||
return {};
|
||||
}
|
||||
|
||||
Optional<Node> AccessibilityWalkerActor::accessibility_node(StringView actor)
|
||||
{
|
||||
auto tab = m_tab.strong_ref();
|
||||
if (!tab)
|
||||
return {};
|
||||
|
||||
auto maybe_node = m_actor_to_node_map.get(actor);
|
||||
if (!maybe_node.has_value() || !maybe_node.value())
|
||||
return {};
|
||||
|
||||
auto const& node = *maybe_node.value();
|
||||
auto identifier = NodeIdentifier::for_node(node);
|
||||
|
||||
return Node { .node = node, .identifier = move(identifier), .tab = tab.release_nonnull() };
|
||||
}
|
||||
|
||||
Optional<Node> AccessibilityWalkerActor::parent_of_accessibility_node_for(WeakPtr<AccessibilityWalkerActor> const& weak_walker, Node const& accessibility_node)
|
||||
{
|
||||
if (auto walker = weak_walker.strong_ref())
|
||||
return walker->parent_of_accessibility_node(accessibility_node);
|
||||
return {};
|
||||
}
|
||||
|
||||
Optional<Node> AccessibilityWalkerActor::parent_of_accessibility_node(Node const& accessibility_node)
|
||||
{
|
||||
auto tab = m_tab.strong_ref();
|
||||
if (!tab)
|
||||
return {};
|
||||
|
||||
auto maybe_parent_node = m_node_to_parent_map.get(&accessibility_node.node);
|
||||
if (!maybe_parent_node.has_value() || !maybe_parent_node.value())
|
||||
return {};
|
||||
|
||||
auto const& parent_node = *maybe_parent_node.value();
|
||||
auto identifier = NodeIdentifier::for_node(parent_node);
|
||||
|
||||
return Node { .node = parent_node, .identifier = move(identifier), .tab = tab.release_nonnull() };
|
||||
}
|
||||
|
||||
Optional<Node> AccessibilityWalkerActor::root_accessibility_node_for(WeakPtr<AccessibilityWalkerActor> const& weak_walker)
|
||||
{
|
||||
if (auto walker = weak_walker.strong_ref())
|
||||
return walker->root_accessibility_node();
|
||||
return {};
|
||||
}
|
||||
|
||||
Optional<Node> AccessibilityWalkerActor::root_accessibility_node()
|
||||
{
|
||||
auto tab = m_tab.strong_ref();
|
||||
if (!tab)
|
||||
return {};
|
||||
|
||||
auto identifier = NodeIdentifier::for_node(m_accessibility_tree);
|
||||
|
||||
return Node { .node = m_accessibility_tree, .identifier = move(identifier), .tab = tab.release_nonnull() };
|
||||
}
|
||||
|
||||
void AccessibilityWalkerActor::populate_accessibility_tree_cache()
|
||||
{
|
||||
m_node_to_parent_map.clear();
|
||||
m_actor_to_node_map.clear();
|
||||
m_node_id_to_actor_map.clear();
|
||||
populate_accessibility_tree_cache(m_accessibility_tree, nullptr);
|
||||
}
|
||||
|
||||
void AccessibilityWalkerActor::populate_accessibility_tree_cache(JsonObject& node, JsonObject const* parent)
|
||||
{
|
||||
auto const& node_actor = actor_for_node(node);
|
||||
node.set("actor"sv, node_actor.name());
|
||||
|
||||
m_node_to_parent_map.set(&node, parent);
|
||||
m_actor_to_node_map.set(node_actor.name(), &node);
|
||||
m_node_id_to_actor_map.set(node_actor.node_identifier().id, node_actor.name());
|
||||
|
||||
auto children = node.get_array("children"sv);
|
||||
if (!children.has_value())
|
||||
return;
|
||||
|
||||
children->values().remove_all_matching([&](JsonValue const& child) {
|
||||
return !is_suitable_for_accessibility_inspection(child);
|
||||
});
|
||||
|
||||
children->for_each([&](JsonValue& child) {
|
||||
populate_accessibility_tree_cache(child.as_object(), &node);
|
||||
});
|
||||
}
|
||||
|
||||
AccessibilityNodeActor const& AccessibilityWalkerActor::actor_for_node(JsonObject const& node)
|
||||
{
|
||||
auto identifier = NodeIdentifier::for_node(node);
|
||||
|
||||
if (auto it = m_node_actors.find(identifier); it != m_node_actors.end()) {
|
||||
if (auto node_actor = it->value.strong_ref())
|
||||
return *node_actor;
|
||||
}
|
||||
|
||||
auto& node_actor = devtools().register_actor<AccessibilityNodeActor>(identifier, *this);
|
||||
m_node_actors.set(identifier, node_actor);
|
||||
|
||||
return node_actor;
|
||||
}
|
||||
|
||||
}
|
||||
55
Libraries/LibDevTools/Actors/AccessibilityWalkerActor.h
Normal file
55
Libraries/LibDevTools/Actors/AccessibilityWalkerActor.h
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibDevTools/Actor.h>
|
||||
#include <LibDevTools/Actors/AccessibilityNodeActor.h>
|
||||
#include <LibDevTools/Forward.h>
|
||||
|
||||
namespace DevTools {
|
||||
|
||||
class DEVTOOLS_API AccessibilityWalkerActor final : public Actor {
|
||||
public:
|
||||
static constexpr auto base_name = "accessibility-walker"sv;
|
||||
|
||||
static NonnullRefPtr<AccessibilityWalkerActor> create(DevToolsServer&, String name, WeakPtr<TabActor>, JsonObject accessibility_tree);
|
||||
virtual ~AccessibilityWalkerActor() override;
|
||||
|
||||
static bool is_suitable_for_accessibility_inspection(JsonValue const&);
|
||||
JsonValue serialize_root() const;
|
||||
JsonValue serialize_node(JsonObject const&) const;
|
||||
|
||||
static Optional<Node> accessibility_node_for(WeakPtr<AccessibilityWalkerActor> const&, StringView actor);
|
||||
Optional<Node> accessibility_node(StringView actor);
|
||||
|
||||
static Optional<Node> parent_of_accessibility_node_for(WeakPtr<AccessibilityWalkerActor> const&, Node const&);
|
||||
Optional<Node> parent_of_accessibility_node(Node const&);
|
||||
|
||||
static Optional<Node> root_accessibility_node_for(WeakPtr<AccessibilityWalkerActor> const&);
|
||||
Optional<Node> root_accessibility_node();
|
||||
|
||||
private:
|
||||
AccessibilityWalkerActor(DevToolsServer&, String name, WeakPtr<TabActor>, JsonObject accessibility_tree);
|
||||
|
||||
virtual void handle_message(Message const&) override;
|
||||
|
||||
void populate_accessibility_tree_cache();
|
||||
void populate_accessibility_tree_cache(JsonObject& node, JsonObject const* parent);
|
||||
|
||||
AccessibilityNodeActor const& actor_for_node(JsonObject const& node);
|
||||
|
||||
WeakPtr<TabActor> m_tab;
|
||||
JsonObject m_accessibility_tree;
|
||||
|
||||
HashMap<JsonObject const*, JsonObject const*> m_node_to_parent_map;
|
||||
HashMap<String, JsonObject const*> m_actor_to_node_map;
|
||||
HashMap<Web::UniqueNodeID, String> m_node_id_to_actor_map;
|
||||
|
||||
HashMap<NodeIdentifier, WeakPtr<AccessibilityNodeActor>> m_node_actors;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
#include <AK/Enumerate.h>
|
||||
#include <AK/JsonArray.h>
|
||||
#include <AK/JsonObject.h>
|
||||
#include <LibDevTools/Actors/AccessibilityActor.h>
|
||||
#include <LibDevTools/Actors/CSSPropertiesActor.h>
|
||||
#include <LibDevTools/Actors/ConsoleActor.h>
|
||||
#include <LibDevTools/Actors/FrameActor.h>
|
||||
|
|
@ -20,12 +21,12 @@
|
|||
|
||||
namespace DevTools {
|
||||
|
||||
NonnullRefPtr<FrameActor> FrameActor::create(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab, WeakPtr<CSSPropertiesActor> css_properties, WeakPtr<ConsoleActor> console, WeakPtr<InspectorActor> inspector, WeakPtr<StyleSheetsActor> style_sheets, WeakPtr<ThreadActor> thread)
|
||||
NonnullRefPtr<FrameActor> FrameActor::create(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab, WeakPtr<CSSPropertiesActor> css_properties, WeakPtr<ConsoleActor> console, WeakPtr<InspectorActor> inspector, WeakPtr<StyleSheetsActor> style_sheets, WeakPtr<ThreadActor> thread, WeakPtr<AccessibilityActor> accessibility)
|
||||
{
|
||||
return adopt_ref(*new FrameActor(devtools, move(name), move(tab), move(css_properties), move(console), move(inspector), move(style_sheets), move(thread)));
|
||||
return adopt_ref(*new FrameActor(devtools, move(name), move(tab), move(css_properties), move(console), move(inspector), move(style_sheets), move(thread), move(accessibility)));
|
||||
}
|
||||
|
||||
FrameActor::FrameActor(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab, WeakPtr<CSSPropertiesActor> css_properties, WeakPtr<ConsoleActor> console, WeakPtr<InspectorActor> inspector, WeakPtr<StyleSheetsActor> style_sheets, WeakPtr<ThreadActor> thread)
|
||||
FrameActor::FrameActor(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab, WeakPtr<CSSPropertiesActor> css_properties, WeakPtr<ConsoleActor> console, WeakPtr<InspectorActor> inspector, WeakPtr<StyleSheetsActor> style_sheets, WeakPtr<ThreadActor> thread, WeakPtr<AccessibilityActor> accessibility)
|
||||
: Actor(devtools, move(name))
|
||||
, m_tab(move(tab))
|
||||
, m_css_properties(move(css_properties))
|
||||
|
|
@ -33,6 +34,7 @@ FrameActor::FrameActor(DevToolsServer& devtools, String name, WeakPtr<TabActor>
|
|||
, m_inspector(move(inspector))
|
||||
, m_style_sheets(move(style_sheets))
|
||||
, m_thread(move(thread))
|
||||
, m_accessibility(move(accessibility))
|
||||
{
|
||||
if (auto tab = m_tab.strong_ref()) {
|
||||
devtools.delegate().listen_for_console_messages(
|
||||
|
|
@ -127,10 +129,12 @@ JsonObject FrameActor::serialize_target() const
|
|||
|
||||
target.set("traits"sv, move(traits));
|
||||
|
||||
if (auto css_properties = m_css_properties.strong_ref())
|
||||
target.set("cssPropertiesActor"sv, css_properties->name());
|
||||
if (auto accessibility = m_accessibility.strong_ref())
|
||||
target.set("accessibilityActor"sv, accessibility->name());
|
||||
if (auto console = m_console.strong_ref())
|
||||
target.set("consoleActor"sv, console->name());
|
||||
if (auto css_properties = m_css_properties.strong_ref())
|
||||
target.set("cssPropertiesActor"sv, css_properties->name());
|
||||
if (auto inspector = m_inspector.strong_ref())
|
||||
target.set("inspectorActor"sv, inspector->name());
|
||||
if (auto style_sheets = m_style_sheets.strong_ref())
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class DEVTOOLS_API FrameActor final : public Actor {
|
|||
public:
|
||||
static constexpr auto base_name = "frame"sv;
|
||||
|
||||
static NonnullRefPtr<FrameActor> create(DevToolsServer&, String name, WeakPtr<TabActor>, WeakPtr<CSSPropertiesActor>, WeakPtr<ConsoleActor>, WeakPtr<InspectorActor>, WeakPtr<StyleSheetsActor>, WeakPtr<ThreadActor>);
|
||||
static NonnullRefPtr<FrameActor> create(DevToolsServer&, String name, WeakPtr<TabActor>, WeakPtr<CSSPropertiesActor>, WeakPtr<ConsoleActor>, WeakPtr<InspectorActor>, WeakPtr<StyleSheetsActor>, WeakPtr<ThreadActor>, WeakPtr<AccessibilityActor>);
|
||||
virtual ~FrameActor() override;
|
||||
|
||||
void send_frame_update_message();
|
||||
|
|
@ -28,7 +28,7 @@ public:
|
|||
JsonObject serialize_target() const;
|
||||
|
||||
private:
|
||||
FrameActor(DevToolsServer&, String name, WeakPtr<TabActor>, WeakPtr<CSSPropertiesActor>, WeakPtr<ConsoleActor>, WeakPtr<InspectorActor>, WeakPtr<StyleSheetsActor>, WeakPtr<ThreadActor>);
|
||||
FrameActor(DevToolsServer&, String name, WeakPtr<TabActor>, WeakPtr<CSSPropertiesActor>, WeakPtr<ConsoleActor>, WeakPtr<InspectorActor>, WeakPtr<StyleSheetsActor>, WeakPtr<ThreadActor>, WeakPtr<AccessibilityActor>);
|
||||
|
||||
void style_sheets_available(JsonObject& response, Vector<Web::CSS::StyleSheetIdentifier> style_sheets);
|
||||
|
||||
|
|
@ -45,6 +45,7 @@ private:
|
|||
WeakPtr<InspectorActor> m_inspector;
|
||||
WeakPtr<StyleSheetsActor> m_style_sheets;
|
||||
WeakPtr<ThreadActor> m_thread;
|
||||
WeakPtr<AccessibilityActor> m_accessibility;
|
||||
|
||||
i32 m_highest_notified_message_index { -1 };
|
||||
i32 m_highest_received_message_index { -1 };
|
||||
|
|
|
|||
|
|
@ -11,20 +11,12 @@
|
|||
#include <AK/Traits.h>
|
||||
#include <LibDevTools/Actor.h>
|
||||
#include <LibDevTools/Forward.h>
|
||||
#include <LibDevTools/Node.h>
|
||||
#include <LibWeb/CSS/Selector.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
|
||||
namespace DevTools {
|
||||
|
||||
struct DEVTOOLS_API NodeIdentifier {
|
||||
static NodeIdentifier for_node(JsonObject const& node);
|
||||
|
||||
bool operator==(NodeIdentifier const&) const = default;
|
||||
|
||||
Web::UniqueNodeID id { 0 };
|
||||
Optional<Web::CSS::PseudoElement> pseudo_element;
|
||||
};
|
||||
|
||||
class NodeActor final : public Actor {
|
||||
public:
|
||||
static constexpr auto base_name = "node"sv;
|
||||
|
|
|
|||
59
Libraries/LibDevTools/Actors/ParentAccessibilityActor.cpp
Normal file
59
Libraries/LibDevTools/Actors/ParentAccessibilityActor.cpp
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibDevTools/Actors/AccessibilityActor.h>
|
||||
#include <LibDevTools/Actors/ParentAccessibilityActor.h>
|
||||
#include <LibDevTools/DevToolsServer.h>
|
||||
|
||||
namespace DevTools {
|
||||
|
||||
NonnullRefPtr<ParentAccessibilityActor> ParentAccessibilityActor::create(DevToolsServer& devtools, String name)
|
||||
{
|
||||
return adopt_ref(*new ParentAccessibilityActor(devtools, move(name)));
|
||||
}
|
||||
|
||||
ParentAccessibilityActor::ParentAccessibilityActor(DevToolsServer& devtools, String name)
|
||||
: Actor(devtools, move(name))
|
||||
{
|
||||
}
|
||||
|
||||
ParentAccessibilityActor::~ParentAccessibilityActor() = default;
|
||||
|
||||
void ParentAccessibilityActor::handle_message(Message const& message)
|
||||
{
|
||||
if (message.type == "bootstrap"sv) {
|
||||
JsonObject state;
|
||||
state.set("canBeDisabled"sv, true);
|
||||
state.set("canBeEnabled"sv, true);
|
||||
|
||||
JsonObject response;
|
||||
response.set("state"sv, move(state));
|
||||
send_response(message, move(response));
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type == "enable"sv) {
|
||||
// First, a change event
|
||||
JsonObject response;
|
||||
response.set("canBeDisabled"sv, true);
|
||||
response.set("type"sv, "canBeDisabledChange"sv);
|
||||
send_response(message, move(response));
|
||||
|
||||
// Then a blank message is expected
|
||||
send_message(JsonObject {});
|
||||
|
||||
// Then each AccessibilityActor is enabled and sends an "init" message
|
||||
for (auto const& [name, actor] : devtools().actor_registry()) {
|
||||
if (auto* accessibility_actor = as_if<AccessibilityActor>(*actor))
|
||||
accessibility_actor->enable();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
send_unrecognized_packet_type_error(message);
|
||||
}
|
||||
|
||||
}
|
||||
28
Libraries/LibDevTools/Actors/ParentAccessibilityActor.h
Normal file
28
Libraries/LibDevTools/Actors/ParentAccessibilityActor.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <LibDevTools/Actor.h>
|
||||
#include <LibDevTools/Forward.h>
|
||||
|
||||
namespace DevTools {
|
||||
|
||||
class DEVTOOLS_API ParentAccessibilityActor final : public Actor {
|
||||
public:
|
||||
static constexpr auto base_name = "parent-accessibility"sv;
|
||||
|
||||
static NonnullRefPtr<ParentAccessibilityActor> create(DevToolsServer&, String name);
|
||||
virtual ~ParentAccessibilityActor() override;
|
||||
|
||||
private:
|
||||
ParentAccessibilityActor(DevToolsServer&, String name);
|
||||
|
||||
virtual void handle_message(Message const&) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <AK/JsonObject.h>
|
||||
#include <LibDevTools/Actors/DeviceActor.h>
|
||||
#include <LibDevTools/Actors/ParentAccessibilityActor.h>
|
||||
#include <LibDevTools/Actors/PreferenceActor.h>
|
||||
#include <LibDevTools/Actors/ProcessActor.h>
|
||||
#include <LibDevTools/Actors/RootActor.h>
|
||||
|
|
@ -56,6 +57,8 @@ void RootActor::handle_message(Message const& message)
|
|||
for (auto const& actor : devtools().actor_registry()) {
|
||||
if (is<DeviceActor>(*actor.value))
|
||||
response.set("deviceActor"sv, actor.key);
|
||||
else if (is<ParentAccessibilityActor>(*actor.value))
|
||||
response.set("parentAccessibilityActor"sv, actor.key);
|
||||
else if (is<PreferenceActor>(*actor.value))
|
||||
response.set("preferenceActor"sv, actor.key);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <AK/JsonArray.h>
|
||||
#include <AK/StringUtils.h>
|
||||
#include <LibDevTools/Actors/AccessibilityNodeActor.h>
|
||||
#include <LibDevTools/Actors/LayoutInspectorActor.h>
|
||||
#include <LibDevTools/Actors/TabActor.h>
|
||||
#include <LibDevTools/Actors/WalkerActor.h>
|
||||
|
|
@ -127,6 +128,54 @@ void WalkerActor::handle_message(Message const& message)
|
|||
return;
|
||||
}
|
||||
|
||||
if (message.type == "getNodeFromActor"sv) {
|
||||
auto path = get_required_parameter<JsonArray>(message, "path"sv);
|
||||
if (!path.has_value())
|
||||
return;
|
||||
|
||||
auto actor_id = get_required_parameter<String>(message, "actorID"sv);
|
||||
if (!actor_id.has_value())
|
||||
return;
|
||||
|
||||
// The ["rawAccessible","DOMNode"] path retrieves the DOM node corresponding to an AccessibilityNodeActor.
|
||||
if (path->size() == 2) {
|
||||
auto const& first = path->at(0);
|
||||
auto const& second = path->at(1);
|
||||
if (first.is_string() && first.as_string() == "rawAccessible"sv
|
||||
&& second.is_string() && second.as_string() == "DOMNode"sv) {
|
||||
|
||||
auto maybe_accessibility_actor = devtools().actor_registry().find(actor_id.value());
|
||||
if (maybe_accessibility_actor == devtools().actor_registry().end()) {
|
||||
send_unknown_actor_error(message, actor_id.value());
|
||||
return;
|
||||
}
|
||||
|
||||
auto accessibility_actor = as<AccessibilityNodeActor>(maybe_accessibility_actor->value.ptr());
|
||||
if (auto node_actor_name = m_dom_node_id_to_actor_map.get(accessibility_actor->node_identifier().id); node_actor_name.has_value()) {
|
||||
auto dom_node = dom_node_for(this, node_actor_name.value());
|
||||
if (!dom_node.has_value()) {
|
||||
send_unknown_actor_error(message, node_actor_name.value());
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject node;
|
||||
node.set("node"sv, serialize_node(dom_node->node));
|
||||
node.set("newParents"sv, JsonArray {});
|
||||
|
||||
response.set("node"sv, move(node));
|
||||
send_response(message, move(response));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JsonObject error;
|
||||
error.set("error"sv, "unrecognizedNodePath"sv);
|
||||
error.set("message"sv, MUST(String::formatted("Unrecognized or missing path for getNodeFromActor: '{}'", message.data.serialized())));
|
||||
send_response(message, move(error));
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type == "getOffsetParent"sv) {
|
||||
response.set("node"sv, JsonValue {});
|
||||
send_response(message, move(response));
|
||||
|
|
@ -388,19 +437,6 @@ JsonValue WalkerActor::serialize_root() const
|
|||
return serialize_node(m_dom_tree);
|
||||
}
|
||||
|
||||
static constexpr Web::DOM::NodeType parse_node_type(StringView type)
|
||||
{
|
||||
if (type == "document"sv)
|
||||
return Web::DOM::NodeType::DOCUMENT_NODE;
|
||||
if (type == "element"sv)
|
||||
return Web::DOM::NodeType::ELEMENT_NODE;
|
||||
if (type == "text"sv)
|
||||
return Web::DOM::NodeType::TEXT_NODE;
|
||||
if (type == "comment"sv)
|
||||
return Web::DOM::NodeType::COMMENT_NODE;
|
||||
return Web::DOM::NodeType::INVALID;
|
||||
}
|
||||
|
||||
JsonValue WalkerActor::serialize_node(JsonObject const& node) const
|
||||
{
|
||||
auto tab = m_tab.strong_ref();
|
||||
|
|
@ -414,7 +450,7 @@ JsonValue WalkerActor::serialize_node(JsonObject const& node) const
|
|||
auto name = node.get_string("name"sv).release_value();
|
||||
auto type = node.get_string("type"sv).release_value();
|
||||
|
||||
auto dom_type = parse_node_type(type);
|
||||
auto dom_type = parse_dom_node_type(type);
|
||||
JsonValue node_value;
|
||||
|
||||
auto is_top_level_document = &node == &m_dom_tree;
|
||||
|
|
@ -450,7 +486,7 @@ JsonValue WalkerActor::serialize_node(JsonObject const& node) const
|
|||
|
||||
if (auto parent_actor = m_dom_node_id_to_actor_map.get(parent_id); parent_actor.has_value()) {
|
||||
if (auto parent_node = WalkerActor::dom_node_for(this, *parent_actor); parent_node.has_value()) {
|
||||
dom_type = parse_node_type(parent_node->node.get_string("type"sv).value());
|
||||
dom_type = parse_dom_node_type(parent_node->node.get_string("type"sv).value());
|
||||
is_displayed = !is_top_level_document && parent_node->node.get_bool("visible"sv).value_or(false);
|
||||
}
|
||||
}
|
||||
|
|
@ -518,14 +554,14 @@ JsonValue WalkerActor::serialize_node(JsonObject const& node) const
|
|||
return serialized;
|
||||
}
|
||||
|
||||
Optional<WalkerActor::DOMNode> WalkerActor::dom_node_for(WeakPtr<WalkerActor> const& weak_walker, StringView actor)
|
||||
Optional<Node> WalkerActor::dom_node_for(WeakPtr<WalkerActor> const& weak_walker, StringView actor)
|
||||
{
|
||||
if (auto walker = weak_walker.strong_ref())
|
||||
return walker->dom_node(actor);
|
||||
return {};
|
||||
}
|
||||
|
||||
Optional<WalkerActor::DOMNode> WalkerActor::dom_node(StringView actor)
|
||||
Optional<Node> WalkerActor::dom_node(StringView actor)
|
||||
{
|
||||
auto tab = m_tab.strong_ref();
|
||||
if (!tab)
|
||||
|
|
@ -538,7 +574,7 @@ Optional<WalkerActor::DOMNode> WalkerActor::dom_node(StringView actor)
|
|||
auto const& dom_node = *maybe_dom_node.value();
|
||||
auto identifier = NodeIdentifier::for_node(dom_node);
|
||||
|
||||
return DOMNode { .node = dom_node, .identifier = move(identifier), .tab = tab.release_nonnull() };
|
||||
return Node { .node = dom_node, .identifier = move(identifier), .tab = tab.release_nonnull() };
|
||||
}
|
||||
|
||||
Optional<JsonObject const&> WalkerActor::find_node_by_selector(JsonObject const& node, StringView selector)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include <LibDevTools/Actor.h>
|
||||
#include <LibDevTools/Actors/NodeActor.h>
|
||||
#include <LibDevTools/Forward.h>
|
||||
#include <LibDevTools/Node.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWebView/Forward.h>
|
||||
|
||||
|
|
@ -28,13 +29,8 @@ public:
|
|||
static bool is_suitable_for_dom_inspection(JsonValue const&);
|
||||
JsonValue serialize_root() const;
|
||||
|
||||
struct DOMNode {
|
||||
JsonObject const& node;
|
||||
NodeIdentifier identifier;
|
||||
NonnullRefPtr<TabActor> tab;
|
||||
};
|
||||
static Optional<DOMNode> dom_node_for(WeakPtr<WalkerActor> const&, StringView actor);
|
||||
Optional<DOMNode> dom_node(StringView actor);
|
||||
static Optional<Node> dom_node_for(WeakPtr<WalkerActor> const&, StringView actor);
|
||||
Optional<Node> dom_node(StringView actor);
|
||||
|
||||
private:
|
||||
WalkerActor(DevToolsServer&, String name, WeakPtr<TabActor>, JsonObject dom_tree);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include <AK/Debug.h>
|
||||
#include <AK/JsonObject.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibDevTools/Actors/AccessibilityActor.h>
|
||||
#include <LibDevTools/Actors/CSSPropertiesActor.h>
|
||||
#include <LibDevTools/Actors/ConsoleActor.h>
|
||||
#include <LibDevTools/Actors/FrameActor.h>
|
||||
|
|
@ -95,8 +96,9 @@ void WatcherActor::handle_message(Message const& message)
|
|||
auto& inspector = devtools().register_actor<InspectorActor>(m_tab);
|
||||
auto& style_sheets = devtools().register_actor<StyleSheetsActor>(m_tab);
|
||||
auto& thread = devtools().register_actor<ThreadActor>();
|
||||
auto& accessibility = devtools().register_actor<AccessibilityActor>(m_tab);
|
||||
|
||||
auto& target = devtools().register_actor<FrameActor>(m_tab, css_properties, console, inspector, style_sheets, thread);
|
||||
auto& target = devtools().register_actor<FrameActor>(m_tab, css_properties, console, inspector, style_sheets, thread, accessibility);
|
||||
m_target = target;
|
||||
|
||||
response.set("type"sv, "target-available-form"sv);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
set(SOURCES
|
||||
Actor.cpp
|
||||
Actors/AccessibilityActor.cpp
|
||||
Actors/AccessibilityNodeActor.cpp
|
||||
Actors/AccessibilityWalkerActor.cpp
|
||||
Actors/ConsoleActor.cpp
|
||||
Actors/CSSPropertiesActor.cpp
|
||||
Actors/DeviceActor.cpp
|
||||
|
|
@ -9,6 +12,7 @@ set(SOURCES
|
|||
Actors/LayoutInspectorActor.cpp
|
||||
Actors/NodeActor.cpp
|
||||
Actors/PageStyleActor.cpp
|
||||
Actors/ParentAccessibilityActor.cpp
|
||||
Actors/PreferenceActor.cpp
|
||||
Actors/ProcessActor.cpp
|
||||
Actors/RootActor.cpp
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ public:
|
|||
using OnTabInspectionComplete = Function<void(ErrorOr<JsonValue>)>;
|
||||
virtual void inspect_tab(TabDescription const&, OnTabInspectionComplete) const { }
|
||||
|
||||
using OnAccessibilityTreeInspectionComplete = Function<void(ErrorOr<JsonValue>)>;
|
||||
virtual void inspect_accessibility_tree(TabDescription const&, OnAccessibilityTreeInspectionComplete) const { }
|
||||
|
||||
using OnDOMNodePropertiesReceived = Function<void(WebView::DOMNodeProperties)>;
|
||||
virtual void listen_for_dom_properties(TabDescription const&, OnDOMNodePropertiesReceived) const { }
|
||||
virtual void stop_listening_for_dom_properties(TabDescription const&) const { }
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <LibCore/Socket.h>
|
||||
#include <LibCore/TCPServer.h>
|
||||
#include <LibDevTools/Actors/DeviceActor.h>
|
||||
#include <LibDevTools/Actors/ParentAccessibilityActor.h>
|
||||
#include <LibDevTools/Actors/PreferenceActor.h>
|
||||
#include <LibDevTools/Actors/ProcessActor.h>
|
||||
#include <LibDevTools/Actors/TabActor.h>
|
||||
|
|
@ -79,6 +80,7 @@ ErrorOr<void> DevToolsServer::on_new_client()
|
|||
register_actor<DeviceActor>();
|
||||
register_actor<PreferenceActor>();
|
||||
register_actor<ProcessActor>(ProcessDescription { .is_parent = true });
|
||||
register_actor<ParentAccessibilityActor>();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@
|
|||
namespace DevTools {
|
||||
|
||||
class Actor;
|
||||
class AccessibilityActor;
|
||||
class AccessibilityNodeActor;
|
||||
class AccessibilityWalkerActor;
|
||||
class Connection;
|
||||
class ConsoleActor;
|
||||
class CSSPropertiesActor;
|
||||
|
|
@ -23,6 +26,7 @@ class InspectorActor;
|
|||
class LayoutInspectorActor;
|
||||
class NodeActor;
|
||||
class PageStyleActor;
|
||||
class ParentAccessibilityActor;
|
||||
class PreferenceActor;
|
||||
class ProcessActor;
|
||||
class RootActor;
|
||||
|
|
@ -35,6 +39,7 @@ class WalkerActor;
|
|||
class WatcherActor;
|
||||
|
||||
struct CSSProperty;
|
||||
struct Node;
|
||||
struct ProcessDescription;
|
||||
struct TabDescription;
|
||||
|
||||
|
|
|
|||
47
Libraries/LibDevTools/Node.h
Normal file
47
Libraries/LibDevTools/Node.h
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/JsonObject.h>
|
||||
#include <LibDevTools/Actors/TabActor.h>
|
||||
#include <LibDevTools/Forward.h>
|
||||
#include <LibWeb/CSS/PseudoElement.h>
|
||||
#include <LibWeb/DOM/NodeType.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
|
||||
namespace DevTools {
|
||||
|
||||
struct DEVTOOLS_API NodeIdentifier {
|
||||
static NodeIdentifier for_node(JsonObject const& node);
|
||||
|
||||
bool operator==(NodeIdentifier const&) const = default;
|
||||
|
||||
Web::UniqueNodeID id { 0 };
|
||||
Optional<Web::CSS::PseudoElement> pseudo_element;
|
||||
};
|
||||
|
||||
struct DEVTOOLS_API Node {
|
||||
JsonObject const& node;
|
||||
NodeIdentifier identifier;
|
||||
NonnullRefPtr<TabActor> tab;
|
||||
};
|
||||
|
||||
static constexpr Web::DOM::NodeType parse_dom_node_type(StringView type)
|
||||
{
|
||||
if (type == "document"sv)
|
||||
return Web::DOM::NodeType::DOCUMENT_NODE;
|
||||
if (type == "element"sv)
|
||||
return Web::DOM::NodeType::ELEMENT_NODE;
|
||||
if (type == "text"sv)
|
||||
return Web::DOM::NodeType::TEXT_NODE;
|
||||
if (type == "comment"sv)
|
||||
return Web::DOM::NodeType::COMMENT_NODE;
|
||||
return Web::DOM::NodeType::INVALID;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ set(SOURCES
|
|||
RootVector.cpp
|
||||
Heap.cpp
|
||||
HeapBlock.cpp
|
||||
WeakBlock.cpp
|
||||
WeakContainer.cpp
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ class Heap;
|
|||
class HeapBlock;
|
||||
class NanBoxedValue;
|
||||
class WeakContainer;
|
||||
class WeakImpl;
|
||||
|
||||
template<typename T>
|
||||
class Function;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2020-2025, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
|
|
@ -20,6 +20,8 @@
|
|||
#include <LibGC/HeapBlock.h>
|
||||
#include <LibGC/NanBoxedValue.h>
|
||||
#include <LibGC/Root.h>
|
||||
#include <LibGC/Weak.h>
|
||||
#include <LibGC/WeakInlines.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
#ifdef HAS_ADDRESS_SANITIZER
|
||||
|
|
@ -28,10 +30,18 @@
|
|||
|
||||
namespace GC {
|
||||
|
||||
static Heap* s_the;
|
||||
|
||||
Heap& Heap::the()
|
||||
{
|
||||
return *s_the;
|
||||
}
|
||||
|
||||
Heap::Heap(void* private_data, AK::Function<void(HashMap<Cell*, GC::HeapRoot>&)> gather_embedder_roots)
|
||||
: HeapBase(private_data)
|
||||
, m_gather_embedder_roots(move(gather_embedder_roots))
|
||||
{
|
||||
s_the = this;
|
||||
static_assert(HeapBlock::min_possible_cell_size <= 32, "Heap Cell tracking uses too much data!");
|
||||
m_size_based_cell_allocators.append(make<CellAllocator>(64));
|
||||
m_size_based_cell_allocators.append(make<CellAllocator>(96));
|
||||
|
|
@ -258,6 +268,7 @@ void Heap::collect_garbage(CollectionType collection_type, bool print_report)
|
|||
mark_live_cells(roots);
|
||||
}
|
||||
finalize_unmarked_cells();
|
||||
sweep_weak_blocks();
|
||||
sweep_dead_cells(print_report, collection_measurement_timer);
|
||||
}
|
||||
|
||||
|
|
@ -462,6 +473,22 @@ void Heap::finalize_unmarked_cells()
|
|||
});
|
||||
}
|
||||
|
||||
void Heap::sweep_weak_blocks()
|
||||
{
|
||||
for (auto& weak_block : m_usable_weak_blocks) {
|
||||
weak_block.sweep();
|
||||
}
|
||||
Vector<WeakBlock&> now_usable_weak_blocks;
|
||||
for (auto& weak_block : m_full_weak_blocks) {
|
||||
weak_block.sweep();
|
||||
if (weak_block.can_allocate())
|
||||
now_usable_weak_blocks.append(weak_block);
|
||||
}
|
||||
for (auto& weak_block : now_usable_weak_blocks) {
|
||||
m_usable_weak_blocks.append(weak_block);
|
||||
}
|
||||
}
|
||||
|
||||
void Heap::sweep_dead_cells(bool print_report, Core::ElapsedTimer const& measurement_timer)
|
||||
{
|
||||
dbgln_if(HEAP_DEBUG, "sweep_dead_cells:");
|
||||
|
|
@ -559,4 +586,21 @@ void Heap::uproot_cell(Cell* cell)
|
|||
m_uprooted_cells.append(cell);
|
||||
}
|
||||
|
||||
WeakImpl* Heap::create_weak_impl(void* ptr)
|
||||
{
|
||||
if (m_usable_weak_blocks.is_empty()) {
|
||||
// NOTE: These are leaked on Heap destruction, but that's fine since Heap is tied to process lifetime.
|
||||
auto* weak_block = WeakBlock::create();
|
||||
m_usable_weak_blocks.append(*weak_block);
|
||||
}
|
||||
|
||||
auto* weak_block = m_usable_weak_blocks.first();
|
||||
auto* new_weak_impl = weak_block->allocate(static_cast<Cell*>(ptr));
|
||||
if (!weak_block->can_allocate()) {
|
||||
m_full_weak_blocks.append(*weak_block);
|
||||
}
|
||||
|
||||
return new_weak_impl;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include <LibGC/Root.h>
|
||||
#include <LibGC/RootHashMap.h>
|
||||
#include <LibGC/RootVector.h>
|
||||
#include <LibGC/WeakBlock.h>
|
||||
#include <LibGC/WeakContainer.h>
|
||||
|
||||
namespace GC {
|
||||
|
|
@ -37,6 +38,8 @@ public:
|
|||
explicit Heap(void* private_data, AK::Function<void(HashMap<Cell*, GC::HeapRoot>&)> gather_embedder_roots);
|
||||
~Heap();
|
||||
|
||||
static Heap& the();
|
||||
|
||||
template<typename T, typename... Args>
|
||||
Ref<T> allocate(Args&&... args)
|
||||
{
|
||||
|
|
@ -81,6 +84,8 @@ public:
|
|||
|
||||
void enqueue_post_gc_task(AK::Function<void()>);
|
||||
|
||||
WeakImpl* create_weak_impl(void*);
|
||||
|
||||
private:
|
||||
friend class MarkingVisitor;
|
||||
friend class GraphConstructorVisitor;
|
||||
|
|
@ -113,6 +118,7 @@ private:
|
|||
void mark_live_cells(HashMap<Cell*, HeapRoot> const& live_cells);
|
||||
void finalize_unmarked_cells();
|
||||
void sweep_dead_cells(bool print_report, Core::ElapsedTimer const&);
|
||||
void sweep_weak_blocks();
|
||||
|
||||
ALWAYS_INLINE CellAllocator& allocator_for_size(size_t cell_size)
|
||||
{
|
||||
|
|
@ -159,6 +165,9 @@ private:
|
|||
AK::Function<void(HashMap<Cell*, GC::HeapRoot>&)> m_gather_embedder_roots;
|
||||
|
||||
Vector<AK::Function<void()>> m_post_gc_tasks;
|
||||
|
||||
WeakBlock::List m_usable_weak_blocks;
|
||||
WeakBlock::List m_full_weak_blocks;
|
||||
} SWIFT_IMMORTAL_REFERENCE;
|
||||
|
||||
inline void Heap::did_create_root(Badge<RootImpl>, RootImpl& impl)
|
||||
|
|
|
|||
|
|
@ -71,6 +71,9 @@ public:
|
|||
|
||||
operator T&() const { return *m_ptr; }
|
||||
|
||||
operator bool() const = delete;
|
||||
bool operator!() const = delete;
|
||||
|
||||
private:
|
||||
T* m_ptr { nullptr };
|
||||
};
|
||||
|
|
|
|||
166
Libraries/LibGC/Weak.h
Normal file
166
Libraries/LibGC/Weak.h
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Badge.h>
|
||||
#include <AK/RefPtr.h>
|
||||
#include <LibGC/Export.h>
|
||||
#include <LibGC/Ptr.h>
|
||||
|
||||
namespace GC {
|
||||
|
||||
class WeakBlock;
|
||||
|
||||
class WeakImpl {
|
||||
public:
|
||||
// NOTE: Null GC::Weaks point at this WeakImpl. This allows Weak to always chase the impl pointer without null-checking it.
|
||||
static GC_API WeakImpl the_null_weak_impl;
|
||||
|
||||
WeakImpl() = default;
|
||||
WeakImpl(void* ptr)
|
||||
: m_ptr(ptr)
|
||||
{
|
||||
}
|
||||
|
||||
void* ptr() const { return m_ptr; }
|
||||
void set_ptr(Badge<WeakBlock>, void* ptr) { m_ptr = ptr; }
|
||||
|
||||
bool operator==(WeakImpl const& other) const { return m_ptr == other.m_ptr; }
|
||||
bool operator!=(WeakImpl const& other) const { return m_ptr != other.m_ptr; }
|
||||
|
||||
void ref() const { ++m_ref_count; }
|
||||
void unref() const
|
||||
{
|
||||
VERIFY(m_ref_count);
|
||||
--m_ref_count;
|
||||
}
|
||||
|
||||
size_t ref_count() const { return m_ref_count; }
|
||||
|
||||
enum class State {
|
||||
Allocated,
|
||||
Freelist,
|
||||
};
|
||||
|
||||
void set_state(State state) { m_state = state; }
|
||||
State state() const { return m_state; }
|
||||
|
||||
private:
|
||||
mutable size_t m_ref_count { 0 };
|
||||
State m_state { State::Allocated };
|
||||
void* m_ptr { nullptr };
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class Weak {
|
||||
public:
|
||||
constexpr Weak() = default;
|
||||
Weak(nullptr_t) { }
|
||||
|
||||
Weak(T const* ptr);
|
||||
Weak(T const& ptr);
|
||||
|
||||
template<typename U>
|
||||
Weak(Weak<U> const& other)
|
||||
requires(IsConvertible<U*, T*>);
|
||||
|
||||
Weak(Ref<T> const& other);
|
||||
|
||||
template<typename U>
|
||||
Weak(Ref<U> const& other)
|
||||
requires(IsConvertible<U*, T*>);
|
||||
|
||||
template<typename U>
|
||||
Weak& operator=(Weak<U> const& other)
|
||||
requires(IsConvertible<U*, T*>)
|
||||
{
|
||||
m_impl = other.impl();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Weak& operator=(Ref<T> const& other);
|
||||
|
||||
template<typename U>
|
||||
Weak& operator=(Ref<U> const& other)
|
||||
requires(IsConvertible<U*, T*>);
|
||||
|
||||
Weak& operator=(T const& other);
|
||||
|
||||
template<typename U>
|
||||
Weak& operator=(U const& other)
|
||||
requires(IsConvertible<U*, T*>);
|
||||
|
||||
Weak& operator=(T const* other);
|
||||
|
||||
template<typename U>
|
||||
Weak& operator=(U const* other)
|
||||
requires(IsConvertible<U*, T*>);
|
||||
|
||||
T* operator->() const
|
||||
{
|
||||
ASSERT(ptr());
|
||||
return ptr();
|
||||
}
|
||||
|
||||
[[nodiscard]] T& operator*() const
|
||||
{
|
||||
ASSERT(ptr());
|
||||
return *ptr();
|
||||
}
|
||||
|
||||
Ptr<T> ptr() const { return static_cast<T*>(impl().ptr()); }
|
||||
|
||||
explicit operator bool() const { return !!ptr(); }
|
||||
bool operator!() const { return !ptr(); }
|
||||
|
||||
operator T*() const { return ptr(); }
|
||||
|
||||
Ref<T> as_nonnull() const
|
||||
{
|
||||
ASSERT(ptr());
|
||||
return *ptr();
|
||||
}
|
||||
|
||||
WeakImpl& impl() const { return *m_impl; }
|
||||
|
||||
private:
|
||||
NonnullRefPtr<WeakImpl> m_impl { WeakImpl::the_null_weak_impl };
|
||||
};
|
||||
|
||||
template<typename T, typename U>
|
||||
inline bool operator==(Weak<T> const& a, Ptr<U> const& b)
|
||||
{
|
||||
return a.ptr() == b.ptr();
|
||||
}
|
||||
|
||||
template<typename T, typename U>
|
||||
inline bool operator==(Weak<T> const& a, Ref<U> const& b)
|
||||
{
|
||||
return a.ptr() == b.ptr();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace AK {
|
||||
|
||||
template<typename T>
|
||||
struct Traits<GC::Weak<T>> : public DefaultTraits<GC::Weak<T>> {
|
||||
static unsigned hash(GC::Weak<T> const& value)
|
||||
{
|
||||
return Traits<T*>::hash(value.ptr());
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct Formatter<GC::Weak<T>> : Formatter<T const*> {
|
||||
ErrorOr<void> format(FormatBuilder& builder, GC::Weak<T> const& value)
|
||||
{
|
||||
return Formatter<T const*>::format(builder, value.ptr());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
77
Libraries/LibGC/WeakBlock.cpp
Normal file
77
Libraries/LibGC/WeakBlock.cpp
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibGC/Cell.h>
|
||||
#include <LibGC/WeakBlock.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#if defined(AK_OS_WINDOWS)
|
||||
# include <AK/Windows.h>
|
||||
# include <memoryapi.h>
|
||||
#endif
|
||||
|
||||
namespace GC {
|
||||
|
||||
WeakImpl WeakImpl::the_null_weak_impl;
|
||||
|
||||
WeakBlock* WeakBlock::create()
|
||||
{
|
||||
#if !defined(AK_OS_WINDOWS)
|
||||
auto* block = (HeapBlock*)mmap(nullptr, WeakBlock::BLOCK_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
||||
VERIFY(block != MAP_FAILED);
|
||||
#else
|
||||
auto* block = (HeapBlock*)VirtualAlloc(NULL, WeakBlock::BLOCK_SIZE, MEM_COMMIT, PAGE_READWRITE);
|
||||
VERIFY(block);
|
||||
#endif
|
||||
return new (block) WeakBlock;
|
||||
}
|
||||
|
||||
WeakBlock::WeakBlock()
|
||||
{
|
||||
for (size_t i = 0; i < IMPL_COUNT; ++i) {
|
||||
m_impls[i].set_ptr({}, i + 1 < IMPL_COUNT ? &m_impls[i + 1] : nullptr);
|
||||
m_impls[i].set_state(WeakImpl::State::Freelist);
|
||||
}
|
||||
m_freelist = &m_impls[0];
|
||||
}
|
||||
|
||||
WeakBlock::~WeakBlock() = default;
|
||||
|
||||
WeakImpl* WeakBlock::allocate(Cell* cell)
|
||||
{
|
||||
auto* impl = m_freelist;
|
||||
if (!impl)
|
||||
return nullptr;
|
||||
VERIFY(impl->ref_count() == 0);
|
||||
m_freelist = impl->ptr() ? static_cast<WeakImpl*>(impl->ptr()) : nullptr;
|
||||
impl->set_ptr({}, cell);
|
||||
impl->set_state(WeakImpl::State::Allocated);
|
||||
return impl;
|
||||
}
|
||||
|
||||
void WeakBlock::deallocate(WeakImpl* impl)
|
||||
{
|
||||
VERIFY(impl->ref_count() == 0);
|
||||
impl->set_ptr({}, m_freelist);
|
||||
impl->set_state(WeakImpl::State::Freelist);
|
||||
m_freelist = impl;
|
||||
}
|
||||
|
||||
void WeakBlock::sweep()
|
||||
{
|
||||
for (size_t i = 0; i < IMPL_COUNT; ++i) {
|
||||
auto& impl = m_impls[i];
|
||||
if (impl.state() == WeakImpl::State::Freelist)
|
||||
continue;
|
||||
auto* cell = static_cast<Cell*>(impl.ptr());
|
||||
if (!cell || !cell->is_marked())
|
||||
impl.set_ptr({}, nullptr);
|
||||
if (impl.ref_count() == 0)
|
||||
deallocate(&impl);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
45
Libraries/LibGC/WeakBlock.h
Normal file
45
Libraries/LibGC/WeakBlock.h
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/IntrusiveList.h>
|
||||
#include <LibGC/Forward.h>
|
||||
#include <LibGC/Weak.h>
|
||||
|
||||
namespace GC {
|
||||
|
||||
class GC_API WeakBlock {
|
||||
public:
|
||||
static constexpr size_t BLOCK_SIZE = 16 * KiB;
|
||||
|
||||
static WeakBlock* create();
|
||||
|
||||
WeakImpl* allocate(Cell*);
|
||||
void deallocate(WeakImpl*);
|
||||
|
||||
bool can_allocate() const { return m_freelist != nullptr; }
|
||||
|
||||
void sweep();
|
||||
|
||||
private:
|
||||
WeakBlock();
|
||||
~WeakBlock();
|
||||
|
||||
IntrusiveListNode<WeakBlock> m_list_node;
|
||||
|
||||
public:
|
||||
using List = IntrusiveList<&WeakBlock::m_list_node>;
|
||||
|
||||
WeakImpl* m_freelist { nullptr };
|
||||
|
||||
static constexpr size_t IMPL_COUNT = (BLOCK_SIZE - sizeof(m_list_node) - sizeof(WeakImpl*)) / sizeof(WeakImpl);
|
||||
WeakImpl m_impls[IMPL_COUNT];
|
||||
};
|
||||
|
||||
static_assert(sizeof(WeakBlock) <= WeakBlock::BLOCK_SIZE);
|
||||
|
||||
}
|
||||
113
Libraries/LibGC/WeakInlines.h
Normal file
113
Libraries/LibGC/WeakInlines.h
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGC/Weak.h>
|
||||
|
||||
namespace GC {
|
||||
|
||||
template<typename T>
|
||||
Weak<T>::Weak(T const* ptr)
|
||||
: m_impl(ptr ? *ptr->heap().create_weak_impl(const_cast<void*>(static_cast<void const*>(ptr))) : WeakImpl::the_null_weak_impl)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Weak<T>::Weak(T const& ptr)
|
||||
: m_impl(*ptr.heap().create_weak_impl(const_cast<void*>(static_cast<void const*>(&ptr))))
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
template<typename U>
|
||||
Weak<T>::Weak(Weak<U> const& other)
|
||||
requires(IsConvertible<U*, T*>)
|
||||
: m_impl(other.impl())
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Weak<T>::Weak(Ref<T> const& other)
|
||||
: m_impl(*other.ptr()->heap().create_weak_impl(other.ptr()))
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
template<typename U>
|
||||
Weak<T>::Weak(Ref<U> const& other)
|
||||
requires(IsConvertible<U*, T*>)
|
||||
: m_impl(*other.ptr()->heap().create_weak_impl(other.ptr()))
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
template<typename U>
|
||||
Weak<T>& Weak<T>::operator=(U const& other)
|
||||
requires(IsConvertible<U*, T*>)
|
||||
{
|
||||
if (ptr() != other) {
|
||||
m_impl = *other.heap().create_weak_impl(const_cast<void*>(static_cast<void const*>(&other)));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Weak<T>& Weak<T>::operator=(Ref<T> const& other)
|
||||
{
|
||||
if (ptr() != other.ptr()) {
|
||||
m_impl = *other.ptr()->heap().create_weak_impl(other.ptr());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
template<typename U>
|
||||
Weak<T>& Weak<T>::operator=(Ref<U> const& other)
|
||||
requires(IsConvertible<U*, T*>)
|
||||
{
|
||||
if (ptr() != other.ptr()) {
|
||||
m_impl = *other.ptr()->heap().create_weak_impl(other.ptr());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Weak<T>& Weak<T>::operator=(T const& other)
|
||||
{
|
||||
if (ptr() != &other) {
|
||||
m_impl = *other.heap().create_weak_impl(const_cast<void*>(static_cast<void const*>(&other)));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Weak<T>& Weak<T>::operator=(T const* other)
|
||||
{
|
||||
if (ptr() != other) {
|
||||
if (other)
|
||||
m_impl = *other->heap().create_weak_impl(const_cast<void*>(static_cast<void const*>(other)));
|
||||
else
|
||||
m_impl = WeakImpl::the_null_weak_impl;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
template<typename U>
|
||||
Weak<T>& Weak<T>::operator=(U const* other)
|
||||
requires(IsConvertible<U*, T*>)
|
||||
{
|
||||
if (ptr() != other) {
|
||||
if (other)
|
||||
m_impl = *other->heap().create_weak_impl(const_cast<void*>(static_cast<void const*>(other)));
|
||||
else
|
||||
m_impl = WeakImpl::the_null_weak_impl;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -10,6 +10,11 @@
|
|||
#include <AK/Checked.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/ShareableBitmap.h>
|
||||
#include <LibGfx/SkiaUtils.h>
|
||||
|
||||
#include <core/SkBitmap.h>
|
||||
#include <core/SkColorSpace.h>
|
||||
#include <core/SkImage.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifdef AK_OS_MACOS
|
||||
|
|
@ -122,6 +127,16 @@ ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_with_anonymous_buffer(BitmapFormat
|
|||
return adopt_nonnull_ref_or_enomem(new (nothrow) Bitmap(format, alpha_type, move(buffer), size));
|
||||
}
|
||||
|
||||
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_with_raw_data(BitmapFormat format, AlphaType alpha_type, ReadonlyBytes raw_data, IntSize size)
|
||||
{
|
||||
if (size_would_overflow(format, size))
|
||||
return Error::from_string_literal("Gfx::Bitmap::create_with_raw_data size overflow");
|
||||
|
||||
auto backing_store = TRY(Bitmap::allocate_backing_store(format, size, InitializeBackingStore::No));
|
||||
raw_data.copy_to(Bytes { backing_store.data, backing_store.size_in_bytes });
|
||||
return AK::adopt_nonnull_ref_or_enomem(new (nothrow) Bitmap(format, alpha_type, size, backing_store));
|
||||
}
|
||||
|
||||
Bitmap::Bitmap(BitmapFormat format, AlphaType alpha_type, Core::AnonymousBuffer buffer, IntSize size)
|
||||
: m_size(size)
|
||||
, m_data(buffer.data<void>())
|
||||
|
|
@ -184,6 +199,24 @@ ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::cropped(Gfx::IntRect crop, Gfx::Colo
|
|||
return new_bitmap;
|
||||
}
|
||||
|
||||
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::scaled(int const width, int const height, ScalingMode const scaling_mode) const
|
||||
{
|
||||
auto const source_info = SkImageInfo::Make(this->width(), this->height(), to_skia_color_type(format()), to_skia_alpha_type(format(), alpha_type()), nullptr);
|
||||
SkPixmap const source_sk_pixmap(source_info, begin(), pitch());
|
||||
SkBitmap source_sk_bitmap;
|
||||
source_sk_bitmap.installPixels(source_sk_pixmap);
|
||||
source_sk_bitmap.setImmutable();
|
||||
|
||||
auto scaled_bitmap = TRY(Gfx::Bitmap::create(format(), alpha_type(), { width, height }));
|
||||
auto const scaled_info = SkImageInfo::Make(scaled_bitmap->width(), scaled_bitmap->height(), to_skia_color_type(scaled_bitmap->format()), to_skia_alpha_type(scaled_bitmap->format(), scaled_bitmap->alpha_type()), nullptr);
|
||||
SkPixmap const scaled_sk_pixmap(scaled_info, scaled_bitmap->begin(), scaled_bitmap->pitch());
|
||||
|
||||
sk_sp<SkImage> source_sk_image = source_sk_bitmap.asImage();
|
||||
if (!source_sk_image->scalePixels(scaled_sk_pixmap, to_skia_sampling_options(scaling_mode)))
|
||||
return Error::from_string_literal("Unable to scale pixels for bitmap");
|
||||
return scaled_bitmap;
|
||||
}
|
||||
|
||||
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::to_bitmap_backed_by_anonymous_buffer() const
|
||||
{
|
||||
if (m_buffer.is_valid()) {
|
||||
|
|
@ -219,7 +252,7 @@ Gfx::ShareableBitmap Bitmap::to_shareable_bitmap() const
|
|||
return Gfx::ShareableBitmap { bitmap_or_error.release_value_but_fixme_should_propagate_errors(), Gfx::ShareableBitmap::ConstructWithKnownGoodBitmap };
|
||||
}
|
||||
|
||||
ErrorOr<BackingStore> Bitmap::allocate_backing_store(BitmapFormat format, IntSize size)
|
||||
ErrorOr<BackingStore> Bitmap::allocate_backing_store(BitmapFormat format, IntSize size, InitializeBackingStore initialize_backing_store)
|
||||
{
|
||||
if (size.is_empty())
|
||||
return Error::from_string_literal("Gfx::Bitmap backing store size is empty");
|
||||
|
|
@ -230,7 +263,11 @@ ErrorOr<BackingStore> Bitmap::allocate_backing_store(BitmapFormat format, IntSiz
|
|||
auto const pitch = minimum_pitch(size.width(), format);
|
||||
auto const data_size_in_bytes = size_in_bytes(pitch, size.height());
|
||||
|
||||
void* data = kcalloc(1, data_size_in_bytes);
|
||||
void* data;
|
||||
if (initialize_backing_store == InitializeBackingStore::Yes)
|
||||
data = kcalloc(1, data_size_in_bytes);
|
||||
else
|
||||
data = kmalloc(data_size_in_bytes);
|
||||
if (data == nullptr)
|
||||
return Error::from_errno(errno);
|
||||
return BackingStore { data, pitch, data_size_in_bytes };
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2024, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2018-2025, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2022, Timothy Slater <tslater2006@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
#include <LibGfx/Color.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <LibGfx/ScalingMode.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
|
|
@ -69,11 +70,14 @@ public:
|
|||
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create(BitmapFormat, AlphaType, IntSize);
|
||||
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create_shareable(BitmapFormat, AlphaType, IntSize);
|
||||
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create_wrapper(BitmapFormat, AlphaType, IntSize, size_t pitch, void*, Function<void()>&& destruction_callback = {});
|
||||
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create_with_raw_data(BitmapFormat, AlphaType, ReadonlyBytes, IntSize);
|
||||
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create_with_anonymous_buffer(BitmapFormat, AlphaType, Core::AnonymousBuffer, IntSize);
|
||||
|
||||
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> clone() const;
|
||||
|
||||
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> cropped(Gfx::IntRect, Gfx::Color outside_color = Gfx::Color::Black) const;
|
||||
ErrorOr<NonnullRefPtr<Bitmap>> scaled(int width, int height, ScalingMode scaling_mode) const;
|
||||
|
||||
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> to_bitmap_backed_by_anonymous_buffer() const;
|
||||
|
||||
[[nodiscard]] ShareableBitmap to_shareable_bitmap() const;
|
||||
|
|
@ -169,7 +173,11 @@ private:
|
|||
Bitmap(BitmapFormat, AlphaType, IntSize, size_t pitch, void*, Function<void()>&& destruction_callback);
|
||||
Bitmap(BitmapFormat, AlphaType, Core::AnonymousBuffer, IntSize);
|
||||
|
||||
static ErrorOr<BackingStore> allocate_backing_store(BitmapFormat format, IntSize size);
|
||||
enum class InitializeBackingStore {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
static ErrorOr<BackingStore> allocate_backing_store(BitmapFormat format, IntSize size, InitializeBackingStore = InitializeBackingStore::Yes);
|
||||
|
||||
IntSize m_size;
|
||||
void* m_data { nullptr };
|
||||
|
|
|
|||
|
|
@ -128,14 +128,9 @@ ErrorOr<Gfx::BitmapSequence> decode(Decoder& decoder)
|
|||
if (size_check.has_overflow() || size_check.value() > bytes.size())
|
||||
return Error::from_string_literal("IPC: Invalid Gfx::BitmapSequence buffer data");
|
||||
|
||||
auto buffer = TRY(Core::AnonymousBuffer::create_with_size(size_in_bytes));
|
||||
auto buffer_bytes = Bytes { buffer.data<u8>(), buffer.size() };
|
||||
|
||||
bytes.slice(bytes_read, size_in_bytes).copy_to(buffer_bytes);
|
||||
|
||||
auto slice = bytes.slice(bytes_read, size_in_bytes);
|
||||
bytes_read += size_in_bytes;
|
||||
|
||||
bitmap = TRY(Gfx::Bitmap::create_with_anonymous_buffer(metadata.format, metadata.alpha_type, move(buffer), metadata.size));
|
||||
bitmap = TRY(Gfx::Bitmap::create_with_raw_data(metadata.format, metadata.alpha_type, slice, metadata.size));
|
||||
}
|
||||
|
||||
bitmaps.append(bitmap);
|
||||
|
|
|
|||
|
|
@ -173,4 +173,9 @@ endif()
|
|||
|
||||
if (HAS_VULKAN)
|
||||
target_link_libraries(LibCore PUBLIC Vulkan::Vulkan Vulkan::Headers)
|
||||
|
||||
if ((LINUX AND NOT ANDROID) OR CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
|
||||
pkg_check_modules(LibDRM REQUIRED libdrm)
|
||||
target_include_directories(LibGfx PUBLIC ${LibDRM_INCLUDE_DIRS})
|
||||
endif()
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -25,6 +25,73 @@
|
|||
|
||||
namespace Gfx {
|
||||
|
||||
namespace {
|
||||
|
||||
char nth_digit(u32 value, u8 digit)
|
||||
{
|
||||
// This helper is used to format integers.
|
||||
// nth_digit(745, 1) -> '5'
|
||||
// nth_digit(745, 2) -> '4'
|
||||
// nth_digit(745, 3) -> '7'
|
||||
|
||||
VERIFY(value < 1000);
|
||||
VERIFY(digit <= 3);
|
||||
VERIFY(digit > 0);
|
||||
|
||||
while (digit > 1) {
|
||||
value /= 10;
|
||||
digit--;
|
||||
}
|
||||
|
||||
return '0' + value % 10;
|
||||
}
|
||||
|
||||
Array<char, 4> format_to_8bit_compatible(u8 value)
|
||||
{
|
||||
// This function formats to the shortest string that roundtrips at 8 bits.
|
||||
// As an example:
|
||||
// 127 / 255 = 0.498 ± 0.001
|
||||
// 128 / 255 = 0.502 ± 0.001
|
||||
// But round(.5 * 255) == 128, so this function returns (note that it's only the fractional part):
|
||||
// 127 -> "498"
|
||||
// 128 -> "5"
|
||||
|
||||
u32 const three_digits = (value * 1000u + 127) / 255;
|
||||
u32 const rounded_to_two_digits = (three_digits + 5) / 10 * 10;
|
||||
|
||||
if ((rounded_to_two_digits * 255 / 100 + 5) / 10 != value)
|
||||
return { nth_digit(three_digits, 3), nth_digit(three_digits, 2), nth_digit(three_digits, 1), '\0' };
|
||||
|
||||
u32 const rounded_to_one_digit = (three_digits + 50) / 100 * 100;
|
||||
if ((rounded_to_one_digit * 255 / 100 + 5) / 10 != value)
|
||||
return { nth_digit(rounded_to_two_digits, 3), nth_digit(rounded_to_two_digits, 2), '\0', '\0' };
|
||||
|
||||
return { nth_digit(rounded_to_one_digit, 3), '\0', '\0', '\0' };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-color-4/#serializing-sRGB-values
|
||||
void Color::serialize_a_srgb_value(StringBuilder& builder) const
|
||||
{
|
||||
// The serialized form is derived from the computed value and thus, uses either the rgb() or rgba() form
|
||||
// (depending on whether the alpha is exactly 1, or not), with lowercase letters for the function name.
|
||||
// NOTE: Since we use Gfx::Color, having an "alpha of 1" means its value is 255.
|
||||
if (alpha() == 0)
|
||||
builder.appendff("rgba({}, {}, {}, 0)", red(), green(), blue());
|
||||
else if (alpha() == 255)
|
||||
builder.appendff("rgb({}, {}, {})", red(), green(), blue());
|
||||
else
|
||||
builder.appendff("rgba({}, {}, {}, 0.{})", red(), green(), blue(), format_to_8bit_compatible(alpha()).data());
|
||||
}
|
||||
|
||||
String Color::serialize_a_srgb_value() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
serialize_a_srgb_value(builder);
|
||||
return builder.to_string_without_validation();
|
||||
}
|
||||
|
||||
String Color::to_string(HTMLCompatibleSerialization html_compatible_serialization) const
|
||||
{
|
||||
// If the following conditions are all true:
|
||||
|
|
@ -45,9 +112,7 @@ String Color::to_string(HTMLCompatibleSerialization html_compatible_serializatio
|
|||
}
|
||||
|
||||
// Otherwise, for sRGB the CSS serialization of sRGB values is used and for other color spaces, the relevant serialization of the <color> value.
|
||||
if (alpha() < 255)
|
||||
return MUST(String::formatted("rgba({}, {}, {}, {})", red(), green(), blue(), alpha() / 255.0));
|
||||
return MUST(String::formatted("rgb({}, {}, {})", red(), green(), blue()));
|
||||
return serialize_a_srgb_value();
|
||||
}
|
||||
|
||||
String Color::to_string_without_alpha() const
|
||||
|
|
@ -422,6 +487,15 @@ Color Color::from_linear_srgb(float red, float green, float blue, float alpha)
|
|||
clamp(lroundf(alpha * 255.f), 0, 255));
|
||||
}
|
||||
|
||||
Color Color::from_linear_display_p3(float r, float g, float b, float alpha)
|
||||
{
|
||||
float x = 0.48657095 * r + 0.26566769 * g + 0.19821729 * b;
|
||||
float y = 0.22897456 * r + 0.69173852 * g + 0.07928691 * b;
|
||||
float z = 0.00000000 * r + 0.04511338 * g + 1.04394437 * b;
|
||||
|
||||
return from_xyz65(x, y, z, alpha);
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-color-4/#predefined-a98-rgb
|
||||
Color Color::from_a98rgb(float r, float g, float b, float alpha)
|
||||
{
|
||||
|
|
@ -453,11 +527,7 @@ Color Color::from_display_p3(float r, float g, float b, float alpha)
|
|||
auto linear_g = to_linear(g);
|
||||
auto linear_b = to_linear(b);
|
||||
|
||||
float x = 0.48657095 * linear_r + 0.26566769 * linear_g + 0.19821729 * linear_b;
|
||||
float y = 0.22897456 * linear_r + 0.69173852 * linear_g + 0.07928691 * linear_b;
|
||||
float z = 0.00000000 * linear_r + 0.04511338 * linear_g + 1.04394437 * linear_b;
|
||||
|
||||
return from_xyz65(x, y, z, alpha);
|
||||
return from_linear_display_p3(linear_r, linear_g, linear_b, alpha);
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-color-4/#predefined-prophoto-rgb
|
||||
|
|
|
|||
|
|
@ -212,6 +212,7 @@ public:
|
|||
static Color from_a98rgb(float r, float g, float b, float alpha = 1.0f);
|
||||
static Color from_display_p3(float r, float g, float b, float alpha = 1.0f);
|
||||
static Color from_lab(float L, float a, float b, float alpha = 1.0f);
|
||||
static Color from_linear_display_p3(float r, float g, float b, float alpha = 1.0f);
|
||||
static Color from_linear_srgb(float r, float g, float b, float alpha = 1.0f);
|
||||
static Color from_pro_photo_rgb(float r, float g, float b, float alpha = 1.0f);
|
||||
static Color from_rec2020(float r, float g, float b, float alpha = 1.0f);
|
||||
|
|
@ -501,6 +502,9 @@ public:
|
|||
String to_string_without_alpha() const;
|
||||
Utf16String to_utf16_string_without_alpha() const;
|
||||
|
||||
void serialize_a_srgb_value(StringBuilder&) const;
|
||||
String serialize_a_srgb_value() const;
|
||||
|
||||
ByteString to_byte_string() const;
|
||||
ByteString to_byte_string_without_alpha() const;
|
||||
static Optional<Color> from_string(StringView);
|
||||
|
|
|
|||
|
|
@ -148,7 +148,10 @@ ErrorOr<void> JPEGLoadingContext::decode()
|
|||
|
||||
// Photoshop writes inverted CMYK data (i.e. Photoshop's 0 should be 255). We convert this
|
||||
// to expected values.
|
||||
if (cinfo.saw_Adobe_marker) {
|
||||
bool should_invert_cmyk = cinfo.jpeg_color_space == JCS_CMYK
|
||||
&& (!cinfo.saw_Adobe_marker || cinfo.Adobe_transform == 0);
|
||||
|
||||
if (should_invert_cmyk) {
|
||||
for (int i = 0; i < cmyk_bitmap->size().height(); ++i) {
|
||||
auto* line = cmyk_bitmap->scanline(i);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
|
||||
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
|
@ -13,6 +14,7 @@
|
|||
#include <LibGfx/Color.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
#include <LibGfx/Gradients.h>
|
||||
#include <LibGfx/ImmutableBitmap.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
|
@ -20,6 +22,7 @@ namespace Gfx {
|
|||
class PaintStyle : public RefCounted<PaintStyle> {
|
||||
public:
|
||||
virtual ~PaintStyle() = default;
|
||||
virtual bool is_visible() const { return true; }
|
||||
};
|
||||
|
||||
class SolidColorPaintStyle : public PaintStyle {
|
||||
|
|
@ -29,6 +32,8 @@ public:
|
|||
return adopt_nonnull_ref_or_enomem(new (nothrow) SolidColorPaintStyle(color));
|
||||
}
|
||||
|
||||
bool is_visible() const override { return m_color.alpha() > 0; }
|
||||
|
||||
Color const& color() const { return m_color; }
|
||||
|
||||
private:
|
||||
|
|
@ -65,11 +70,47 @@ public:
|
|||
|
||||
Optional<float> repeat_length() const { return m_repeat_length; }
|
||||
|
||||
bool is_visible() const override
|
||||
{
|
||||
return any_of(m_color_stops, [](auto& stop) { return stop.color.alpha() > 0; });
|
||||
}
|
||||
|
||||
private:
|
||||
Vector<ColorStop, 4> m_color_stops;
|
||||
Optional<float> m_repeat_length;
|
||||
};
|
||||
|
||||
class CanvasPatternPaintStyle : public PaintStyle {
|
||||
public:
|
||||
enum class Repetition : u8 {
|
||||
Repeat,
|
||||
RepeatX,
|
||||
RepeatY,
|
||||
NoRepeat
|
||||
};
|
||||
|
||||
static ErrorOr<NonnullRefPtr<CanvasPatternPaintStyle>> create(RefPtr<ImmutableBitmap> image, Repetition repetition)
|
||||
{
|
||||
return adopt_nonnull_ref_or_enomem(new (nothrow) CanvasPatternPaintStyle(image, repetition));
|
||||
}
|
||||
|
||||
RefPtr<ImmutableBitmap> image() const { return m_image; }
|
||||
Repetition repetition() const { return m_repetition; }
|
||||
Optional<AffineTransform> const& transform() const { return m_transform; }
|
||||
void set_transform(AffineTransform const& transform) { m_transform = transform; }
|
||||
|
||||
private:
|
||||
CanvasPatternPaintStyle(RefPtr<ImmutableBitmap> image, Repetition repetition)
|
||||
: m_image(image)
|
||||
, m_repetition(repetition)
|
||||
{
|
||||
}
|
||||
|
||||
RefPtr<ImmutableBitmap> m_image;
|
||||
Repetition m_repetition { Repetition::Repeat };
|
||||
Optional<AffineTransform> m_transform;
|
||||
};
|
||||
|
||||
// The following paint styles implement the gradients required for the HTML canvas.
|
||||
// These gradients are (unlike CSS ones) not relative to the painted shape, and do not
|
||||
// support premultiplied alpha.
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public:
|
|||
virtual void draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const& src_bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode, Optional<Gfx::Filter> filters, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) = 0;
|
||||
|
||||
virtual void stroke_path(Gfx::Path const&, Gfx::Color, float thickness) = 0;
|
||||
virtual void stroke_path(Gfx::Path const&, Gfx::Color, float thickness, float blur_radius, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) = 0;
|
||||
virtual void stroke_path(Gfx::Path const&, Gfx::Color, float thickness, float blur_radius, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator, Gfx::Path::CapStyle, Gfx::Path::JoinStyle, float miter_limit, Vector<float> const& dash_array, float dash_offset) = 0;
|
||||
virtual void stroke_path(Gfx::Path const&, Gfx::PaintStyle const&, Optional<Gfx::Filter>, float thickness, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) = 0;
|
||||
virtual void stroke_path(Gfx::Path const&, Gfx::PaintStyle const&, Optional<Gfx::Filter>, float thickness, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator, Gfx::Path::CapStyle const&, Gfx::Path::JoinStyle const&, float miter_limit, Vector<float> const&, float dash_offset) = 0;
|
||||
|
||||
|
|
@ -43,6 +43,8 @@ public:
|
|||
virtual void restore() = 0;
|
||||
|
||||
virtual void clip(Gfx::Path const&, Gfx::WindingRule) = 0;
|
||||
|
||||
virtual void reset() = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,23 +2,27 @@
|
|||
* Copyright (c) 2024, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2024-2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
* Copyright (c) 2024, Lucien Fiorini <lucienfiorini@gmail.com>
|
||||
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#define AK_DONT_REPLACE_STD
|
||||
|
||||
#include <AK/GenericShorthands.h>
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <LibGfx/Filter.h>
|
||||
#include <LibGfx/ImmutableBitmap.h>
|
||||
#include <LibGfx/PainterSkia.h>
|
||||
#include <LibGfx/PathSkia.h>
|
||||
#include <LibGfx/SkiaUtils.h>
|
||||
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <core/SkCanvas.h>
|
||||
#include <core/SkImage.h>
|
||||
#include <core/SkPath.h>
|
||||
#include <core/SkPathEffect.h>
|
||||
#include <effects/SkBlurMaskFilter.h>
|
||||
#include <effects/SkDashPathEffect.h>
|
||||
#include <effects/SkGradientShader.h>
|
||||
|
|
@ -43,14 +47,12 @@ struct PainterSkia::Impl {
|
|||
}
|
||||
};
|
||||
|
||||
static void apply_paint_style(SkPaint& paint, Gfx::PaintStyle const& style)
|
||||
static void apply_paint_style(SkPaint& paint, PaintStyle const& style)
|
||||
{
|
||||
if (is<Gfx::SolidColorPaintStyle>(style)) {
|
||||
auto const& solid_color = static_cast<Gfx::SolidColorPaintStyle const&>(style);
|
||||
paint.setColor(to_skia_color(solid_color.color()));
|
||||
} else if (is<Gfx::CanvasLinearGradientPaintStyle>(style)) {
|
||||
auto const& linear_gradient = static_cast<Gfx::CanvasLinearGradientPaintStyle const&>(style);
|
||||
auto const& color_stops = linear_gradient.color_stops();
|
||||
if (auto const& solid_color = as_if<SolidColorPaintStyle>(style)) {
|
||||
paint.setColor(to_skia_color(solid_color->color()));
|
||||
} else if (auto const& linear_gradient = as_if<Gfx::CanvasLinearGradientPaintStyle>(style)) {
|
||||
auto const& color_stops = linear_gradient->color_stops();
|
||||
|
||||
Vector<SkColor> colors;
|
||||
colors.ensure_capacity(color_stops.size());
|
||||
|
|
@ -61,16 +63,13 @@ static void apply_paint_style(SkPaint& paint, Gfx::PaintStyle const& style)
|
|||
positions.append(color_stop.position);
|
||||
}
|
||||
|
||||
Array<SkPoint, 2> points;
|
||||
points[0] = to_skia_point(linear_gradient.start_point());
|
||||
points[1] = to_skia_point(linear_gradient.end_point());
|
||||
Array points { to_skia_point(linear_gradient->start_point()), to_skia_point(linear_gradient->end_point()) };
|
||||
|
||||
SkMatrix matrix;
|
||||
auto shader = SkGradientShader::MakeLinear(points.data(), colors.data(), positions.data(), color_stops.size(), SkTileMode::kClamp, 0, &matrix);
|
||||
paint.setShader(shader);
|
||||
} else if (is<Gfx::CanvasRadialGradientPaintStyle>(style)) {
|
||||
auto const& radial_gradient = static_cast<Gfx::CanvasRadialGradientPaintStyle const&>(style);
|
||||
auto const& color_stops = radial_gradient.color_stops();
|
||||
} else if (auto const* radial_gradient = as_if<CanvasRadialGradientPaintStyle>(style)) {
|
||||
auto const& color_stops = radial_gradient->color_stops();
|
||||
|
||||
Vector<SkColor> colors;
|
||||
colors.ensure_capacity(color_stops.size());
|
||||
|
|
@ -81,10 +80,10 @@ static void apply_paint_style(SkPaint& paint, Gfx::PaintStyle const& style)
|
|||
positions.append(color_stop.position);
|
||||
}
|
||||
|
||||
auto start_center = radial_gradient.start_center();
|
||||
auto end_center = radial_gradient.end_center();
|
||||
auto start_radius = radial_gradient.start_radius();
|
||||
auto end_radius = radial_gradient.end_radius();
|
||||
auto start_center = radial_gradient->start_center();
|
||||
auto end_center = radial_gradient->end_center();
|
||||
auto start_radius = radial_gradient->start_radius();
|
||||
auto end_radius = radial_gradient->end_radius();
|
||||
|
||||
auto start_sk_point = to_skia_point(start_center);
|
||||
auto end_sk_point = to_skia_point(end_center);
|
||||
|
|
@ -92,6 +91,34 @@ static void apply_paint_style(SkPaint& paint, Gfx::PaintStyle const& style)
|
|||
SkMatrix matrix;
|
||||
auto shader = SkGradientShader::MakeTwoPointConical(start_sk_point, start_radius, end_sk_point, end_radius, colors.data(), positions.data(), color_stops.size(), SkTileMode::kClamp, 0, &matrix);
|
||||
paint.setShader(shader);
|
||||
} else if (auto const* canvas_pattern = as_if<CanvasPatternPaintStyle>(style)) {
|
||||
auto image = canvas_pattern->image();
|
||||
if (!image)
|
||||
return;
|
||||
auto const* sk_image = image->sk_image();
|
||||
|
||||
auto repetition = canvas_pattern->repetition();
|
||||
auto repeat_x = first_is_one_of(repetition, CanvasPatternPaintStyle::Repetition::Repeat, CanvasPatternPaintStyle::Repetition::RepeatX);
|
||||
auto repeat_y = first_is_one_of(repetition, CanvasPatternPaintStyle::Repetition::Repeat, CanvasPatternPaintStyle::Repetition::RepeatY);
|
||||
|
||||
// FIXME: Implement sampling configuration.
|
||||
SkSamplingOptions sk_sampling_options { SkFilterMode::kLinear };
|
||||
Optional<SkMatrix> transformation_matrix;
|
||||
|
||||
if (canvas_pattern->transform().has_value()) {
|
||||
auto const& transform = canvas_pattern->transform().value();
|
||||
transformation_matrix = SkMatrix::MakeAll(
|
||||
transform.a(), transform.c(), transform.e(),
|
||||
transform.b(), transform.d(), transform.f(),
|
||||
0, 0, 1);
|
||||
}
|
||||
auto shader = sk_image->makeShader(
|
||||
repeat_x ? SkTileMode::kRepeat : SkTileMode::kDecal,
|
||||
repeat_y ? SkTileMode::kRepeat : SkTileMode::kDecal,
|
||||
sk_sampling_options, transformation_matrix.has_value() ? &transformation_matrix.value() : nullptr);
|
||||
paint.setShader(move(shader));
|
||||
} else {
|
||||
dbgln("FIXME: Unsupported PaintStyle");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -115,6 +142,9 @@ static SkPaint to_skia_paint(Gfx::PaintStyle const& style, Optional<Gfx::Filter
|
|||
PainterSkia::PainterSkia(NonnullRefPtr<Gfx::PaintingSurface> painting_surface)
|
||||
: m_impl(adopt_own(*new Impl { move(painting_surface) }))
|
||||
{
|
||||
m_impl->with_canvas([this](auto& canvas) {
|
||||
m_initial_save_count = canvas.save();
|
||||
});
|
||||
}
|
||||
|
||||
PainterSkia::~PainterSkia() = default;
|
||||
|
|
@ -188,7 +218,7 @@ void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thi
|
|||
});
|
||||
}
|
||||
|
||||
void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thickness, float blur_radius, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator)
|
||||
void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thickness, float blur_radius, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator, Gfx::Path::CapStyle cap_style, Gfx::Path::JoinStyle join_style, float miter_limit, Vector<float> const& dash_array, float dash_offset)
|
||||
{
|
||||
// Skia treats zero thickness as a special case and will draw a hairline, while we want to draw nothing.
|
||||
if (thickness <= 0)
|
||||
|
|
@ -200,6 +230,10 @@ void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thi
|
|||
paint.setStyle(SkPaint::kStroke_Style);
|
||||
paint.setStrokeWidth(thickness);
|
||||
paint.setColor(to_skia_color(color));
|
||||
paint.setStrokeCap(to_skia_cap(cap_style));
|
||||
paint.setStrokeJoin(to_skia_join(join_style));
|
||||
paint.setStrokeMiter(miter_limit);
|
||||
paint.setPathEffect(SkDashPathEffect::Make(dash_array.data(), dash_array.size(), dash_offset));
|
||||
paint.setBlender(to_skia_blender(compositing_and_blending_operator));
|
||||
auto sk_path = to_skia_path(path);
|
||||
impl().with_canvas([&](auto& canvas) {
|
||||
|
|
@ -312,4 +346,11 @@ void PainterSkia::clip(Gfx::Path const& path, Gfx::WindingRule winding_rule)
|
|||
});
|
||||
}
|
||||
|
||||
void PainterSkia::reset()
|
||||
{
|
||||
impl().with_canvas([&](auto& canvas) {
|
||||
canvas.restoreToCount(m_initial_save_count);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/CompositingAndBlendingOperator.h>
|
||||
#include <LibGfx/Painter.h>
|
||||
#include <LibGfx/PaintingSurface.h>
|
||||
|
|
@ -23,7 +22,7 @@ public:
|
|||
virtual void fill_rect(Gfx::FloatRect const&, Color) override;
|
||||
virtual void draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const& src_bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode, Optional<Gfx::Filter>, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) override;
|
||||
virtual void stroke_path(Gfx::Path const&, Gfx::Color, float thickness) override;
|
||||
virtual void stroke_path(Gfx::Path const&, Gfx::Color, float thickness, float blur_radius, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) override;
|
||||
virtual void stroke_path(Gfx::Path const&, Gfx::Color, float thickness, float blur_radius, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator, Gfx::Path::CapStyle, Gfx::Path::JoinStyle, float miter_limit, Vector<float> const& dash_array, float dash_offset) override;
|
||||
virtual void stroke_path(Gfx::Path const&, Gfx::PaintStyle const&, Optional<Gfx::Filter>, float thickness, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) override;
|
||||
virtual void stroke_path(Gfx::Path const&, Gfx::PaintStyle const&, Optional<Gfx::Filter>, float thickness, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator, Gfx::Path::CapStyle const&, Gfx::Path::JoinStyle const&, float miter_limit, Vector<float> const&, float dash_offset) override;
|
||||
virtual void fill_path(Gfx::Path const&, Gfx::Color, Gfx::WindingRule) override;
|
||||
|
|
@ -33,11 +32,13 @@ public:
|
|||
virtual void save() override;
|
||||
virtual void restore() override;
|
||||
virtual void clip(Gfx::Path const&, Gfx::WindingRule) override;
|
||||
virtual void reset() override;
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
Impl& impl() { return *m_impl; }
|
||||
NonnullOwnPtr<Impl> m_impl;
|
||||
u32 m_initial_save_count { 0 };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Pavel Shliak <shlyakpavel@gmail.com>
|
||||
* Copyright (c) 2024, Lucien Fiorini <lucienfiorini@gmail.com>
|
||||
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
|
@ -11,6 +12,7 @@
|
|||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/CompositingAndBlendingOperator.h>
|
||||
#include <LibGfx/Filter.h>
|
||||
#include <LibGfx/PaintStyle.h>
|
||||
#include <LibGfx/PathSkia.h>
|
||||
#include <LibGfx/ScalingMode.h>
|
||||
#include <LibGfx/WindingRule.h>
|
||||
|
|
@ -20,7 +22,6 @@
|
|||
#include <core/SkImageFilter.h>
|
||||
#include <core/SkPaint.h>
|
||||
#include <core/SkPath.h>
|
||||
#include <core/SkPathEffect.h>
|
||||
#include <core/SkSamplingOptions.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@
|
|||
# include <AK/NonnullRefPtr.h>
|
||||
# include <AK/RefCounted.h>
|
||||
# include <vulkan/vulkan.h>
|
||||
# ifdef AK_OS_LINUX
|
||||
# if defined(AK_OS_LINUX) || defined(AK_OS_FREEBSD)
|
||||
# include <libdrm/drm_fourcc.h>
|
||||
// Sharable Vulkan images are currently only implemented on Linux
|
||||
// Sharable Vulkan images are currently only implemented on Linux and FreeBSD
|
||||
# define USE_VULKAN_IMAGES 1
|
||||
# endif
|
||||
|
||||
|
|
|
|||
|
|
@ -21,10 +21,7 @@ ConnectionBase::ConnectionBase(IPC::Stub& local_stub, NonnullOwnPtr<Transport> t
|
|||
{
|
||||
m_transport->set_up_read_hook([this] {
|
||||
NonnullRefPtr protect = *this;
|
||||
|
||||
if (auto result = drain_messages_from_peer(); result.is_error())
|
||||
dbgln("Read hook error while draining messages: {}", result.error());
|
||||
|
||||
drain_messages_from_peer();
|
||||
handle_messages();
|
||||
});
|
||||
}
|
||||
|
|
@ -96,7 +93,7 @@ void ConnectionBase::wait_for_transport_to_become_readable()
|
|||
m_transport->wait_until_readable();
|
||||
}
|
||||
|
||||
ErrorOr<void> ConnectionBase::drain_messages_from_peer()
|
||||
ConnectionBase::PeerEOF ConnectionBase::drain_messages_from_peer()
|
||||
{
|
||||
auto schedule_shutdown = m_transport->read_as_many_messages_as_possible_without_blocking([&](auto&& raw_message) {
|
||||
if (auto message = try_parse_message(raw_message.bytes, raw_message.fds)) {
|
||||
|
|
@ -117,10 +114,10 @@ ErrorOr<void> ConnectionBase::drain_messages_from_peer()
|
|||
deferred_invoke([this] {
|
||||
shutdown();
|
||||
});
|
||||
return Error::from_string_literal("IPC connection EOF");
|
||||
return PeerEOF::Yes;
|
||||
}
|
||||
|
||||
return {};
|
||||
return PeerEOF::No;
|
||||
}
|
||||
|
||||
OwnPtr<IPC::Message> ConnectionBase::wait_for_specific_endpoint_message_impl(u32 endpoint_magic, int message_id)
|
||||
|
|
@ -140,7 +137,7 @@ OwnPtr<IPC::Message> ConnectionBase::wait_for_specific_endpoint_message_impl(u32
|
|||
break;
|
||||
|
||||
wait_for_transport_to_become_readable();
|
||||
if (drain_messages_from_peer().is_error())
|
||||
if (drain_messages_from_peer() == PeerEOF::Yes)
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,11 @@ protected:
|
|||
|
||||
OwnPtr<IPC::Message> wait_for_specific_endpoint_message_impl(u32 endpoint_magic, int message_id);
|
||||
void wait_for_transport_to_become_readable();
|
||||
ErrorOr<void> drain_messages_from_peer();
|
||||
enum class PeerEOF {
|
||||
No,
|
||||
Yes
|
||||
};
|
||||
PeerEOF drain_messages_from_peer();
|
||||
|
||||
void handle_messages();
|
||||
|
||||
|
|
|
|||
|
|
@ -31,11 +31,6 @@ public:
|
|||
, ClientEndpoint::template Proxy<ServerEndpoint>(*this, {})
|
||||
, m_client_id(client_id)
|
||||
{
|
||||
this->transport().set_up_read_hook([this] {
|
||||
NonnullRefPtr protect = *this;
|
||||
// FIXME: Do something about errors.
|
||||
(void)this->drain_messages_from_peer();
|
||||
});
|
||||
}
|
||||
|
||||
virtual ~ConnectionFromClient() override = default;
|
||||
|
|
|
|||
|
|
@ -87,39 +87,6 @@ void LabelledStatement::dump(int indent) const
|
|||
m_labelled_item->dump(indent + 2);
|
||||
}
|
||||
|
||||
// 15.2.5 Runtime Semantics: InstantiateOrdinaryFunctionExpression, https://tc39.es/ecma262/#sec-runtime-semantics-instantiateordinaryfunctionexpression
|
||||
Value FunctionExpression::instantiate_ordinary_function_expression(VM& vm, Utf16FlyString given_name) const
|
||||
{
|
||||
auto& realm = *vm.current_realm();
|
||||
|
||||
if (given_name.is_empty())
|
||||
given_name = Utf16FlyString {};
|
||||
|
||||
auto own_name = name();
|
||||
auto has_own_name = !own_name.is_empty();
|
||||
|
||||
auto const& used_name = has_own_name ? own_name : given_name;
|
||||
|
||||
auto environment = GC::Ref { *vm.running_execution_context().lexical_environment };
|
||||
if (has_own_name) {
|
||||
VERIFY(environment);
|
||||
environment = new_declarative_environment(*environment);
|
||||
MUST(environment->create_immutable_binding(vm, own_name, false));
|
||||
}
|
||||
|
||||
auto private_environment = vm.running_execution_context().private_environment;
|
||||
|
||||
auto closure = ECMAScriptFunctionObject::create_from_function_node(*this, used_name, realm, environment, private_environment);
|
||||
|
||||
// FIXME: 6. Perform SetFunctionName(closure, name).
|
||||
// FIXME: 7. Perform MakeConstructor(closure).
|
||||
|
||||
if (has_own_name)
|
||||
MUST(environment->initialize_binding(vm, own_name, closure, Environment::InitializeBindingHint::Normal));
|
||||
|
||||
return closure;
|
||||
}
|
||||
|
||||
Optional<Utf16String> CallExpression::expression_string() const
|
||||
{
|
||||
if (is<Identifier>(*m_callee))
|
||||
|
|
@ -355,7 +322,6 @@ ThrowCompletionOr<ECMAScriptFunctionObject*> ClassExpression::create_class_const
|
|||
}
|
||||
|
||||
auto prototype = Object::create_prototype(realm, proto_parent);
|
||||
VERIFY(prototype);
|
||||
|
||||
// FIXME: Step 14.a is done in the parser. By using a synthetic super(...args) which does not call @@iterator of %Array.prototype%
|
||||
auto const& constructor = *m_constructor;
|
||||
|
|
@ -853,14 +819,14 @@ FunctionNode::FunctionNode(RefPtr<Identifier const> name, ByteString source_text
|
|||
|
||||
FunctionNode::~FunctionNode() = default;
|
||||
|
||||
void FunctionNode::set_shared_data(RefPtr<SharedFunctionInstanceData> shared_data) const
|
||||
void FunctionNode::set_shared_data(GC::Ptr<SharedFunctionInstanceData> shared_data) const
|
||||
{
|
||||
m_shared_data = move(shared_data);
|
||||
}
|
||||
|
||||
RefPtr<SharedFunctionInstanceData> FunctionNode::shared_data() const
|
||||
GC::Ptr<SharedFunctionInstanceData> FunctionNode::shared_data() const
|
||||
{
|
||||
return m_shared_data;
|
||||
return m_shared_data.ptr();
|
||||
}
|
||||
|
||||
void FunctionNode::dump(int indent, ByteString const& class_name) const
|
||||
|
|
@ -1621,80 +1587,6 @@ bool ImportStatement::has_bound_name(Utf16FlyString const& name) const
|
|||
});
|
||||
}
|
||||
|
||||
// 14.2.3 BlockDeclarationInstantiation ( code, env ), https://tc39.es/ecma262/#sec-blockdeclarationinstantiation
|
||||
void ScopeNode::block_declaration_instantiation(VM& vm, Environment* environment) const
|
||||
{
|
||||
// See also B.3.2.6 Changes to BlockDeclarationInstantiation, https://tc39.es/ecma262/#sec-web-compat-blockdeclarationinstantiation
|
||||
auto& realm = *vm.current_realm();
|
||||
|
||||
VERIFY(environment);
|
||||
|
||||
// 1. Let declarations be the LexicallyScopedDeclarations of code.
|
||||
|
||||
// 2. Let privateEnv be the running execution context's PrivateEnvironment.
|
||||
auto private_environment = vm.running_execution_context().private_environment;
|
||||
|
||||
// Note: All the calls here are ! and thus we do not need to TRY this callback.
|
||||
// We use MUST to ensure it does not throw and to avoid discarding the returned ThrowCompletionOr<void>.
|
||||
// 3. For each element d of declarations, do
|
||||
MUST(for_each_lexically_scoped_declaration([&](Declaration const& declaration) {
|
||||
auto is_constant_declaration = declaration.is_constant_declaration();
|
||||
// NOTE: Due to the use of MUST with `create_immutable_binding` and `create_mutable_binding` below,
|
||||
// an exception should not result from `for_each_bound_name`.
|
||||
// a. For each element dn of the BoundNames of d, do
|
||||
MUST(declaration.for_each_bound_identifier([&](Identifier const& identifier) {
|
||||
if (identifier.is_local()) {
|
||||
// NOTE: No need to create bindings for local variables as their values are not stored in an environment.
|
||||
return;
|
||||
}
|
||||
|
||||
auto const& name = identifier.string();
|
||||
|
||||
// i. If IsConstantDeclaration of d is true, then
|
||||
if (is_constant_declaration) {
|
||||
// 1. Perform ! env.CreateImmutableBinding(dn, true).
|
||||
MUST(environment->create_immutable_binding(vm, name, true));
|
||||
}
|
||||
// ii. Else,
|
||||
else {
|
||||
// 1. Perform ! env.CreateMutableBinding(dn, false). NOTE: This step is replaced in section B.3.2.6.
|
||||
if (!MUST(environment->has_binding(name)))
|
||||
MUST(environment->create_mutable_binding(vm, name, false));
|
||||
}
|
||||
}));
|
||||
|
||||
// b. If d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration, then
|
||||
if (is<FunctionDeclaration>(declaration)) {
|
||||
// i. Let fn be the sole element of the BoundNames of d.
|
||||
auto& function_declaration = static_cast<FunctionDeclaration const&>(declaration);
|
||||
|
||||
// ii. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv.
|
||||
auto function = ECMAScriptFunctionObject::create_from_function_node(
|
||||
function_declaration,
|
||||
function_declaration.name(),
|
||||
realm,
|
||||
environment,
|
||||
private_environment);
|
||||
|
||||
// iii. Perform ! env.InitializeBinding(fn, fo). NOTE: This step is replaced in section B.3.2.6.
|
||||
if (function_declaration.name_identifier()->is_local()) {
|
||||
auto& running_execution_context = vm.running_execution_context();
|
||||
auto number_of_registers = running_execution_context.executable->number_of_registers;
|
||||
auto number_of_constants = running_execution_context.executable->constants.size();
|
||||
auto local_index = function_declaration.name_identifier()->local_index();
|
||||
if (local_index.is_variable()) {
|
||||
running_execution_context.local(local_index.index + number_of_registers + number_of_constants) = function;
|
||||
} else {
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
} else {
|
||||
VERIFY(is<DeclarativeEnvironment>(*environment));
|
||||
static_cast<DeclarativeEnvironment&>(*environment).initialize_or_set_mutable_binding({}, vm, function->name(), function);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// 16.1.7 GlobalDeclarationInstantiation ( script, env ), https://tc39.es/ecma262/#sec-globaldeclarationinstantiation
|
||||
ThrowCompletionOr<void> Program::global_declaration_instantiation(VM& vm, GlobalEnvironment& global_environment) const
|
||||
{
|
||||
|
|
|
|||
|
|
@ -176,12 +176,6 @@ public:
|
|||
: ASTNode(move(source_range))
|
||||
{
|
||||
}
|
||||
|
||||
Bytecode::Executable* bytecode_executable() const { return m_bytecode_executable; }
|
||||
void set_bytecode_executable(Bytecode::Executable* bytecode_executable) { m_bytecode_executable = make_root(bytecode_executable); }
|
||||
|
||||
private:
|
||||
GC::Root<Bytecode::Executable> m_bytecode_executable;
|
||||
};
|
||||
|
||||
// 14.13 Labelled Statements, https://tc39.es/ecma262/#sec-labelled-statements
|
||||
|
|
@ -340,8 +334,6 @@ public:
|
|||
ThrowCompletionOr<void> for_each_var_function_declaration_in_reverse_order(ThrowCompletionOrVoidCallback<FunctionDeclaration const&>&& callback) const;
|
||||
ThrowCompletionOr<void> for_each_var_scoped_variable_declaration(ThrowCompletionOrVoidCallback<VariableDeclaration const&>&& callback) const;
|
||||
|
||||
void block_declaration_instantiation(VM&, Environment*) const;
|
||||
|
||||
ThrowCompletionOr<void> for_each_function_hoistable_with_annexB_extension(ThrowCompletionOrVoidCallback<FunctionDeclaration&>&& callback) const;
|
||||
|
||||
auto const& local_variables_names() const { return m_local_variables_names; }
|
||||
|
|
@ -799,10 +791,9 @@ public:
|
|||
bool uses_this_from_environment() const { return m_parsing_insights.uses_this_from_environment; }
|
||||
|
||||
virtual bool has_name() const = 0;
|
||||
virtual Value instantiate_ordinary_function_expression(VM&, Utf16FlyString given_name) const = 0;
|
||||
|
||||
RefPtr<SharedFunctionInstanceData> shared_data() const;
|
||||
void set_shared_data(RefPtr<SharedFunctionInstanceData>) const;
|
||||
GC::Ptr<SharedFunctionInstanceData> shared_data() const;
|
||||
void set_shared_data(GC::Ptr<SharedFunctionInstanceData>) const;
|
||||
|
||||
virtual ~FunctionNode();
|
||||
|
||||
|
|
@ -824,7 +815,7 @@ private:
|
|||
|
||||
Vector<LocalVariable> m_local_variables_names;
|
||||
|
||||
mutable RefPtr<SharedFunctionInstanceData> m_shared_data;
|
||||
mutable GC::Root<SharedFunctionInstanceData> m_shared_data;
|
||||
};
|
||||
|
||||
class FunctionDeclaration final
|
||||
|
|
@ -849,7 +840,6 @@ public:
|
|||
void set_should_do_additional_annexB_steps() { m_is_hoisted = true; }
|
||||
|
||||
bool has_name() const override { return true; }
|
||||
Value instantiate_ordinary_function_expression(VM&, Utf16FlyString) const override { VERIFY_NOT_REACHED(); }
|
||||
|
||||
virtual ~FunctionDeclaration() { }
|
||||
|
||||
|
|
@ -876,8 +866,6 @@ public:
|
|||
|
||||
bool has_name() const override { return !name().is_empty(); }
|
||||
|
||||
Value instantiate_ordinary_function_expression(VM&, Utf16FlyString given_name) const override;
|
||||
|
||||
virtual ~FunctionExpression() { }
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -645,15 +645,15 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> AssignmentExpression::g
|
|||
|
||||
if (expression.is_computed()) {
|
||||
if (!lhs_is_super_expression)
|
||||
generator.emit_put_by_value(*base, *computed_property, rval, Bytecode::Op::PropertyKind::KeyValue, move(base_identifier));
|
||||
generator.emit_put_by_value(*base, *computed_property, rval, Bytecode::PutKind::Normal, move(base_identifier));
|
||||
else
|
||||
generator.emit_put_by_value_with_this(*base, *computed_property, *this_value, rval, Op::PropertyKind::KeyValue);
|
||||
generator.emit_put_by_value_with_this(*base, *computed_property, *this_value, rval, PutKind::Normal);
|
||||
} else if (expression.property().is_identifier()) {
|
||||
auto identifier_table_ref = generator.intern_identifier(as<Identifier>(expression.property()).string());
|
||||
if (!lhs_is_super_expression)
|
||||
generator.emit_put_by_id(*base, identifier_table_ref, rval, Bytecode::Op::PropertyKind::KeyValue, generator.next_property_lookup_cache(), move(base_identifier));
|
||||
generator.emit_put_by_id(*base, identifier_table_ref, rval, Bytecode::PutKind::Normal, generator.next_property_lookup_cache(), move(base_identifier));
|
||||
else
|
||||
generator.emit<Bytecode::Op::PutByIdWithThis>(*base, *this_value, identifier_table_ref, rval, Bytecode::Op::PropertyKind::KeyValue, generator.next_property_lookup_cache());
|
||||
generator.emit<Bytecode::Op::PutNormalByIdWithThis>(*base, *this_value, identifier_table_ref, rval, generator.next_property_lookup_cache());
|
||||
} else if (expression.property().is_private_identifier()) {
|
||||
auto identifier_table_ref = generator.intern_identifier(as<PrivateIdentifier>(expression.property()).string());
|
||||
generator.emit<Bytecode::Op::PutPrivateById>(*base, identifier_table_ref, rval);
|
||||
|
|
@ -1151,19 +1151,19 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ObjectExpression::gener
|
|||
generator.push_home_object(object);
|
||||
|
||||
for (auto& property : m_properties) {
|
||||
Bytecode::Op::PropertyKind property_kind;
|
||||
Bytecode::PutKind property_kind;
|
||||
switch (property->type()) {
|
||||
case ObjectProperty::Type::KeyValue:
|
||||
property_kind = Bytecode::Op::PropertyKind::DirectKeyValue;
|
||||
property_kind = Bytecode::PutKind::Own;
|
||||
break;
|
||||
case ObjectProperty::Type::Getter:
|
||||
property_kind = Bytecode::Op::PropertyKind::Getter;
|
||||
property_kind = Bytecode::PutKind::Getter;
|
||||
break;
|
||||
case ObjectProperty::Type::Setter:
|
||||
property_kind = Bytecode::Op::PropertyKind::Setter;
|
||||
property_kind = Bytecode::PutKind::Setter;
|
||||
break;
|
||||
case ObjectProperty::Type::ProtoSetter:
|
||||
property_kind = Bytecode::Op::PropertyKind::ProtoSetter;
|
||||
property_kind = Bytecode::PutKind::Prototype;
|
||||
break;
|
||||
case ObjectProperty::Type::Spread:
|
||||
generator.emit<Bytecode::Op::PutBySpread>(object, TRY(property->key().generate_bytecode(generator)).value());
|
||||
|
|
@ -1175,13 +1175,13 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ObjectExpression::gener
|
|||
Bytecode::IdentifierTableIndex key_name = generator.intern_identifier(string_literal.value());
|
||||
|
||||
Optional<ScopedOperand> value;
|
||||
if (property_kind == Bytecode::Op::PropertyKind::ProtoSetter) {
|
||||
if (property_kind == Bytecode::PutKind::Prototype) {
|
||||
value = TRY(property->value().generate_bytecode(generator)).value();
|
||||
} else {
|
||||
auto identifier = string_literal.value();
|
||||
if (property_kind == Bytecode::Op::PropertyKind::Getter)
|
||||
if (property_kind == Bytecode::PutKind::Getter)
|
||||
identifier = Utf16String::formatted("get {}", identifier);
|
||||
else if (property_kind == Bytecode::Op::PropertyKind::Setter)
|
||||
else if (property_kind == Bytecode::PutKind::Setter)
|
||||
identifier = Utf16String::formatted("set {}", identifier);
|
||||
|
||||
auto name = generator.intern_identifier(identifier);
|
||||
|
|
@ -2540,7 +2540,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> TaggedTemplateLiteral::
|
|||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(raw_string_regs.size(), raw_strings_array, raw_string_regs);
|
||||
}
|
||||
|
||||
generator.emit_put_by_id(strings_array, generator.intern_identifier("raw"_utf16_fly_string), raw_strings_array, Bytecode::Op::PropertyKind::KeyValue, generator.next_property_lookup_cache());
|
||||
generator.emit_put_by_id(strings_array, generator.intern_identifier("raw"_utf16_fly_string), raw_strings_array, Bytecode::PutKind::Normal, generator.next_property_lookup_cache());
|
||||
|
||||
auto arguments = generator.allocate_register();
|
||||
if (!argument_regs.is_empty())
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ namespace JS::Bytecode {
|
|||
O(MathSin, math_sin, Math, sin, 1) \
|
||||
O(MathCos, math_cos, Math, cos, 1) \
|
||||
O(MathTan, math_tan, Math, tan, 1) \
|
||||
O(OrdinaryHasInstance, ordinary_has_instance, InternalBuiltin, ordinary_has_instance, 1) \
|
||||
O(ArrayIteratorPrototypeNext, array_iterator_prototype_next, ArrayIteratorPrototype, next, 0) \
|
||||
O(MapIteratorPrototypeNext, map_iterator_prototype_next, MapIteratorPrototype, next, 0) \
|
||||
O(SetIteratorPrototypeNext, set_iterator_prototype_next, SetIteratorPrototype, next, 0) \
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ Executable::Executable(
|
|||
size_t number_of_property_lookup_caches,
|
||||
size_t number_of_global_variable_caches,
|
||||
size_t number_of_registers,
|
||||
bool is_strict_mode)
|
||||
Strict strict)
|
||||
: bytecode(move(bytecode))
|
||||
, string_table(move(string_table))
|
||||
, identifier_table(move(identifier_table))
|
||||
|
|
@ -33,7 +33,7 @@ Executable::Executable(
|
|||
, constants(move(constants))
|
||||
, source_code(move(source_code))
|
||||
, number_of_registers(number_of_registers)
|
||||
, is_strict_mode(is_strict_mode)
|
||||
, is_strict_mode(strict == Strict::Yes)
|
||||
{
|
||||
property_lookup_caches.resize(number_of_property_lookup_caches);
|
||||
global_variable_caches.resize(number_of_global_variable_caches);
|
||||
|
|
|
|||
|
|
@ -10,8 +10,9 @@
|
|||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/Utf16FlyString.h>
|
||||
#include <AK/WeakPtr.h>
|
||||
#include <LibGC/CellAllocator.h>
|
||||
#include <LibGC/Weak.h>
|
||||
#include <LibGC/WeakInlines.h>
|
||||
#include <LibJS/Bytecode/IdentifierTable.h>
|
||||
#include <LibJS/Bytecode/Label.h>
|
||||
#include <LibJS/Bytecode/StringTable.h>
|
||||
|
|
@ -37,11 +38,12 @@ struct PropertyLookupCache {
|
|||
GetPropertyInPrototypeChain,
|
||||
};
|
||||
Type type { Type::Empty };
|
||||
WeakPtr<Shape> from_shape;
|
||||
WeakPtr<Shape> shape;
|
||||
GC::Weak<Shape> from_shape;
|
||||
GC::Weak<Shape> shape;
|
||||
Optional<u32> property_offset;
|
||||
WeakPtr<Object> prototype;
|
||||
WeakPtr<PrototypeChainValidity> prototype_chain_validity;
|
||||
GC::Weak<Object> prototype;
|
||||
GC::Weak<PrototypeChainValidity> prototype_chain_validity;
|
||||
Optional<u32> shape_dictionary_generation;
|
||||
};
|
||||
AK::Array<Entry, max_number_of_shapes_to_remember> entries;
|
||||
};
|
||||
|
|
@ -73,7 +75,7 @@ public:
|
|||
size_t number_of_property_lookup_caches,
|
||||
size_t number_of_global_variable_caches,
|
||||
size_t number_of_registers,
|
||||
bool is_strict_mode);
|
||||
Strict);
|
||||
|
||||
virtual ~Executable() override;
|
||||
|
||||
|
|
@ -90,6 +92,8 @@ public:
|
|||
size_t number_of_registers { 0 };
|
||||
bool is_strict_mode { false };
|
||||
|
||||
size_t registers_and_constants_and_locals_count { 0 };
|
||||
|
||||
struct ExceptionHandlers {
|
||||
size_t start_offset;
|
||||
size_t end_offset;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2024, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2021-2025, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
|
@ -179,7 +179,7 @@ CodeGenerationErrorOr<void> Generator::emit_function_declaration_instantiation(E
|
|||
if (!function.is_strict_mode()) {
|
||||
bool can_elide_lexical_environment = !scope_body || !scope_body->has_non_local_lexical_declarations();
|
||||
if (!can_elide_lexical_environment) {
|
||||
emit<Op::CreateLexicalEnvironment>(function.shared_data().m_lex_environment_bindings_count);
|
||||
emit<Op::CreateLexicalEnvironment>(OptionalNone {}, function.shared_data().m_lex_environment_bindings_count);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -218,6 +218,14 @@ CodeGenerationErrorOr<void> Generator::emit_function_declaration_instantiation(E
|
|||
CodeGenerationErrorOr<GC::Ref<Executable>> Generator::compile(VM& vm, ASTNode const& node, FunctionKind enclosing_function_kind, GC::Ptr<ECMAScriptFunctionObject const> function, MustPropagateCompletion must_propagate_completion, Vector<LocalVariable> local_variable_names)
|
||||
{
|
||||
Generator generator(vm, function, must_propagate_completion);
|
||||
|
||||
if (is<Program>(node))
|
||||
generator.m_strict = static_cast<Program const&>(node).is_strict_mode() ? Strict::Yes : Strict::No;
|
||||
else if (is<FunctionBody>(node))
|
||||
generator.m_strict = static_cast<FunctionBody const&>(node).in_strict_mode() ? Strict::Yes : Strict::No;
|
||||
else if (is<FunctionDeclaration>(node))
|
||||
generator.m_strict = static_cast<FunctionDeclaration const&>(node).is_strict_mode() ? Strict::Yes : Strict::No;
|
||||
|
||||
generator.m_local_variables = local_variable_names;
|
||||
|
||||
generator.switch_to_basic_block(generator.make_block());
|
||||
|
|
@ -260,14 +268,6 @@ CodeGenerationErrorOr<GC::Ref<Executable>> Generator::compile(VM& vm, ASTNode co
|
|||
}
|
||||
}
|
||||
|
||||
bool is_strict_mode = false;
|
||||
if (is<Program>(node))
|
||||
is_strict_mode = static_cast<Program const&>(node).is_strict_mode();
|
||||
else if (is<FunctionBody>(node))
|
||||
is_strict_mode = static_cast<FunctionBody const&>(node).in_strict_mode();
|
||||
else if (is<FunctionDeclaration>(node))
|
||||
is_strict_mode = static_cast<FunctionDeclaration const&>(node).is_strict_mode();
|
||||
|
||||
size_t size_needed = 0;
|
||||
for (auto& block : generator.m_root_basic_blocks) {
|
||||
size_needed += block->size();
|
||||
|
|
@ -453,7 +453,7 @@ CodeGenerationErrorOr<GC::Ref<Executable>> Generator::compile(VM& vm, ASTNode co
|
|||
generator.m_next_property_lookup_cache,
|
||||
generator.m_next_global_variable_cache,
|
||||
generator.m_next_register,
|
||||
is_strict_mode);
|
||||
generator.m_strict);
|
||||
|
||||
Vector<Executable::ExceptionHandlers> linked_exception_handlers;
|
||||
|
||||
|
|
@ -477,6 +477,8 @@ CodeGenerationErrorOr<GC::Ref<Executable>> Generator::compile(VM& vm, ASTNode co
|
|||
executable->argument_index_base = number_of_registers + number_of_constants + number_of_locals;
|
||||
executable->length_identifier = generator.m_length_identifier;
|
||||
|
||||
executable->registers_and_constants_and_locals_count = executable->number_of_registers + executable->constants.size() + executable->local_variable_names.size();
|
||||
|
||||
generator.m_finished = true;
|
||||
|
||||
return executable;
|
||||
|
|
@ -570,10 +572,58 @@ bool Generator::emit_block_declaration_instantiation(ScopeNode const& scope_node
|
|||
if (!needs_block_declaration_instantiation)
|
||||
return false;
|
||||
|
||||
// FIXME: Generate the actual bytecode for block declaration instantiation
|
||||
// and get rid of the BlockDeclarationInstantiation instruction.
|
||||
auto environment = allocate_register();
|
||||
emit<Bytecode::Op::CreateLexicalEnvironment>(environment);
|
||||
start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
|
||||
emit<Bytecode::Op::BlockDeclarationInstantiation>(scope_node);
|
||||
|
||||
MUST(scope_node.for_each_lexically_scoped_declaration([&](Declaration const& declaration) {
|
||||
auto is_constant_declaration = declaration.is_constant_declaration();
|
||||
// NOTE: Due to the use of MUST with `create_immutable_binding` and `create_mutable_binding` below,
|
||||
// an exception should not result from `for_each_bound_name`.
|
||||
// a. For each element dn of the BoundNames of d, do
|
||||
MUST(declaration.for_each_bound_identifier([&](Identifier const& identifier) {
|
||||
if (identifier.is_local()) {
|
||||
// NOTE: No need to create bindings for local variables as their values are not stored in an environment.
|
||||
return;
|
||||
}
|
||||
|
||||
auto const& name = identifier.string();
|
||||
|
||||
// i. If IsConstantDeclaration of d is true, then
|
||||
if (is_constant_declaration) {
|
||||
// 1. Perform ! env.CreateImmutableBinding(dn, true).
|
||||
emit<Bytecode::Op::CreateImmutableBinding>(environment, intern_identifier(name), true);
|
||||
}
|
||||
// ii. Else,
|
||||
else {
|
||||
// 1. Perform ! env.CreateMutableBinding(dn, false). NOTE: This step is replaced in section B.3.2.6.
|
||||
emit<Bytecode::Op::CreateMutableBinding>(environment, intern_identifier(name), false);
|
||||
}
|
||||
}));
|
||||
|
||||
// b. If d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration, then
|
||||
if (is<FunctionDeclaration>(declaration)) {
|
||||
// i. Let fn be the sole element of the BoundNames of d.
|
||||
auto& function_declaration = static_cast<FunctionDeclaration const&>(declaration);
|
||||
|
||||
// ii. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv.
|
||||
auto fo = allocate_register();
|
||||
emit<Bytecode::Op::NewFunction>(fo, function_declaration, OptionalNone {});
|
||||
|
||||
// iii. Perform ! env.InitializeBinding(fn, fo). NOTE: This step is replaced in section B.3.2.6.
|
||||
if (function_declaration.name_identifier()->is_local()) {
|
||||
auto local_index = function_declaration.name_identifier()->local_index();
|
||||
if (local_index.is_variable()) {
|
||||
emit<Bytecode::Op::Mov>(local(local_index), fo);
|
||||
} else {
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
} else {
|
||||
emit<Bytecode::Op::InitializeLexicalBinding>(intern_identifier(function_declaration.name()), fo);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -761,21 +811,21 @@ CodeGenerationErrorOr<void> Generator::emit_store_to_reference(JS::ASTNode const
|
|||
if (super_reference.referenced_name.has_value()) {
|
||||
// 5. Let propertyKey be ? ToPropertyKey(propertyNameValue).
|
||||
// FIXME: This does ToPropertyKey out of order, which is observable by Symbol.toPrimitive!
|
||||
emit_put_by_value_with_this(*super_reference.base, *super_reference.referenced_name, *super_reference.this_value, value, Op::PropertyKind::KeyValue);
|
||||
emit_put_by_value_with_this(*super_reference.base, *super_reference.referenced_name, *super_reference.this_value, value, PutKind::Normal);
|
||||
} else {
|
||||
// 3. Let propertyKey be StringValue of IdentifierName.
|
||||
auto identifier_table_ref = intern_identifier(as<Identifier>(expression.property()).string());
|
||||
emit<Bytecode::Op::PutByIdWithThis>(*super_reference.base, *super_reference.this_value, identifier_table_ref, value, Bytecode::Op::PropertyKind::KeyValue, next_property_lookup_cache());
|
||||
emit<Bytecode::Op::PutNormalByIdWithThis>(*super_reference.base, *super_reference.this_value, identifier_table_ref, value, next_property_lookup_cache());
|
||||
}
|
||||
} else {
|
||||
auto object = TRY(expression.object().generate_bytecode(*this)).value();
|
||||
|
||||
if (expression.is_computed()) {
|
||||
auto property = TRY(expression.property().generate_bytecode(*this)).value();
|
||||
emit_put_by_value(object, property, value, Op::PropertyKind::KeyValue, {});
|
||||
emit_put_by_value(object, property, value, PutKind::Normal, {});
|
||||
} else if (expression.property().is_identifier()) {
|
||||
auto identifier_table_ref = intern_identifier(as<Identifier>(expression.property()).string());
|
||||
emit_put_by_id(object, identifier_table_ref, value, Bytecode::Op::PropertyKind::KeyValue, next_property_lookup_cache());
|
||||
emit_put_by_id(object, identifier_table_ref, value, Bytecode::PutKind::Normal, next_property_lookup_cache());
|
||||
} else if (expression.property().is_private_identifier()) {
|
||||
auto identifier_table_ref = intern_identifier(as<PrivateIdentifier>(expression.property()).string());
|
||||
emit<Bytecode::Op::PutPrivateById>(object, identifier_table_ref, value);
|
||||
|
|
@ -804,15 +854,15 @@ CodeGenerationErrorOr<void> Generator::emit_store_to_reference(ReferenceOperands
|
|||
}
|
||||
if (reference.referenced_identifier.has_value()) {
|
||||
if (reference.base == reference.this_value)
|
||||
emit_put_by_id(*reference.base, *reference.referenced_identifier, value, Bytecode::Op::PropertyKind::KeyValue, next_property_lookup_cache());
|
||||
emit_put_by_id(*reference.base, *reference.referenced_identifier, value, Bytecode::PutKind::Normal, next_property_lookup_cache());
|
||||
else
|
||||
emit<Bytecode::Op::PutByIdWithThis>(*reference.base, *reference.this_value, *reference.referenced_identifier, value, Bytecode::Op::PropertyKind::KeyValue, next_property_lookup_cache());
|
||||
emit<Bytecode::Op::PutNormalByIdWithThis>(*reference.base, *reference.this_value, *reference.referenced_identifier, value, next_property_lookup_cache());
|
||||
return {};
|
||||
}
|
||||
if (reference.base == reference.this_value)
|
||||
emit_put_by_value(*reference.base, *reference.referenced_name, value, Op::PropertyKind::KeyValue, {});
|
||||
emit_put_by_value(*reference.base, *reference.referenced_name, value, PutKind::Normal, {});
|
||||
else
|
||||
emit_put_by_value_with_this(*reference.base, *reference.referenced_name, *reference.this_value, value, Op::PropertyKind::KeyValue);
|
||||
emit_put_by_value_with_this(*reference.base, *reference.referenced_name, *reference.this_value, value, PutKind::Normal);
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
@ -1153,20 +1203,38 @@ void Generator::emit_get_by_value_with_this(ScopedOperand dst, ScopedOperand bas
|
|||
emit<Op::GetByValueWithThis>(dst, base, property, this_value);
|
||||
}
|
||||
|
||||
void Generator::emit_put_by_id(Operand base, IdentifierTableIndex property, Operand src, Op::PropertyKind kind, u32 cache_index, Optional<IdentifierTableIndex> base_identifier)
|
||||
void Generator::emit_put_by_id(Operand base, IdentifierTableIndex property, Operand src, PutKind kind, u32 cache_index, Optional<IdentifierTableIndex> base_identifier)
|
||||
{
|
||||
auto string = m_identifier_table->get(property);
|
||||
if (!string.is_empty() && !(string.code_unit_at(0) == '0' && string.length_in_code_units() > 1)) {
|
||||
auto property_index = string.to_number<u32>(TrimWhitespace::No);
|
||||
if (property_index.has_value() && property_index.value() < NumericLimits<u32>::max()) {
|
||||
emit<Op::PutByNumericId>(base, property_index.release_value(), src, kind, cache_index, move(base_identifier));
|
||||
#define EMIT_PUT_BY_NUMERIC_ID(kind) \
|
||||
case PutKind::kind: \
|
||||
emit<Op::Put##kind##ByNumericId>(base, property_index.release_value(), src, cache_index, move(base_identifier)); \
|
||||
break;
|
||||
switch (kind) {
|
||||
JS_ENUMERATE_PUT_KINDS(EMIT_PUT_BY_NUMERIC_ID)
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
#undef EMIT_PUT_BY_NUMERIC_ID
|
||||
return;
|
||||
}
|
||||
}
|
||||
emit<Op::PutById>(base, property, src, kind, cache_index, move(base_identifier));
|
||||
#define EMIT_PUT_BY_ID(kind) \
|
||||
case PutKind::kind: \
|
||||
emit<Op::Put##kind##ById>(base, property, src, cache_index, move(base_identifier)); \
|
||||
break;
|
||||
switch (kind) {
|
||||
JS_ENUMERATE_PUT_KINDS(EMIT_PUT_BY_ID)
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
#undef EMIT_PUT_BY_ID
|
||||
}
|
||||
|
||||
void Generator::emit_put_by_value(ScopedOperand base, ScopedOperand property, ScopedOperand src, Bytecode::Op::PropertyKind kind, Optional<IdentifierTableIndex> base_identifier)
|
||||
void Generator::emit_put_by_value(ScopedOperand base, ScopedOperand property, ScopedOperand src, Bytecode::PutKind kind, Optional<IdentifierTableIndex> base_identifier)
|
||||
{
|
||||
if (property.operand().is_constant() && get_constant(property).is_string()) {
|
||||
auto property_key = MUST(get_constant(property).to_property_key(vm()));
|
||||
|
|
@ -1175,19 +1243,46 @@ void Generator::emit_put_by_value(ScopedOperand base, ScopedOperand property, Sc
|
|||
return;
|
||||
}
|
||||
}
|
||||
emit<Op::PutByValue>(base, property, src, kind, base_identifier);
|
||||
#define EMIT_PUT_BY_VALUE(kind) \
|
||||
case PutKind::kind: \
|
||||
emit<Op::Put##kind##ByValue>(base, property, src, move(base_identifier)); \
|
||||
break;
|
||||
switch (kind) {
|
||||
JS_ENUMERATE_PUT_KINDS(EMIT_PUT_BY_VALUE)
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
#undef EMIT_PUT_BY_VALUE
|
||||
}
|
||||
|
||||
void Generator::emit_put_by_value_with_this(ScopedOperand base, ScopedOperand property, ScopedOperand this_value, ScopedOperand src, Bytecode::Op::PropertyKind kind)
|
||||
void Generator::emit_put_by_value_with_this(ScopedOperand base, ScopedOperand property, ScopedOperand this_value, ScopedOperand src, Bytecode::PutKind kind)
|
||||
{
|
||||
if (property.operand().is_constant() && get_constant(property).is_string()) {
|
||||
auto property_key = MUST(get_constant(property).to_property_key(vm()));
|
||||
if (property_key.is_string()) {
|
||||
emit<Op::PutByIdWithThis>(base, this_value, intern_identifier(property_key.as_string()), src, kind, m_next_property_lookup_cache++);
|
||||
#define EMIT_PUT_BY_ID_WITH_THIS(kind) \
|
||||
case PutKind::kind: \
|
||||
emit<Op::Put##kind##ByIdWithThis>(base, this_value, intern_identifier(property_key.as_string()), src, m_next_property_lookup_cache++); \
|
||||
break;
|
||||
switch (kind) {
|
||||
JS_ENUMERATE_PUT_KINDS(EMIT_PUT_BY_ID_WITH_THIS)
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
#undef EMIT_PUT_BY_ID_WITH_THIS
|
||||
return;
|
||||
}
|
||||
}
|
||||
emit<Bytecode::Op::PutByValueWithThis>(base, property, this_value, src, kind);
|
||||
#define EMIT_PUT_BY_VALUE_WITH_THIS(kind) \
|
||||
case PutKind::kind: \
|
||||
emit<Op::Put##kind##ByValueWithThis>(base, property, this_value, src); \
|
||||
break;
|
||||
switch (kind) {
|
||||
JS_ENUMERATE_PUT_KINDS(EMIT_PUT_BY_VALUE_WITH_THIS)
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
#undef EMIT_PUT_BY_VALUE_WITH_THIS
|
||||
}
|
||||
|
||||
void Generator::emit_iterator_value(ScopedOperand dst, ScopedOperand result)
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ public:
|
|||
grow(sizeof(OpType));
|
||||
void* slot = m_current_basic_block->data() + slot_offset;
|
||||
new (slot) OpType(forward<Args>(args)...);
|
||||
static_cast<OpType*>(slot)->set_strict(m_strict);
|
||||
if constexpr (OpType::IsTerminator)
|
||||
m_current_basic_block->terminate({});
|
||||
m_current_basic_block->add_source_map_entry(slot_offset, { m_current_ast_node->start_offset(), m_current_ast_node->end_offset() });
|
||||
|
|
@ -110,6 +111,7 @@ public:
|
|||
grow(size_to_allocate);
|
||||
void* slot = m_current_basic_block->data() + slot_offset;
|
||||
new (slot) OpType(forward<Args>(args)...);
|
||||
static_cast<OpType*>(slot)->set_strict(m_strict);
|
||||
if constexpr (OpType::IsTerminator)
|
||||
m_current_basic_block->terminate({});
|
||||
m_current_basic_block->add_source_map_entry(slot_offset, { m_current_ast_node->start_offset(), m_current_ast_node->end_offset() });
|
||||
|
|
@ -329,10 +331,10 @@ public:
|
|||
void emit_get_by_value(ScopedOperand dst, ScopedOperand base, ScopedOperand property, Optional<IdentifierTableIndex> base_identifier = {});
|
||||
void emit_get_by_value_with_this(ScopedOperand dst, ScopedOperand base, ScopedOperand property, ScopedOperand this_value);
|
||||
|
||||
void emit_put_by_id(Operand base, IdentifierTableIndex property, Operand src, Op::PropertyKind kind, u32 cache_index, Optional<IdentifierTableIndex> base_identifier = {});
|
||||
void emit_put_by_id(Operand base, IdentifierTableIndex property, Operand src, PutKind kind, u32 cache_index, Optional<IdentifierTableIndex> base_identifier = {});
|
||||
|
||||
void emit_put_by_value(ScopedOperand base, ScopedOperand property, ScopedOperand src, Bytecode::Op::PropertyKind, Optional<IdentifierTableIndex> base_identifier);
|
||||
void emit_put_by_value_with_this(ScopedOperand base, ScopedOperand property, ScopedOperand this_value, ScopedOperand src, Bytecode::Op::PropertyKind);
|
||||
void emit_put_by_value(ScopedOperand base, ScopedOperand property, ScopedOperand src, Bytecode::PutKind, Optional<IdentifierTableIndex> base_identifier);
|
||||
void emit_put_by_value_with_this(ScopedOperand base, ScopedOperand property, ScopedOperand this_value, ScopedOperand src, Bytecode::PutKind);
|
||||
|
||||
void emit_iterator_value(ScopedOperand dst, ScopedOperand result);
|
||||
void emit_iterator_complete(ScopedOperand dst, ScopedOperand result);
|
||||
|
|
@ -383,6 +385,8 @@ private:
|
|||
Vector<FlyString> language_label_set;
|
||||
};
|
||||
|
||||
Strict m_strict { Strict::No };
|
||||
|
||||
BasicBlock* m_current_basic_block { nullptr };
|
||||
ASTNode const* m_current_ast_node { nullptr };
|
||||
UnwindContext const* m_current_unwind_context { nullptr };
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2024, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2021-2025, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
#include <AK/Function.h>
|
||||
#include <AK/Span.h>
|
||||
#include <LibJS/Bytecode/Executable.h>
|
||||
#include <LibJS/Bytecode/PutKind.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibJS/SourceRange.h>
|
||||
|
||||
|
|
@ -23,7 +24,6 @@
|
|||
O(BitwiseNot) \
|
||||
O(BitwiseOr) \
|
||||
O(BitwiseXor) \
|
||||
O(BlockDeclarationInstantiation) \
|
||||
O(Call) \
|
||||
O(CallBuiltin) \
|
||||
O(CallConstruct) \
|
||||
|
|
@ -37,6 +37,8 @@
|
|||
O(CopyObjectExcludingProperties) \
|
||||
O(CreateArguments) \
|
||||
O(CreateLexicalEnvironment) \
|
||||
O(CreateImmutableBinding) \
|
||||
O(CreateMutableBinding) \
|
||||
O(CreatePrivateEnvironment) \
|
||||
O(CreateRestParams) \
|
||||
O(CreateVariable) \
|
||||
|
|
@ -122,12 +124,37 @@
|
|||
O(PrepareYield) \
|
||||
O(PostfixDecrement) \
|
||||
O(PostfixIncrement) \
|
||||
O(PutById) \
|
||||
O(PutByNumericId) \
|
||||
O(PutByIdWithThis) \
|
||||
O(PutNormalById) \
|
||||
O(PutOwnById) \
|
||||
O(PutGetterById) \
|
||||
O(PutSetterById) \
|
||||
O(PutPrototypeById) \
|
||||
O(PutNormalByNumericId) \
|
||||
O(PutOwnByNumericId) \
|
||||
O(PutGetterByNumericId) \
|
||||
O(PutSetterByNumericId) \
|
||||
O(PutPrototypeByNumericId) \
|
||||
O(PutNormalByIdWithThis) \
|
||||
O(PutOwnByIdWithThis) \
|
||||
O(PutGetterByIdWithThis) \
|
||||
O(PutSetterByIdWithThis) \
|
||||
O(PutPrototypeByIdWithThis) \
|
||||
O(PutNormalByNumericIdWithThis) \
|
||||
O(PutOwnByNumericIdWithThis) \
|
||||
O(PutGetterByNumericIdWithThis) \
|
||||
O(PutSetterByNumericIdWithThis) \
|
||||
O(PutPrototypeByNumericIdWithThis) \
|
||||
O(PutBySpread) \
|
||||
O(PutByValue) \
|
||||
O(PutByValueWithThis) \
|
||||
O(PutNormalByValue) \
|
||||
O(PutOwnByValue) \
|
||||
O(PutGetterByValue) \
|
||||
O(PutSetterByValue) \
|
||||
O(PutPrototypeByValue) \
|
||||
O(PutNormalByValueWithThis) \
|
||||
O(PutOwnByValueWithThis) \
|
||||
O(PutGetterByValueWithThis) \
|
||||
O(PutSetterByValueWithThis) \
|
||||
O(PutPrototypeByValueWithThis) \
|
||||
O(PutPrivateById) \
|
||||
O(ResolveSuperBase) \
|
||||
O(ResolveThisBinding) \
|
||||
|
|
@ -161,7 +188,7 @@ public:
|
|||
constexpr static bool IsTerminator = false;
|
||||
static constexpr bool IsVariableLength = false;
|
||||
|
||||
enum class Type {
|
||||
enum class Type : u8 {
|
||||
#define __BYTECODE_OP(op) \
|
||||
op,
|
||||
ENUMERATE_BYTECODE_OPS(__BYTECODE_OP)
|
||||
|
|
@ -175,6 +202,9 @@ public:
|
|||
void visit_operands(Function<void(Operand&)> visitor);
|
||||
static void destroy(Instruction&);
|
||||
|
||||
Strict strict() const { return m_strict; }
|
||||
void set_strict(Strict strict) { m_strict = strict; }
|
||||
|
||||
protected:
|
||||
explicit Instruction(Type type)
|
||||
: m_type(type)
|
||||
|
|
@ -186,6 +216,7 @@ protected:
|
|||
|
||||
private:
|
||||
Type m_type {};
|
||||
Strict m_strict {};
|
||||
};
|
||||
|
||||
class InstructionStreamIterator {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -25,36 +25,32 @@ public:
|
|||
explicit Interpreter(VM&);
|
||||
~Interpreter();
|
||||
|
||||
[[nodiscard]] Realm& realm() { return *m_realm; }
|
||||
[[nodiscard]] Object& global_object() { return *m_global_object; }
|
||||
[[nodiscard]] DeclarativeEnvironment& global_declarative_environment() { return *m_global_declarative_environment; }
|
||||
[[nodiscard]] Realm& realm() { return *m_running_execution_context->realm; }
|
||||
[[nodiscard]] Object& global_object() { return *m_running_execution_context->global_object; }
|
||||
[[nodiscard]] DeclarativeEnvironment& global_declarative_environment() { return *m_running_execution_context->global_declarative_environment; }
|
||||
VM& vm() { return m_vm; }
|
||||
VM const& vm() const { return m_vm; }
|
||||
|
||||
ThrowCompletionOr<Value> run(Script&, GC::Ptr<Environment> lexical_environment_override = nullptr);
|
||||
ThrowCompletionOr<Value> run(SourceTextModule&);
|
||||
|
||||
ThrowCompletionOr<Value> run(Bytecode::Executable& executable, Optional<size_t> entry_point = {}, Value initial_accumulator_value = js_special_empty_value())
|
||||
{
|
||||
auto result_and_return_register = run_executable(executable, entry_point, initial_accumulator_value);
|
||||
return move(result_and_return_register.value);
|
||||
}
|
||||
ThrowCompletionOr<Value> run_executable(ExecutionContext&, Executable&, Optional<size_t> entry_point);
|
||||
|
||||
struct ResultAndReturnRegister {
|
||||
ThrowCompletionOr<Value> value;
|
||||
Value return_register_value;
|
||||
};
|
||||
ResultAndReturnRegister run_executable(Bytecode::Executable&, Optional<size_t> entry_point, Value initial_accumulator_value = js_special_empty_value());
|
||||
ThrowCompletionOr<Value> run_executable(ExecutionContext& context, Executable& executable, Optional<size_t> entry_point, Value initial_accumulator_value)
|
||||
{
|
||||
context.registers_and_constants_and_locals_and_arguments_span()[0] = initial_accumulator_value;
|
||||
return run_executable(context, executable, entry_point);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE Value& accumulator() { return reg(Register::accumulator()); }
|
||||
ALWAYS_INLINE Value& saved_return_value() { return reg(Register::saved_return_value()); }
|
||||
Value& reg(Register const& r)
|
||||
{
|
||||
return m_registers_and_constants_and_locals_arguments.data()[r.index()];
|
||||
return m_running_execution_context->registers_and_constants_and_locals_and_arguments()[r.index()];
|
||||
}
|
||||
Value reg(Register const& r) const
|
||||
{
|
||||
return m_registers_and_constants_and_locals_arguments.data()[r.index()];
|
||||
return m_running_execution_context->registers_and_constants_and_locals_and_arguments()[r.index()];
|
||||
}
|
||||
|
||||
[[nodiscard]] Value get(Operand) const;
|
||||
|
|
@ -63,6 +59,8 @@ public:
|
|||
Value do_yield(Value value, Optional<Label> continuation);
|
||||
void do_return(Value value)
|
||||
{
|
||||
if (value.is_special_empty_value())
|
||||
value = js_undefined();
|
||||
reg(Register::return_value()) = value;
|
||||
reg(Register::exception()) = js_special_empty_value();
|
||||
}
|
||||
|
|
@ -75,8 +73,8 @@ public:
|
|||
|
||||
void enter_object_environment(Object&);
|
||||
|
||||
Executable& current_executable() { return *m_current_executable; }
|
||||
Executable const& current_executable() const { return *m_current_executable; }
|
||||
Executable& current_executable() { return *m_running_execution_context->executable; }
|
||||
Executable const& current_executable() const { return *m_running_execution_context->executable; }
|
||||
|
||||
ExecutionContext& running_execution_context() { return *m_running_execution_context; }
|
||||
|
||||
|
|
@ -95,17 +93,10 @@ private:
|
|||
ExitFromExecutable,
|
||||
ContinueInThisExecutable,
|
||||
};
|
||||
[[nodiscard]] HandleExceptionResponse handle_exception(size_t& program_counter, Value exception);
|
||||
[[nodiscard]] HandleExceptionResponse handle_exception(u32& program_counter, Value exception);
|
||||
|
||||
VM& m_vm;
|
||||
Optional<size_t> m_scheduled_jump;
|
||||
GC::Ptr<Executable> m_current_executable { nullptr };
|
||||
GC::Ptr<Realm> m_realm { nullptr };
|
||||
GC::Ptr<Object> m_global_object { nullptr };
|
||||
GC::Ptr<DeclarativeEnvironment> m_global_declarative_environment { nullptr };
|
||||
Span<Value> m_registers_and_constants_and_locals_arguments;
|
||||
ExecutionContext* m_running_execution_context { nullptr };
|
||||
ReadonlySpan<Utf16FlyString> m_identifier_table;
|
||||
};
|
||||
|
||||
JS_API extern bool g_dump_bytecode;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user